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.