跨越进程边界共享内核对象有三种方法:
复制对象句柄
共享跨越进程边界的内核对象的最后一个方法是使用
BOOL DuplicateHandle( HANDLE hSourceProcessHandle, HANDLE hSourceHandle, HANDLE hTargetProcessHandle, PHANDLE phTargetHandle, DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwOptions);
简单说来,该函数取出一个进程的句柄表中的项目,并将该项目拷贝到另一个进程的句柄表中。
DuplicateHandle 函数最普通的用法要涉及系统中运行的3个不同进程。
第一和第三个参数hSourceProcessHandle和hTargetProcessHandle是内核对象句柄。这些句柄本身必须与调用DuplicateHandle 函数的进程相关。此外,这两个参数必须标识进程的内核对象。
第二个参数hSourceHandle是任何类型的内核对象的句柄。但是该句柄值与调用DuplicateHandle的进程并无关系。相反,该句柄必须与hSourceProcessHandle句柄标识的进程相关。
第四个参数phTagetHandle是HANDLE 变量的地址,它将接收获取源进程句柄信息拷贝的项目索引。返回的句柄值与hTargetProcessHandle标识的进程相关。
最后3 个参数用于指明该目标进程的内核对象句柄表项目中使用的访问屏蔽值和继承性标志。dwOptions参数可以是0 (零),也可以是下面两个标志的任何组合:DUPLICATE_SAME_ACCESS和DUPLICATE_CLOSE_SOURCE。
如果设定了DUPLICATE_SAME_ACCESS标志,则告诉DuplicateHandle 函数,你希望目标进程的句柄拥有与源进程句柄相同的访问屏蔽。使用该标志将使DuplicateHandle忽略它的dwDesiredAccess参数。
如果设定了DUPLICATE_CLOSE_SOURCE标志,则可以关闭源进程中的句柄。该标志使得一个进程能够很容易地将内核对象传递给另一个进程。当使用该标志时,内核对象的使用计数不会受到影响。
例子
下面用一个例子来说明DuplicateHandle函数是如何运行的。在这个例子中。
Process S 是目前可以访问某个内核对象的源进程,
Process T 是将要获取对该内核对象的访问权的目标进程。
Process C 是执行对DuplicateHandle 调用的催化进程。
Process C 的句柄表包含两个句柄值,即1 和2 。句柄值1 用于标识Process S 的进程内核对象,句柄值2 则用于标识Process T 的进程内核对象。
索引 | 内核对象内存块的指针 | 访问屏蔽(标志位的D W O R D ) | 标志(标志位的D W O R D ) |
1 | 0xF0000000(Process S 的内核对象) | 0 x ? ? ? ? ? ? ? ? | 0 x 0 0 0 0 0 0 0 0 |
2 | 0xF0000010(Process T 的内核对象) | 0 x ? ? ? ? ? ? ? ? | 0 x 0 0 0 0 0 0 0 0 |
Process S 的句柄表,它包含句柄值为2 的单个项目。该句柄可以标识任何类型的内核对象,就是说它不必是进程的内核对象。
索引 | 内核对象内存块的指针 | 访问屏蔽(标志位的D W O R D ) | 标志(标志位的D W O R D ) |
1 | 0 x 0 0 0 0 0 0 0 0 | (无) | (无) |
2 | 0 x F 0 0 0 0 0 2 0 | 0 x ? ? ? ? ? ? ? ? | 0 x 0 0 0 0 0 0 0 0 |
Process C 调用DuplicateHandle 函数之前Process T 的句柄表包含的项目。如你所见,Process T 的句柄表只包含句柄值为2 的单个项目,句柄项目1 目前未用。
索引 | 内核对象内存块的指针 | 访问屏蔽(标志位的D W O R D ) | 标志(标志位的D W O R D ) |
1 | 0 x 0 0 0 0 0 0 0 0 | (无) | (无) |
2 | 0 x F 0 0 0 0 0 3 0 | 0 x ? ? ? ? ? ? ? ? | 0 x 0 0 0 0 0 0 0 0 |
如果Process C 现在使用下面的代码来调用DuplicateHandle ,那么只有Process T 的句柄表改变更。
索引 | 内核对象内存块的指针 | 访问屏蔽(标志位的D W O R D ) | 标志(标志位的D W O R D ) |
1 | 0 x F 0 0 0 0 0 2 0 | 0 x ? ? ? ? ? ? ? ? | 0 x 0 0 0 0 0 0 0 1 |
2 | 0 x F 0 0 0 0 0 3 0 | 0 x ? ? ? ? ? ? ? ? | 0 x 0 0 0 0 0 0 0 0 |
Process S 的句柄表中的第二项已经被拷贝到Process T 的句柄表中的第一项(见粉红色)。
DuplicateHandle 也已经将值1 填入Process C 的hObj变量中。值1是Process T 的句柄表中的索引,新项目将被放入该索引。
由于DUPLICATE_SAME_ACCESS标志被传递给了DuplicateHandle,因此Process T 的句柄表中该句柄的访问屏蔽与Process S 的句柄表项目中的访问屏蔽是相同的。
另外,传递DUPLICATE_SAME_ACCESS标志将使DuplicateHandle忽略它的DesiredAccess参数。最后请注意,继承位标志已经被打开,因为给DuplicateHandle 的bInheritHandle参数传递的是TRUE。
显然,你永远不会像在这个例子中所做的那样,调用传递硬编码数字值的DuplicateHandle函数。这里使用硬编码数字,只是为了展示函数是如何运行的。在实际应用程序中,变量可能拥有各种不同的句柄值,可以传递该变量,作为函数的参数。
与继承性一样,DuplicateHandle函数存在的奇怪现象之一是,目标进程没有得到关于新内核对象现在可以访问它的通知。因此,Process C 必须以某种方式来通知Process T,它现在拥有对内核对象的访问权,并且必须使用某种形式的进程间通信方式,以便将h O b j 中的句柄值传递给Process T 。显然,使用命令行参数或者改变Process T 的环境变量是不行的,因为该进程已经启动运行。因此必须使用窗口消息或某种别的IPC机制。
上面是DuplicateHandle的最普通的用法。它是个非常灵活的函数。不过,它很少在涉及3 个不同进程的情况下被使用(因为Process C 不可能知道对象的句柄值正在被Proces s使用)。
通常,当只涉及两个进程时,才调用DuplicateHandle 函数。比如一个进程拥有对另一个进程想要访问的对象的访问权,或者一个进程想要将内核对象的访问权赋予另一个进程。
例如,Process S 拥有对一个内核对象的访问权,并且想要让Process T 能够访问该对象。若要做到这一点,可以像下面这样调用DuplicateHandle :
// ALL of the following code is executed by Process S. // Createamutex object accessible by Process S. HANDLE hObjProcessS = CreateMutex(NULL, FALSE, NULL); // Open ahandle to Process T's kernel object. HANDLE hProcessT = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessIdT); // Anuninitilized handle relative to Process T. HANDLE hObjProcessT; // GiveProcess T accesss to our mutex object DuplicateHandle(GetCurrentProcess(), hObjProcessS, hProcessT, & hObjProcessT, 0 , FALSE, DUPLICATE_SAME_ACCESS); // Usesome IPC mechanism to get the handle // valuein hOnjProcess S into Process T // We nolonger need to communicate with Process T. CloseHandle(hProcessT); // WhenProcess S no longer needs to Use the mutex, // itshould close it. CloseHandle(hObjProcessS);
在这个例子中,对GetCurrentProcess 的调用将返回一个伪句柄,该句柄总是用来标识调用端的进程Process S 。一旦DuplicateHandle 返回,hObjProcessT 就是与Process T 相关的句柄,它所标识的对象与引用Process S 中的代码时hObjProcess S 的句柄标识的对象相同。
Process S 决不应该执行下面的代码:
//ProcessS should never attempt to close the duplicated handle
CloseHandle(hObjProcessT);
如果Process S 要执行该代码,那么对代码的调用可能失败,也可能不会失败。如果Process S 恰好拥有对内核对象的访问权,其句柄值与hObjProcess T 的值相同,那么调用就会成功。该代码的调用将会关闭某个对象,这样Process S 就不再拥有对它的访问权,这当然会导致应用程序产生不希望有的行为特性。
下面是使用DuplicateHandle函数的另一种方法
假设一个进程拥有对一个文件映射对象的读和写访问权。在某个位置上,一个函数被调用,它通过读取文件映射对象来访问它。为了使应用程序更加健壮,可以使用DuplicateHandle 为现有的对象创建一个新句柄,并确保这个新句柄拥有对该对象的只读访问权。然后将只读句柄传递给该函数,这样,该函数中的代码就永远不会偶然对该文件映射对象执行写入操作。下面这个代码说明了这个例子:
intWINAPI WinMain(HINSTANCE hinstExe, HINSTANCE, PSTR pszCmdLine, int nCmdShow) { // Create a file-mapping object; // the handle has read/write access. HANDLE hFileMapRW = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGF_READWRITE, 0 , 10240 , NULL); // Create anotherhandle to the file-mappingobject; // the handle has read-only access. HANDLE hFileMapRO; DuplicateHandle(GetCurrentProcess(),hFileMapRW, GetCurrentProcess(), & hFileMdpRO, FILE_MAP_READ, FALSE, 0 ); // Call the function that should only read // from the mapping. ReadFromTheFileMapping(hFileMapRO); // Close the read-only file-mapping object. CloseHandle(hFileMapRO); // We can still read/write the file-mapping // object using hFileMapRW.When the main code // doesnot access the file mapping anymore CloseHandle(hFileMapRW); }