最近学校做课程设计,好多朋友问我关于windows内核对象的相关问题,其实我也不懂太多,只能假装自己懂,然后满口答应过些天一定给个答复。这就是中国人的特点,不懂也要装懂,打肿脸也要充胖子。可已经答应别人了,也不能就此长眠于地下吧~只能拿起我的老办法,去图书馆了。这一查不打紧,关于windows编程的书那是书山书海啊!~当时就晕了,不过我还是硬着头皮看了些。看到有所收获时就记录下来,免得过段时间忘了没地方去查,这里就和大家分享了。
Windows内核对象是什么我这里就不介绍了,任何一本关于windows编程的书都会介绍,我这里就把我找到的关于跨越进程边界共享内核对象的方法和大家分享了。
在windows系统中想要跨越进程边界共享内核有3种方法,当然不排除以后还有更多方法,或是一些变种的方法。
- 继承对象句柄
看到继承大家肯定不陌生,了解一点面向对象知识的好青年都知道这是个什么意思。简单来说就是你爸爸生你,你继承你爸爸的一点特性罢了。当两个进程具有父子关系时,就可以使用这种对象句柄的继承方法来跨越进程边界,共享进程内核对象。父进程在创建子进程时可以为子进程赋予父进程拥有的内核对象的访问权限。不过想要这么做就些前提条件。
首先,当父进程在创建内核对象时必须向系统指明,它希望这个内核对象的句柄是个可继承的句柄。这里不是说内核对象可以继承,而仅仅是句柄可以继承。怎么实现这个设定呢?父进程只需要指定一个SECURITY_ATTRIBUTES结构,并将这个结构的bInheritHandle属性设置为TRUE就行了,然后在创建内核对象时把这个结构体传递给创建函数,那么返回的句柄就是一个可继承的句柄。像下面这样:
- SECURITY_ATTRIBUTES s;
- s.nLength = sizeof(s);
- s.lpSecurityDescriptor = NULL;
- s.bInheritHandle = TRUR;
- //创建一个互斥对象,返回的句柄可继承
- HANDLE hMutex = CreateMutex(&s, FALSE, NULL);
然后在创建子进程时将入参bInheritHandles设置为TRUE就行了,像下面这样:
- CreateProcess(
- PCTSTR pszApplicationName,
- PTSTR pszCommandLine,
- PSECURITY_ATTRIBUTES psaProcess,
- PSECURITY_ATTRIBUTES pszThread,
- BOOL bInheritHandles, //设置为TRUE就行了
- DWORD dwCreationFlags,
- PVOID pvEnvironment,
- PCTSTR pszCurrentDirectory,
- LPSTARTUPINFO pStartupInfo,
- PPROCESS_INFOMATION pProcessInfomation);
当把bInheritHandles设置为TRUE时,系统创建这个子进程后会把父进程中能够继承的所有句柄项拷贝到子进程的句柄表中,这样子进程就可以控制父进程所拥有的内核对象了,当然这个过程不是实时的,也就是说在创建了子进程后,如果父进程再创建了一个可继承的内核对象时,这个在子进程生成后创建的可继承内核对象是不会被这个子进程继承的。真绕口。。。简单点就是时过境迁,物是人非,过了这村就没这店了。
- 使用命名对象
这个方法是个人都能想到,当你想找一个人的时候,首先想到的是他的名字(也可能是他的长相,好吧,就当我YY吧!),我们在创建内核对象时为他们命名就可以在其他进程需要使用这个内核对象时调用它的名字就行了。比如我上面那个创建互斥内核对象的函数CreateMutex(),它的原型其实是下面这样的:
- HANDLE CreateMutex(
- PSECURITY_ATTRIBUTES psa,
- BOOL bInitialOwner,
- PCTSTR pszName);
这个函数的最后一个参数就是这个内核的名字,我上面传递的是NULL,也就是说我创建的是一个没有名字的内核对象,但我如果传递一个以0结尾的字符串指针时,这个内核对象就有了名字。不过系统中的所有内核对象共享一个名字空间,这意味着如果你创建的内核对象的名字已经存在使用了,那么你将会创建失败。当你自己创建命名对象时,系统会查找命令空间中的所有内核对象,如果发现有同名的对象就检查对象的类型,如果类型相同再检查调用者是否对该对象有完整的访问权限,如果有这种权限才会把内核对象的句柄拷贝到该进程的句柄表,否则将创建对象失败,当然你可以很明显的看出即使创建对象成功也不一定是创建了一个新的对象,而有可能是一个已经存在的内核对象,所以这个时候如果你想要区分开这种创建全新的内核对象和返回已经存在的内核对象,那么你就应该使用open这套函数,而不是create。比如:
- HANDLE OpenMutex(
- DWORD dwDesiredAccess,
- BOOL bInheritHandle,
- PCTSTR pszName);
这套函数只打开已经存在名字的内核对象,而不是创建它,那么当找不到该名字的内核对象时就会返回NULL。这就是使用命名的方法来共享内核对象。
- 复制对象句柄
最后一个方法是复制对象的句柄,通过上面2种方法相信你已经能看出所谓的共享内核对象,就是把一个进程的内核对象句柄表复制到另一个进程的句柄表中。那么我们就可以直接调用DuplicateHandle函数来实现对象句柄的复制。函数原型如下:
- BOOL DuplicateHandle(
- HANDLE hSourceProcessHandle,
- HANDLE hSourceHandle,
- HANDLE hTargetProcessHandle,
- PHANDLE phTargetHandle,
- DWORD dwDesiredAccess,
- BOOL hInheritHandle,
- DWORD dwOptions);
该函数实际上需要3个进程来实现,调用DuplicateHandle函数的进程B可以把进程A能够访问的内核对象句柄传递给进程C,这个进程拥有进程A和进程C的句柄hSourceProcessHandle和hTargetProcessHandle,而hSourceHandle参数就是进程A能够访问的内核句柄,phTargetHandle参数是进程C接受hSourceHandle句柄在进程C句柄表中的地址,在调用的时候把DUPLICATE_SAME_ACCESS标志传递给dwOptions参数就行了。但这有个问题,就是即使你调用了这个函数,使得进程C拥有了对某个内核对象的访问权,但进程C并不知道这一点,所以一般来说进程A还需要通知进程C它已经获得了的权限。
这三种方法当中我更倾向于使用第一种方法,因为通过父子进程继承内核对象的访问方式更加安全,而且逻辑会更加清晰,而第二种和第三种总让人有不放心的感觉。呵呵~~个人愚见,还请见谅~~~爱生活~~爱coding~~