使用C/ c++设置UTF-8输入并通过管道从Powershell获取UTF-8输出



我无法将正确的utf-8字符串写入powershell子进程。ASCII字符可以工作,但utf-8字符除外。'ü',会有不同的解释。从相同的powershell子进程中读取时也有同样的问题。

总结:我想在我的程序中使用utf-8编码的powershell。

更新:使用AllocConsole();分配一个控制台,然后调用SetConsoleCP(CP_UTF8);SetConsoleOutputCP(CP_UTF8);,正如@mklement在他的回答中提到的,如果您有一个没有任何控制台的GUI应用程序,则对我有效。如果你有一个控制台应用程序,你不需要手动分配控制台。

更新2:如果您有一个GUI并调用AllocConsole(),那么您可以随后调用ShowWindow(GetConsoleWindow(), SW_HIDE);来隐藏控制台,如这里所述。

我已经试过了:

  • 设置输入输出编码为utf-8进程内$OutputEncoding = [System.Console]::OutputEncoding = [System.Console]::InputEncoding = [System.Text.Encoding]::UTF8
  • 对UTF-16做同样的处理,以防有bug,例如...ext.Encoding]::Unicode
  • 对ISO-Latin 1 (cp1252)执行相同操作
  • 使用wchar_t作为所有测试编码的缓冲区和输入
  • 测试给定字符串的字节顺序
  • 测试Unicode(每个字符4字节而不是2字节)
  • 自己一点一点的构建字符串
  • 设置编译器标志为D UNICODE

代码示例:

std::string test("ls än");
DWORD ret = WriteFile(std_in_write, test.c_str(), test.size(), &number_of_bytes_written, nullptr);
if (ret == 0) {
throw PowershellHelper::Exception(PowershellHelper::Exception::Error::COULD_NOT_WRITE_TO_FILE, GetLastError());
}
输出:ls├ñ

示例代码:

HANDLE std_in_read = nullptr;
HANDLE std_in_write = nullptr;
HANDLE std_out_read = nullptr;
HANDLE std_out_write = nullptr;
SECURITY_ATTRIBUTES security_attr;
STARTUPINFO startup_info;
PROCESS_INFORMATION process_information;
DWORD buffer_size = 1000000;
security_attr = {sizeof(SECURITY_ATTRIBUTES), nullptr, true};
if (!CreatePipe(&std_in_read, &std_in_write, &security_attr, buffer_size)) {
throw PowershellHelper::Exception(PowershellHelper::Exception::Error::COULD_NOT_CREATE_IN_PIPE, GetLastError());
}
if (!CreatePipe(&std_out_read, &std_out_write, &security_attr, buffer_size)) {
throw PowershellHelper::Exception(PowershellHelper::Exception::Error::COULD_NOT_CREATE_OUT_PIPE, GetLastError());
}
GetStartupInfo(&startup_info);
startup_info.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
startup_info.wShowWindow = SW_HIDE;
startup_info.hStdOutput = std_out_write;
startup_info.hStdError = std_out_write;
startup_info.hStdInput = std_in_read;
if (!CreateProcess(TEXT(default_powershell_path), nullptr, nullptr, nullptr, TRUE, 0, nullptr, TEXT(default_windows_path), &startup_info, &process_information)) {
throw PowershellHelper::Exception(PowershellHelper::Exception::Error::COULD_NOT_CREATE_PROCESS, GetLastError());
}
std::string test("ls än");
DWORD ret = WriteFile(std_in_write, test.c_str(), test.size(), &number_of_bytes_written, nullptr);
if (ret == 0) {
throw PowershellHelper::Exception(PowershellHelper::Exception::Error::COULD_NOT_WRITE_TO_FILE, GetLastError());
}
DWORD dword_read;
while (true) {
DWORD total_bytes_available;
if (PeekNamedPipe(std_out_read, nullptr, 0, nullptr, &total_bytes_available, nullptr) == 0) {
throw PowershellHelper::Exception(PowershellHelper::Exception::Error::COULD_NOT_COPY_FROM_PIPE, GetLastError());
}
if (total_bytes_available != 0) {
DWORD minimum = min(buffer_size, total_bytes_available);
char buf[buffer_size];
if (ReadFile(std_out_read, buf, minimum, &dword_read, nullptr) == 0) {
throw PowershellHelper::Exception(PowershellHelper::Exception::Error::COULD_NOT_READ_FILE, GetLastError());
}
std::string tmp(buf);
std::cout << tmp << std::endl;
}
if (total_bytes_available == 0) {
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}

注意:没有重复的redirect-input-and-output-of-powershell-exe-to-pipes-in-c,因为代码只适用于ASCII字符,根本不处理utf-8字符。

也不要重复c- gets -utf-8-output-from-createprocess,因为建议的解决方案不会像上面提到的那样工作,我想输入utf-8和读取utf-8。

在创建PowerShell进程之前,您需要将控制台输入和输出代码页设置为65001(UTF-8),通过SetConsoleCPSetConsoleOutputCPWinAPI函数,因为PowerShell CLI使用它们来解码其stdin输入和编码其stdout输出。

(相比之下,$OutputEncoding = [System.Console]::OutputEncoding = [System.Console]::InputEncoding = [System.Text.Encoding]::UTF8仅在从 PowerShell进行外部程序调用时应用内部PowerShell会话)

注意:如果调用进程本身不是一个控制台应用程序,你可能必须在调用SetConsoleCPSetConsoleOutputCP之前分配一个控制台,使用AllocConsoleWinAPI函数,但我坦率地不清楚(a)是否使这个控制台立即可见(这可能是不希望的)和(b)CreateProcess调用是否自动使用这个控制台。

如果不工作,您可以通过cmd.exe调用chcp,然后再调用powershell.exe,沿着cmd /c "chcp 65001 >NUL & powershell -c ...";chcp 65001设置控制台代码页为65001,即UTF-8。

(这会带来额外的开销,但是与powershell.exe进程相比,cmd.exe进程相对轻量级,chcp.com进程也是如此)。

下面是一个示例命令,您可以从PowerShell中运行来演示:

& {
# Save the current code pages.
$prevInCp, $prevOutCp = [Console]::InputEncoding, [Console]::OutputEncoding
# Write the UTF-8 encoded form of string 'kö' to a temp. file.
# Note: In PowerShell (Core) 7+, use -AsByteStream instead of -Encoding Byte
Set-Content temp1.txt -Encoding Byte ([Text.UTF8Encoding]::new().GetBytes('kö'))
# Switch to UTF-8, pipe the UTF-8 file's content to PowerShell's stdin,
# verify that it was decoded correctly, and output it, again encoded as UTF-8.
cmd /c 'chcp 65001 >NUL & type temp1.txt | powershell -nop -c "$stdinLine = @($input)[0]; $stdinLine -eq ''kö''; Write-Output $stdinLine" > temp2.txt'
# Read the temporary file as UTF-8 and echo its content.
Get-Content -Encoding Utf8 temp2.txt
# Clean up.
Remove-Item temp[12].txt
# Restore the original code pages.
[Console]::InputEncoding = $prevInCp; [Console]::OutputEncoding = $prevOutCp
}

这将输出以下内容,表明powershell调用既正确读取了UTF-8编码的输入,又将其输出为UTF-8:

True
ö

注意:

可以通过使用进程内PowerShell SDK绕过字符编码问题作为创建powershell.exe子进程的替代方案,尽管我不知道这在c++中有多痛苦。有关c#示例,请参阅此答案。

最新更新