簡體   English   中英

使用 C/C++ 通過管道到/從 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。

更新:使用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 執行相同操作,例如...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。

在創建 PowerShell 進程之前,您需要通過SetConsoleCPSetConsoleOutputCP WinAPI 函數控制台輸入和輸出代碼頁設置為65001 (UTF-8) ,因為 PowerShell CLI使用它們來解碼其 stdin 輸入並編碼其 stdout 輸出.

(相比之下, $OutputEncoding = [System.Console]::OutputEncoding = [System.Console]::InputEncoding = [System.Text.Encoding]::UTF8PowerShell 進行外部程序調用時僅適用於 PowerShell 會話內.)

注意:如果調用進程本身不是控制台應用程序,您可能必須在調用SetConsoleCPSetConsoleOutputCP之前分配一個控制台,使用AllocConsole WinAPI 函數,但坦率地說,我不清楚 (a) 這是否會使該控制台立即可見(這可能是不希望的)和 (b) 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作為創建powershell.exe子進程的替代方法來繞過字符編碼問題,盡管我不知道 C++ 有多么痛苦。 有關 C# 示例,請參閱此答案

我編寫了一個小型 C++ 庫,它允許在 Windows 控制台上輸入和輸出 UTF-8。 您可以將 cin >>、getline()、scanf() 等與 Unicode UTF-8 一起使用。

https://github.com/Jalopy-Tech/WUTF8Console

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM