我有一个GUI应用程序,该应用程序应该能够以批处理模式运行(从控制台),并将消息输出到stdout和stderr。如果需要,我还应该能够将这些消息重定向到文件中。此外,我使用许可库检查许可,并以批处理方式将其消息写入stderr。
我知道GUI应用程序没有stdout / stderr句柄,因此我使用了这篇文章:https://www.tillett.info/2013/05/13/how-to-create-a-windows-program-that-works-as-both-as-a-gui-and-console-application/ alonog和其他stackoverflow帖子。
经过多次试验和错误,我设法编写了以下代码来处理大多数情况:
它在Windows 7和Windows 10上的cmdline,powerShell和tcsh(通过cygwin)上完美工作。但是,在win10中,使用tcsh不会在控制台上显示到stderr的库消息,尽管应用程序中还有其他stderr消息确实出现。
static bool attachOutputToConsole() {
char buf[5000] = {0};
char buf2[200] = {0};
// state 0
sprintf(buf2, "0) outHandle: %ld, error1: %ld, errHandle: %ld, error2: %ld, fileType1: %ld, errorf1: %ld, fileType2: %ld, errorf2: %ld,\n",
GetStdHandle(STD_OUTPUT_HANDLE), GetLastError(), GetStdHandle(STD_ERROR_HANDLE), GetLastError(), (long) GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)), GetLastError(), (long) GetFileType(GetStdHandle(STD_ERROR_HANDLE)), GetLastError());
strcat(buf, buf2);
printf("%s", buf);
if(!(GetStdHandle(STD_OUTPUT_HANDLE) == NULL)) {
if (GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)) == FILE_TYPE_DISK){
// if output has been directed to a file, then do nothing
// I know that this doesn't handle the case when stderr isn't redirected to the file with stdout
// but Let's assume for now that stdout and stderr will be redirected together to the file
// state 1-1
sprintf(buf2, "1-1) outHandle: %ld, error1: %ld, errHandle: %ld, error2: %ld, fileType1: %ld, errorf1: %ld, fileType2: %ld, errorf2: %ld,\n",
GetStdHandle(STD_OUTPUT_HANDLE), GetLastError(), GetStdHandle(STD_ERROR_HANDLE), GetLastError(), (long) GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)), GetLastError(), (long) GetFileType(GetStdHandle(STD_ERROR_HANDLE)), GetLastError());
strcat(buf, buf2);
printf("%s", buf);
return false;
}
else {
// When it's being opened through another program for example
// GetFileType(outHandle) would be FILE_TYPE_PIPE
// state 1-2
sprintf(buf2, "1-2) outHandle: %ld, error1: %ld, errHandle: %ld, error2: %ld, fileType1: %ld, errorf1: %ld, fileType2: %ld, errorf2: %ld,\n",
GetStdHandle(STD_OUTPUT_HANDLE), GetLastError(), GetStdHandle(STD_ERROR_HANDLE), GetLastError(), (long) GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)), GetLastError(), (long) GetFileType(GetStdHandle(STD_ERROR_HANDLE)), GetLastError());
strcat(buf, buf2);
printf("%s", buf);
}
}
// If output not directed to a file, try attaching to parent console or create a new one
if(!AttachConsole( ATTACH_PARENT_PROCESS )){
// handle cases when there is no parent console
// for example using .bat file, run program or a scheduled task
// or even when called from another process at which case:
// GetFileType(outHandle) would be FILE_TYPE_PIPE
// state 2-1
sprintf(buf2, "2-1) outHandle: %ld, error1: %ld, errHandle: %ld, error2: %ld, fileType1: %ld, errorf1: %ld, fileType2: %ld, errorf2: %ld,\n",
GetStdHandle(STD_OUTPUT_HANDLE), GetLastError(), GetStdHandle(STD_ERROR_HANDLE), GetLastError(), (long) GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)), GetLastError(), (long) GetFileType(GetStdHandle(STD_ERROR_HANDLE)), GetLastError());
strcat(buf, buf2);
printf("%s", buf);
AllocConsole();
freopen("CONOUT$", "w", stderr);
freopen("CONOUT$", "w", stdout);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
// state 2-2
sprintf(buf2, "2-2) outHandle: %ld, error1: %ld, errHandle: %ld, error2: %ld, fileType1: %ld, errorf1: %ld, fileType2: %ld, errorf2: %ld,\n", GetStdHandle(STD_OUTPUT_HANDLE),
GetLastError(), GetStdHandle(STD_ERROR_HANDLE), GetLastError(), (long) GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)), GetLastError(), (long) GetFileType(GetStdHandle(STD_ERROR_HANDLE)), GetLastError());
strcat(buf, buf2);
printf("%s", buf);
}
else {
// when it's started from cmd line or cygwin/tcsh console, use the existing console
// state 3-1
sprintf(buf2, "3-1) outHandle: %ld, error1: %ld, errHandle: %ld, error2: %ld, fileType1: %ld, errorf1: %ld, fileType2: %ld, errorf2: %ld,\n",
GetStdHandle(STD_OUTPUT_HANDLE), GetLastError(), GetStdHandle(STD_ERROR_HANDLE), GetLastError(), (long) GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)), GetLastError(), (long) GetFileType(GetStdHandle(STD_ERROR_HANDLE)), GetLastError());
strcat(buf, buf2);
printf("%s", buf);
freopen("CONOUT$", "w", stderr);
freopen("CONOUT$", "w", stdout);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
// state 3-2
sprintf(buf2, "3-2) outHandle: %ld, error1: %ld, errHandle: %ld, error2: %ld, fileType1: %ld, errorf1: %ld, fileType2: %ld, errorf2: %ld,\n", GetStdHandle(STD_OUTPUT_HANDLE), GetLastError(), GetStdHandle(STD_ERROR_HANDLE), GetLastError(), (long) GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)), GetLastError(), (long) GetFileType(GetStdHandle(STD_ERROR_HANDLE)), GetLastError());
strcat(buf, buf2);
printf("%s", buf);
}
// redirect stdout, stdin to the console, either new or attached
return true;
}
static void sendEnterKey() {
INPUT ip;
// Set up a generic keyboard event.
ip.type = INPUT_KEYBOARD;
ip.ki.wScan = 0; // hardware scan code for key
ip.ki.time = 0;
ip.ki.dwExtraInfo = 0;
// Send the "Enter" key
ip.ki.wVk = 0x0D; // virtual-key code for the "Enter" key
ip.ki.dwFlags = 0; // 0 for key press
SendInput(1, &ip, sizeof(INPUT));
// Release the "Enter" key
ip.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release
SendInput(1, &ip, sizeof(INPUT));
}
int PASCAL _tWinMain(a1, a2, a3, a4)
HINSTANCE a1;
HINSTANCE a2;
LPTSTR a3;
int a4;
{
int i = 0;
bool console = false;
extern int __argc;
extern LPTSTR *__targv;
Argc = __argc;
Argv = __targv;
HandleInstance = a1;
HandlePreviousInstance = a2;
lpCmdLine = a3;
nShowCmd = a4;
if (Argc >=2){
for(i=0; i<Argc; i++){
if(lstrcmp(Argv[i], L"-batch") == 0)
console = attachOutputToConsole();
}
}
// This message appears normally on cygwin win10
fprintf(stderr, "stderr: before\n");
// license checking happens here
// The problem is in here, the library internal stderr messages doesn't appear on cygwin win10
mainInitialize();
mainStartUp();
mainRelease();
// This message appears normally on cygwin win10
fprintf(stderr, "stderr: after\n");
if (console && (GetConsoleWindow() == GetForegroundWindow())){
// just to get the prompt again
sendEnterKey();
fclose(stdout);
fclose(stderr);
FreeConsole();
}
return(ExitStatus);
}
我制作了一些调试消息,以查看上述所有状态的句柄值以及其处理方式:
cmdline win7
0) outHandle: 7, error1: 6, errHandle: 11, error2: 6, fileType1: 0, errorf1: 6, fileType2: 0, errorf2: 0,
1-2) outHandle: 7, error1: 6, errHandle: 11, error2: 6, fileType1: 0, errorf1: 6, fileType2: 0, errorf2: 6,
3-1) outHandle: 7, error1: 6, errHandle: 11, error2: 6, fileType1: 2, errorf1: 6, fileType2: 2, errorf2: 6,
3-2) outHandle: 7, error1: 6, errHandle: 11, error2: 6, fileType1: 2, errorf1: 6, fileType2: 2, errorf2: 6,
stderr: before
// some stdout messages when license exists
// or the library intenral "No license" message through stderr
stderr: after
cygwin/tcsh win7
0) outHandle: 15, error1: 6, errHandle: 15, error2: 6, fileType1: 0, errorf1: 6, fileType2: 0, errorf2: 0,
1-2) outHandle: 15, error1: 6, errHandle: 15, error2: 6, fileType1: 0, errorf1: 6, fileType2: 0, errorf2: 6,
3-1) outHandle: 15, error1: 6, errHandle: 15, error2: 6, fileType1: 2, errorf1: 6, fileType2: 2, errorf2: 6,
3-2) outHandle: 15, error1: 6, errHandle: 15, error2: 6, fileType1: 2, errorf1: 6, fileType2: 2, errorf2: 6,
stderr: before
// some stdout messages when license exists
// or the library intenral "No license" message through stderr
stderr: after
cmdline win10
0) outHandle: 0, error1: 6, errHandle: 0, error2: 6, fileType1: 0, errorf1: 6, fileType2: 0, errorf2: 0,
3-1) outHandle: 500, error1: 50, errHandle: 504, error2: 50, fileType1: 2, errorf1: 50, fileType2: 2, errorf2: 50,
3-2) outHandle: 500, error1: 0, errHandle: 504, error2: 0, fileType1: 2, errorf1: 0, fileType2: 2, errorf2: 0,
stderr: before
// some stdout messages when license exists
// or the library intenral "No license" message through stderr
stderr: after
cygwin/tcsh win10
0) outHandle: 0, error1: 6, errHandle: 376, error2: 6, fileType1: 0, errorf1: 6, fileType2: 0, errorf2: 0,
3-1) outHandle: 520, error1: 6, errHandle: 376, error2: 6, fileType1: 2, errorf1: 6, fileType2: 0, errorf2: 50,
3-2) outHandle: 520, error1: 6, errHandle: 376, error2: 6, fileType1: 2, errorf1: 6, fileType2: 0, errorf2: 0,
stderr: before
// some stdout messages when license exists
// The library internal "No license" message through stderr doesn't show at this case
stderr: after
这最后一个很奇怪,因为您甚至可以看到stderr从一开始就具有价值。
cygwin版本是1.7.35(0.287 / 5/3),tcsh版本是6.18.01
值得一提的是,我使用以下.bat文件启动cygwin。 @回声关闭
d:
chdir d:\cygwin\bin
REM "nodosfilewarning": turns off windows PATH warnings
set CYGWIN=rxvt notitle glob nodosfilewarning
bash --login -i -c "ksh -l -c tcsh"
cmdline win7
我认为它可能与conhost.exe或与STARTF_USESTDHANDLES有关,但是我根本无法弄清。
很抱歉,冗长,混乱且可能令人困惑的帖子,但我们将不胜感激。