有3个不同的机制 :
1对象句柄的继承性
只有当进程具有父子关系时,才能使用对象句柄的继承性。
首先,当父进程创建内核对象时,必须向系统指明,它希望对象的句柄是个可继承的句柄。请记住,虽然内核对象句柄具有继承性,但是内核对象本身不具备继承性。
若要创建能继承的句柄,父进程必须指定一个SECURITY_A TTRIBUTES结构并对它进行初始化 ,
下 面 的 代 码 用 于 创 建 一 个 互 斥 对 象 ,并将一个可继承的句柄返回给它:
SECURITY_ATTRIBUTES sa;
sa.nLength=sizeof(sa);
sa.lpSecurityDescriptor=NULL; //默认安全性
sa.bInheritHandle=TRUE; //让该句柄具有继承性
HANDLE hMutex = CreateMutex(&sa,FALSE,NULL);
使用对象句柄继承性时要执行的下一个步骤是让父进程生成子进程。这要使用C r e a t eP r o c e s s函数来完成:
BOOL CreateProcess(
PCTSTR pszApplicationName,
PTSTR pszCommandLine,
PSECURITY_ATTRIBUTES psaProcess,
PSECURITY_ATTRIBUTES pszThread,
BOOL bInheritHandles,
DWORD dwCreationFlags,
PVOID pvEnviroment,
PCTSTR pszCurrentDirectory,
LPSTARTUPINFO pStartupInfo,
PPROCESS_INFORMATION pProcessInformation);
注意 bInheritHandles这个参数 ,一般 来 说 , 当 生 成 一 个 进 程 时 , 将 为 该 参 数 传 递 FA L S E。 该 值 告 诉 系 统 , 不 希 望 子 进 程 继 承 父 进程的句柄表中的可继承句柄。但是,如果为该参数传递T R U E ,那么子进程就可以继承父进程的可继承句柄值。
除了拷贝句柄表项目外,系统还要递增内核对象的使用计数,因为现在两个进程都使用该对象。如果要撤消内核对象,那么父进程和子进程必须调用该对象上的C l o s e H a n d l e函 数 , 也可以终止进程的运行。
2改变句柄的标志
有时会遇到这样一种情况,父进程创建一个内核对象,以便检索可继承的句柄,然后生成两个子进程。父进程只想要一个子进程来继承内核对象的句柄。
可以调用SeHandleInformation函数:
BOOL SetHandleInformation(
HANDLE hObject,
DWORD daMask,
DWORD dwFlags);
可以看到,该函数拥有 3 个参数。第一个参数h O b j e c t 用 于 标 识 一 个 有 效 的 句 柄 。 第 二 个 参数d w M a s k告 诉 该 函 数 想 要 改 变 哪 个 或 那 几 个 标 志 。 目 前 有 两 个 标 志 与 每 个 句 柄 相 关 联 :
#define HANDLE_FLAG_INFERIT 0✖️00000001
#define HANDLE_FLAG_PROTECT_FROM_CLOSE 0✖️00000002
要 打开一个内核对象句柄的继承标志,请创建下面的代码:
SetHandleInformation(hobj,HANDLE_FLAG_INHERIT,HANDLE_FLAG_INHERIT);
若要关闭该标志,请创建下面的代码:
SetHandleInformation(hobj,HANDLE_FLAG_INHERIT,0);
H A N D L E _ F L A G _ P R O T E C T _ F R O M _ C L O S E标 志 用 于 告 诉 系 统 , 该 句 柄 不 应 该 被 关 闭 :
SetHandleInformation(hobj,HANDLE_FLAG_PROTECT_FROM_CLOSE,HANDLE_FLAG_PROTECT_FROM_CLOSE);
CloseHandle(hobj);//Exception is raised
3命名对象
共享跨越进程边界的内核对象的第二种方法是给对象命名 ,下面的所有函数都可以创建命名的内核对象 :
HANDLE CreateMutex(
PSECURITY_ATTRIBUTES psa,
BOOL bInitialOwner,
PCTSTR pszName
);
..
CreateEvent
..
CreateSemaphore
..
CreateWaitableTimer
..
CreateFileMapping
..
CreateJobObject
..
所有这些函数都有一个共同的最后参数 p s z N a m e。当为该参数传递N U L L 时,就向系统指明了想创建一个未命名的(匿名)内核对象。
如果试图创建一个称为“ J e ff O b j” 的 对 象 , 那 么 不 能 保 证 系 统 中不存在一个名字为“J e ff O b j ” 的 对 象 。 更 为 糟 糕 的 是 , 所 有 这 些 对 象 都 共 享 单 个 名 空 间 。 由 于这个原因,对下面这个C r e a t e S e m a p h o r e函数的调用将总是返回N U L L :
HANDLE hMutex=CreateMutex(NULL,FALSE,"JeffObj");
HANDLE hSem=CreateSemaphore(NULL,1,1,"JeffObj");
DWORD dwErrorCode=GetLastError();
如果在执行上面的代码后观察d w E r r o r c o d e的值,会看到返回的代码是6 (E R R O R _INV ALID_HANDLE)。
应用程序先确定是否创建了一个新内核对象,否则就打开了一个现有的对象。方法是在调用C r e a t e *函数后立即调用G e t L a s t E r r o r:
if(GetLastError()==ERROR_ALREADY_EXISTS){
<span style="white-space:pre"> </span>//打开一个现有的句柄对象
}else{
<span style="white-space:pre"> </span>//创建一个新句柄对象
}
打开一个现有的句柄对象,调用下面显示的O p e n * 函数中的某一个:
HANDLE OpenMutex(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
PCTSTR pszName
);
..
OpenEvent
..
OpenSemaphore
..
OpenWaitableTimer
..
OpenFileMapping
..
OpenJobObject
..
最后一个参数p s z N a m e用 于 指 明 内 核 对 象 的 名 字 。
Window程序防止双开:
i
nt WINAPI WinMain(HINSTANCE hinstExe,HINSTANCE,PSTR pszCmdLine,int nCmdShow){
HANDLE h= CreateMutex(NULL,FLASE,"myMutex");
if(GetLastError()==ERROR_ALREADY_EXITSTS){
//说明已经有一个实例程序在运行,这个试图双开的程序即将结束
return 0;
}
//这里是你的第一个实例程序,依然在运行中
...
//结束时要把句柄关闭
CloseHandle(h);
return 0;
}
5复制对象句柄
共享跨越进程边界的内核对象的最后一个方法是使用D u p l i c a t e H a n d l e函数 :
BOOL DuplicateHandle(
HANDLE hSourceProcessHandle,
HANDLE hSourceHandle,
HANDLE hTargetProcessHandle,
PHANDLE phTargetHandle,
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwOptions
);
D u p l i c a t e H a n d l e函数最普通的用法要涉及系统中运行的3个不同进程。
当调用 D u p l i c a t e H a n d l e函数时,第一和第三个参数h S o u r c e P r o c e s s H a n d l e和 h Ta rg e tP r o c e s s H a n d l e是内核对象句柄。这些句柄本身必须与调用D u p l i c a t e H a n d l e函 数 的 进 程 相 关 。
第二个参数 h S o u r c e H a n d l e是任何类型的内核对象的句柄。但是该句柄值与调用D u p l i c a t eH a n d l e的进程并无关系。相反,该句柄必须与h S o u r c e P r o c e s s H a n d l e句 柄 标 识 的 进 程 相 关 。 第四个参数phTargetHandle是HANDLE变量的地址,它将接收获取源进程句柄信息拷贝的项目索引。
D u p l i c a t e H a n d l e的最后3 个 参 数 用 于 指 明 该 目 标 进 程 的 内 核 对 象 句 柄 表 项 目 中 使 用 的 访 问屏 蔽 值 和 继 承 性 标 志 。d w O p t i o n s参 数 可 以 是0 ( 零 ), 也 可 以 是 下 面 两 个 标 志 的 任 何 组 合 :DUPLICA TE_SAME_ACCESS和DUPLICA TE_CLOSE_SOURCE。
如果设定了DUPLICATE_SAME_ACCESS标志,则告诉DuplicateHandle函数,你希望目标进程的句柄拥有与源进程句柄相同的访问屏蔽。使用该标志将使D u p l i c a t e H a n d l e忽略它的d w D e s i r e d A c c e s s参数。
如果设定了 DUPLICA TE_CLOSE_SOURCE标志,则可以关闭源进程中的句柄。该标志使得一个进程能够很容易地将内核对象传递给另一个进程。当使用该标志时,内核对象的使用计数不会受到影响。
可以像下面这样调用 D u p l i c a t e H a n d l e :
//下面这些代码是运行在Process S
//在Process S中创建互次对象
HANDLE hObjProcessS=CreateMutes(NULL,FALSE,NULL);//匿名对象
//打开Process T的内核对象
HANDLE hProcessT=OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwProcessIdT);
//没有初始化的句柄对象,即将把 hObjProcessS复制给它并属于Process T
HANDLE hObjProcessT;
DuplicateHandle(GetCurrentProcess(),HObjProcessS,hProcessT,&hObjProcessT,0,FALSE,DUPLICATE_SAME_ACESS);
..
..
//不再使用ProcessT 时,关闭
CloseHandle(hProcessT);
//不再使用互斥对象时,
CloseHandle(hObjProcessS);
//一旦 D u p l i c a t e H a n d l e返回, h O b j P r o c e s s T 就是与 P r o c e s s T 相关的句柄
//一定不要执行下面的代码。
//CloseHandle(hObjProcessT);
下面是使用 D u p l i c a t e H a n d l e函 数 的 另 一 种 方 法 。 假 设 一 个 进 程 拥 有 对 一 个 文 件 映 射 对 象 的读和写访问权。在某个位置上,一个函数被调用,它通过读取文件映射对象来访问它。为了使应 用 程 序 更 加 健 壮 , 可 以 使 用D u p l i c a t e H a n d l e为 现 有 的 对 象 创 建 一 个 新 句 柄 , 并 确 保 这 个 新 句柄拥有对该对象的只读访问权。然后将只读句柄传递给该函数,这样,该函数中的代码就永远不会偶然对该文件映射对象执行写入操作。下面这个代码说明了这个例子:
int WINAPI WinMain(HINSTANCE,hinstExe,HINSTANCE,LPSTR szCmdLine,intnCmdShow){
HANDLE hFileMapRW= CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,10240,NULL);
HANDLE hFileMapRO;
DuplicateHandle(GetCurrentProcess(),hFileMapRW,GetCurrentProcess(),&hFileMapRO,FILE_MAP_READ,FALSE,0);
ReadFromTheFileMapping(hFileMapRO);
//...Read only
CloseHandle(hFileMapRO);
//....Read and write
CloseHandle(hFileMapRW);
}