Как мне хорошо играть с Cygwin?

У меня есть программа на C/C++, написанная для Windows и скомпилированная с Visual Studio. И это инструмент командной строки, который означает, что он может быть разумно запущен под Cygwin Bash, вероятно, в MinTTY, и некоторые из его пользователей сейчас делают это.

Я бы хотел изменить свою программу, чтобы она лучше играла с Cygwin.

В настоящее время моя программа использует API-интерфейсы Windows Console для вывода довольно цветного текста и управления курсором, а также для того, чтобы вообще быть "интерактивным" (или "интерактивным" для определения "интерактивный") 1980-х годов, по крайней мере, когда его вывод не передано в файл.

Чтобы моя программа играла лучше с Cygwin, я могу сделать так, чтобы она излучала ANSI \x1B[... escape-коды вместо вызова консольных API, но настоящая проблема в том, чтобы знать, когда это сделать.


Я думаю, что есть четыре случая:

  1. Вызывается как интерактивная консольная программа Windows. -> (Используйте консольные API.)

  2. Вызывается как обычная консольная программа Windows, но пользователь где-то передает данные. -> (Никаких стилей или интерактивных звонков вообще.)

  3. Вызывается как интерактивная программа Bash или в рамках MinTTY. -> (Используйте управляющие коды ANSI.)

  4. Вызывается Bash, но пользователь где-то отправляет свой вывод. -> (Никаких стилей или интерактивных звонков вообще.)

Я могу отличить случай 1, проверяя, GetConsoleMode() преуспевает или терпит неудачу для stdout справиться; если это удастся, у меня определенно будет консоль Windows, и это тот случай, когда я включаю код для использования API-интерфейсов консоли.

Я хотел бы по-другому относиться к случаю 3 и использовать для него коды ANSI, но, к сожалению, случаи 2, 3 и 4 кажутся в основном неразличимыми:

  • stdout ручка, кажется, просто непрозрачный объект FILE_TYPE_PIPE для всех трех случаев.
  • Я могу использовать OSTYPE переменная окружения и, по крайней мере, предположить, что я нахожусь под Cygwin, который отличает случай 2 от случаев 3 и 4
  • Если бы я мог связать против cygwin1.dll Я мог бы как-то использовать его isatty(), но я не могу ссылаться на это, так как многие из моих пользователей не имеют (и не будут) установлен Cygwin. (И я совсем не уверен, что это сработает, даже если это будет возможно.)
  • MSVC родной _isatty() думает, что Cygwin Bash - это труба, а не интерактивная оболочка, потому что она просто использует GetFileType() глубоко под капотом.

Короче говоря, я не вижу очевидного способа отделить случай 3 от случая 4.

Прямо сейчас я отношусь к случаю 3 точно так же, как к случаям 2 и 4, и пользователи Cygwin (включая меня) получают дурацкое взаимодействие по телетайпу вместо дружественного интерактивного полноэкранного дисплея, и мне бы очень хотелось это исправить.


Так есть ли какой-нибудь способ, которым я могу отличить интерактивный вызов Cygwin от других вызовов, чтобы моя нативная программа Windows работала хорошо при вызове Cygwin Bash или других подобных оболочек?

(Важное примечание: есть одно решение, которое не обсуждается: программа не будет скомпилирована как нативная программа Cygwin. Это программа Windows, скомпилированная с цепочкой инструментов Microsoft и не скомпилированная с GCC. Цель состоит в том, чтобы изменить / обновить его так, чтобы он вел себя как можно лучше под Cygwin - без фактической связи с какой-либо из библиотек Cygwin. Я могу только разумно распространять один исполняемый файл для Windows, а не два.)

1 ответ

Если бы я мог связываться с cygwin1.dll, я мог бы каким-то образом использовать его isatty(), но я не могу связываться с этим, поскольку у многих из моих пользователей не установлен (и не будет) установлен Cygwin. (И я совсем не уверен, что это сработает, даже если это будет возможно.)

Хорошо, вы не можете статически связываться с этой DLL, но это не значит, что вы не можете попытаться загрузить ее динамически.

Вы могли бы попытаться LoadLibrary("cygwin1.dll"), Если это не удается, значит, он не работает под Cygwin, и вы можете использовать встроенные функции Windows для определения типа консоли.

С другой стороны, если вы можете открыть эту библиотеку (что означает, что Cygwin доступен в текущей системе), то вы сможете выполнить динамический вызов isatty() и используйте результат, чтобы увидеть, перенаправлен ли вывод.

Редактировать: не так просто просто загрузить Cygwin DLL и вызвать функцию, вам нужно сначала инициализировать библиотеку, как показано в этой ссылке

Выписка:

static BOOL setup_root()
{
    HMODULE hCygwin = NULL;

    // Load the cygwin dll into our application.
    if(!load_cygwin_library(&hCygwin))
        return FALSE;

    // Init the cygwin environment. (Required)
    if(!init_cygwin_library(hCygwin))
        return FALSE;

с обоими интересными процедурами инициализации. Если init_cygwin_library не вызывается, дальнейшие звонки могут не работать:

static BOOL load_cygwin_library(HMODULE* hCygwin)
{
    if((*hCygwin = GetModuleHandleW(cyglibrary)) == NULL)
        if((*hCygwin = LoadLibraryW(cyglibrary)) == NULL)
            return FALSE;
    return TRUE;
}

static BOOL init_cygwin_library(HMODULE hCygwin)
{
    cygwin_dll_init_fn cygwin_dll_init = NULL;
    if((cygwin_dll_init = (cygwin_dll_init_fn)GetProcAddress(hCygwin,"cygwin_dll_init")) == NULL) {
        FreeLibrary(hCygwin);
        return FALSE;
    }
    cygwin_dll_init();
    return TRUE;
}
Другие вопросы по тегам