适用于GUI应用程序的Cygwin / tcsh stderr在Windows 10中不起作用

时间:2018-10-03 23:11:47

标签: console cygwin tcsh

我有一个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有关,但是我根本无法弄清。

很抱歉,冗长,混乱且可能令人困惑的帖子,但我们将不胜感激。

0 个答案:

没有答案