分享

进程间通信---1.匿名管道

 semo_zhang 2013-06-08
1、匿名管道
实现进程间通信,是各个进程间互相交流的桥梁。
本节所讲述的匿名管道有以下几个主要的性质:
1、匿名管道只能实现本地进程间的通信,跨网络的进程间的通信无法通过匿名管道实现的。
2、匿名管道采用半双工的方式通信,且通信的进程间是有血缘关系的,及父子关系或者兄弟关系进程,由此可知并不是本地计算机上任意两个进程就能通过匿名管道进行通信。
3、匿名管道有个重要的功能就是可以通过匿名管道的来实现子进程输出的的重定向。
下面是一个小小的实例,表现了重定向在具体模型中怎么用:

比如我现在建立一个 Win32 的 Console 程序,然后在其中使用如下代码来输出一些信息:

#include <iostream>
using namespace std;
 
int main(int argc, char * argv)
{
    cout<<"Zachary  XiaoZhen "<<endl<<endl;
    cout<<"Happy  New   Year"<<endl<<endl;
    
    system("pause");
}

那么在默认下,编译运行上面的代码时,Windows 会弹出一个黑框框,并且在这个黑框框中显示一些信息,

image

为什么一定要将输出的信息显示在这个黑框框中呢?有没有办法让其显示在我们自己定义的文本框中呢?

而后我们再看一幅截图:

QQ截图未命名

上面画了很多红线的这个区域中的信息来自那里呢?为什么会在这个文本框中输出呢?

其实这就可以通过匿名管道来实现,

在卸载 QQ 游戏这幅截图中呢,其实运行了两个进程,

一个就是我们看到的这个输出了图形界面的进程,我们称之为卸载表象进程(父进程),

而另外一个用来执行真正意义上的卸载的进程我们称之为卸载实质进程(子进程)。

其实该卸载表象进程在其执行过程中创建了卸载实质进程来执行真正的卸载操作,

而后,卸载实质进程会输出上面用红色矩形标记的区域中的信息,

如果我们使用默认的输出的话,卸载实质进程会将上面红色区域标记中的信息输出到默认的黑框框中,

但是我们可以使用匿名管道来更改卸载实质进程的输出,

让其将输出数据输入到匿名管道中,而后卸去除格式载表象进程从匿名管道中读取到这些输出数据,

然后再将这些数据显示到卸载表象进程的文本框格式刷格式刷就可以了。

而上面的这种用来更改卸载实质进程的输出的技术就称之为输出重定向。

当然与之相对的还有输入重定向的。

我们可以让一个进程的输入来自于匿名管道,而不是我们在黑框框中输入数据。

上面的这个重定向不就是利用匿名管道实现的父进程和子进程之间的通信嘛。

             匿名管道相关函数:

匿名管道的创建

BOOL WINAPI CreatePipe(
          __out   PHANDLE hReadPipe,
          __out   PHANDLE hWritePipe,
          __in    LPSECURITY_ATTRIBUTES lpPipeAttributes,
          __in    DWORD nSize );

参数 hReadPipe 为输出参数,该句柄代表管道的读取句柄。

参数 hWritePipe 为输出参数,该句柄代表管道的写入句柄。

参数 lpPipeAttributes 为一个输入参数,指向一个 SECURITY_ATTRIBUTES 的结构体指针,

其检测返回的句柄是否能够被子进程继承,如果此参数为 NULL ,则表明句柄不能被继承,

在匿名管道中,由于匿名管道要在父子进程之间进行通信,

而子进程如果想要获得匿名管道的读写句柄,则其只能通过从父进程继承获得,

当一个子进程从其父进程处继承了匿名管道的读写句柄以后,

子进程和父进程之间就可以通过这个匿名管道的读写句柄进行通信了。

所以在这里必须构建一个 SECURITY_ATTRIBUTES 的结构体,

并且该结构体的第三个结构成员变量 bInheritHandle 参数必须设置为 TRUE

从而让子进程可以继承父进程所创建的匿名管道的读写句柄。

typedef struct _SECURITY_ATTRIBUTES {
 
    DWORD nLength;
 
    LPVOID lpSecurityDescriptor;
 
    BOOL bInheritHandle;
 
} SECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;

参数 nSize 用来指定缓冲区的大小,

如果此参数设置为 0 ,则表明系统将使用默认的缓冲区大小。一般将该参数设置为 0 即可。

         

              

子进程的创建

BOOL  CreateProcess( 
        LPCWSTR pszImageName,  LPCWSTR pszCmdLine, 
        LPSECURITY_ATTRIBUTES psaProcess, 
        LPSECURITY_ATTRIBUTES psaThread, 
        BOOL fInheritHandles,  DWORD fdwCreate, 
        LPVOID pvEnvironment,  LPWSTR pszCurDir, 
        LPSTARTUPINFOW psiStartInfo, 
        LPPROCESS_INFORMATION pProcInfo );

参数 pszImageName 是一个指向 NULL 终止的字符串,用来指定可执行程序的名称。

参数 pszCmdLine 用来指定传递给新进程的命令行字符串,一般做法是在 pszImageName 中传递可执行文件的名称,

pszCmdLine 中传递命令行参数。

参数 psaProcess 即代表当 CreateProcess 函数创建进程时,需要给进程对象设置一个安全性。

参数 psaThread 代表当 CreateProcess 函数创建新进程后,需要给该进程的主线程对象设置一个安全性。

参数 fInheritHandles 用来指定父进程随后创建的子进程是否能够继承父进程的对象句柄,

如果该参数设置为 TRUE ,则父进程的每一个可继承的打开句柄都将被子进程所继承,

继承的句柄与原始的句柄拥有同样的访问权。

在匿名管道的使用中,因为子进程需要使用父进程中创建的匿名管道的读写句柄,

所以应该将这个参数设置为 TRUE ,从而可以让子进程继承父进程创建的匿名管道的读写句柄。

参数 fdwCreate 用来指定控件优先级类和进程创建的附加标记。

如果只是为了启动子进程,则并不需要设置它创建的标记,可以将此参数设置为 0

对于这个参数的具体取值列表可以参考 MSDN 。

参数 pvEnvironment 代表指向环境块的指针,

如果该参数设置为 NULL ,则默认将使用父进程的环境。通常给该参数传递 NULL

参数 pszCurDir 用来指定子进程当前的路径,

这个字符串必须是一个完整的路径名,其包括驱动器的标识符,

如果此参数设置为 NULL ,那么新的子进程将与父进程拥有相同的驱动器和目录。

参数 psiStartInfo 指向一个 StartUpInfo 的结构体的指针,用来指定新进程的主窗口如何显示。

typedef struct _STARTUPINFOA {
 
    DWORD cb;
 
    LPSTR lpReserved;
 
    LPSTR lpDesktop;
 
    LPSTR 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;
 
} STARTUPINFOA, *LPSTARTUPINFOA;

对于 dwFlags 参数来说,如果其设置为 STARTF_USESTDHANDLES

则将会使用该 STARTUPINFO 结构体中的 hStdInput hStdOutput hStdError 成员,

来设置新创建的进程的标准输入,标准输出,标准错误句柄。

参数 pProcInfo 为一个输出参数,

指向一个 PROCESS_INFORMATION 结构体的指针,用来接收关于新进程的标识信息。

typedef struct _PROCESS_INFORMATION 
{             
    HANDLE hProcess;             
    HANDLE hThread;             
    DWORD dwProcessId;              
    DWORD dwThreadId; 
 
}PROCESS_INFORMATION;

其中 hProcess hThread 分别用来标识新创建的进程句柄和新创建的进程的主线程句柄。

dwProcessId dwThreadId 分别是全局进程标识符和全局线程标识符。

前者可以用来标识一个进程,后者用来标识一个线程。

        

示例:匿名管道实现父子进程间通信

父进程实现:(简单 MFC 程序)

项目结构:

image

消息以及成员函数和成员变量的声明:

public:
    //创建匿名管道
    afx_msg void OnBnClickedBtnCreatePipe();
    //写匿名管道
    afx_msg void OnBnClickedBtnWritePipe();
    //读匿名管道
    afx_msg void OnBnClickedBtnReadPipe();
 
    //定义父进程读匿名管道的成员函数
    void ParentReadPipe(void);
    //定义父进程写匿名管道的成员函数
    void ParentWritePipe(void);
    //创建 SECURITY_ATTRIBUTES 结构的成员函数
    void CreateSecurityAttributes(PSECURITY_ATTRIBUTES pSa);
    //创建 STARTUPINFO 结构的成员函数
    void CreateStartUpInfo(LPSTARTUPINFO lpStartUpInfo);
    //创建匿名管道的成员函数
    void CreateNoNamedPipe(void);
 
    //分别代表要从匿名管道中读的数据和要写到匿名管道中的数据
    CString m_CStrReadPipe;
    CString m_CStrWritePipe;
 
    //保存创建匿名管道后所得到的对匿名管道的读写句柄
    HANDLE hPipeRead;
    HANDLE hPipeWrite;
    
    //保证匿名管道只创建一次
    BOOL m_PipeIsCreated;

消息映射表定义:

const int        dataLength    = 100;
 
CNoNamedPipeParentDlg::CNoNamedPipeParentDlg(CWnd* pParent /*=NULL*/)
    : CDialogEx(CNoNamedPipeParentDlg::IDD, pParent)
    , m_CStrReadPipe(_T(""))
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
 
    m_PipeIsCreated = FALSE;
}
 
void CNoNamedPipeParentDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Text(pDX, IDC_EDIT_WRITE_PIPE, m_CStrWritePipe);
    DDX_Text(pDX, IDC_EDIT_READ_PIPE, m_CStrReadPipe);
}
 
BEGIN_MESSAGE_MAP(CNoNamedPipeParentDlg, CDialogEx)
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(IDC_BTN_CREATE_PIPE, 
                &CNoNamedPipeParentDlg::OnBnClickedBtnCreatePipe)
    ON_BN_CLICKED(IDC_BTN_WRITE_PIPE, 
                &CNoNamedPipeParentDlg::OnBnClickedBtnWritePipe)
    ON_BN_CLICKED(IDC_BTN_READ_PIPE, 
                &CNoNamedPipeParentDlg::OnBnClickedBtnReadPipe)
END_MESSAGE_MAP()

消息处理函数:

//创建匿名管道按钮的消息处理函数
void CNoNamedPipeParentDlg::OnBnClickedBtnCreatePipe()
{
    if(m_PipeIsCreated == FALSE)
    {
        this->CreateNoNamedPipe();
    }
}
 
 
//写入数据到匿名管道中按钮的消息处理函数
void CNoNamedPipeParentDlg::OnBnClickedBtnWritePipe()
{
    this->ParentWritePipe();
}
 
 
//从匿名管道中读取数据按钮的消息处理函数
void CNoNamedPipeParentDlg::OnBnClickedBtnReadPipe()
{
    this->ParentReadPipe();
}
 
 
//接收数据
void CNoNamedPipeParentDlg::ParentReadPipe(void)
{
    DWORD            dwRead;
    char *            pReadBuf;
    CString            cStrRecvData;
 
    pReadBuf = new char[dataLength];
    memset(pReadBuf, 0, dataLength);
 
    if(!ReadFile(hPipeRead, pReadBuf, dataLength, &dwRead, NULL))
    {
        MessageBox(TEXT("   从匿名管道接收数据失败 ..."), 
            TEXT("提示"), MB_ICONERROR);
        return;
    }
 
    cStrRecvData = "   从匿名管道接收数据成功:    ";
    cStrRecvData += pReadBuf;
 
    this->m_CStrReadPipe.Empty();
 
    this->m_CStrReadPipe = pReadBuf;
    UpdateData(FALSE);
 
    MessageBox(cStrRecvData, TEXT("提示"), MB_ICONINFORMATION);
}
 
 
//发送数据
void CNoNamedPipeParentDlg::ParentWritePipe(void)
{
    UpdateData();
 
    if(!this->m_CStrWritePipe.IsEmpty())
    {
        char *            pSendData;
        DWORD            dwWrite;
        CString            cStrSendData;
 
        //在这里需要将 Unicode 字符集转换为 ASCII 字符集
        pSendData = new char[this->m_CStrWritePipe.GetLength() + 1];
        memset(pSendData, 0, this->m_CStrWritePipe.GetLength() + 1);
        for(int i=0;i<this->m_CStrWritePipe.GetLength();i++)
        {
            pSendData[i] = (char)this->m_CStrWritePipe.GetAt(i);
        }
 
        if(!WriteFile(hPipeWrite, pSendData, 
            this->m_CStrWritePipe.GetLength() + 1, &dwWrite, NULL))
        {
            MessageBox(TEXT("   给匿名管道发送数据失败 ..."), 
                TEXT("提示"), MB_ICONERROR);
            return;
        }
 
        cStrSendData = "   给匿名管道发送数据成功:    ";
        cStrSendData += this->m_CStrWritePipe;
 
        this->m_CStrWritePipe.Empty();
        UpdateData(FALSE);
 
        MessageBox(cStrSendData, TEXT("提示"), MB_ICONINFORMATION);
    }
    else
    {
        MessageBox(TEXT("   请先输入要发送给匿名管道的数据 ..."), 
            TEXT("提示"), MB_ICONERROR);
    }
}
 
 
//创建 SECURITY_ATTRIBUTES 结构
void CNoNamedPipeParentDlg::CreateSecurityAttributes(PSECURITY_ATTRIBUTES pSa)
{
    //这里必须将 bInheritHandle 设置为 TRUE,
    //从而使得子进程可以继承父进程创建的匿名管道的句柄
    pSa->bInheritHandle = TRUE;
    pSa->lpSecurityDescriptor = NULL;
    pSa->nLength = sizeof(SECURITY_ATTRIBUTES);
}
 
 
//用来初始化新进程的 STARTUPINFO 成员
void CNoNamedPipeParentDlg::CreateStartUpInfo(LPSTARTUPINFO lpStartUpInfo)
{
    memset(lpStartUpInfo, 0, sizeof(STARTUPINFO));
 
    lpStartUpInfo->cb = sizeof(STARTUPINFO);
    lpStartUpInfo->dwFlags = STARTF_USESTDHANDLES;
 
    //子进程的标准输入句柄为父进程管道的读数据句柄
    lpStartUpInfo->hStdInput = hPipeRead;
 
    //子进程的标准输出句柄为父进程管道的写数据句柄
    lpStartUpInfo->hStdOutput = hPipeWrite;
 
    //子进程的标准错误处理句柄和父进程的标准错误处理句柄一致
    lpStartUpInfo->hStdError = GetStdHandle(STD_ERROR_HANDLE);
}
 
 
//创建匿名管道
void CNoNamedPipeParentDlg::CreateNoNamedPipe(void)
{
    SECURITY_ATTRIBUTES                sa;
    PROCESS_INFORMATION                processInfo;
    STARTUPINFO                        startUpInfo;
 
    CreateSecurityAttributes(&sa);
    if(!CreatePipe(&hPipeRead, &hPipeWrite, &sa, 0))
    {
        MessageBox(TEXT("   创建匿名管道失败 ..."), 
            TEXT("提示"), MB_ICONERROR);
        return;
    }
 
    CreateStartUpInfo(&startUpInfo);
    if(!CreateProcess(TEXT("NoNamedPipeChild.exe"), 
        NULL, NULL, NULL, TRUE, 
        CREATE_NEW_CONSOLE, NULL, NULL, 
        &startUpInfo, &processInfo))
    {
        CloseHandle(hPipeRead);
        CloseHandle(hPipeWrite);
 
        hPipeWrite = NULL;
        hPipeRead = NULL;
 
        MessageBox(TEXT("   创建子进程失败 ..."), 
            TEXT("提示"), MB_ICONERROR);
        return;
    }
    else
    {
        m_PipeIsCreated = TRUE;
 
        //对于 processInfo.hProcess 和 processInfo.hThread 
        //这两个句柄不需要使用,所以释放资源
        CloseHandle(processInfo.hProcess);
        CloseHandle(processInfo.hThread);
    }
}

子进程实现:(简单 MFC 程序)

项目结构:

image

消息以及成员函数和成员变量的声明:

// 实现
protected:
    HICON m_hIcon;
 
    // 生成的消息映射函数
    virtual BOOL OnInitDialog();
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    DECLARE_MESSAGE_MAP()
public:
    afx_msg void OnBnClickedBtnWritePipe();
    afx_msg void OnBnClickedBtnReadPipe();
 
    //保存从父进程得到针对于匿名管道的读写句柄
    HANDLE hPipeRead;
    HANDLE hPipeWrite;
 
    //分别代表要从匿名管道中读的数据和要写到匿名管道中的数据
    CString m_CStrWritePipe;
    CString m_CStrReadPipe;
 
    //子进程读取匿名管道
    void ChildReadPipe(void);
    //子进程写匿名管道
    void ChildWritePipe(void);
    //子进程获取从父进程处继承得到的关于匿名管道的读写句柄
    void GetReadWriteHandleFromParent(void);
 
    //只需要获取一次匿名管道的读写句柄即可
    BOOL m_IsGettedParentHandle;

消息映射表定义:

const int        dataLength    = 100;
 
CNoNamedPipeChildDlg::CNoNamedPipeChildDlg(CWnd* pParent /*=NULL*/)
    : CDialogEx(CNoNamedPipeChildDlg::IDD, pParent)
    , m_CStrWritePipe(_T(""))
    , m_CStrReadPipe(_T(""))
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
 
    this->m_IsGettedParentHandle = FALSE;
}
 
void CNoNamedPipeChildDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Text(pDX, IDC_EDIT_WRITE_PIPE, m_CStrWritePipe);
    DDX_Text(pDX, IDC_EDIT_READ_PIPE, m_CStrReadPipe);
}
 
BEGIN_MESSAGE_MAP(CNoNamedPipeChildDlg, CDialogEx)
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(ID_BTN_WRITE_PIPE, 
            &CNoNamedPipeChildDlg::OnBnClickedBtnWritePipe)
    ON_BN_CLICKED(ID_BTN_READ_PIPE, 
            &CNoNamedPipeChildDlg::OnBnClickedBtnReadPipe)
END_MESSAGE_MAP()

消息处理函数:

//往匿名管道中写入数据按钮的消息处理函数
void CNoNamedPipeChildDlg::OnBnClickedBtnWritePipe()
{
    //如果子进程还没有获得对匿名管道的读写句柄的话需要先获取句柄
    this->GetReadWriteHandleFromParent();
 
    ChildWritePipe();
}
 
 
//从匿名管道中读取数据按钮的消息处理函数
void CNoNamedPipeChildDlg::OnBnClickedBtnReadPipe()
{
    //如果子进程还没有获得对匿名管道的读写句柄的话需要先获取句柄
    this->GetReadWriteHandleFromParent();
 
    ChildReadPipe();
}
 
//从匿名管道读取数据成员函数
void CNoNamedPipeChildDlg::ChildReadPipe(void)
{
    DWORD            dwRead;
    char *            pReadBuf;
    CString            cStrRecvData;
 
    pReadBuf = new char[dataLength];
    memset(pReadBuf, 0, dataLength);
 
    //读取数据
    if(!ReadFile(hPipeRead, pReadBuf, dataLength, &dwRead, NULL))
    {
        MessageBox(TEXT("   从匿名管道接收数据失败 ..."), 
            TEXT("提示"), MB_ICONERROR);
        return;
    }
 
    cStrRecvData = "   从匿名管道接收数据成功:    ";
    cStrRecvData += pReadBuf;
 
    this->m_CStrReadPipe.Empty();
    this->m_CStrReadPipe = pReadBuf;
    UpdateData(FALSE);
 
    MessageBox(cStrRecvData, TEXT("提示"), MB_ICONINFORMATION);
}
 
//往匿名管道中写入数据
void CNoNamedPipeChildDlg::ChildWritePipe(void)
{
    UpdateData();
 
    if(!this->m_CStrWritePipe.IsEmpty())
    {
        char *                pSendData;
        DWORD                dwWrite;
        CString                cStrSendData;
 
        //在这里需要将 Unicode 字符集转换为 ASCII 字符集
        pSendData = new char[this->m_CStrWritePipe.GetLength() + 1];
        memset(pSendData, 0, this->m_CStrWritePipe.GetLength() + 1);
        for(int i=0;i<this->m_CStrWritePipe.GetLength();i++)
        {
            pSendData[i] = (char)this->m_CStrWritePipe.GetAt(i);
        }
 
        //写入数据
        if(!WriteFile(hPipeWrite, pSendData, 
            this->m_CStrWritePipe.GetLength(), &dwWrite, NULL))
        {
            MessageBox(TEXT("   给匿名管道发送数据失败 ..."), 
                TEXT("提示"), MB_ICONERROR);
            return;
        }
 
        cStrSendData = "给匿名管道发送数据成功:    ";
        cStrSendData += this->m_CStrWritePipe;
 
        this->m_CStrWritePipe.Empty();
        UpdateData(FALSE);
 
        MessageBox(cStrSendData, TEXT("提示"), MB_ICONINFORMATION);
    }
    else
    {
        MessageBox(TEXT("   请先输入要发送给匿名管道的数据 ..."), 
            TEXT("提示"), MB_ICONERROR);
    }
}
 
//需要获取继承自父进程的匿名管道读写句柄
void CNoNamedPipeChildDlg::GetReadWriteHandleFromParent(void)
{
    if(this->m_IsGettedParentHandle == FALSE)
    {
        hPipeRead = GetStdHandle(STD_INPUT_HANDLE);
        hPipeWrite = GetStdHandle(STD_OUTPUT_HANDLE);
 
        this->m_IsGettedParentHandle = TRUE;
    }
}

效果展示:

首先需要将子进程的可执行文件拷贝到父进程所在目录下,否则创建进程时会找不到子进程的可执行文件。 

image

启动父进程可执行文件,并单击创建匿名管道按钮,此时会弹出子进程窗口(新建了进程):

image

再在父进程的左边文本框中输入数据,单击写入数据按钮:

image

再在子进程窗口中单击读取数据按钮:

image

再在子进程窗口左边的文本框中输入数据,单击写入数据按钮:

image

再在父进程窗口中单击读取数据按钮:

image

             

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多