Чтение Unicode из перенаправленного STDOUT (C ++, Win32 API, Qt) - PullRequest
4 голосов
/ 07 июля 2010

У меня есть приложение C ++, которое динамически загружает подключаемые библиотеки DLL.DLL отправляет текстовый вывод через std :: cout и std :: wcout.Интерфейс на основе Qt должен захватывать весь текстовый вывод из DLL и отображать его.Подход с заменой потокового буфера не полностью работает, так как DLL могут иметь разные экземпляры cout / wcout из-за различий в библиотеках времени выполнения.Таким образом, я применил перенаправление STDOUT для Windows следующим образом:

StreamReader::StreamReader(QObject *parent) :
    QThread(parent)
{
    // void
}

void StreamReader::cleanUp()
{
    // restore stdout
    SetStdHandle (STD_OUTPUT_HANDLE, oldStdoutHandle);

    CloseHandle(stdoutRead);
    CloseHandle(stdoutWrite);
    CloseHandle (oldStdoutHandle);

    hConHandle = -1;

    initDone = false;
}

bool StreamReader::setUp()
{

    if (initDone)
    {
        if (this->isRunning())
            return true;
        else
            cleanUp();
    }

    do
    {
        // save stdout
        oldStdoutHandle = ::GetStdHandle (STD_OUTPUT_HANDLE);

        if (INVALID_HANDLE_VALUE == oldStdoutHandle)
            break;

        if (0 == ::CreatePipe(&stdoutRead, &stdoutWrite, NULL, 0))
            break;

        // redirect stdout, stdout now writes into the pipe
        if (0 == ::SetStdHandle(STD_OUTPUT_HANDLE, stdoutWrite))
            break;

        // new stdout handle
        HANDLE lStdHandle = ::GetStdHandle(STD_OUTPUT_HANDLE);

        if (INVALID_HANDLE_VALUE == lStdHandle)
            break;

        hConHandle = ::_open_osfhandle((intptr_t)lStdHandle, _O_TEXT);
        FILE *fp = ::_fdopen(hConHandle, "w");

        if (!fp)
            break;

        // replace stdout with pipe file handle
        *stdout = *fp;

        // unbuffered stdout
        ::setvbuf(stdout, NULL, _IONBF, 0);

        hConHandle = ::_open_osfhandle((intptr_t)stdoutRead, _O_TEXT);

        if (-1 == hConHandle)
            break;

        return initDone = true;

    } while(false);


    cleanUp();

    return false;
}

void StreamReader::run()
{
    if (!initDone)
    {
        qCritical("Stream reader is not initialized!");
        return;
    }

    qDebug() << "Stream reader thread is running...";

    QString s;
    DWORD nofRead  = 0;
    DWORD nofAvail = 0;

    char buf[BUFFER_SIZE+2] = {0};

    for(;;)
    {
        PeekNamedPipe(stdoutRead, buf, BUFFER_SIZE, &nofRead, &nofAvail, NULL);

        if (nofRead)
        {
            if (nofAvail >= BUFFER_SIZE)
            {
                while (nofRead >= BUFFER_SIZE)
                {
                    memset(buf, 0, BUFFER_SIZE);
                    if (ReadFile(stdoutRead, buf, BUFFER_SIZE, &nofRead, NULL)
                        && nofRead)
                    {
                        s.append(buf);
                    }
                }
            }
            else
            {
                memset(buf, 0, BUFFER_SIZE);
                if (ReadFile(stdoutRead, buf, BUFFER_SIZE, &nofRead, NULL)
                    && nofRead)
                {
                    s.append(buf);
                }

            }

            // Since textReady must emit only complete lines,
            // watch for LFs
            if (s.endsWith('\n')) // may be emmitted
            {
                emit textReady(s.left(s.size()-2));
                s.clear();
            }
            else    // last line is incomplete, hold emitting
            {
                if (-1 != s.lastIndexOf('\n'))
                {
                    emit textReady(s.left(s.lastIndexOf('\n')-1));
                    s = s.mid(s.lastIndexOf('\n')+1);
                }
            }

            memset(buf, 0, BUFFER_SIZE);
        }
    }

    // clean up on thread finish
    cleanUp();
}

Однако у этого решения есть препятствие - библиотека времени выполнения C, которая зависит от локали.Таким образом, любой вывод, отправленный в wcout, не достигает моего буфера, потому что среда выполнения C обрезает строки до непечатных символов ASCII, присутствующих в строках в кодировке UTF-16.Вызов setlocale () демонстрирует, что среда выполнения C выполняет пере / кодирование строк. setlocale () мне не поможет, по той причине, что нет знания языка или локали текста, так как подключаемые библиотеки DLL читаются извне системы и могут смешиваться разные языки.После размышлений я решил отказаться от этого решения и вернуться к замене буфера cout / wcout и наложить требование к DLL вызывать метод инициализации по двум причинам: UTF16 не передается в мой буфер, а затем проблема выяснения кодировкив буфере.Тем не менее, мне все еще интересно, есть ли способ получить строки UTF-16 через среду выполнения C в канал «как есть», без зависящего от локали преобразования?

ps любые предложения по перенаправлению cout / wcout в пользовательский интерфейса не два упомянутых подхода приветствуются:)

Заранее спасибо!

Ответы [ 3 ]

1 голос
/ 14 июля 2010

Проблема здесь в том, что преобразование кода из wchar_t в char выполняется полностью внутри подключаемой библиотеки DLL, какой бы реализацией cout / wcout она ни пользовалась (что, как вы говорите) может не совпадать с тем, что используется основным приложением). Таким образом, единственный способ заставить его вести себя по-другому - это каким-то образом перехватить этот механизм, например, с заменой streambuf.

Однако, как вы подразумеваете, любой код, который вы пишете в основном приложении, не обязательно будет совместим с реализацией библиотеки, которую использует DLL. Например, если вы реализуете потоковый буфер в основном приложении, он не обязательно будет использовать тот же ABI, что и потоковые буферы в DLL. Так что это рискованно.

Я предлагаю вам внедрить DLL-оболочку, которая использует ту же версию библиотеки C ++, что и плагин, так что она гарантированно совместима, и в этой DLL-оболочке выполните необходимое вмешательство в cout / wcout. Он может загружать плагин динамически, и поэтому может быть повторно использован с любым плагином, который использует эту версию библиотеки. В качестве альтернативы, вы можете создать некоторый исходный код многократного использования, который можно скомпилировать специально для каждого плагина, создав таким образом очищенную версию каждого плагина.

После того, как DLL обернута, вы можете заменить потоковый буфер в cout / wcout, который сохраняет данные в памяти, как я думаю, вы изначально планировали, и вам вообще не пришлось возиться с файловыми дескрипторами.

PS: Если вам когда-нибудь понадобится создать wstream, конвертирующий в UTF-8 и обратно, то я рекомендую использовать Boost's utf8_codecvt_facet в качестве очень аккуратного способа сделать это. Он прост в использовании, и в документации есть пример кода. (В этом случае вам придется скомпилировать версию Boost специально для той версии библиотеки, которую использует плагин, но не в общем случае.)

0 голосов
/ 11 июля 2010

Попробуйте:

std::wcout.imbue(std::locale("en_US.UTF-8"));

Это зависит от потока и лучше, чем использование глобальной библиотеки C setlocale().

Однако вам, возможно, придется настроить имя локали в соответствии с тем, что поддерживает ваша среда выполнения.

0 голосов
/ 10 июля 2010

Я не знаю, возможно ли это, но, возможно, вы могли бы запустить DLL в отдельном процессе и записать выходные данные этого процесса с эквивалентом Windows pipe (что бы это ни было, но Qt QProcess долженпозаботься об этом за тебя).Это будет похоже на то, как Firefox работает с плагинами процессов (по умолчанию в 3.6.6, но это было сделано некоторое время с 64-битным Firefox и 32-битным плагином Flash).Вам нужно было бы придумать какой-то способ взаимодействия с DLL в отдельном процессе, например, совместно используемую память, но это должно быть возможно.Не обязательно красиво, но возможно.

...