Windows跨进程共享内核对象

对于Windows内核对象,如互斥锁,信号量,线程对象,文件映射对象等,有时候需要多个进程共享这些对象以协同完成任务,此时就需要跨进程来共享内核对象,对于内核对象判定最简单方法就是在创建过程中有一个 LPSECURITY_ATTRIBUTES 参数。共享内核对象有三种方法:

 

一、继承对象句柄

对于每个进程,系统会分配一个句柄表,句柄表中的每个句柄有个标志位来代表该句柄是否可以继承。当该进程创建子进程时会遍历这个表,如果可以继承,则将这个句柄复制到子进程的句柄表中,由于句柄是内核对象,所以是独立于进程而存在的,对于句柄有一个引用计数,当Create或继承的时候会将引用计数加1,当CloseHandle时会将引用计数减一,当减完后引用计数为0则删除该内核对象。所以当父进程创建子进程,并指定了某个句柄可继承,当子进程创建完成后,就算父进程调用CloseHandle那个句柄,也不会删除,因为子进程创建完成后,引用计数又加一了。

以CreateEvent为例:HANDLE WINAPI  CreateEventA(
    _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes,
    _In_ BOOL bManualReset,
    _In_ BOOL bInitialState,
    _In_opt_ LPCSTR lpName
    );
所有内核对象创建时都需要指定参数1,如果不需要继承则直接设为NULL即可,如果需要继承,则需要定义一个SECURITY_ATTRIBUTES结构体,并将bInheritHandle成员设为TRUE。

SECURITY_ATTRIBUTES sa;
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
sa.nLength = sizeof(sa);

HANDLE hEvent= CreateEvent(&sa, TRUE, TRUE,NULL);

除了在创建时指定是否可继承,如果父进程生成了两个子进程,而只希望一个进程可以继承这个句柄,可以在创建完成后手动修改句柄的继承状态,通过SetHandleInformation函数来指定。

BOOL WINAPI SetHandleInformation(
    _In_ HANDLE hObject,//待指定的对象
    _In_ DWORD dwMask,//待修改的标志位
    _In_ DWORD dwFlags//待修改的标志位的值
    );
目前可修改的dwMask有两个属性值HANDLE_FLAG_INHERIT(是否可继承), HANDLE_FLAG_PROTECT_FROM_CLOSE(是否保护不可被关闭)。HANDLE_FLAG_PROTECT_FROM_CLOSE被指定后,子进程便不可以通过CloseHandle来关闭这个句柄,调用CloseHandle会引发异常,一般不用指定,除非特殊需求。
假设指定hEvent对象可继承,则可以SetHandleInformation(hEvent,HANDLE_FLAG_INHERIT,HANDLE_FLAG_INHERIT); 若要取消让它不可以继承,可以通过SetHandleInformation(hEvent,HANDLE_FLAG_INHERIT,0);

既然有SetXXXInfo那对应的就有GetXXXInfo,所以获取句柄状态可以通过使用GetHandleInformation,(windows的api名字取得真是通俗易懂==)。

DWORD dwFlags = 0;
GetHandleInformation(hEvent, &dwFlags);
bool bok = (dwFlags&HANDLE_FLAG_INHERIT);
if(bok)
{
    cout<<"Handle can be inherit"<<endl;
}

二、通过命名对象创建

对于创建内核对象时很多时候都有个参数用于指定名字,例如上面的CreateEvent的参数四为lpName。这个名字用来代表这个内核对象,如果多个内核对象创建时取的同一个名字相当于就是同一个对象了,调用GetLastError会返回ERROR_ALREADY_EXISTS。使用一个简单的例子来说明问题,比如使用Event来控制单生产者,单消费者,可能会写出下面的代码(为了简单说明问题,代码中缓冲区其实没有共享)

代码是运行在同一个进程下的。假设现在用两个进程来协同完成这项工作,则可以使用命名对象来实现。看下面两个进程的实现方法

代码中输出了句柄的标识符,可以看到是相同的。两个进程共享了eventEmpty和eventFull对象,左边生产者会等待右边消费者来给eventEmpty对象设置信号,右边消费者会等待生产者设置eventFull对象的信号,不管先启动哪个进程,这项任务都可以很好的完成。
另外一个很多博客都说的应用,就是来控制一个程序只允许执行一次也可使用这个命名对象来实现,可以在应用程序入口处创建一个内核对象,如果调用GetLastError后返回ERROR_ALREADY_EXISTS,则关闭应用程序即可。
另外,除了使用CreateXXX,也可以使用OpenXXX,参数都是相同的,区别就是OpenXXX如果该命名对象不存在则返回NULL,而CreateXXX如果该命名对象不存在则新建一个。

三、通过复制对象句柄

复制对象句柄是通过DuplicateHandle来实现,它的函数原型如下

BOOL DuplicateHandle(
   HANDLE hSourceProcessHandle,
   HANDLE hSourceHandle,
   HANDLE hTargetProcessHandle,
   PHANDLE phTargetHandle,
   DWORD dwDesiredAccess,
   BOOL bInheritHandle,
   DWORD dwOptions);

简单说来,该函数取出一个进程的句柄表中的项目,并将该项目拷贝到另一个进程的句柄表中。

第一和第三个参数hSourceProcessHandle和hTargetProcessHandle是内核对象句柄。这些句柄本身必须与调用DuplicateHandle 函数的进程相关。此外,这两个参数必须标识进程的内核对象。
第二个参数hSourceHandle是任何类型的内核对象的句柄。但是该句柄值与调用DuplicateHandle的进程并无关系。相反,该句柄必须与hSourceProcessHandle句柄标识的进程相关。
第三个参数hTargetProcessHandle时拷贝给某个进程的进程对象
第四个参数phTagetHandle是HANDLE 变量的地址,它将接收获取源进程句柄信息拷贝的项目索引。返回的句柄值与hTargetProcessHandle标识的进程相关。
最后3 个参数用于指明该目标进程的内核对象句柄表项目中使用的访问屏蔽值和继承性标志。dwOptions参数可以是0 (零),也可以是下面两个标志的任何组合:DUPLICATE_SAME_ACCESS和DUPLICATE_CLOSE_SOURCE。

如果设定了DUPLICATE_SAME_ACCESS标志,则告诉DuplicateHandle 函数,你希望目标进程的句柄拥有与源进程句柄相同的访问屏蔽。使用该标志将使DuplicateHandle忽略它的dwDesiredAccess参数。

如果设定了DUPLICATE_CLOSE_SOURCE标志,则可以关闭源进程中的句柄。该标志使得一个进程能够很容易地将内核对象传递给另一个进程。当使用该标志时,内核对象的使用计数不会受到影响。

所以DuplicateHandle一般可以设计两个进程或者三个进程,两个进程的情况比如一个进程拥有对另一个进程想要访问的对象的访问权,或者一个进程想要将内核对象的访问权赋予另一个进程。三个进程的情况就是进程C的句柄表中有S和T的进程内核对象的句柄,然后可以把S中的某个内核对象复制给T。但时T并不会知道它什么时候收到了这个复制的内核对象,所以需要自己手动通过Windows消息机制或其他某种方法来告诉它。
用DumplicateHandle改编上面的例子:

梳理下用到的API:
SetConsleTitle:设置控制台的标题,用以保证在FindWindow时找到独一无二的窗口
FindWindow:根据窗口标题名获取窗口句柄
GetWindowThreadProcessId:根据窗口句柄获取进程ID
上图必须要先启动右边的进程,然后左边的进程才能获取到右边的进程ID,并复制句柄到右边的进程的句柄列表中。然后右边的进程再输入句柄的值,从而协调完成任务,效果与上面创建命名对象相同。

注意:在左边的进程中由于target_empty与target_full是用来传递给右边进程的,所以在左边进程中不应该调用CloseHandle(target_full);否则将会使该句柄计数减一,关闭后右边的行为将不可预知。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值