windows下匿名管道的使用
windows中有时候需要使用多进程进行编程,有的时候是因为一个复杂的过程需要开启一个新的进程进行处理,又或者仅仅是需要调用一个已经存在的exe等等,创建多个进程时,进程之间的通信是一个重要的问题,下面讨论一下如何使用匿名管道,在父子进程中进行通信。
使用情况:父进程使用createProcess与子进程进行通信。
管道(pipe)和现实中的管道的作用非常相似,从一端放入东西(数据),这些东西(数据)就可以通过管道,从另一端被取出来。在这里,管道的两端就是计算机上需要通信的两个进程,一个进程向管道发送数据,另一个进程从管道读取数据,这样就完成了进程间的通信。
首要问题:两个进程既然要通过管道进行通信,那么这两个进程必须获得一个(同一个)管道,当父进程创建了一个管道之后,子进程如何得到父进程创建的这个管道呢,如果这个管道是此处所讨论的匿名管道,那么子进程可以会通过集成的方式的到父进程创建的管道。
由于父进程创建子进程的时候,子进程并没有集成父进程的地址空间(每个进程都有自己独立的地址空间),所以子进程并不能直接访问父进程的数据,也就是说这两个进程之间无法向同一个进程中不同的线程那样直接使用变量等进行通信。但是子进程创建之后会集成父进程所创建的能够被继承的句柄,因此,在父进程创建管道的时候(实际上或得到创建的管道的句柄),通过制定这个管道句柄为可集成的,创建子进程的时候,这个句柄就有机会被子进程集成得到(实际上还需要在调用createProcess函数创建子进程时指明继承特性,如下述)。
创建管道函数如下:
BOOL __stdcal CreatePipe(
PHANDLE
hReadPipe,
PHANDLE
hWritePipe,
LPSECURITY_ATTRIBUTES
lpPipeAttributes,
DWORD nSize
);
其中hReadPipe,hWritePipe这两个句柄为创建的管道的写句柄和读句柄,从名称就可以看出,我们使用hWritePipe句柄向管道写入数据,使用hReadPipe句柄从管道读出数据,nSize指定创建管道的大小,如果为0,则使用windows提供的默认值。
第三个参数是一个LPSECURITY_ATTRIBUTES类型的结构体,定义如下:
typedef struct _SECURITY_ATTRIBUTES {
DWORD
nLength;
LPVOID
lpSecurityDescriptor;
BOOL bInheritHandle;
}
SECURITY_ATTRIBUTES;
nLength永远被设置为sizeof(SECURITY_ATTRIBUTES),lpSecurityDescriptor为安全描述符,可以简单设置为NULL,使用默认设置,与本处讨论无关,第三个参数bInheritHandle设置为true时,表示使用这个安全属性(SECURITY_ATTRIBUTES)创建的内核对象,是可以被子进程继承的,同理,如果bInheritHandle为false,子进程则无法继承父进程创建的相应句柄,也就无法对这个句柄进行访问。
到此,第一个问题就解决了。在父进程中创建一个管道句柄,创建的时候通过设置参数lpPipeAttributes的成员bInheritHandle为true,使得子进程可以继承到父进程创建的句柄,那么子进程该如何使用父进程创建的句柄呢,显然它无法直接取得父进程的任何变量,那么就有了第二个问题,句柄的传递。
在讨论父进程如何将句柄传递给子进程之前,有必要先看一下创建子进程的函数:
BOOL __stdcal CreateProcess(
LPCSTR
lpApplicationName,
LPSTR
lpCommandLine,
LPSECURITY_ATTRIBUTES
lpProcessAttributes,
LPSECURITY_ATTRIBUTES
lpThreadAttributes,
BOOL
bInheritHandles,
DWORD
dwCreationFlags,
LPVOID lpEnvironment,
LPCSTR lpCurrentDirectory,
LPSTARTUPINFOA
lpStartupInfo,
LPPROCESS_INFORMATION
lpProcessInformation
);
其中lpApplicationName设定子进程的名字,lpCommandLine设定子进程的参数,lpProcessAttributes和lpThreadAttributes设定子进程的安全性,dwCreationFlags设定子进程将如何创建,lpEnvironment设定子进程的环境块,lpCurrentDirectory设定子进程的当前目录,这些与本主题关系不大,不详加解释。具体用法可查阅相关资料。进程创建过程中,本函数提供了进程创建需要的各种参数,其中的bInheritHandles,在本主题至关重要,如果希望子进程能够继承父进程的句柄,需要设置bInheritHandles为true,这样,在子进程中就能访问父进程中那些可以被继承的句柄,对父进程中那些不能继承的句柄,子进程依旧不能访问。若bInheritHandles为false,则子进程不能访问父进程所有的句柄。父进程创建的句柄可以通过lpCommandLine参数传递给子进程,下面以创建文件句柄为例给出具体方法:
父进程中:HANDLE file;
... ...
createProcess("child.exe",(char*)&file,0,0,TRUE,NULL,0,0,&StartupInfo,&ProcessInformation);
创建成功后,子进程中:
HANDLE cFile = (HANDLE)(*argv[0]);
这样,在子进程中就可以得到父进程传递过来的文件句柄,从而对文件进行读写。此处是父子进程之间进行通信,我们采用另一种方法传递句柄。
createProcess中的参数lpStartupInfo里面有很多的成员,在此不一一列举,我们只看我们所需要的三个:hStdError,hStdOutput,hStdInput,也就是标准错误输出句柄,标准输出句柄,标准输入句柄。在父进程中可以指定子进程的这三个句柄,父进程可以通过将createPipe生成的句柄付给这三个句柄,之后在子进程中,子进程将标准句柄取出,即得到了父进程传递给它的句柄,从而可以使用这个句柄进行沟通:
可能的简要伪代码如下:
父进程:
CreatePipe(&hRead, &hWrite, &saAttr, 0);
STARTUPINFO ssi;
ssi.hStdError = hWrite;
ssi.hStdOutput = hWrite;
createProcess("child.exe",handleBuffer,0,0,TRUE,NULL,0,0,&ssi,&ProcessInformation);
readMessage(hRead);
子进程:
HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
writeMessage(hStdout);