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

 
调用DuplicateHandle之后,进程T的句柄表

索引

指向内核对象快的指针

访问掩码(包含标志位的一个DWORD)

标志

1

0xF0000020

0X????????

0X00000000

2

0xF0000030(任意内核对象)

0X????????

0X00000000

可以看出调用DuplicateHandle之后,进程T的句柄表中,在索引1的位置增加了一条来自进程S句柄表的数据;如果把函数的参数bInheritHandle设为TRUE,新增记录的标志就为0x00000001,表示内核对象是可继承的。
进程通信实例
进程通信至少包括四种方式,分别是剪贴板、匿名管道、命名管道、邮槽。(有点晕,我后面把剪贴板都写成剪切板了?说能告诉我这两者有什么区别?)
①剪切板

剪切板是系统维护管理的内存区域。简单来说复制就是把数据放入内存区域,粘贴从内存区域取出数据。
设置剪切板数据的过程应该是这样:
先用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上的读取数据,弹出对话框。重点是子进程必须是通过父进程创建的,不能直接编译运行子进程。写入数据和读取数据可以随便点...

 

posted @ 2020-05-11 01:00  vocus  阅读(643)  评论(0编辑  收藏  举报