我正在尝试重定向Windows中子进程的stdout
。两者都是控制台程序。我没有子进程的源代码,因此无法强制其刷新缓冲区。如here和here所述,对于printf
和类似的实现,C运行时会缓冲除控制台和打印机以外的所有内容。因此,解决方案显然是适当地使用CreateConsoleScreenBuffer
创建控制台屏幕缓冲区。我正在使用the approach from codeproject。
PROCESS_INFORMATION pi;
HANDLE hConsole;
const COORD origin = { 0, 0 };
// Default security descriptor. Set inheritable.
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE; // So the child can use it
// Create and initialize screen buffer
hConsole = CreateConsoleScreenBuffer(
GENERIC_READ | GENERIC_WRITE, // Desired access
FILE_SHARE_WRITE | FILE_SHARE_READ, // share mode to child processes
&sa, // SECURITY_ATTRIBUTES
CONSOLE_TEXTMODE_BUFFER, // Must be this.
NULL // Reserved. Must be NULL
);
DWORD dwDummy;
FillConsoleOutputCharacter(hConsole, '\0', MAXLONG, origin, &dwDummy)
现在,我将孩子的标准输出指向控制台并开始该过程
STARTUPINFO si;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_FORCEOFFFEEDBACK | STARTF_USESTDHANDLES; // first one prevents cursor from showing loading.
si.hStdOutput = hConsole;
//...
// Get the command line and environmental block
//...
if (! CreateProcess(
NULL, // module name.
(char*)command_line.c_str(), // command line
NULL, // process SECURITY_ATTRIBUTES
NULL, // thread SECURITY_ATTRIBUTES
TRUE, // inherit handles
NULL, // creation flags
enviros, // environmentBlock (enviros=NULL for testing)
cDir, // working directory
&si, // STARTUP_INFO object
&pi // PROCESSINFO
) ){
auto test = GetLastError();
CloseHandle(hConsole);
return false;
}
CloseHandle(pi.hThread);
然后,我可以循环使用ReadConsoleOutputCharacter
来获取输出,如codeproject链接所示。看起来像
//... some initialization
GetConsoleScreenBufferInfo(hConsole, &csbi);
DWORD count = (csbi.dwCursorPosition.Y - lastpos.Y)*lineWidth + csbi.dwCursorPosition.X - lastpos.X;
LPTSTR buffer = (LPTSTR)LocalAlloc(0, count * sizeof(TCHAR));
ReadConsoleOutputCharacter(hConsole, buffer, count, lastpos, &count);
DWORD dwDummy;
FillConsoleOutputCharacter(hConsole, '\0', count, lastpos, &dwDummy);
//... Now move the cursor and grab the data from `buffer`
在Windows 7 / 8.1上,这对于所有程序均适用。 在Windows 10上,某些程序似乎绕过提供的句柄并直接打印到父控制台上,从而阻止我按需获取输出。
我还有其他线索。如果我强制该过程创建新的控制台窗口,即
CreateProcess(NULL, (char*)command_line.c_str(), NULL, NULL, TRUE, CREATE_NEW_CONSOLE, enviros, cDir, &si, &pi)
但仍重定向STARTUPINFO
对象中的句柄,新控制台将显示一行The system cannot write to the specified device
,该行恰好是Windows错误代码ERROR_WRITE_FAULT = 29
的确切措辞在the MSDN docs中。仅对于那些无法按预期运行的程序会发生这种情况。在其他程序中,新控制台为空白,并且仍可以按预期运行。
我的第一个想法是权限问题,但是我对相关可执行文件的目录具有开放权限。
我尝试过的事情
CreateProcess
中显式设置工作目录SECURITY_ATTRIBUTES
的{{1}}对象添加超许可DACL CreateProcess
的{{1}}对象添加超许可DACL 更深
谢谢@PaulSanders的建议。
为了帮助可能需要帮助的任何人,我提供了a modified version of the RTConsole code from the codeproject page。我认为它应该在Visual Studio中进行编译,只需重新定位即可。在第135行附近,我在输出的前面添加了一个小字符串,该字符串采用了预期的路径。为了方便起见,我还在其中提供了预编译的版本。
EWBF miner是一个不起作用的软件示例。为了使用上面提供的代码进行快速测试,您可以运行
SECURITY_ATTRIBUTES
您将看到输出中不存在前置标志。
另一方面,使用ccminer,您将在运行时获得预期的行为
CreateConsoleScreenBuffer
答案 0 :(得分:3)
Windows 10中新的控制台实现存在一个错误,其中高级别WriteConsole
和WriteFile
到非活动屏幕缓冲区,而总是写入活动屏幕缓冲区。低级WriteConsoleOutput[Character]
可以正常工作。使用旧版控制台也可以。您可以在属性对话框中启用旧版控制台。
请注意,如果进程由于CREATE_NEW_CONSOLE
标志而分配了新的控制台,则该进程不能在父级控制台的屏幕缓冲区中使用继承的句柄。尝试写入屏幕缓冲文件将失败,因为它未绑定到调用方的附加控制台(即conhost.exe实例)。
已绑定的控制台文件包括“ CON”,“ CONIN $”,“ CONOUT $”和来自CreateConsoleScreenBuffer
的新屏幕缓冲区。还有一些未绑定的输入和输出控制台文件,这些文件被设置为分配新控制台时的标准句柄(例如,通过AllocConsole()
)。这些句柄访问任何连接的控制台[*]的输入缓冲区和活动屏幕缓冲区。请注意,进程可以具有绑定到多个控制台的控制台文件的句柄,并且可以使用AttachConsole
在控制台之间切换。
还请注意,某些程序打开“ CONOUT $”而不是写入StandardOutput
和StandardError
句柄,尤其是在它们需要控制台而不是标准句柄的情况下(例如,管道或磁盘文件)。在这种情况下,在hStdOutput
中设置STARTUPINFO
是不够的。您必须通过SetConsoleActiveScreenBuffer
暂时使新的屏幕缓冲区处于活动状态。这不会影响调用方的标准句柄。它在连接的控制台中设置活动屏幕缓冲区,这是“ CONOUT $”打开的内容。子进程退出后,或者您知道子进程已经打开并写入新的屏幕缓冲区之后,可以恢复以前的屏幕缓冲区。
[*]在Windows 8+中,这些控制台文件由condrv.sys设备驱动程序实现,并在“ \ Device \ ConDrv”上打开。它们分别被命名为“控制台”,“ CurrentIn”,“ CurrentOut”,“ ScreenBuffer”,“ Input”和“ Output”。控制台连接本身的文件名是“ Connect”。在内部将后者作为进程ConsoleHandle
打开,在某些情况下,控制台API会隐式使用该进程(例如GetConsoleWindow
,GetConsoleCP
和GetConsoleTitle
)。