大家好,又见面了,我是你们的朋友全栈君。
- 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)是在父进程和子进程间单向传输数据的一种未命名的管道,只能在本地计算机中使用,而不可用于网络间的通信。
代码语言:javascript复制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.
代码语言:javascript复制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.
比如一个控制台程序打印一行文字:
会在windows弹出的对话框中输出:
为什么会输出到这里而不是别的地方呢?因为这里就是所说的StdOut(标准输出)的地方。如果你想输出到别的地方,那就得把stdout重定向到别的地方才行。
比如,某网友写了一个重定向程序将stdout重定向到自己写的一个窗口中,就会产生如下的效果:
- 先详细介绍一下管道,这里以匿名管道为例:
第一:匿名管道只能实现本地进程之间的通信,不能实现跨网络之间的进程间的通信。
第二:匿名管道只能实现父进程和子进程之间的通信,而不能实现任意两个本地进程之间的通信。
匿名管道主要用于本地父进程和子进程之间的通信,在父进程中的话,首先是要创建一个匿名管道,在创建匿名管道成功后,可以获取到对这个匿名管道的读写句柄,然后父进程就可以向这个匿名管道中写入数据和读取数据了,但是如果要实现的是父子进程通信的话,那么还必须在父进程中创建一个子进程,同时,这个子进程必须能够继承和使用父进程的一些公开的句柄,因为在子进程中必须要使用父进程创建的匿名管道的读写句柄,通过这个匿名管道才能实现父子进程的通信,所以必须继承父进程的公开句柄。同时在创建子进程的时候,必须将子进程的标准输入句柄设置为父进程中创建匿名管道时得到的读管道句柄,将子进程的标准输出句柄设置为父进程中创建匿名管道时得到的写管道句柄。然后在子进程就可以读写匿名管道了。
- 下面来讲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;
}
- 参考:
MSDN: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx
http://qiusuoge.com/11496.html
http://www.cnblogs.com/vicsmb/archive/2012/02/03/2337049.html
一篇比较好的参考文章:
http://www.cnblogs.com/BoyXiao/archive/2011/01/01/1923828.html
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/154568.html原文链接:https://javaforall.cn