Cygwin/tcsh stderr для приложений с графическим интерфейсом не работает в Windows 10
У меня есть приложение с графическим интерфейсом, которое должно быть в состоянии работать в пакетном режиме (из консоли) и выводить сообщения в stdout и stderr. Я также должен иметь возможность перенаправить эти сообщения в файлы, если это необходимо. Кроме того, я использую библиотеку лицензирования, которая проверяет лицензию и записывает ее сообщения в stderr в пакетном режиме.
Я знаю, что приложения с графическим интерфейсом не имеют ручек stdout / stderr, поэтому я использовал эту статью: https://www.tillett.info/2013/05/13/how-to-create-a-windows-program- это работает как и GUI, и как консольное приложение / alonog с другими сообщениями stackru.
После многочисленных проб и ошибок мне удалось написать приведенный ниже код для обработки большинства случаев:
Он отлично работает на cmdline, powerShell и tcsh (через cygwin) в windows 7 и windows 10. Однако в win10 сообщения библиотеки для stderr не отображаются на консоли при использовании tcsh, хотя другие сообщения 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
Также стоит упомянуть, что я запускаю cygwin, используя следующий файл.bat. @ эхо выключено
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, но я не могу понять это вообще.
Извините за длинный, грязный и, возможно, запутанный пост, но любая помощь будет оценена.