簡體   English   中英

如何讀取和寫入為 c++ 中的子進程創建的管道

[英]How can I read and write to pipes created for a child process in c++

我正在制作一個測試程序,它打開一個控制台應用程序並讀取它的標准輸入寫入它的標准 output,但是管道有問題。 我正在使用命名管道,因為我可能必須運行這個線程化的甚至打開多個可執行文件才能同時進行通信。 這些將需要保持運行並不斷接受輸入並發出輸出,就像在控制台計算器中一樣,它會詢問您是否要進行另一次計算或在每次計算后退出。

使用錯誤檢查我發現管道已成功創建,我將它們應用於 startupInfo 結構並成功打開可執行文件。 此處請注意,如果我在調用 createProcess 后立即在 visual studio 中設置斷點,子進程會出現在我的任務管理器中,檢查 STILL_ACTIVE 為真並且在 pipe 處達到峰值顯示一個空的 pipe。如果沒有斷點已設置然后我看不到它,並且檢查 STILL_ACTIVE 是錯誤的。

為了簡化問題,我回到了基礎,一個簡單的 hello world 可執行文件在 c++ 中。計算器將是下一個測試。 這會將 hello world 打印到控制台,並通過 cin:get() 等待按下回車鍵。 我用測試儀運行它並嘗試從子進程中讀取“Hello World”。 我什么也得不到。

最終項目將是開源的,我不希望用戶必須下載任何其他庫來編譯項目,而 Boost::Process 實際上需要安裝 2 次,因為進程還不是標准的。

我知道我很接近,這是我的簡單測試器作為一個文件,其中進程 class 被提取到主文件中內聯。 注意:我在我的編譯器中啟用了 c++20。

// Tester.cpp 
#include <string>
#include <string_view>
#include <vector>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <io.h>
#include <fcntl.h>
#include <windows.h>

int main()
{
    std::string data = "";
    int id = 1;
    std::string executable = "HelloWorld.exe";

    if (_access((executable).c_str(), 0) != -1)
    {
        std::cerr << "Error: Executable file not found: " << executable << std::endl;
        exit(0);
    }

    SECURITY_ATTRIBUTES saAttr{};
    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    saAttr.bInheritHandle = TRUE;
    saAttr.lpSecurityDescriptor = NULL;

    //Pipe names
    std::wstring pipeErr = L"\\\\.\\pipe\\err_" + std::to_wstring(id);
    std::wstring pipeOut = L"\\\\.\\pipe\\out_" + std::to_wstring(id);
    std::wstring pipeIn = L"\\\\.\\pipe\\in_" + std::to_wstring(id);    

    // The Child error pipe for reading
    CreateNamedPipeW(pipeErr.c_str(), PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 1, 1024, 1024, 0, NULL);
    HANDLE err_pipe = CreateFileW(pipeErr.c_str(), GENERIC_READ | GENERIC_WRITE, 0, &saAttr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);

    // The Child out pipe for reading
    CreateNamedPipeW(pipeOut.c_str(), PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 1, 1024, 1024, 0, NULL);
    HANDLE out_pipe = CreateFileW(pipeOut.c_str(), GENERIC_READ | GENERIC_WRITE, 0, &saAttr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
    
    // The Child in pipe for writing
    CreateNamedPipeW(pipeIn.c_str(), PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 1024, 1024, 0, NULL);
    HANDLE in_pipe = CreateFileW(pipeIn.c_str(), GENERIC_READ | GENERIC_WRITE, 0, &saAttr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
    
    if (in_pipe == INVALID_HANDLE_VALUE || out_pipe == INVALID_HANDLE_VALUE || err_pipe == INVALID_HANDLE_VALUE)
    {
        std::cout << "Error Creating Handles, Code: " << GetLastError() << std::endl;
        return 0;
    }

    // Make sure the handles' inheritance is set correctly
    if (!SetHandleInformation(in_pipe, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) ||
        !SetHandleInformation(out_pipe, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT) ||
        !SetHandleInformation(err_pipe, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT))
    {
        std::cerr << "Error: Failed to set handle information for the child process" << std::endl;
        return 0;
    }
    
    // Set up the startup info struct
    STARTUPINFOA startupInfo;
    memset(&startupInfo, 0, sizeof(startupInfo));
    startupInfo.cb = sizeof(STARTUPINFOA);
    startupInfo.hStdInput = in_pipe;
    startupInfo.hStdOutput = out_pipe;
    startupInfo.hStdError = err_pipe;
    startupInfo.dwFlags |= STARTF_USESTDHANDLES;
    
    // Set up the process info struct
    PROCESS_INFORMATION processInfo;
    memset(&processInfo, 0, sizeof(processInfo));
    
    // Create the child process
    if (CreateProcessA(NULL, executable.data(), NULL, NULL, TRUE, 0, NULL, NULL, &startupInfo, &processInfo) == 0)
    {
        std::cerr << "Error: Failed to create the child process" << std::endl;
        return 0;
    }

    // Set the pipes to non-blocking mode
    DWORD mode = PIPE_NOWAIT;
    SetNamedPipeHandleState(out_pipe, &mode, NULL, NULL);
    SetNamedPipeHandleState(err_pipe, &mode, NULL, NULL);
    SetNamedPipeHandleState(in_pipe, &mode, NULL, NULL);

    Sleep(500); //wait for child to start, may not be neccesary
    
    // Get the exit code of the child process
    DWORD exitCode;
    GetExitCodeProcess(processInfo.hProcess, &exitCode);

    if (exitCode == STILL_ACTIVE) {
        // Set up the read buffer
        char buffer[1024];
        memset(buffer, 0, sizeof(buffer));
        DWORD bytesRead = 0;
        DWORD bytesAvail = 0;

        // Check if there is data available to read from the pipe
        if (!PeekNamedPipe(out_pipe, buffer, sizeof(buffer), &bytesRead, &bytesAvail, NULL)) {
            std::cerr << "PeekNamedPipe failed (" << GetLastError() << ").\n";
            return 0;
        }

        if (bytesAvail == 0)
        {
            std::cerr << "Pipe is empty" << std::endl;
        }

        if (!ReadFile(out_pipe, buffer, sizeof(buffer) - 1, &bytesRead, NULL))
        {
            std::cerr << "Failed to read from pipe. Error code: " << GetLastError() << std::endl;
            return 0;
        }
        data = buffer;

    }
    if (data == "") {
        std::cout << "Something went wrong. Code: " << GetLastError() << std::endl;
    }
    else {
        std::cout << data << std::endl;
    }

    std::cout << "Press any key." << std::endl;
    std::cin.get();
    return 0;
}

並且,作為參考,這里是 helloworld.exe:

// HelloWorld.cpp
#include <iostream>

int main()
{
    std::cout << "Hello World!" << std::endl;
    std::cin.get();
}

感謝@Igor Tandetnik!

這是工作的 Tester.cpp:

// Tester.cpp 
#include <string>
#include <string_view>
#include <vector>
#include <iostream>
#include <fstream>
#include <filesystem>
#include <io.h>
#include <fcntl.h>
#include <windows.h>
#include <aclapi.h>

constexpr auto BUFSIZE = 4096;

int main()
{
    std::string data = "";
    int id = 1;
    std::wstring executable = L"HelloWorld.exe";
    std::wstring argv = L"";
    
    std::string name_c = "";
    std::string path_c = "";

    HANDLE hChildStd_IN_Rd = NULL;
    HANDLE hChildStd_IN_Wr = NULL;
    HANDLE hChildStd_OUT_Rd = NULL;
    HANDLE hChildStd_OUT_Wr = NULL;
    HANDLE hChildStd_ERR_Rd = NULL;
    HANDLE hChildStd_ERR_Wr = NULL;

    size_t size;
    wcstombs_s(&size, nullptr, 0, executable.c_str(), executable.length());
    name_c.resize(size);
    wcstombs_s(&size, name_c.data(), name_c.size(), executable.c_str(), executable.length());

    wchar_t current_dir[FILENAME_MAX];
    if (_wgetcwd(current_dir, FILENAME_MAX) == nullptr) {
        std::cerr << "Error getting current working directory. Code:" << GetLastError() << std::endl;
        exit(0);
    }

    wchar_t path_exe[MAX_PATH];
    GetModuleFileName(NULL, path_exe, MAX_PATH);
    std::wstring path = path_exe;
    path = std::filesystem::path(path).parent_path();
    path += L"\\";
    path += executable;

    wcstombs_s(&size, nullptr, 0, path.c_str(), path.length());
    path_c.resize(size);
    wcstombs_s(&size, path_c.data(), path_c.size(), path.c_str(), path.length());

    int found = _waccess_s(path.c_str(), 0);
    if (found != 0)
    {
        std::cerr << "Error: Executable file not found: " << name_c << std::endl;
        exit(0);
    }

    SECURITY_ATTRIBUTES sa_attr{};
    sa_attr.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa_attr.bInheritHandle = TRUE;
    sa_attr.lpSecurityDescriptor = NULL;

    // Create the pipes
    if (!CreatePipe(&hChildStd_OUT_Rd, &hChildStd_OUT_Wr, &sa_attr, 0)
        || !CreatePipe(&hChildStd_IN_Rd, &hChildStd_IN_Wr, &sa_attr, 0)
        || !CreatePipe(&hChildStd_ERR_Rd, &hChildStd_ERR_Wr, &sa_attr, 0)) {
        std::cout << "Error Creating Pipes, Code: " << GetLastError() << std::endl;
        return 1;
    }
    
    if (hChildStd_OUT_Rd == INVALID_HANDLE_VALUE || hChildStd_OUT_Wr == INVALID_HANDLE_VALUE 
        || hChildStd_IN_Rd == INVALID_HANDLE_VALUE || hChildStd_IN_Wr == INVALID_HANDLE_VALUE
        || hChildStd_ERR_Rd == INVALID_HANDLE_VALUE || hChildStd_ERR_Wr == INVALID_HANDLE_VALUE)
    {
        std::cout << "Error Creating Handles, Code: " << GetLastError() << std::endl;
        return 1;
    }

    // Set up the startup info struct
    STARTUPINFOW startup_info;
    ZeroMemory(&startup_info, sizeof(STARTUPINFOW));
    startup_info.cb = sizeof(STARTUPINFOW);
    startup_info.hStdOutput = hChildStd_OUT_Wr;
    startup_info.hStdError = hChildStd_ERR_Wr;
    startup_info.hStdInput = hChildStd_IN_Rd;
    startup_info.dwFlags |= STARTF_USESTDHANDLES;
    
    // Set up the process info struct
    PROCESS_INFORMATION process_info;
    memset(&process_info, 0, sizeof(process_info));
    
    // Create the child process
    if (!CreateProcess(path.data(), NULL, &sa_attr, NULL, TRUE, 0, NULL, NULL, &startup_info, &process_info))
    {
        std::cerr << "Error: Failed to create the child process. Code: " << GetLastError() << std::endl;
        return 1;
    }

    // Get the exit code of the child process
    DWORD exitCode;
    GetExitCodeProcess(process_info.hProcess, &exitCode);

    if (exitCode != STILL_ACTIVE) {
        std::wcout << "Unable to Start Process: " << executable.c_str() << std::endl;
        return 1;
    }
    std::wcout << "Started Process: " << executable.c_str() << std::endl;

   Sleep(500); //wait for child to start, may not be neccesary
    
    // Get the exit code of the child process
    GetExitCodeProcess(process_info.hProcess, &exitCode);

    if (exitCode == STILL_ACTIVE) {
        // Set up the read buffer
        DWORD bytesRead{}, dwWritten{};
        CHAR buffer[BUFSIZE]{};
        BOOL bSuccess = FALSE;
        HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);

        bSuccess = ReadFile(hChildStd_OUT_Rd, buffer, BUFSIZE, &bytesRead, NULL);
        if (!bSuccess || bytesRead == 0)
        {
            std::cerr << "Failed to read from pipe. Error code: " << GetLastError() << std::endl;
            return 1;
        }
        std::vector<char> v_data(buffer, buffer + bytesRead);
        data = std::string(v_data.data(), v_data.size());

    }
    std::cout << "Recieved from Child: " << data << std::endl;
    if (data == "") {
        std::cout << "Something went wrong. Code: " << GetLastError() << std::endl;
        return 1;
    }
    else {
        std::cout << data << std::endl;
    }
    CloseHandle(process_info.hThread);
    std::cout << "Press any key." << std::endl;
    std::cin.get();
    return 0;
}

暫無
暫無

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

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