C++ 进程和匿名管道使用学习

平台Windows10 + VS2015学习内容进程的创建使用(CreateProcess方式)父子进程间匿名管道通信相关函数及参数介绍CreatePipe函数:该的原型为CreatePipe(_Out_ PHANDLE hReadPipe,_Out_ PHANDLE hWritePipe,_In_opt_ ...

平台

Windows10 + VS2015

学习内容

  • 进程的创建使用(CreateProcess方式)
  • 父子进程间匿名管道通信

相关函数及参数介绍

  • CreatePipe函数:该的原型为
CreatePipe(_Out_ PHANDLE hReadPipe,
           _Out_ PHANDLE hWritePipe,
           _In_opt_ LPSECURITY_ATTRIBUTES lpPipeAttributes,
           _In_ DWORD nSize);

hReadPipe:返回一个可用于读管道数据的文件句柄;hWritePipe:返回一个可用于写管道数据的文件句柄;lpPipeAttributes:传入一个SECURITY_ATTRIBUTES结构的指针,该结构用于决定该函数返回的句柄是否可被子进程继承;nSize:管道的缓冲区大小,但是这仅仅只是一个理想值,系统根据这个值创建大小相近的缓冲区。如果传入0 ,那么系统将使用一个默认的缓冲区大小。需要注意读写管道数据的句柄参数位置

  • LPSECURITY_ATTRIBUTES类型:相关定义内容为
typedef struct _SECURITY_ATTRIBUTES {
    DWORD nLength;
    LPVOID lpSecurityDescriptor;
    BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;

nLength:结构体的大小,可用sizeof取得;lpSecurityDescriptor:安全描述符;bInheritHandle:安全描述的对象能否被新创建的进程继承。

  • CreateProcess函数:WIN32API函数CreateProcess用来创建一个新的进程和它的主线程,这个新进程运行指定的可执行文件。该函数原型有两种可能,当前环境已定义UNICODE,只参考CreateProcessW
#ifdef UNICODE
#define CreateProcess  CreateProcessW
#else
#define CreateProcess  CreateProcessA
#endif // !UNICODE

WINBASEAPI
BOOL
WINAPI
CreateProcessW(
    _In_opt_ LPCWSTR lpApplicationName,
    _Inout_opt_ LPWSTR lpCommandLine,
    _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
    _In_ BOOL bInheritHandles,
    _In_ DWORD dwCreationFlags,
    _In_opt_ LPVOID lpEnvironment,
    _In_opt_ LPCWSTR lpCurrentDirectory,
    _In_ LPSTARTUPINFOW lpStartupInfo,
    _Out_ LPPROCESS_INFORMATION lpProcessInformation
    );

lpApplicationName:指向一个NULL结尾的、用来指定可执行模块的字符串,这个参数可以被设为NULL,在这种情况下,可执行模块的名字必须处于 lpCommandLine 参数最前面并由空格符与后面的字符分开;lpCommandLine:指向一个以NULL结尾的字符串,该字符串指定要执行的命令行,这个参数可以为空,那么函数将使用lpApplicationName参数指定的字符串当做要运行的程序的命令行;lpProcessAttributes:指向一个SECURITY_ATTRIBUTES结构体,这个结构体决定是否返回的句柄可以被子进程继承;lpThreadAttributes:同lpProcessAttribute,不过这个参数决定的是线程是否被继承.通常置为NULL;bInheritHandles:指示新进程是否从调用进程处继承了句柄,如果参数的值为真,调用进程中的每一个可继承的打开句柄都将被子进程继承,被继承的句柄与原进程拥有完全相同的值和访问权限;dwCreationFlags:指定附加的、用来控制优先类和进程的创建的标志;lpEnvironment:指向一个新进程的环境块,如果此参数为空,新进程使用调用进程的环境;lpCurrentDirectory:指向一个以NULL结尾的字符串,这个字符串用来指定子进程的工作路径,这个字符串必须是一个包含驱动器名的绝对路径;lpStartupInfo指向一个用于决定新进程的主窗体如何显示的STARTUPINFO结构体;lpProcessInformation:指向一个用来接收新进程的识别信息的PROCESS_INFORMATION结构体。

  • dwCreationFlags参数:dwCreationFlags可选类型列表如下
参数值 描述
CREATE_DEFAULT_ERROR_MODE 新的进程不继承调用进程的错误模式
CREATE_NEW_CONSOLE 新的进程将使用一个新的控制台,而不是继承父进程的控制台。这个标志不能与DETACHED_PROCESS标志一起使用
CREATE_NEW_PROCESS_GROUP 新进程将是一个进程树的根进程
CREATE_SEPARATE_WOW_VDM 如果被设置,新进程将会在一个私有的虚拟DOS机(VDM)中运行
CREATE_SHARED_WOW_VDM 如果WIN.INI中的Windows段的DefaultSeparateVDM选项被设置为真,这个标识使得CreateProcess函数越过这个选项并在共享的虚拟DOS机中运行新进程
CREATE_SUSPENDED 新进程的主线程会以暂停的状态被创建,直到调用ResumeThread函数被调用时才运行
CREATE_UNICODE_ENVIRONMENT 如果被设置,由lpEnvironment参数指定的环境块使用Unicode字符,如果为空,环境块使用ANSI字符
DEBUG_PROCESS 如果这个标志被设置,调用进程将被当做一个调试程序,并且新进程会被当做被调试的进程。系统把被调试程序发生的所有调试事件通知给调试器,如果使用这个标志创建进程,只有调用进程(调用CreateProcess函数的进程)可以调用WaitForDebugEvent函数
DEBUG_ONLY_THIS_PROCESS 如果此标志没有被设置且调用进程正在被调试,新进程将成为调试调用进程的调试器的另一个调试对象。如果调用进程没有被调试,有关调试的行为就不会产生
DETACHED_PROCESS 对于控制台进程,新进程没有访问父进程控制台的权限。新进程可以通过AllocConsole函数自己创建一个新的控制台。这个标志不可以与CREATE_NEW_CONSOLE标志一起使用
CREATE_NO_WINDOW 系统不为新进程创建CUI窗口,使用该标志可以创建不含窗口的CUI程序
HIGH_PRIORITY_CLASS 指示这个进程将执行时间临界的任务,所以它必须被立即运行以保证正确。这个优先级的程序优先于正常优先级或空闲优先级的程序
IDLE_PRIORITY_CLASS 指示这个进程的线程只有在系统空闲时才会运行并且可以被任何高优先级的任务打断
NORMAL_PRIORITY_CLASS 指示这个进程没有特殊的任务调度要求
REALTIME_PRIORITY_CLASS 指示这个进程拥有可用的最高优先级。一个拥有实时优先级的进程的线程可以打断所有其他进程线程的执行,包括正在执行重要任务的系统进程
  • STARTUPINFO参数:该参数原型有两种可能,当前环境已定义UNICODE,只参考STARTUPINFOW
#ifdef UNICODE
typedef STARTUPINFOW STARTUPINFO;
typedef LPSTARTUPINFOW LPSTARTUPINFO;
#else
typedef STARTUPINFOA STARTUPINFO;
typedef LPSTARTUPINFOA LPSTARTUPINFO;
#endif // UNICODE

typedef struct _STARTUPINFOW {
    DWORD   cb;
    LPWSTR  lpReserved;
    LPWSTR  lpDesktop;
    LPWSTR  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;
} STARTUPINFOW, *LPSTARTUPINFOW;

结构体参数内容较多,练习使用时只用到了cb``dwFlags``wShowWindow``hStdInput``hStdOutput``hStdError,具体参数解释如下表

参数 描述
cb 包含STARTUPINFO结构中的字节数,应用程序必须将cb初始化为sizeof(STARTUPINFO)
lpReserved 保留,必须初始化为NULL
lpDesktop 用于标识启动应用程序所在的桌面的名字。如果该桌面存在,新进程便与指定的桌面相关联。如果桌面不存在,便创建一个带有默认属性的桌面,并使用为新进程指定的名字。如果lpDesktop是NULL(这是最常见的情况),那么该进程将与当前桌面相关联
lpTitle 用于设定控制台窗口的名称。如果lpTitle是NULL,则可执行文件的名字将用作窗口名
dwX 用于设定应用程序窗口在屏幕上应该放置的位置的x坐标(以像素为单位)
dwY 用于设定应用程序窗口在屏幕上应该放置的位置的y坐标(以像素为单位),只有当子进程用CW_USEDEFAULT作为CreateWindow的x参数来创建它的第一个重叠窗口时,才使用这两个坐标。若是创建控制台窗口的应用程序,这些成员用于指明控制台窗口的左上角
dwXSize 用于设定应用程序窗口的宽度(以像素为单位)当子进程将CW_USEDEFAULT用作CreateWindow的nWidth参数来创建它的第一个重叠窗口时,才使用
dwYSize 用于设定应用程序窗口的长度(以像素为单位)当子进程将CW_USEDEFAULT用作CreateWindow的nWidth参数来创建它的第一个重叠窗口时,才使用
dwXCountChars 用于设定子应用程序的控制台窗口的宽度(以字符为单位)
dwYCountChars 用于设定子应用程序的控制台窗口的高度(以字符为单位)
dwFillAttribute 用于设定子应用程序的控制台窗口使用的文本和背景颜色
dwFlags 子进程窗口标志
wShowWindow 用于设定如果子应用程序初次调用的ShowWindow将SW_SHOWDEFAULT作为nCmdShow参数传递时,该应用程序的第一个重叠窗口应该如何出现
cbReserved2 保留,必须被初始化为0
lpReserved2 保留,必须被初始化为NULL
hStdInput 用于设定供控制台输入用的缓存的句柄
hStdOutput 用于设定供控制台输出用的缓存的句柄
hStdError 用于设定供控制台输出用的缓存的句柄
  • dwFlags参数:该参数可选类型如下
参数 描述
STARTF_USESIZE 使用dwXSize和dwYSize成员
STARTF_USESHOWWINDOW 使用wShowWindow成员
STARTF_USEPOSITION 使用dwX和dwY成员
STARTF_USECOUNTCHARS 使用dwXCountChars和dwYCountChars成员
STARTF_USEFILLATTRIBUTE 使用dwFillAttribute成员
STARTF_USESTDHANDLES 使用hStdInput、hStdOutput和hStdError成员
STARTF_RUN_FULLSCREEN 强制在x86计算机上运行的控制台应用程序以全屏幕方式启动运行

父进程代码

1 .父进程的功能是创建管道和创建进程
2 .创建成功后等待控制台输入信息并通过管道发送给子进程
3 .等待一段时间后从管道读取数据
4 .将管道数据输出到控制台

#include <iostream>
#include <windows.h>
using namespace std;

char rxbuff[100] = { 0 };
char txbuff[100] = { 0 };
DWORD txcount = 0, rxcount = 0;

void main(void)
{
    SECURITY_ATTRIBUTES sa;
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = TRUE;

    //管道创建
    HANDLE hwrite, hread;
    if (CreatePipe(&hread,&hwrite,&sa,0)==NULL)
    {
        cout << "pPipe Create error!" << endl;
        return;
    }

    //进程创建
    STARTUPINFO si;
    ZeroMemory(&si,sizeof(STARTUPINFO));
    si.cb = sizeof(STARTUPINFO);
    si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_SHOW;
    si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
    si.hStdOutput = hwrite;
    si.hStdInput = hread;

    PROCESS_INFORMATION pi; 
    TCHAR exe[] = TEXT("E:\\Soft_Pro\\VC_work\\MD5_Check\\Debug\\MD5_Check.exe");
    //创建子进程
    if (CreateProcess(NULL,
                      exe,
                      NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)==NULL)
    {
        cout << "进程创建错误" << endl;
        CloseHandle(hread);
        CloseHandle(hwrite);
        hread = NULL;
        hwrite = NULL;
        return;
    }
    else
    {
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
    }

    cin >> txbuff;
    //父进程往管道写数据
    if (!WriteFile(hwrite, txbuff, sizeof(txbuff), &txcount, NULL))
    {
        cout << "父进程写数据失败!" << endl;
        return;
    }
    else
    {
        cout << "父进程写数据成功!" << endl;
    }

    Sleep(300);

    //父进程从管道读取数据
    if (!ReadFile(hread,rxbuff,100,&rxcount,NULL))
    {
        cout << "父进程读取失败!" << endl;
        return;
    }
    else
    {
        cout << "父进程管道读取数据:" << rxbuff << endl;
    }

    system("pause");
}

子进程代码

1 .子进程获取管道数据的文件句柄
2 .子进程从输入端读取管道数据
3 .子进程将读取的数据从输出端发送至管道

#include <iostream>
#include <windows.h>
using namespace std;

char rxbuff[100] = {0};
char txbuff[100] = {0};
DWORD txcount = 0, rxcount = 0;
HANDLE hRead, hWrite;

int main(void)
{
    hRead = GetStdHandle(STD_INPUT_HANDLE);
    hWrite = GetStdHandle(STD_OUTPUT_HANDLE);

        //读管道数据
    if (!ReadFile(hRead,rxbuff,100,&rxcount,NULL))
    {
        cout << "子进程读管道失败" << endl;
        return 0;
    }
    sprintf_s(txbuff, "子进程应答:%s\n", rxbuff);

    Sleep(200);

        //写管道数据
    if (!WriteFile(hWrite,txbuff,sizeof(txbuff),&txcount,NULL))
    {
        cout << "子进程写失败!" << endl;
        return 0;
    }

    return 0;
}

父进程运行结果

额外说明

  • CreateProcess的lpCommandLine参数使用TEXT("path")方式创建进程时总是报错,后来在一篇博客里看到了解决方案
  • 初次学习还有部分问题,后续继续补充

本文标题为:C++ 进程和匿名管道使用学习