WindowsAPI 之 CreatePipe、CreateProcess[通俗易懂]

WindowsAPI 之 CreatePipe、CreateProcess[通俗易懂]MSDN介绍CreatePipeApipeisasectionofsharedmemorythatprocessesuseforcommunication.Theproce

大家好,又见面了,我是你们的朋友全栈君。

  • MSDN介绍

CreatePipe

A pipe is a section of shared memory that processes use for communication. The process that creates a pipe is the pipe server. A process that connects to a pipe is a pipe client. One process writes information to the pipe, then the other process reads the information from the pipe. This overview describes how to create, manage, and use pipes.

管 道(Pipe)实际是用于进程间通信的一段共享内存,创建管道的进程称为
管道服务器,连接到一个管道的进程为
管道客户机。一个进程在向管道写入数据后,另 一进程就可以从管道的另一端将其读取出来。匿名管道(Anonymous Pipes)是在父进程和子进程间单向传输数据的一种未命名的管道,只能在本地计算机中使用,而不可用于网络间的通信。
BOOL WINAPI CreatePipe(
  _Out_    PHANDLE               hReadPipe,
  _Out_    PHANDLE               hWritePipe,
  _In_opt_ LPSECURITY_ATTRIBUTES lpPipeAttributes,
  _In_     DWORD                 nSize
);

Return value

If the function succeeds, the return value is nonzero.

If the function fails, the return value is zero. To get extended error information, call GetLastError.

Remarks

CreatePipe creates the pipe, assigning the specified pipe size to the storage buffer. CreatePipe also creates handles that the process uses to read from and write to the buffer in subsequent calls to the ReadFile and WriteFile functions.

To read from the pipe, a process uses the read handle in a call to the ReadFile function. ReadFile returns when one of the following is true: a write operation completes on the write end of the pipe, the number of bytes requested has been read, or an error occurs.

When a process uses WriteFile to write to an anonymous pipe, the write operation is not completed until all bytes are written. If the pipe buffer is full before all bytes are written, WriteFile does not return until another process or thread uses ReadFile to make more buffer space available.

Anonymous pipes are implemented using a named pipe with a unique name. Therefore, you can often pass a handle to an anonymous pipe to a function that requires a handle to a named pipe.

If CreatePipe fails, the contents of the output parameters are indeterminate. No assumptions should be made about their contents in this event.

To free resources used by a pipe, the application should always close handles when they are no longer needed, which is accomplished either by calling the CloseHandle function or when the process associated with the instance handles ends. Note that an instance of a pipe may have more than one handle associated with it. An instance of a pipe is always deleted when the last handle to the instance of the named pipe is closed.

CreateProcess

Creates a new process and its primary thread. The new process runs in the security context of the calling process.
BOOL WINAPI CreateProcess(  _In_opt_    LPCTSTR               lpApplicationName,  _Inout_opt_ LPTSTR                lpCommandLine,  _In_opt_    LPSECURITY_ATTRIBUTES lpProcessAttributes,  _In_opt_    LPSECURITY_ATTRIBUTES lpThreadAttributes,  _In_        BOOL                  bInheritHandles,  _In_        DWORD                 dwCreationFlags,  _In_opt_    LPVOID                lpEnvironment,  _In_opt_    LPCTSTR               lpCurrentDirectory,  _In_        LPSTARTUPINFO         lpStartupInfo,  _Out_       LPPROCESS_INFORMATION lpProcessInformation);

  • 先简单介绍一下重定向

stdin是标准输入,stdout是标准输出,stderr是标准错误输出。大多数的命令行程序从stdin输入,输出到stdout或 stderr,有时我们需要重定向stdout,stderr,stdin。比如:将输出写入文件,又或者我们要将命令行程序输出结果显示到 Windows对话框中。

在Windows编程中,重定向需要用到管道(Pipe)的概念。管道是一种用于在进程间共享数据的机制。一个管道类似于一个管子的两端,一端是写入的,一端是读出的。由一个进程从写入端写入、另一个进程从读出端读出,从而实现通信,就向一个“管道”一样。

重定向的原理是:

首先声明两个概念:主程序(重定向的操纵者)、子进程(被重定向的子进程)

如果要重定位stdout的话,先生成一个管道, 管道的写入端交给子进程去写,主程序从管道的读出端读数据,然后可以把数据写成文件、显示等等。重定向stderr和stdout是相同的。

同理,要重定向stdin的话,生成一个管道, 管道的写入端由主程序写,子进程从管道的读出端读数据。

其中需要用到几个Windows API :  CreatePipe, DuplicateHandle, CreateProcess, ReadFile, WriteFile 等,函数详解可参见MSDN.

比如一个控制台程序打印一行文字:

WindowsAPI 之 CreatePipe、CreateProcess[通俗易懂]

会在windows弹出的对话框中输出:

WindowsAPI 之 CreatePipe、CreateProcess[通俗易懂]

为什么会输出到这里而不是别的地方呢?因为这里就是所说的StdOut(标准输出)的地方。如果你想输出到别的地方,那就得把stdout重定向到别的地方才行。

 

比如,某网友写了一个重定向程序将stdout重定向到自己写的一个窗口中,就会产生如下的效果:

WindowsAPI 之 CreatePipe、CreateProcess[通俗易懂]

 

  • 先详细介绍一下管道,这里以匿名管道为例:

第一:匿名管道只能实现本地进程之间的通信,不能实现跨网络之间的进程间的通信。

第二:匿名管道只能实现父进程和子进程之间的通信,而不能实现任意两个本地进程之间的通信。

匿名管道主要用于本地父进程和子进程之间的通信,在父进程中的话,首先是要创建一个匿名管道,在创建匿名管道成功后,可以获取到对这个匿名管道的读写句柄,然后父进程就可以向这个匿名管道中写入数据和读取数据了,但是如果要实现的是父子进程通信的话,那么还必须在父进程中创建一个子进程,同时,这个子进程必须能够继承和使用父进程的一些公开的句柄,因为在子进程中必须要使用父进程创建的匿名管道的读写句柄,通过这个匿名管道才能实现父子进程的通信,所以必须继承父进程的公开句柄。同时在创建子进程的时候,必须将子进程的标准输入句柄设置为父进程中创建匿名管道时得到的读管道句柄,将子进程的标准输出句柄设置为父进程中创建匿名管道时得到的写管道句柄。然后在子进程就可以读写匿名管道了。

 

  • 下面来讲CreatePipe:

CreatePipe时会获取两个句柄,一个是读句柄,一个是写句柄(这里的读句柄表示要从哪里读取数据,写句柄表示要把数据写到哪里)。

父进程可以调用进程创建函数CreateProcess()生成子进程。如果父进程要发送数据到子进程,父进程可调用WriteFile()将数据写入到管 道(传递管道写句柄给函数),子进程则调用GetStdHandle()取得管道的读句柄,将该句柄传入ReadFile()后从管道读取数据。(如果是父进程从子进程读取数据,那么由子进程调用GetStdHandle()取得管道的写入句柄,并调用WriteFile()将数据写入到管道。然后,父进程调用ReadFile()从管道读取出数据(传递管道读句柄给函数))//GetStdHandle()是由子进程调用

在 用WriteFile()函数向管道写入数据时,只有在向管道写完指定字节的数据后或是在有错误发生时函数才会返回。如管道缓冲已满而数据还没有写 完,WriteFile()将要等到另一进程对管道中数据读取以释放出更多可用空间后才能够返回。管道服务器在调用CreatePipe()创建管道时以 参数nSize对管道的缓冲大小作了设定。
   匿名管道并不支持
异步读、写操作,这也就意味着不能在匿名管道中使用ReadFileEx()和WriteFileEx()(它只能用于异步读写文件操作,异步操作完成后会调用指定的回调函数),而且ReadFile() 和WriteFile()中的lpOverLapped参数也将被忽略。匿名管道将在读、写句柄都被关闭后退出,也可以在进程中调用 CloseHandle()函数来关闭此句柄(个人理解就是,匿名管道,只能是你全部往管道中读写完之前,就不能干别的事,只能写或等待(管道满的时候处在等待状态);而子进程在全部接收完管道的数据之前也只能读或等待(没数据时等待),也不能去干其它的事)。

根据上边API的原型,通过hReadPipe和hWritePipe所指向的句柄可分别以只读、只写的方式去访问管道。在使用匿名管道通信时,服务器进程(父进程)必须将其中的一个句柄传送给客户机进程。句柄的传递多通过
继承来完成(如何继承?请往下看),服务器进程也允许这些句柄为子进程所继承。

在调用CreatePipe()函数时,如果管道服务器将lpPipeAttributes 指向的SECURITY_ATTRIBUTES数据结构的数据成员bInheritHandle设置为TRUE,那么CreatePipe()创建的管道读、写句柄将会被继承(管道服务器可调用DuplicateHandle()函数改变管道句柄的继承。管道服务器可以为一个可继承的管道句柄创建一个不可 继承的副本或是为一个不可继承的管道句柄创建一个可继承的副本。CreateProcess()函数还可以使管道服务器有能力决定子进程对其可继承句柄是 全部继承还是不继承)。

在生成子进程之前,父进程首先调用Win32 API SetStdHandle()使子进程、父进程可共用标准输入、标准输出和标准错误句柄(StdOut、StdIn、StdErr)。当父进程向子进程发送数据时,用SetStdHandle()将 管道的读句柄赋予标准输入句柄(这样就不会从标准输入读入数据,而从读句柄所表示的位置读取数据);在从子进程接收数据时,则用SetStdHandle()将管道的写句柄赋予标准输出(或标准错误)句柄。然后,父进程可以调用进程创建函数CreateProcess()生成子进程。如果父进程要发送数据到子进程,父进程可调用WriteFile()将数据写入到管道(传 递管道写句柄给函数),子进程则调用GetStdHandle()取得管道的读句柄,将该句柄传入ReadFile()后从管道读取数据。//SetStdHandle()是由父进程调用

  • 举例:
#include <iostream>
#include <windows.h>
#include <Shlwapi.h>

using namespace std;

#define BUFSIZE 4096


int main()
{
    BOOL bRet = FALSE;
    DWORD dwRead = 0;
    DWORD dwAvail = 0;
    char cbBuf[4096] = { 0 };
    HANDLE hReadPipe = NULL;
    HANDLE hWritePipe = NULL;
    SECURITY_ATTRIBUTES sa;
    sa.nLength = sizeof(sa);
    sa.bInheritHandle = TRUE;
    sa.lpSecurityDescriptor = NULL;
    char *pCommandLine = new TCHAR[0x200];//
    char szPath[] = "C:\\Windows\\System32\\calc.exe";
    CreatePipe(&hReadPipe, &hWritePipe, &sa, 0);
    STARTUPINFO si = { 0 };
    si.cb = sizeof(STARTUPINFO);
    GetStartupInfo(&si);
    si.wShowWindow = SW_HIDE;
    si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
    si.hStdError = hWritePipe;
    si.hStdOutput = hWritePipe;
    PROCESS_INFORMATION   pi = { 0 };

    memset(pCommandLine, 0, sizeof(szPath));
    lstrcpy(pCommandLine, szPath);

    if (!CreateProcess(NULL, pCommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi))//创建子进程
    {
        if (pCommandLine)
            delete pCommandLine;

        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
        CloseHandle(hReadPipe);
        CloseHandle(hWritePipe);

        return 1;
    }
    std::string strResult;
    do
    {
        cout << "test.." << endl;
        if (!PeekNamedPipe(hReadPipe, NULL, NULL, &dwRead, &dwAvail, NULL) || dwAvail <= 0)//PeekNamePipe用来预览一个管道中的数据,用来判断管道中是否为空
        {
             break;
         }
        if (ReadFile(hReadPipe, cbBuf, BUFSIZE, &dwRead, NULL))//这里是读管道,即便已经没有数据,仍然会等待接收数据,因为,子进程会认为父进程仍有数据要发送,只是暂时没法送,
        {                                                        //所以,会“卡”在这里。所以才需要PeekNamePipe
            if (dwRead == 0)
                break;
            cout << dwRead << endl;
            cout << cbBuf << endl;
        }
    } while (TRUE);


    if (pCommandLine)
        delete pCommandLine;
    cout << "delete" << endl;
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    CloseHandle(hReadPipe);
    CloseHandle(hWritePipe);

    return 0;
}

 

  • 参考:
 一篇比较好的参考文章:
 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/154568.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 带case操作的update语句_多个case when嵌套

    带case操作的update语句_多个case when嵌套1、场景:由于多次循环执行数据库操作是非常耗费性能的。因此,我们需要尽可能一条UPDATE语句更新多条数据。2、方式:casewhen拼凑UPDATE表名SET(目标字段)BRANCH_NO=CASEWHEN(筛选条件)BANK_BRANCH_ID=’-10212’THEN ‘TU32958123’WHENBANK_BRANCH_ID=’-10213’THEN ‘TU32958112’ELSE’测试’END,COMMENTS=CASEWH

  • kafka 教程_kafka适合以下哪种场景

    kafka 教程_kafka适合以下哪种场景一、基本概念介绍Kafka是一个分布式的、可分区的、可复制的消息系统。它提供了普通消息系统的功能,但具有自己独特的设计。这个独特的设计是什么样的呢?首先让我们看几个基本的消息系统术语:Kafka将消息以topic为单位进行归纳。将向Kafkatopic发布消息的程序成为producers.将预订topics并消费消息的程序成为consumer.Kafka以集群的方式运行,可以由一个或多个服务组成…

    2022年10月17日
  • docker修改mysql密码_mysql重新设置密码

    docker修改mysql密码_mysql重新设置密码进入容器dockerexec-it{容器ID}/bin/bash调整MySQL配置文件,设置跳过权限控制:echo”skip-grant-tables”>>/etc/mysql/conf.d/docker.cnf警告:这就意味着任何用户都能登录进来,并进行任何操作,相当不安全。退出容器:exit重启容器:dockerrestart{容器ID}再次进入容器:dockerexec-it{容器ID}/bin/bash登录mysql(无需密码):my.

    2022年10月15日
  • linux内核定时器实验

    linux内核定时器实验文章目录一、linux时间管理和内核定时器简介1.内核时间管理简介2.内核定时器简介1.init_timer函数2.add_timer函数3.del_timer函数4.del_timer_sync函数5.mod_timer函数3.linux内核短延时函数二、硬件原理图分析三、实验程序编写1.修改设备树文件2.定时器驱动程序编写3.编写测试APP四、运行测试定时器是我们最常用到的功能,一般用来完成定时功能,本章我们就来学习一下Linux内核提供的定时器API函数,通过这些定时器

  • MySQL数据库cpu飙升到500%的话他怎么处理?[通俗易懂]

    MySQL数据库cpu飙升到500%的话他怎么处理?

  • BootStrap初始

    序言什么是BootstrapBootstrap是Twitter开源的基于HTML、CSS、JavaScript的前端框架。它是为实现快速开发Web应用程序而设计的一套前端工具包。它支持响应式布

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号