Cygwin / tcsh stderr для приложений с графическим интерфейсом не работает в Windows 10 - PullRequest
0 голосов
/ 04 октября 2018

У меня есть приложение с графическим интерфейсом, которое должно работать в пакетном режиме (из консоли) и выводить сообщения в stdout и stderr.Я также должен иметь возможность перенаправить эти сообщения в файлы, если это необходимо.Кроме того, я использую библиотеку лицензирования, которая проверяет лицензию и записывает ее сообщения в stderr в пакетном режиме.

Я знаю, что приложения с графическим интерфейсом не имеют дескрипторов 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.

После нескольких проб и ошибок мне удалось написать приведенный ниже код для обработки большинства случаев:

Он отлично работает на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.@echo off

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, но я не могу понять это вообще.

Извините за долго, грязный и, возможно, запутанный пост, но любая помощь будет принята.

...