1、进程内核对象句柄表
2、创建与关闭内核对象
3、进程间内核对象句柄的共享
1、进程内核对象句柄表
当一个进程初始化时,系统会为它分配一张内核对象句柄表。这个表只用于管理内核对象句柄。GDI和用户对象不会影响到这张表。表中的每一项有4个域。 即句柄ID,对象内存地址,访问屏蔽位,标志位。
当一个进程初始化时,句柄表为空。 现在假设你调用CreateFile函数创建一个文件对象,内核首先会查找当前进程中的句柄表,将一个空的表项取出来,并分配一个内核对象,将该对象的地址写入第二个域(即,对象内存地址),然后访问屏蔽设置为完全访问,相应的标志位也会被设置。用于内核对象创建时返回与进程相关的句柄,而这些句柄可以在同一个进程中被其所有线程使用。该句柄是一个非透明值,有可能返回的是句柄表中的索引,也有可能是一个其它的值。但这个句柄的确是可以与这个内核对象相关联。
2、创建与关闭内核对象
如果创建内核对象失败,那返回的句柄通常为NULL,但也有可能是INVALID_HANDLE_VALUE,例如CreateFile函数。当查看一个函数的返回值时,应该特别小心。
无论你是怎么创建内核对象,都应该调用CloseHandle将其释放。内核会检测这个对象的引用计数,当发现他的引用计数为0的时候,便会将其释放。 并且,如果一个进程中,如果调用了CloseHandle,则这个HANDLE对此进程则会变成无效,而对于其它使用这个内核的进程来说,并没有影响。
3、进程间内核对象句柄的共享
当一个进程创建出一个内核对象时,这个对象就与此进程相关联。但是,并不是所有情况都是如此,许多时候,我们需要多个进程之间共享内核对象。共享一个内核对象句柄的方法大概有三种。 1、通过继承共享,2、通过有名内核对象共享。3、通过句柄拷贝共享。
注:共享的是内核对象的句柄,内核对象属于内核,无法共享。
通过继承共享则要求共享的进程间是父子关系。 如一个进程中调用CreateFile创建了一个文件对象。如果我们想要这个对象的句柄在这个进程的子进程中被使用。则我们在创建这个对象时,对SECURYTY_ATTRIBUTES结构中的bInheritHande设置为TRUE。 这样这个句柄就能够被子进程共享。 而我们在创建子进程时,需在调用CreateProcess时将其参数bInheritHandles传递为TRUE,标志着他需要继承父进程的句柄。
值得注意的是,继承句柄相当于是对父进程中句柄的拷贝,当一个进程被创建时,如果指定为需要对父进程中的句柄进行继承,则系统会查找其父进程中的句柄表,找到可以继承的句柄,并将其整个表项复制到子进程的句柄表中。内核对象的引用计数相应增加1。 因此,如果一个子进程被创建后。父进程又创建了一个可以继承的对象句柄。但这个句柄不会被子进程继承。
另外一个问题就是,子进程无法知道自己拥有了该句柄的使用权,此时就需要通过一些手法来让子进程知道。最常用的便是通过参数传递。将句柄值通过pCommandLine传递给子进程,子进程解析后使用。也可以通过消息或是环境变量的方式通知。。
通过有名对象进行对象句柄共享是最简单的方式,像事件,信号量,互斥变量等都支持有名创建。如创建CreateMutex的时候,只需为其最后一个参数传递一个0结尾的字符串,便可以标志这个变量。需要注意的是,这个名字很有可能冲突,不同种内的内核对象共同使用一个名字池,如果已经有这个名字,并且先前使用此名字的内核对象也是一个Mutex,则当调用GetLastError()时,会返回ERROR_ALREADY_EXISTS。 系统将直接返回其句柄,并将其引用计数加1。否则返回NULL,表示创建失败。
由于这个特性,我们可以使用它来强制你的程序只能开一个实例。代码如下
最后一个共享变量的方式就是采用DuplicateHandle进行拷贝,具体用法参照MSDN即可。
对象的句柄是一个非透明的值,在不同的场合,会有不同的句柄与同一个内核对象对应。标志着不同的权限,不同的环境。 因此用到的时候要十分小心。并且应该注意句柄的关闭,以防止运行过程中内在泄漏。同时应该注意句柄值的有效性,不要使用一个已经释放了的句柄值。