使用 C/C++ 通过管道 to/from Powershell 设置 UTF-8 输入并获取 UTF-8 输出
Set UTF-8 Input and Get UTF-8 Output through pipe to/from Powershell with C/C++
我无法将正确的 utf-8 字符串写入 powershell 子进程。 ASCII 字符有效,但 utf-8 字符有效,例如'ü', 会有不同的解释。从同一个 powershell 子进程读取时出现同样的问题。
总结:我想通过我的程序使用 utf-8 编码的 powershell。
更新:
如果您有一个没有任何控制台的 GUI 应用程序,如 @mklement 在他的回答中提到的那样,使用 AllocConsole();
分配一个控制台,然后调用 SetConsoleCP(CP_UTF8);
和 SetConsoleOutputCP(CP_UTF8);
,这对我有用。如果您有控制台应用程序,则不必手动分配控制台。
更新二:
如果你有一个 GUI 并调用 AllocConsole()
,你可以在之后调用 ShowWindow(GetConsoleWindow(), SW_HIDE);
来隐藏控制台,如 here.
所述
到目前为止我尝试过的:
- 将输入和输出编码设置为 utf-8
$OutputEncoding = [System.Console]::OutputEncoding = [System.Console]::InputEncoding = [System.Text.Encoding]::UTF8
进程内
- 对 UTF-16 做同样的事情以防出现错误,例如
...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-getting-utf-8-output-from-createprocess 的副本,因为建议的解决方案不会像上面提到的那样工作,我想输入 utf-8 以及读取 utf-8。
您需要将控制台输入和输出代码页设置为 65001
(UTF-8) 在 创建您的 PowerShell 进程之前,通过 SetConsoleCP
and SetConsoleOutputCP
WinAPI functions, because the PowerShell CLI 使用它们来解码其 stdin 输入并对其 stdout 输出进行编码。
(相比之下,$OutputEncoding = [System.Console]::OutputEncoding = [System.Console]::InputEncoding = [System.Text.Encoding]::UTF8
仅在从 PowerShell 进行外部程序调用时应用 PowerShell 会话内。)
注意:如果调用进程本身不是 控制台 应用程序,您可能必须在调用 之前分配一个控制台 13=] 和 SetConsoleOutputCP
,使用 AllocConsole
WinAPI function, but I'm frankly unclear on (a) whether that makes this console instantly visible (which may be undesired) and (b) whether the CreateProcess
调用然后自动使用此控制台。
它不起作用,您可以通过 cmd.exe
调用并在调用 powershell.exe
之前调用 chcp
,按照 cmd /c "chcp 65001 >NUL & powershell -c ..."
的方式; chcp 65001
将控制台代码页设置为 65001
,即 UTF-8。
(这会引入额外的开销,但是 cmd.exe
进程与 powershell.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 as an alternative to creating a powershell.exe
child process, though I don't know how painful that is from C++. For a C# example, see .
绕过 字符编码问题
我无法将正确的 utf-8 字符串写入 powershell 子进程。 ASCII 字符有效,但 utf-8 字符有效,例如'ü', 会有不同的解释。从同一个 powershell 子进程读取时出现同样的问题。
总结:我想通过我的程序使用 utf-8 编码的 powershell。
更新:
如果您有一个没有任何控制台的 GUI 应用程序,如 @mklement 在他的回答中提到的那样,使用 AllocConsole();
分配一个控制台,然后调用 SetConsoleCP(CP_UTF8);
和 SetConsoleOutputCP(CP_UTF8);
,这对我有用。如果您有控制台应用程序,则不必手动分配控制台。
更新二:
如果你有一个 GUI 并调用 AllocConsole()
,你可以在之后调用 ShowWindow(GetConsoleWindow(), SW_HIDE);
来隐藏控制台,如 here.
到目前为止我尝试过的:
- 将输入和输出编码设置为 utf-8
$OutputEncoding = [System.Console]::OutputEncoding = [System.Console]::InputEncoding = [System.Text.Encoding]::UTF8
进程内 - 对 UTF-16 做同样的事情以防出现错误,例如
...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-getting-utf-8-output-from-createprocess 的副本,因为建议的解决方案不会像上面提到的那样工作,我想输入 utf-8 以及读取 utf-8。
您需要将控制台输入和输出代码页设置为 65001
(UTF-8) 在 创建您的 PowerShell 进程之前,通过 SetConsoleCP
and SetConsoleOutputCP
WinAPI functions, because the PowerShell CLI 使用它们来解码其 stdin 输入并对其 stdout 输出进行编码。
(相比之下,$OutputEncoding = [System.Console]::OutputEncoding = [System.Console]::InputEncoding = [System.Text.Encoding]::UTF8
仅在从 PowerShell 进行外部程序调用时应用 PowerShell 会话内。)
注意:如果调用进程本身不是 控制台 应用程序,您可能必须在调用 之前分配一个控制台 13=] 和 SetConsoleOutputCP
,使用 AllocConsole
WinAPI function, but I'm frankly unclear on (a) whether that makes this console instantly visible (which may be undesired) and (b) whether the CreateProcess
调用然后自动使用此控制台。
它不起作用,您可以通过 cmd.exe
调用并在调用 powershell.exe
之前调用 chcp
,按照 cmd /c "chcp 65001 >NUL & powershell -c ..."
的方式; chcp 65001
将控制台代码页设置为 65001
,即 UTF-8。
(这会引入额外的开销,但是 cmd.exe
进程与 powershell.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 as an alternative to creating a powershell.exe
child process, though I don't know how painful that is from C++. For a C# example, see