0

I am trying to make a program with two console windows. The only way I have seen to do this is to start a child process and then generate a console for the child window. However, I need the parent process to be able to send information to the child to be able to print to their console. I found out that the correct way to do this seems to be using pipes between the processes.

To do this I have been opening a pipe and then creating the child and then making sure they inherit the correct handles and closing the unnecessary handles. Then I use the WriteFile function and ReadFile function from the winapi to send an receive the data. However, it seems that the ReadFile function will only stop and actually read the buffer when I close the handle that I used on the writing end. How am I supposed to send more information afterwords if I close the handle? Is each pipe only a 1 time sending method?

The code I have been using has been loosely following this: https://learn.microsoft.com/en-us/windows/win32/ProcThread/creating-a-child-process-with-redirected-input-and-output

Here is the current code of the parent:

#define UNICODE
#include <windows.h>
#include <stdio.h>
#include <iostream>
#include <string>
#include <cstring>


int main(int argc, char* argv[]) {

    HANDLE ChildStd_IN_Rd = NULL; // Handle for reading from pipe (to be sent to child)
    HANDLE ChildStd_IN_Wr = NULL; // Handle for writing to the pipe (used by parent)

    SECURITY_ATTRIBUTES saAttr; // Used as input for the create pipe function to tell it to give handles

    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); // Set size
    saAttr.bInheritHandle = TRUE; // Tell child process to inherit handles
    saAttr.lpSecurityDescriptor = NULL;

    // Create a pipe for the child process's STDIN.  
   if (! CreatePipe(&ChildStd_IN_Rd, &ChildStd_IN_Wr, &saAttr, 0)) 
      std::cout << "Failed to create pipe" << std::endl; 

    // Ensure the write handle to the pipe for STDIN is not inherited. 
   if ( ! SetHandleInformation(ChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0) )
      std::cout << "Failed to set Handle information" << std::endl; 

    STARTUPINFO si; // This stores info for the main console when a new process console is made
    PROCESS_INFORMATION pi; // Stores the information for the new process being created

    // This sets the memeory of the location of these to zero and then fills them with the correct info
    // I am not sure why they set this to zero but it was done on the example I am following (https://learn.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output)
    ZeroMemory(&pi, sizeof(pi));
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    si.hStdInput = ChildStd_IN_Rd; // Set the standard input of the child to the handle we made for the pipe
    si.dwFlags |= STARTF_USESTDHANDLES; // Tells the child to use the above handle for the std input
    

    // Now this starts the actual child process
    if ( !CreateProcess(L"test1Child.exe",
        NULL, // This is a command line to run
        NULL,
        NULL,
        TRUE,
        CREATE_NEW_CONSOLE,
        NULL,
        NULL,
        &si,
        &pi)) {
            std::cout << "Failed to create Child" << std::endl;
            return 0;
        }
    else {
        CloseHandle(ChildStd_IN_Rd); // This will close the handle to the read pipe which is now already duplicated to the Child process
                                    // Just a note, it took way longer than I think it should to find out that each process has a duplicate handle. So closing the handle here only closes it for the parent and not the child.
    }


    std::string toSend; // String to send
    toSend = "Hello There";
    int bytesToSend = toSend.length() + 1; // Get the length of the string to know how much to send
    char* toSend_char = new char [toSend.length()+1]; // Make the char* array to store the string to send
    std::strcpy(toSend_char, toSend.c_str()); // Put the string into the char* array

    DWORD bytesWritten; // Number of bytes actually written
    BOOL writeSuccess; // Bool to see if the write was succesful

    writeSuccess = WriteFile(ChildStd_IN_Wr, toSend_char, bytesToSend, &bytesWritten, NULL); // Write it BABY!
    CloseHandle(ChildStd_IN_Wr);

    // Trying to send it again

    toSend = "Hello There2";
    bytesToSend = toSend.length() + 1; // Get the length of the string to know how much to send
    toSend_char = new char [toSend.length()+1]; // Make the char* array to store the string to send
    std::strcpy(toSend_char, toSend.c_str()); // Put the string into the char* array

    writeSuccess = WriteFile(ChildStd_IN_Wr, toSend_char, bytesToSend, &bytesWritten, NULL); // Write it BABY!
    CloseHandle(ChildStd_IN_Wr);

    std::cout << "If this is a zero the second write failed: " << writeSuccess << std::endl;
    system("pause");
    
    // Cose the process and thread handles of the child
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);

    return 0;
}

And this is the code for the child:

#include <iostream>
#include <windows.h>
#include <stdio.h>
#include <string>

#define BUFSIZE 4096

int main(int argc, char* argv[]) {


    CHAR readBuf[BUFSIZE]; // Buffer to read to
    DWORD numBytesRead; // To store the number of Bytes that were read
    HANDLE Stdin; // Handle for the standard input from the other function
    Stdin = GetStdHandle(STD_INPUT_HANDLE); // Set the input handle to the standard input handle. This should be the one given to the process by the parent

    BOOL readSuccess; // Bool to check if the read was successful
    std::string readString; // String Read in

    for (;;) {
        
        readSuccess = ReadFile(Stdin, readBuf, BUFSIZE, &numBytesRead, NULL); // Read the Pipe
        if (!readSuccess || numBytesRead == 0) {
            break;
        }
        readString = readBuf;
        std::cout << readString << std::endl;
    }

    for (;;) {

        readSuccess = ReadFile(Stdin, readBuf, BUFSIZE, &numBytesRead, NULL);
        if (!readSuccess || numBytesRead == 0) {
            break;
        }
        readString = readBuf;
        std::cout << readString << std::endl;
    }

    std::cout << "If this is a 0 then it failed: " << readSuccess << std::endl;

    Sleep(1000);

    return 0;
}   

On my machine the code will successfully send the string in the first WriteFile and then the child will get it but it will fail in the second WriteFile (I assume because the Handle is now closed). The child process does end and I assume this is because the second ReadFile does not block because the WriteFile handle is already closed.

I have looked around quite a bit on if pipes between processes are 1 time use but I have not been able to find anything. My idea now is to include a new pipe handle with every message I want to send however that seems bad. For every message I need to create a new pipe and then send and close the proper handles to allow myself to send another.

If this is what I need to do that is okay and I will do it but I am wondering if there is a better way.

  • If you want your client app to keep reading additionally sent strings then you need to create a main loop in your client main, becuase at the moment once the first successfull read has taken place, it will will just sleep for a second then exit. – user20716902 Jan 20 '23 at 16:30
  • There is a second ReadFile call after the first loop. From what I understood about ReadFile it should block and only read from the buffer when the Write side closes its handle. Of course I need to close the Write handle then to send over the pipe but that is what this question is about. – Slippery_Chickenz Jan 20 '23 at 16:37
  • You coded the child process to exit when it gets an end of file indication. If you don't want to do that, then you simply need to implement The Golden Rule Of Computer Programming: "your computer always does exactly what you tell it to do instead of what you want it to do". If you want your child process to do what it needs to do without needing to close its pipe, then just tell your child process what, exactly, it should do, and how. – Sam Varshavchik Jan 20 '23 at 16:58
  • I have updated the code once again. I have added another for loop for the second read file statement. The first ReadFile does not stop until I Close the handle in the parent after the first Write statement. Then I try and write again to the second for loop read. The second WriteFile and Read file both error out with though and again I assume this is because I closed the handle. – Slippery_Chickenz Jan 20 '23 at 17:58
  • The quick and dirty solution is to use a named pipe. See this answer https://stackoverflow.com/questions/593175/breaking-readfile-blocking-named-pipe-windows-api – Pulpo Jan 21 '23 at 11:33
  • `CloseHandle(ChildStd_IN_Wr); /* ... */ WriteFile(ChildStd_IN_Wr, /* ... */);` - That doesn't look like a well thought out plan. – IInspectable Jan 21 '23 at 12:32

0 Answers0