内核对象的句柄是进程相关的,这样更可靠,更安全;Windows提供下列N种机制来允许进程共享内核对象。
使用对象句柄继承
只有进程间存在父子关系才能使用对象句柄继承;系统会遍历父进程的句柄表将所有有效的句柄完整的复制到子进程的句柄表中(这个动作只会在创建子进程的进行一次,后续父进程再创建句柄对象,子进程不会再继承),并且增加句柄的使用计数(类似于智能指针,Close等操作只是将使用计数减一)。
CreateProcess创建进程,指定参数bInheritHandles为TRUE指定为父子进程,进行对象句柄继承,句柄跟进程有关,只有继承后子进程才能使用父进程句柄;
使用进程间通信技术传递
- 例如:通过命令行参数将句柄值传递给子进程)
- 通过设置父进程环境变量,子进程获取GetEnvironmentVariable;
- 如果子进程是有窗口的程序 通过函数WaitForInputIdle等待窗口程序初始化完成后 向窗口发消息;
改变句柄状态(控制哪些子进程可以继承内核对象句柄)
父进程跟孙进程通信,不让子进程关闭句柄。通过SetHandleInfomation改变具柄状态,但是子进程也可以改这个状态从而关闭句柄(这就很尴尬了)。
SetHandleInformation具体使用请看MSDN
BOOL WINAPI SetHandleInformation(
_In_ HANDLE hObject,//要设置其信息的对象的句柄。
_In_ DWORD dwMask,
_In_ DWORD dwFlags
);
//每个句柄都关联了两个标志
#define HANDLE_FLAG_INHERIT 0x00000001
#define HANDLE_FLAG_PROTECT_FROM_CLOSE 0x00000002
//打开内核对象句柄的继承标志
SetHandleInformation(hObj, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
//关闭内核对象句柄的继承标志
SetHandleInformation(hObj, HANDLE_FLAG_INHERIT, 0) ;
//HANDLE_FLAG_PROTECT_FROM_CLOSE标志告诉系统不允许关闭句柄
SetHandleInformation(hObj, HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE);
CloseHandle(h0bj); //会引发异常
命名对象
通过对象的名称来共享内核对象,并不需要父子进程关系; windows大多数对象都可以进行命名,但是并没有提供保证为内核对象指定的名称是唯一的,所以命名可以以“公司名称+随机字符+名称”等方式来命名。以这样的私有命名方式,防止单实例程序被攻击(命名冲突,导致一直不能启动程序)。
调用( Create )系列函数可以通过调用GetLastError来判断是打开了一个已用的对象还是创建了一个新的对象。(Open)系列函数如果不存在否则返回NULL。
应用例子:可以利用命名对象来防止程序运行多个实例。
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName //对象名称 传入NULL创建匿名对象
);
HANDLE hMutex = ::OpenMutex(MUTEX_ALL_ACCESS, FALSE, “Company_0DntbbRl5qP3_AppName”);
if ( hMutex )
{
// 应用程序已存在实例
}
else
{
// 不存在,创建命名对象
hMutex = ::CreateMutex(0, FALSE, “Company_0DntbbRl5qP3_AppName”);
}
复制对象句柄
获得一个进程的句柄表的一个记录项,然后在另外一个进程的句柄表中创建这个记录项的一个副本。同样的目标进程并不知道它能访问什么内核对象,必须使用进程间通信方式告诉它句柄值;这时已经启动,所以不能使用命令行参数或进程的环境变量。
BOOL WINAPI DuplicateHandle(
_In_ HANDLE hSourceProcessHandle, // 源进程内核对象
_In_ HANDLE hSourceHandle, // 源任何类型内核对象;不能与调用DuplicateHandle函数的进程相关,必须与hSourceProcessHandle句柄所标识的进程相关(除非hSourceProcessHandle就是调用DuplicateHandle函数的进程,即当前进程)
_In_ HANDLE hTargetProcessHandle, // 目标进程内核对象
_Out_ LPHANDLE lpTargetHandle, // HANDLE变量的地址,用来接收复制得到的HANDLE值;不能调用CloseHandle(除非hTargetProcessHandle就是调用DuplicateHandle函数的进程,即当前进程)
_In_ DWORD dwDesiredAccess,
_In_ BOOL bInheritHandle,
_In_ DWORD dwOptions
);
BTW:子进程与父进程通信不应该使用进程ID,应该使用内核对象或者窗口句柄等更持久的通信机制。因为系统分配进程ID是会回收重新使用的。