Created
May 28, 2022 14:04
-
-
Save ianfun/7372c8de868dee5282a4a9cf9ec94ef5 to your computer and use it in GitHub Desktop.
Read child process’s stdout and stderr using Named Pipe and IO Completion port(IOCP) asynchronously
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <windows.h> | |
#include <assert.h> | |
#include <stdio.h> | |
struct Stdio; | |
typedef void (*Callback)(struct Stdio* self, DWORD len); | |
struct Stdio { | |
OVERLAPPED ol; | |
HANDLE pipe; | |
BYTE buf[100]; | |
Callback callback; | |
}; | |
typedef struct CTX { | |
struct Stdio Stdout, Stderr, Stdin; | |
} CTX, *LCTX, *LPCTX; | |
HANDLE Hstdout=INVALID_HANDLE_VALUE; | |
HANDLE Hstderr=INVALID_HANDLE_VALUE; | |
DWORD WINAPI Worker(LPVOID iocp){ | |
struct Stdio *stdio; | |
OVERLAPPED *ol; | |
DWORD dwIoSize; | |
for(;;){ | |
if (!GetQueuedCompletionStatus(iocp, &dwIoSize, (PDWORD_PTR)&stdio, &ol, INFINITE) || dwIoSize==0 || ol==NULL || stdio==NULL){ | |
switch (GetLastError()){ | |
case ERROR_BROKEN_PIPE: | |
puts("the process has been exited, exit thread..."); | |
break; | |
default: | |
printf("error = %d, exit thread\n", GetLastError()); | |
} | |
break; | |
} | |
stdio->callback(stdio, dwIoSize); | |
} | |
return 0; | |
} | |
void OnStdoutRead(struct Stdio *self, DWORD len){ | |
WriteConsoleA(Hstdout, self->buf, len, NULL, NULL); | |
ReadFile(self->pipe, self->buf, sizeof(self->buf), NULL, &self->ol); | |
} | |
void OnStderrRead(struct Stdio *self, DWORD len){ | |
WriteConsoleA(Hstderr, self->buf, len, NULL, NULL); | |
ReadFile(self->pipe, self->buf, sizeof(self->buf), NULL, &self->ol); | |
} | |
int wmain(){ | |
assert(Hstdout = GetStdHandle(STD_OUTPUT_HANDLE)); | |
assert(Hstderr = GetStdHandle(STD_ERROR_HANDLE)); | |
HANDLE Pstdout, Pstderr; // child process's | |
CTX ctx{.Stdout=Stdio{.callback=OnStdoutRead}, .Stderr=Stdio{.callback=OnStderrRead}}; | |
HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); | |
HANDLE hThread = CreateThread(NULL, 0, Worker, iocp, 0, NULL); // Worker thread | |
SECURITY_ATTRIBUTES sa{.nLength=sizeof(SECURITY_ATTRIBUTES), .bInheritHandle=TRUE}; | |
const WCHAR* pipe_name1 = L"\\\\.\\Pipe\\child-1"; | |
assert((ctx.Stdout.pipe = CreateNamedPipeW( | |
pipe_name1, | |
PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, | |
PIPE_TYPE_BYTE, | |
1, | |
4096, | |
4096, | |
5000, | |
NULL))!=INVALID_HANDLE_VALUE); | |
assert(INVALID_HANDLE_VALUE != (Pstdout = CreateFileW( | |
pipe_name1, | |
GENERIC_WRITE, | |
0, | |
&sa, | |
OPEN_EXISTING, | |
FILE_FLAG_OVERLAPPED, | |
NULL))); | |
const WCHAR *pipe_name2 = L"\\\\.\\Pipe\\child-2"; | |
assert((ctx.Stderr.pipe = CreateNamedPipeW( | |
pipe_name2, | |
PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, | |
PIPE_TYPE_BYTE, | |
1, | |
4096, | |
4096, | |
5000, | |
NULL))!=INVALID_HANDLE_VALUE); | |
assert((Pstderr = CreateFileW( | |
pipe_name2, | |
GENERIC_WRITE, | |
0, | |
&sa, | |
OPEN_EXISTING, | |
FILE_FLAG_OVERLAPPED, | |
NULL))!=INVALID_HANDLE_VALUE); | |
STARTUPINFOW si{.cb = sizeof(si), | |
.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW, | |
.wShowWindow = SW_HIDE, | |
.hStdInput = GetStdHandle(STD_INPUT_HANDLE), // use current stdin | |
.hStdOutput = Pstdout, | |
.hStdError = Pstderr}; | |
WCHAR cmd[] = L"powershell"; // or cmd, py, bash... | |
PROCESS_INFORMATION pInfo{}; | |
assert(CreateProcessW( | |
NULL, cmd, NULL, NULL, | |
TRUE, 0, NULL, NULL, | |
&si, &pInfo)); | |
assert(CloseHandle(Pstdout)); // we don't need this | |
assert(CloseHandle(Pstderr)); // we don't need this | |
assert(CreateIoCompletionPort(ctx.Stdout.pipe, iocp, (ULONG_PTR)&ctx.Stdout, 0)); | |
assert(CreateIoCompletionPort(ctx.Stderr.pipe, iocp, (ULONG_PTR)&ctx.Stderr, 0)); | |
ReadFile(ctx.Stdout.pipe, ctx.Stdout.buf, sizeof(ctx.Stdout.buf), NULL, &ctx.Stdout.ol); | |
ReadFile(ctx.Stderr.pipe, ctx.Stdout.buf, sizeof(ctx.Stderr.buf), NULL, &ctx.Stderr.ol); | |
WaitForSingleObject(pInfo.hProcess, INFINITE); // wait for process exit | |
PostQueuedCompletionStatus(iocp, 0, 0, NULL); // tell IOCP Worker exit | |
WaitForSingleObject(hThread, INFINITE); // wait for thread exit | |
assert(CloseHandle(hThread)); | |
assert(CloseHandle(ctx.Stderr.pipe)); | |
assert(CloseHandle(ctx.Stdout.pipe)); | |
assert(CloseHandle(pInfo.hProcess)); | |
assert(CloseHandle(iocp)); | |
assert(CloseHandle(Hstderr)); | |
puts("exit main function..."); // !!important: before close stdout | |
assert(CloseHandle(Hstdout)); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment