win32内核对象共享和进程通信实例(一)
接《多线程测试(c/c++和python)(二)》,那就不可避免去了解进程的概念,继续深入就是windows的内核对象了。手里的参考是《windows核心编程》和孙鑫的MFC教程,以及侯俊杰的《深入浅出MFC》。接下来写的,可以看作这几个参考资料的学习笔记。
据说MFC早就过时了,但是它用起来方便,对于我了解windows运行机制有很大的帮助;甚至于我看到很多讲解网络编程原理的书用到的都是MFC,所以对我来说,不论过时与否,起码是很有用。当然我在测试一个实例的时候,尽量少使用MFC。
跨进程边界共享内核对象
很多时候,不同进程的线程需要共享内核对象。而内核对象的句柄是与每一个进程相关的,并不是系统全局的,这有利于内核对象的安全性,但是也给访问带来了一些麻烦。比如:一个进程如何获得“另一个进程正在使用的内核对象”呢?
实现进程共享内核对象,有三种方式:使用对象句柄继承;为对象命名;复制对象句柄。
①使用对象句柄继承
父进程在有一个或多个内核对象句柄可以使用的情况下,可以允许子进程访问父进程的内核对象。换句话说,子进程可以继承父进程的对象句柄。
为了创建一个可继承的句柄,父进程必须分配并初始化一个SECURITY_ATTRIBUTES结构,并将这个结构的地址传给具体的Create函数。
msdn中解释SECURITY_ATTRIBUTES结构为一个对象提供安全描述,并通过指定结构的可遗传来指定句柄是否可检索(?)。它定义如下:
typedef struct _SECURITY_ATTRIBUTES { // sa DWORD nLength; LPVOID lpSecurityDescriptor; BOOL bInheritHandle; } SECURITY_ATTRIBUTES;
第一个参数指定结构体的大小;第二个参数,安全描述指针,如果为NULL,表示使用默认安全描述;第三个成员,直译有点奇怪,原文是:Specifies whether the returned handle is inherited when a new process is created. If this member is TRUE, the new process inherits the handle。意思应该是设置TRUE的时候被创建的新进程能继承父进程的句柄。
注意msdn上说的貌似是,父进程创建子进程的时候,子进程(new process)继承父进程的句柄。这里说的还是父进程创建一个内核对象,指定这个新创建的内核对象返回的句柄是可继承的(还没有到创建新进程的步骤)。比如创建一个互斥对象,并指示返回的句柄是可以继承的:
SECURITY_ATTRIBUTES sa; sa.nLenth=sizeof(sa); sa.lpSecurityDescriptor=NULL; sa.bInheritHandle=TRUE; HANDLE hMutex=CreateMutex(&sa,FALSE,NULL);
这样一来返回的hMutex就是可继承的。通过把安全描述结构的bInheritHandle成员设置为True,句柄表关于这个互斥对象的标志位就被设为1。之前提到过进程会维护一个句柄表,初始化是空的,创建一个内核对象,就会在句柄表上增加一条关于新创建内核对象的信息。《windows核心编程》中提供的句柄表结构是这样:
索引 |
指向内核对象内存块的指针 |
访问掩码(包含标志位的一个DWORD) |
标志 |
1 |
0xF0000000 |
0X???????? |
0x00000000 |
2 |
0X00000000 |
(不可用) |
(不可用) |
3 |
0XF000010 |
0x???????? |
0x00000001 |
为了使用对象句柄继承,需要由父进程生成子进程,可通过CreateProcess函数:
BOOL CreateProcess( LPCTSTR lpApplicationName,// pointer to name of executable module LPTSTR lpCommandLine, // pointer to command line string LPSECURITY_ATTRIBUTES lpProcessAttributes, // process security attributes LPSECURITY_ATTRIBUTES lpThreadAttributes, // thread security attributes BOOL bInheritHandles, // handle inheritance flag DWORD dwCreationFlags, // creation flags LPVOID lpEnvironment, // pointer to new environment block LPCTSTR lpCurrentDirectory, // pointer to current directory name LPSTARTUPINFO lpStartupInfo, // pointer to STARTUPINFO LPPROCESS_INFORMATION lpProcessInformation // pointer to PROCESS_INFORMATION );
把第五个参数bInheritHandles设置为TRUE,那么子进程就会继承父进程中“可继承的句柄”。子进程被创建也会分配一张句柄表,当它继承了父进程中“可继承的句柄”,实际上是系统为它把父进程句柄表中可继承的句柄,复制到它的句柄表中。与此同时内核对象的使用计数也加一。
为了使子进程得到它想要的一个内核对象的句柄值,最常见的方式是将句柄值作为命令行参数传给子线程。子线程的初始化代码将解析命令行(比方调用_stscanf_s)并提取句柄值。子进程获得句柄值之后,就会拥有和父进程一样的内核对象访问权限。(我猜这里要表达的意思是,通过继承,子进程只是拥有了对内核对象的访问权限,但是它有时候并不知道自己继承了哪些对象句柄;开发父进程的程序员可以给个文档说明给你,告诉你有父进程里有哪些内核对象是可继承的。你运行你的子进程的时候,可以给子进程传一个句柄的命令行参数)
也可以使用进程通信将继承的内核对象句柄从父进程传入子进程,还有一种方法是让父进程向其环境块添加一个环境变量。变量名称子进程应该知道,变量的值应该是被继承的内核对象的句柄值。这样在父进程生成子进程的时候,这个子进程继承父进程的环境变量,可以通过GetEnvironmentVariable来获得这个继承到的内核对象的句柄值。
改变句柄标志,可以通过SetHandleInformation函数改变内核对象句柄的继承标志:
BOOL SetHandleInformation( HANDLE hObject, // handle to an object DWORD dwMask, // specifies flags to change DWORD dwFlags // specifies new values for flags );
②为对象命名
可以创建命名内核对象的函数有:CreateMutex,CreateEvent,CreateSemaphore,CreateWaitableTimer,CreateFileMapping,CreateJobObject等。
这些函数的最后一个参数都是pszName,如果赋值NULL,表示创建一个匿名的内核对象。为了创建一个可以在进程中共享的内核对象,必须为该对象命名。
比如进程A,创建了一个互斥对象,并命名为JeffMutex:
HANDLE hMutexProcessA=CreateMutex(NULL,FALSE,TEXT"JeffMutex"));
接着,进程B试图调用CreateMutex创建一个互斥对象,同样命名为JeffMutex:
HANDLE hMutexProcessB=CreateMutex(NULL,FALSE,TEXT"JeffMutex"));
这样一来,系统会首先检查(系统中)是否存在一个名字是JeffMutex的内核对象,如果存在就检查该内核对象的类型,也确定是互斥对象类型,那么会执行一次安全检查,检查B进程是否拥有对这个线程的完全访问权限。如果也有访问权限,则会在B的句柄表中查找一个空白的记录项,并将其初始化为指向这个内核对象。这样对于进程B来说,并没有创建一个新的内核对象,而是打开了一个现有的内核对象。关于创建内核对象的函数总是返回具有完全访问权限的句柄,如果想限制一个句柄的访问权限,可以使用这些函数的扩展版本。
与此同时我们也可以用Open*函数直接打开一个现有的内核对象,如OpenMutex等。
③复制对象句柄
可以使用DuplicateHandle函数复制一个对象句柄
BOOL DuplicateHandle( HANDLE hSourceProcessHandle, // handle to the source process HANDLE hSourceHandle, // handle to duplicate HANDLE hTargetProcessHandle, // handle to process to duplicate to LPHANDLE lpTargetHandle, // pointer to duplicate handle DWORD dwDesiredAccess, // access for duplicate handle BOOL bInheritHandle, // handle inheritance flag DWORD dwOptions // optional actions );
这个函数获得一个进程句柄表的一个记录项,然后在另一个进程的句柄表中创建这个记录项的副本。
假设进程S想把一个Mutex对象的访问权授予进程T。那么可以使用下面的方法:
HANDLE hObjInPorcessS=CreateMutex(NULL,FALSE,NULL); HANDLE hProcessT=OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwProcessIdT); HANDLE hObjInProcessT; DuplicateHandle(GetCurrentProcess(),hObjInProcessS,hProcessT,&hObjInProcessT,0,FALSE,DUPLICATE_SAME_ACCESS); CloseHandle(hProcessT); CloseHandle(hObjInProcessS);
下面是调用DuplicateHandle之前和之后,进程S,进程T句柄表的变化情况:
进程S的句柄表
索引 |
指向内核对象快的指针 |
访问掩码(包含标志位的一个DWORD) |
标志 |
1 |
0x00000000 |
(不可用) |
(不可用) |
2 |
0xF0000020(任意内核对象) |
0X???????? |
0X00000000 |
调用DuplicateHandle之前,进程T的句柄表
索引 |
指向内核对象快的指针 |
访问掩码(包含标志位的一个DWORD) |
标志 |
1 |
0x00000000 |
(不可用) |
(不可用) |
2 |
0xF0000030(任意内核对象) |
0X???????? |
0X00000000 |
索引 |
指向内核对象快的指针 |
访问掩码(包含标志位的一个DWORD) |
标志 |
1 |
0xF0000020 |
0X???????? |
0X00000000 |
2 |
0xF0000030(任意内核对象) |
0X???????? |
0X00000000 |
剪切板是系统维护管理的内存区域。简单来说复制就是把数据放入内存区域,粘贴从内存区域取出数据。
设置剪切板数据的过程应该是这样:
先用GlobalAlloc从全局堆中分配出一块内存
HGLOBAL GlobalAlloc( UINT uFlags, // allocation attributes DWORD dwBytes // number of bytes to allocate );
GlobalAlloc第一个参数表示“如何分配内存”,当设置为0的时候,表示默认的GMEM_FIXED方式,分配一块固定的内存,返回一个内存地址指针;还可以设置为GMEM_MOVEABLE表示分配一块可移动的内存,返回一个内存对象句柄,可以使用GlobalLock把句柄转化为指针。
LPVOID GlobalLock( HGLOBAL hMem // handle to the global memory object );
GlobalLock锁定一个全部内存对象并且返回内存对象快第一个字节的指针。它的参数是内存对象的句柄,在这里是GlobalAlloc函数将uFlags设为GMEM_MOVEABLE的返回值。每个内存对象的内部数据结构包含了一个锁的计数。对于移动内存对象。GlobalLock把它的锁计数加一;使用GlobalUnlock函数可以把它的锁计数减一。如果进程使用GlobalLock对一个对象加锁,那么在调用GlobalUnlock之前,被加锁的内存不会被移除或废弃,除非该内存对象被GlobalReAlloc函数重新分配。
然后是打开剪切板,使用OpenClipboard函数:
BOOL OpenClipboard( HWND hWndNewOwner // handle to window opening clipboard打开剪切板的窗口 );
成功返回内非零值,失败返回零。当有其他窗口打开了剪切板,打开剪切板就会失败。对于一个应用程序来说,在每一次成功调用OpenClipboard之后,都应该调用CloseClipboard来关闭剪切板。
使用EmptyClipboard函数清空剪切板,并且获得剪切板的所有权。
BOOL EmptyClipboard(VOID)
使用SetClipboardData设置剪切板数据
HANDLE SetClipboardData( UINT uFormat, // clipboard format HANDLE hMem // data handle );
SetClickboardData将数据以指定的剪切板格式放置在剪切板上。第一个参数指定剪切板格式,可以使用注册剪切板格式和标准的剪切板格式,标准的格式有CF_BITMAP(位图句柄),CF_TEXT(文本格式)等等。
第二个参数为数据句柄。就是使用GlobalAlloc开辟内存的句柄。
测试实例:
#include<windows.h> #include<iostream.h> int main(){ SetConsoleTitle("ClipboardForCommunication"); HWND hwnd=FindWindow(NULL,"ClipboardForCommunication"); //打开剪切板 if(OpenClipboard(hwnd)){ //清空剪切板,并且获得拥有权 EmptyClipboard(); //创建全局内存对象 char *str="A bird fly to sky..."; HANDLE hMem=GlobalAlloc(GMEM_MOVEABLE,strlen(str)+1); //锁定内存块,并且返回内存块数据结构地址 char *data=(char *)GlobalLock(hMem); //把字符串赋拷贝到内存地址 strcpy(data,str); //解锁 GlobalUnlock(hMem); //把内存块中的数据放置在剪切板上 SetClipboardData(CF_TEXT,hMem); }else{ cout<<"Fail to open clipboard"; return 0; } return 0; }
为了减少代码量,我这用console了,先给console窗口设置一个标题,然后使用FindWindow获得它的窗口句柄。运行这个程序将“A bird fly to sky...”设置到剪切板,可以找个记事本粘贴直接粘贴。
然后从剪切板获得数据:
使用OpenClipboard打开剪切板;
使用IsClipboardFormatAvailable判断剪切板是否有指定格式的数据:
BOOL IsClipboardFormatAvailable( UINT format // clipboard format );
使用GetClipboardData获得剪切板数据:
HANDLE GetClipboardData( UINT uFormat // clipboard format );
如果成功,返回剪切板对象句柄(剪切板存放数据句柄),失败返回空
使用GlobalLock将句柄转化为指向内容的指针
....
测试实例:
#include<windows.h> #include<iostream.h> int main(){ SetConsoleTitle("ClipboardForCommunication1"); HWND hwnd=FindWindow(NULL,"ClipboardForCommunication1"); if(OpenClipboard(hwnd)){ if(IsClipboardFormatAvailable(CF_TEXT)){ HANDLE hClip=GetClipboardData(CF_TEXT); char *str=(char *)GlobalLock(hClip); cout<<str<<endl; }else{ cout<<"Clipboard does not exit data of text format"; return 0; } }else{ cout<<"Fail to open clipboard!"; return 0; } return 0; }
可以先运行设置数据的实例,再运行这个实例;或者随便复制一段文字,再运行这个实例。
②匿名管道
首先明确一下,管道是内核对象,匿名管道的通信是父子管道进程之间的通信,为了实现通信,需要使用对象句柄继承。
使用CreatePipe创建匿名管道,返回管道读写句柄
BOOL CreatePipe( PHANDLE hReadPipe, // pointer to read handle PHANDLE hWritePipe, // pointer to write handle LPSECURITY_ATTRIBUTES lpPipeAttributes, // pointer to security attributes DWORD nSize // pipe size );
第一个参数,管道读句柄;第二个参数,管道写句柄;第三个参数,指向安全属性结构,指明返回的句柄是否能被子进程继承;第四个参数,管道大小。
这里要对lpPipeAttributes进行设置,让子进程继承父进程的句柄。创建管道函数跟创建互斥对象函数有区别,互斥对象句柄通过函数返回值返回;创建管道函数,由参数返回,即hReadPipe和hWritePipe。但是无关返回方式,反正是要让子进程继承这两个读写句柄就行了(也就是把父进行的句柄表中在标志位设置为1,前面创建可继承的句柄说明过)
使用CreateProcess创建子进程(一个子管道)
BOOL CreateProcess(
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
首先要把bInheritHandles设置为TRUE,让子进程继承父进程句柄(复制父进程句柄表中可被继承的内核对象信息到子进程的句柄表)
另外还有一个参数,LPSTARTUPINFO,它指向一个STARTUPINFO结构体,用来指定新进程的主窗口如何显示。
STARTUPINFO结构体,被用于指定新线程主窗口的属性。对于图形用户接口(GUI)进程,它的信息影响到创建第一个窗口的CreateWindow函数,和显示窗口的ShowWindow函数。对于控制台进程,它的信息影响控制台窗口窗口。进程可以通过GetStartupInfo函数获得指定的STARTUPINFO信息。它的成员变量有点夸张:
typedef struct _STARTUPINFO { // si DWORD cb; LPTSTR lpReserved; LPTSTR lpDesktop; LPTSTR lpTitle; DWORD dwX; DWORD dwY; DWORD dwXSize; DWORD dwYSize; DWORD dwXCountChars; DWORD dwYCountChars; DWORD dwFillAttribute; DWORD dwFlags; WORD wShowWindow; WORD cbReserved2; LPBYTE lpReserved2; HANDLE hStdInput; HANDLE hStdOutput; HANDLE hStdError; } STARTUPINFO, *LPSTARTUPINFO;
它有一个dwFlags成员,用来指定子进程创建窗口的时候,结构体中的成员是否被使用。当把dwFlags设置为STARTF_USESTDHANDLES的时候,需要设置结构体成员中的hStdInput,hStdOutput和hStdError三个参数,分别表示子进程的标准输入、标准输出和标准出错句柄。我们再把创建管道的时候,返回的hReadPipe和hWritePipe分别赋值给hStdInput和hStdOutput,这样子进程可以通过读取自己的输入输出句柄获得管道的读写句柄。
使用ReadFile和WriteFile向管道读写数据
......
实例代码
PipParent
#include<windows.h> #define IDB_CREATE 1000 #define IDB_WRITE 1001 #define IDB_READ 1002 HINSTANCE hInstance; LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM); int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd){ hInstance=hInstance; WNDCLASS wc; wc.cbClsExtra=0; wc.cbWndExtra=0; wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); wc.hCursor=LoadCursor(NULL,IDC_ARROW); wc.hIcon=LoadIcon(NULL,IDI_APPLICATION); wc.hInstance=hInstance; wc.lpfnWndProc=WndProc; wc.lpszClassName="Pipe_Parent"; wc.lpszMenuName=NULL; wc.style=CS_HREDRAW|CS_VREDRAW; if(!RegisterClass(&wc)){ MessageBox(NULL,"Rigister Window Class failed","error",MB_ICONERROR); return 0; } HWND hwnd=CreateWindow("Pipe_parent","Father Pipe",WS_OVERLAPPEDWINDOW,300,300,340,150,NULL,NULL,hInstance,NULL); ShowWindow(hwnd,nShowCmd); UpdateWindow(hwnd); MSG msg; while(GetMessage(&msg,NULL,0,0)){ TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam){ HDC hdc; PAINTSTRUCT ps; static HANDLE hReadPipe,hWritePipe; switch(message){ case WM_CREATE: CreateWindow("Button","创建管道",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,10,10,100,100,hwnd,(HMENU)IDB_CREATE,hInstance,NULL); CreateWindow("Button","写入数据",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,120,10,100,100,hwnd,(HMENU)IDB_WRITE,hInstance,NULL); CreateWindow("Button","读取数据",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,230,10,100,100,hwnd,(HMENU)IDB_READ,hInstance,NULL); return 0; case WM_PAINT: hdc=BeginPaint(hwnd,&ps); EndPaint(hwnd,&ps); return 0; case WM_COMMAND: switch(LOWORD(wParam)){ case IDB_CREATE: { //创建管道 SECURITY_ATTRIBUTES sa; sa.bInheritHandle=TRUE; sa.lpSecurityDescriptor=NULL; sa.nLength=sizeof(SECURITY_ATTRIBUTES); if(!CreatePipe(&hReadPipe,&hWritePipe,&sa,0)){ //创建管道失败 MessageBox(hwnd,"Fail to create a pipe","error",MB_ICONERROR); return 0; } //创建线程 STARTUPINFO si; ZeroMemory(&si,sizeof(STARTUPINFO)); si.dwFlags=STARTF_USESTDHANDLES; si.hStdInput=hReadPipe; si.hStdOutput=hWritePipe; si.hStdError=GetStdHandle(STD_ERROR_HANDLE); PROCESS_INFORMATION pi; TCHAR pipeChildPath[]="..\\PipeChild\\Debug\\PipeChild.exe"; //开启的子进程所在目录 if(!CreateProcess(pipeChildPath,NULL,NULL,NULL,TRUE,0,NULL,NULL,&si,&pi)) { CloseHandle(hReadPipe); CloseHandle(hWritePipe); hReadPipe=NULL; hWritePipe=NULL; MessageBox(hwnd,"Fail to Create a process","error",MB_ICONERROR); return 0; }else{ CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } return 0; } case IDB_WRITE: { //写入 TCHAR writeBuff[]="A Bird Fly To Sky...(from parent pipe)"; DWORD dwWrite; if(!WriteFile(hWritePipe,writeBuff,strlen(writeBuff),&dwWrite,NULL)){ MessageBox(hwnd,"Fail to Write data to pipe","error",MB_ICONERROR); return 0; } return 0; } case IDB_READ: { //读取数据 TCHAR readBuff[100]; ZeroMemory(readBuff,sizeof(readBuff)); DWORD dwRead; if(!ReadFile(hReadPipe,readBuff,100,&dwRead,NULL)){ MessageBox(hwnd,"Fail to Read data from pipe","error",MB_ICONERROR); return 0; }else{ MessageBox(hwnd,readBuff,"msg",MB_OK); } return 0; } } case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd,message,wParam,lParam); }
PipChild
#include<windows.h> #define IDB_WRITE 1010 #define IDB_READ 1011 HINSTANCE hInstance; LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM); int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd){ hInstance=hInstance; WNDCLASS wc; wc.cbClsExtra=0; wc.cbWndExtra=0; wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); wc.hCursor=LoadCursor(NULL,IDC_ARROW); wc.hIcon=LoadIcon(NULL,IDI_APPLICATION); wc.hInstance=hInstance; wc.lpfnWndProc=WndProc; wc.lpszClassName="Pipe_Child"; wc.lpszMenuName=NULL; wc.style=CS_HREDRAW|CS_VREDRAW; if(!RegisterClass(&wc)){ MessageBox(NULL,"Rigister Window Class failed","error",MB_ICONERROR); return 0; } HWND hwnd=CreateWindow("Pipe_Child","Chile Pipe",WS_OVERLAPPEDWINDOW,700,300,240,150,NULL,NULL,hInstance,NULL); ShowWindow(hwnd,nShowCmd); UpdateWindow(hwnd); MSG msg; while(GetMessage(&msg,NULL,0,0)){ TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam){ HDC hdc; PAINTSTRUCT ps; static HANDLE hRead,hWrite; switch(message){ case WM_CREATE: hRead=GetStdHandle(STD_INPUT_HANDLE); hWrite=GetStdHandle(STD_OUTPUT_HANDLE); CreateWindow("Button","写入数据",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,10,10,100,100,hwnd,(HMENU)IDB_WRITE,hInstance,NULL); CreateWindow("Button","读取数据",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON,120,10,100,100,hwnd,(HMENU)IDB_READ,hInstance,NULL); return 0; case WM_PAINT: hdc=BeginPaint(hwnd,&ps); EndPaint(hwnd,&ps); return 0; case WM_COMMAND: switch(LOWORD(wParam)){ case IDB_WRITE: { //写入 TCHAR writeBuff[]="The Sky Becomes lively Rise...(from child pipe)"; DWORD dwWrite; if(!WriteFile(hWrite,writeBuff,strlen(writeBuff),&dwWrite,NULL)){ MessageBox(hwnd,"Fail to Write data to pipe","error",MB_ICONERROR); return 0; } return 0; } case IDB_READ: { //读取数据 TCHAR readBuff[100]; ZeroMemory(readBuff,sizeof(readBuff)); DWORD dwRead; if(!ReadFile(hRead,readBuff,100,&dwRead,NULL)){ MessageBox(hwnd,"Fail to Read data from pipe","error",MB_ICONERROR); return 0; }else{ MessageBox(hwnd,readBuff,"msg",MB_OK); } return 0; } } case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd,message,wParam,lParam); }
为了运行效果更明显,这里用win32应用程序了。另外我尽量每次都贴出完整的代码,这样浪费篇幅,但是比较实用。这也是我尽量不写MFC程序的原因;另外这两个程序可能有些资源没有完全释放...
程序运行过程如下:
先运行PipeParent,显示Father Pipe窗口,点击创建管道,Child Pipe窗口显示,点击Child Pipe上的写入数据,再点击Father Pipe上的读取数据,弹出对话框。重点是子进程必须是通过父进程创建的,不能直接编译运行子进程。写入数据和读取数据可以随便点...