我正在尝试从c中的程序创建重定向io到
cmd.exe
进程,并使用管道来处理io以处理我的程序的输入命令并返回输出。
我想让
cmd.exe
进程在后台运行,(我知道每次需要处理某些内容时我都可以使用 cmd /C <command>
)。
这是我现在的方法。
stdin
,另一个用于 stdout
。cmd.exe
将 CreateProcess
创建为子进程,并使用 STARTUPINFO
将进程 stdin
和 stdout
设置为 stdin
管道的写入端和 stdout
管道的读取端.stdin
将命令输入到 WriteFile
管道的写入端。stdout
从 ReadFile
管道的读取端读取输出。这是我现在脑子里的代码。
// NOTE: This code is for demonstration purposes only.
// It is not optimized for performance or error handling.
#include <windows.h>
#include <stdio.h>
#include <string.h>
int main() {
BOOL success = FALSE;
PROCESS_INFORMATION ProcessInfo = {};
STARTUPINFO StartupInfo = {};
SECURITY_ATTRIBUTES SecurityAttr = {};
HANDLE StdinRead, StdinWrite;
HANDLE StdoutRead, StdoutWrite;
CHAR buffer[1024] = "echo HelloWorld!\n";
DWORD WrittenBytes = 0;
DWORD ReadedBytes = 0;
SecurityAttr.bInheritHandle = TRUE;
if (!CreatePipe(&StdinRead, &StdinWrite, &SecurityAttr, 0)) {
fprintf(stderr, "[!] Stdin pipe creation failed.\n");
exit(1);
}
if (!SetHandleInformation(StdinWrite, HANDLE_FLAG_INHERIT, 0)) {
fprintf(stderr, "[!] Stdin SetHandleInformation failed.\n");
exit(1);
}
if (!CreatePipe(&StdoutRead, &StdoutWrite, &SecurityAttr, 0)) {
fprintf(stderr, "[!] Stdout pipe creation failed.\n");
exit(1);
}
if (!SetHandleInformation(StdoutRead, HANDLE_FLAG_INHERIT, 0)) {
fprintf(stderr, "[!] Stdout SetHandleInformation failed.\n");
exit(1);
}
StartupInfo.cb = sizeof(STARTUPINFO);
StartupInfo.hStdError = StdoutWrite;
StartupInfo.hStdOutput = StdoutWrite;
StartupInfo.hStdInput = StdinRead;
StartupInfo.dwFlags = STARTF_USESTDHANDLES;
success =
CreateProcess("C:\\Windows\\System32\\cmd.exe", NULL, NULL, NULL, TRUE,
CREATE_NO_WINDOW, NULL, NULL, &StartupInfo, &ProcessInfo);
if (!success) {
fprintf(stderr, "[!] CreateProcess failed.\n");
exit(1);
}
CloseHandle(StdoutWrite);
CloseHandle(StdinRead);
success = WriteFile(StdinWrite, buffer, strlen(buffer), &WrittenBytes, NULL);
if (!success) {
fprintf(stderr, "[!] WriteFile failed.\n");
exit(1);
}
while (TRUE) {
success = ReadFile(StdoutRead, buffer, sizeof(buffer), &ReadedBytes, NULL);
if (!success) {
fprintf(stderr, "[!] ReadFile failed.\n");
exit(1);
}
buffer[ReadedBytes] = 0;
printf("[*] ReadFile output: %s\n", buffer);
}
return 0;
}
问题在于,当
ReadFile
写入标准输出并且没有更多数据可供读取时,cmd.exe
调用会生成死锁。
程序的输出是这样的。
[*] ReadFile output: Microsoft Windows 10.0.19043
[*] ReadFile output:
D:cmd_stdio>HelloWorld!
D:cmd_stdio>^C
我必须
CTRL+C
才能关闭。
在微软文章中关于
Creating a Child Process with Redirected Input and Output
// 关闭管道句柄,以便子进程停止读取。
就我而言,我不想关闭
stdin
管道写入句柄。
我有一个想法来解决这个问题(但我不喜欢它),通过附加一个结束标志并检查该标志的缓冲区末尾
// NOTE: This code is for demonstration purposes only.
// It is not optimized for performance or error handling.
#include <stdio.h>
#include <string.h>
#include <windows.h>
#define END_FLAG "[cmd is done]"
int main() {
// same code as above
strcat(buffer, "; echo " END_FLAG "\n");
success = WriteFile(StdinWrite, buffer, strlen(buffer), &WrittenBytes, NULL);
ZeroMemory(buffer, sizeof(buffer));
if (!success) {
fprintf(stderr, "[!] WriteFile failed.\n");
exit(1);
}
while (TRUE) {
success = ReadFile(StdoutRead, buffer + index, sizeof(buffer) - index,
&ReadedBytes, NULL);
if (!success) {
fprintf(stderr, "[!] ReadFile failed.\n");
exit(1);
}
buffer[index + ReadedBytes] = 0;
index += ReadedBytes;
if (strstr(buffer, END_FLAG) != NULL) {
printf("[*] Command execution completed.\n");
break;
}
}
printf("[*] ReadFile output: %s\n", buffer);
return 0;
}
// (you could create a 64-bit flag and XOR it with the last 8bytes of written data to the buffer, this code only for explaining my point)
我的问题是:是否有更好的方法来知道子进程是否已完成写入并准备好接收输入?
Microsoft 文章(和您的代码)的设置方式,正在使用同步管道和同步 IO。标准输出管道保持打开状态,直到子进程终止,从而结束阻塞
ReadFile
。
在没有
cmd
的 /C
的情况下,当用户关闭控制台窗口时会发生这种情况,但您没有创建控制台窗口,因此 ReadFile
无限期地阻塞(除非您发送另一个命令),因为管道永远不会关闭(除非有人杀死 cmd
进程)。
因此,要么使用
cmd /C
,以便在命令完成时进程结束,要么使用异步管道,以便可以在管道读取上设置超时。