捕获生成的进程标准输出作为unicode

时间:2017-01-03 09:23:52

标签: c++ winapi unicode stdout io-redirection

在我的C ++ / WinAPI代码中,我想运行一些命令并捕获它们的输出。为了测试非ASCII输出,我将网络连接重命名为(1920, 2560)并运行Ethérnét אבג БбГгДд。在命令提示符下运行时,输出正确(使用Courier New等支持字体时可见):

ipconfig

我尝试在the example in this answer之后将输出重定向到管道。但是从C:\>ipconfig Windows IP Configuration Ethernet adapter Ethérnét אבג БбГгДд: (...) 返回的字节数组不是unicode - 它在CP_OEMCP(在我的情况下是CP437)中编码,因此希伯来语和俄语字符以“?”形式出现。由于字符已经丢失,因此无法进一步处理它们。

显然这是可能的,因为控制台窗口中的cmd可以实现。我该怎么办?

2 个答案:

答案 0 :(得分:3)

ipconfig在检测到输出设备是控制台时会产生Unicode输出,否则会输出ANSI输出。这可能是一种向后兼容性措施。

出于同样的原因,大多数其他内置命令行工具可能只是ANSI或者行为与ipconfig相同。在Windows中,命令行工具也适用于命令行;不鼓励程序员炮轰他们并解析输出。相反,您应该使用相应的API。

如果您知道自己期望的语言,则可以选择保留内容的代码页。

@Jonathan添加: 未记录: 结果表明,您可以使用环境变量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按预期进行了!请注意,这是没有记录的,但它对我有用。

附录: 从Windows 10 v1809开始,另一种方法是创建pseudoconsole.

答案 1 :(得分:-1)

控制台应用程序可以使用不同的输出方式。

  • 对于控制台句柄,我们可以使用WriteConsoleW来输出 UNICODE
  • 如果我们想要使用WriteConsoleAWriteFile作为控制台 句柄首先需要将UNICODE文本转换为多字节 WideCharToMultiByteCodePage := GetConsoleOutputCP()
  • 如果我们最初没有UNICODE文本输出(比如UTF-8或者说 Ansi),首先需要将其转换为UNICODE MultiByteToWideCharCP_UTF8CP_ACP)然后 已经再次将其转换为多字节WideCharToMultiByte(GetConsoleOutputCP(), ..)

通常(默认情况下)GetConsoleOutputCP()返回与GetOEMCP()相同的值,因此在MultiByteToWideCharWideCharToMultiByte中与CP_OEMCP具有相同的效果(此常量值)被翻译为GetOEMCP()

当输出句柄重定向到文件时,只需使用WriteFile。但是应用程序可以以任何格式将数据写入文件:UNICODEAnsiCP_ACP),UTF-8CP_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代码页(当前系统区域设置)中不存在的字符,则会出现问题。如果您有CP437 - 如果使用UNICODE - &gt;,希伯来语和俄语字符将会丢失CP_OEMCP,只需要使用unicode直接输出到控制台和文件。这是可能的 - 取决于具体的应用。比如说route.exe这是不可能的。对于ipconfig.exe这可能,因为它总是以unicode格式写入控制台,如果将unicode设置为utf-8,也可以在"OutputEncoding""Unicode"中写入文件}或"UTF-8"