2010-07-07 3 views
4

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

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 runtime усекает строки на непечатаемых ASCII-символах, присутствующих в кодированных строках UTF-16. Вызов setlocale() демонстрирует, что C runtime делает строку re/encoding. setlocale() не помогает мне по той причине, что нет никакого знания языка или языка текста, поскольку подключаемые DLL-файлы читаются извне системы, и могут быть разные языки смешанными. После предоставления N-мысли я решил отказаться от этого решения и вернуться к замене буфера cout/wcout и потребовать, чтобы библиотеки DLL вызывали метод инициализации по двум причинам: UTF16 не переходил в мой буфер, а затем проблема определения кодирование в буфере. Тем не менее, мне все еще интересно, есть ли способ получить строки UTF-16 через C runtime в pipe 'as is', без зависящего от локали преобразования?

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

Заранее благодарю вас!

+0

Вы правы, что проблема заключается в том, (в соответствии с требованиями стандарта) 'std :: wcout'. Это было бы самым простым способом, если бы оба отправителя и получателя использовали обычные функции Windows API. – Philipp

+0

Спасибо, Филипп! Из-за устаревших выпусков плагинов используют cout/wcout, и я должен заставить их работать. –

+0

Если они пишут 'cout', вы можете попытаться заставить их использовать UTF-8, но я не знаю, как обращаться с' wcout', так как для стандартного требуется кодирование некоторой локальной кодировки, которая обычно будет устаревшей 8- бит. – Philipp

ответ

0

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

+0

Это интересный подход! Я не уверен, что это практично в моем конкретном случае, но общая идея интересна. Я определенно попробую это из любопытства. –

+0

@Sergei: Я думаю, у вас все еще будет проблема, что wcout подключаемого модуля задохнется, когда он попытается вывести символ не-ascii. –

0

Try:

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

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

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

+0

Проблема здесь, по крайней мере, в том, что локаль неизвестна. Строки кодируются UCS2, но языковой стандарт невозможно сказать заранее. Вот почему подход к целому языку неприемлем. Однако спасибо за подсказку, я проверю, как установка locale на std :: wcout влияет на выход. –

+0

@Sergei Eliseev: На самом деле это не зависит от локали пользователя вообще, потому что локаль строится явно по имени и ассоциируется только с одним потоком. Другие потоки могут иметь другие локали. Выход из глобальных локалей - одна из величайших вещей, которые C++ std lib получил правильно, ИМХО. –

+0

@Sergei Eliseev: Я, наконец, позаимствовал Windows-машину, чтобы попробовать это, и я обнаружил, что Windows (XP, по крайней мере) не поддерживает использование кодовой страницы UTF-8 в именах локалей. IOW, std :: locale ("English_USA.UTF-8") выбрасывает std :: runtime_error ("bad locale name"), тогда как std :: locale ("English_USA.1252") в порядке. Я посмотрел это онлайн, и я думаю, что это потому, что UTF-8 может генерировать более двух байтов на символ Unicode. Итак, мое решение не работает на Windows, к сожалению, хотя это и происходит в Linux. Извини за это. Я буду проверять вещи в Windows в будущем, а не делать предположения. –

1

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

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

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

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

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

Смежные вопросы