2017-01-03 17 views
3

В моем коде C++/WinAPI я хочу запустить некоторые команды и захватить их вывод. Чтобы протестировать вывод без ASCII, я переименовал свое сетевое подключение в Ethérnét אבג БбГгДд и запустил ipconfig. При работе в командной строке, то выход выходит правильно (виден при использовании поддерживающего шрифта как Courier New):Capture ispwned process stdout как unicode

C:\>ipconfig 
Windows IP Configuration 

Ethernet adapter Ethérnét אבג БбГгДд: 
(...) 

Я попытался перенаправить вывод в трубу, после the example in this answer. Но массив байтов, возвращаемый с ReadFile(), не является юникодом - он закодирован в CP_OEMCP (CP437 в моем случае), и поэтому символы на иврите и русском языке выходят как «?». Поскольку символы уже потеряны, дальнейшая обработка не может их восстановить.

Очевидно, это возможно, так как это делает CMD в окне консоли. Как мне это сделать?

+0

ReadFile возвращает байты, он не знает, что такое Unicode. Покажите, как обрабатывается его буфер. –

+0

Я проверил возвращенные байты от отладчика, и они закодированы в CP437, причем иврит/русские символы заменены фактическими '?' S. Поскольку символы потеряны, обработка не восстановит это. Я хотел знать, как cmd.exe (или окно консоли?) Удается правильно захватить эти символы. – Jonathan

+0

поэтому преобразуйте его в Юникод с помощью 'MultiByteToWideChar (CP_OEMCP,' - символы не потеряны – RbMm

ответ

3

Казалось бы, что ipconfig производит вывод Unicode, когда он обнаруживает, что выходное устройство - это консоль, а выход ANSI - в противном случае. Вероятно, это будет мера обратной совместимости.

Большинство других встроенных средств командной строки, вероятно, либо ANSI-only, либо ведут себя так же, как ipconfig, по той же причине. В Windows средства командной строки предназначены для использования в командной строке; программистам не рекомендуется обходить их и анализировать выходные данные. Вместо этого вы должны использовать соответствующие API.

Если вы знаете, на каком языке вы ожидаете, вы можете выбрать кодовую страницу, которая сохранит содержимое.

Добавил: @ Джонатан недокументированных: Оказывается, вы можете контролировать кодирование встроенных команд с помощью переменной окружения OutputEncoding. Я тестировал с Ipconfig, но предположительно она работает с другими встроенными средствами, а также:

> for %e in ("" Unicode Ansi UTF8) do (set OutputEncoding=%~e& ipconfig >ipconfig-%~e.txt) 
> (set OutputEncoding= & ipconfig 1>ipconfig-.txt) 
> (set OutputEncoding=Unicode & ipconfig 1>ipconfig-Unicode.txt) 
> (set OutputEncoding=Ansi & ipconfig 1>ipconfig-Ansi.txt) 
> (set OutputEncoding=UTF8 & ipconfig 1>ipconfig-UTF8.txt) 

И действительно, IPCONFIG - * .txt являются enconded, как и ожидалось.! Обратите внимание, что это недокументировано, но это работает для меня.

+0

Это объясняет это. Я заглянул в 'ipconfig' и добавил свои выводы к ответу. Мне хотелось бы установить CP_OEMCP на CP_UTF8 (и CP_ACP тоже) ... – Jonathan

+0

@Jonathan, фрагмент кода, который вы опубликовали, достигнут только в том случае, когда вывод находится на консоли, это не имеет отношения к случаю, когда выход был перенаправлен на трубу. Однако интересно, что именно C-среда выполнения отвечает за преобразование из UTF-16 в текущую локаль. Из того, что я вижу в источнике CRT, он использует 'wcstomb_s' для этого, хотя я смотрю на CRT Visual Studio, не совсем так же, как тот, который встроен в Windows. К сожалению, похоже, нет никакого способа заставить CRT генерировать UTF-8. –

+1

Действительно, мой код не имеет значения. Тем не менее, я обнаружил, что преобразование происходит внутри 'ipconfig.exe' - и вы можете управлять кодовой страницей, используя недокументированную переменную envcode. Я добавлю образец к вашему ответу. – Jonathan

-1

консольное приложение может использовать разные способы вывода.

  • для консоли ручки можно использовать WriteConsoleW для вывода уже в UNICODE.
  • , если мы хотим использовать WriteConsoleA или WriteFile для консоли ручки нужно сначала преобразовать UNICODE текст Многобайтовыестроки по WideCharToMultiByte с CodePage := GetConsoleOutputCP()
  • если мы не UNICODE текст изначально для выхода (скажем UTF-8 или Ansi), нужно сначала преобразовать его в UNICODE по MultiByteToWideCharCP_UTF8 или CP_ACP), а затем уже снова преобразовать его в многобайтную WideCharToMultiByte(GetConsoleOutputCP(), ..)

обычно (по умолчанию) GetConsoleOutputCP() возвращают то же значение, как GetOEMCP(), поэтому имеют тот же эффект в MultiByteToWideChar и WideCharToMultiByte как CP_OEMCP (это постоянное значение переводится в GetOEMCP())

когда выходная ручка перенаправляется в файл нужно использовать только WriteFile только. однако приложение может записывать данные в файл в любом формате: UNICODE, Ansi (CP_ACP), UTF-8 (CP_UTF8) и т. д. какой формат будет использоваться - очень зависит от конкретного применения. вы не можете полностью контролировать это. обычный вы получите многобайтовый вывод в кодировке CP_OEMCP. то вам нужно решить, как это сделать - быстрее всего вам понадобится сначала преобразовать его в UNICODE и использовать форму unicode. если вам нужно Ansi - вам понадобится сделать еще одно преобразование.

сказать, если вы попытаетесь использовать вывод трубы в CP_OEMCP кодировке с OutputDebugStringA - вы получили ошибку (не читаемый) для текста без английского. , но после 2-х переходов CP_OEMCP ->UNICODE ->CP_ACP вы можете исправить текст, отображаемый с OutputDebugStringA , но потому, что OutputDebugStringW существует - здесь достаточно только UNICODE конвертировать

также некоторые приложения имеют специальные опции для вывода управления в файл формата.скажем ipconfig.exe ищет "OutputEncoding" Окружающая среда Переменная и зависит от нее строковое значение ("Unicode", "Ansi", "UTF-8") Производит различные выходные данные. по умолчанию (если эта переменная среды не существует или неизвестно) CP_OEMCP

пример процедуры считывания трубок. Предположим, что входные данные в CP_OEMCP кодирования:

void OnRead(PVOID buf, ULONG cbTransferred) 
{ 
    if (cbTransferred) 
    { 
     if (int len = MultiByteToWideChar(CP_OEMCP, 0, (PSTR)buf, cbTransferred, 0, 0)) 
     { 
      PWSTR pwz = (PWSTR)alloca((1 + len) * sizeof(WCHAR)); 

      if (len = MultiByteToWideChar(CP_OEMCP, 0, (PSTR)buf, cbTransferred, pwz, len)) 
      { 
       if (g_bUseAnsi) 
       { 
        if (cbTransferred = WideCharToMultiByte(CP_ACP, 0, pwz, len, 0, 0, 0, 0)) 
        { 
         PSTR psz = (PSTR)alloca(cbTransferred + 1); 

         if (cbTransferred = WideCharToMultiByte(CP_ACP, 0, pwz, len, psz, cbTransferred, 0, 0)) 
         { 
          DoPrint(psz, cbTransferred, OutputDebugStringA); 
         } 
        } 
       } 
       else 
       { 
        DoPrint(pwz, len, OutputDebugStringW); 
       } 
      } 
     } 
    } 
} 

// debugger can incomplete print too big buffer, so split it on small chunks 
template<typename T> void DoPrint(T* p, ULONG len, void (WINAPI* fnOutput)(const T*)) 
{ 
    ULONG cb; 
    T* q = p; 
    do 
    { 
     cb = min(len, 256); 

     q = p + cb; 

     T c = *q; 

     *q = 0; 

     fnOutput(p); 

     *q = c; 

     p = q; 

    } while (len -= cb); 
} 

о вашем конкретном случае - ipconfig.exe используется WriteConsoleW для вывода на консоль. в результате он не зависит от текущего языкового стандарта системы и может корректировать отображение многоязычного текста. но другие инструменты, такие как route.exe, использовали WriteFile для вывода (оба для консоли и файла) и конвертировали до этого UNICODE текст в несколько байт WideCharToMultiByte(CP_OEMCP,..) - в результате здесь возникнут проблемы, если попробуйте отобразить символы, которые не существуют на кодовой странице CP_OEMCP (текущий system locale). если у вас есть CP437 - Иврит и русские символы будут потеряны, если использовать UNICODE ->CP_OEMCP, нужно только прямое выход с юникодом в консоль и файл. это возможно - зависимость от конкретного применения. для скажем route.exe это невозможно. для ipconfig.exe это возможно, потому что он всегда писать на консоль в формате Юникод, и может записать в файл также в unicode или utf-8, если вы установите "OutputEncoding" в "Unicode" или "UTF-8"

+0

Это не учитывает многобайтовые символы, которые разбивают пакеты. Если [IsDBCSLeadByte] (https://msdn.microsoft.com/en-us/library/windows/desktop/dd318664.aspx) является «TRUE» для конечного блока кода, преобразование прерывает как этот блок, так и следующее блок байтов. – IInspectable

+0

@Инспективный - что не получается? ты о ? – RbMm

+0

Прошу прощения, это язык, который я не понимаю. – IInspectable