我如何在c++中读写为子进程创建的管道

6g8kf2rb  于 2023-01-18  发布在  其他
关注(0)|答案(1)|浏览(137)

我正在做一个测试程序,它可以打开一个控制台应用程序,读取它的标准输入,写入它的标准输出,但是我遇到了管道的问题。我使用命名管道,因为我可能必须运行这个线程,甚至一次打开多个可执行文件进行通信。这些需要保持运行,并不断地接受输入和给予。就像在控制台计算器中,询问您是否需要另一次计算或在每次计算后退出。
使用错误检查我发现管道创建成功,我将它们应用到startupInfo结构并成功打开可执行文件。这里需要注意的是,如果我在调用createProcess之后立即在visual studio中设置断点,子进程确实会显示在我的任务管理器中,检查STILL_ACTIVE为真,管道的峰值显示一个空管道。如果没有设置断点,那么我看不到它,检查STILL_ACTIVE为假。
为了简化这个问题,我回到了基础,一个简单的hello world在c中的可执行文件。计算器将是下一个测试。它将hello world打印到控制台,并通过cin:get()等待按下enter键。我用测试器运行了这个测试,并试图从子进程中读取“Hello World”。我什么也没得到。
最终项目将是开源的,我不希望用户下载任何其他库来编译项目,Boost::Process实际上需要2次安装,因为Process还不是标准的。
我知道我已经很接近了,下面是我的简单测试器,它是一个文件,其中的流程类被提取出来内联在main中。我已经在我的编译器中启用了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();
}
euoag5mw

euoag5mw1#

感谢@伊戈尔·坦德尼克!
下面是工作的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;
}

相关问题