win32 process control

I needed to run some commands from juce, and getting their stdout or redirecting their output to a file (neither works with the generic juce File call) so i wrote something myself, it’s taken out of context but you get an idea. There is a static call to just call a process and enable output redirection.

ProcessPipe.cpp

#include "ProcessPipe.h"

ProcessPipe::ProcessPipe(const String &_processCommand, const uint32 _executionTimeout, const bool _showOutput) 
	:	executionTimeout(_executionTimeout), 
		processCommand(_processCommand),
		showOutput(_showOutput)
{
}

ProcessPipe::~ProcessPipe()
{
}

const bool ProcessPipe::createProcess()
{
	STARTUPINFO siStartInfo;
	BOOL bSuccess = FALSE;
	SECURITY_ATTRIBUTES saAttr; 

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

	if (!CreatePipe(&pipeStdoutRead, &pipeStdoutWrite, &saAttr, 0))
	{
		return (false);
	}
	
	if (!SetHandleInformation(pipeStdoutRead, HANDLE_FLAG_INHERIT, 0))
	{
		return (false);
	}

	ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );

	ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );
	siStartInfo.cb = sizeof(STARTUPINFO);
	siStartInfo.hStdError = pipeStdoutWrite;
	siStartInfo.hStdOutput = pipeStdoutWrite;
	siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
	if (!showOutput)
	{
		siStartInfo.dwFlags |= STARTF_USESHOWWINDOW;
		siStartInfo.wShowWindow = SW_HIDE;
	}

	bSuccess = CreateProcess(NULL, (LPSTR)processCommand.toUTF8(), NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo);
	if (!bSuccess)
	{
		startTimer (executionTimeout);
		return (false);
	}
	else
	{
		CloseHandle(piProcInfo.hProcess);
		CloseHandle(piProcInfo.hThread);
		return (true);
	}
}

const bool ProcessPipe::readOutput(MemoryBlock &output)
{
	DWORD dwRead;
	BOOL bSuccess = FALSE;
	HANDLE hParentStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
	uint32 dataRead = 0;

	if (!CloseHandle(pipeStdoutWrite)) 
	{
		return (false);
	}
	
	MemoryBlock data(BUFSIZE*8, true);

	for (;;)
	{
		bSuccess = ReadFile (pipeStdoutRead, data, BUFSIZE, &dwRead, NULL);
		dataRead = dataRead + dwRead;

		if(!bSuccess || dwRead == 0)
		{
			output.setSize (dataRead);
			output.copyFrom (data.getData(), 0, dataRead);
			break;
		}
	}
	return (true);
}

void ProcessPipe::timerCallback()
{
	Logger::writeToLog (T("timerCallback()"));
	DWORD exitStatus;

	if (GetExitCodeProcess (piProcInfo.hProcess, &exitStatus))
	{
		if (exitStatus == STILL_ACTIVE)
		{
			if (!TerminateProcess (piProcInfo.hProcess, 13))
			{
				AlertWindow::showMessageBox (AlertWindow::WarningIcon, T("Blad podczas proby zakonczenia procesu"), T("Proces ktory osiagnal timeout nie moze zostac zakonczony: ") + processCommand);
			}
		}
	}
	else
	{
		AlertWindow::showMessageBox (AlertWindow::WarningIcon, T("Blad podczas proby zakonczenia procesu"), T("Nie mozna odczytac statusu procesu: ") + processCommand);
	}
}

const bool ProcessPipe::startConsoleApp (const String commandLine, const String arguments)
{
	STARTUPINFO         siStartupInfo;
    PROCESS_INFORMATION piProcessInfo;

    memset(&siStartupInfo, 0, sizeof(siStartupInfo));
    memset(&piProcessInfo, 0, sizeof(piProcessInfo));

    siStartupInfo.cb = sizeof(siStartupInfo);

	const String args = T(" /C \"") + commandLine + T("\" ") + arguments;
	Logger::writeToLog (T("ProcessPipe: ") + args);

	char wpath[256];
	GetWindowsDirectory (wpath, 256);

	String cmd(String(wpath) + T("\\system32\\cmd.exe"));

	if(CreateProcess(cmd.toUTF8(),     // Application name
					(LPSTR)args.toUTF8(),                 // Application arguments
                     0,
                     0,
                     FALSE,
                     CREATE_DEFAULT_ERROR_MODE,
                     0,
                     0,                              // Working directory
                     &siStartupInfo,
                     &piProcessInfo) == FALSE)
	{
		return (false);
	}
	else
	{
		return (true);
	}
}

ProcessPipe.h

#include "Includes.h"

#define BUFSIZE 8192

class ProcessPipe : public Timer
{
	public:
		ProcessPipe(const String &_processCommand, const uint32 _executionTimeout=1000, const bool _showOutput=false);
		~ProcessPipe();
	
		const bool createProcess ();
		const bool readOutput (MemoryBlock &output);
		void timerCallback();

		static const bool startConsoleApp (const String commandLine, const String arguments);
	private:
		String processCommand;
		uint32 executionTimeout;
		HANDLE pipeStdoutRead;
		HANDLE pipeStdoutWrite;
		PROCESS_INFORMATION piProcInfo;
		bool showOutput;
};

Oh, I wrote something like this a while ago! :slight_smile: in case you’re interested, here’s a link to the code. It might be a little more complex than your example here; i made it originally as a means of launching a compiler, so i wanted it to be able to handle queues of commands for batch command-line execution. [also does stdout redirection, though it has handy callbacks 'n all.]

[edit: by more complex, i think i mean handles-more-requirements, it’s probably actually simpler to use; e.g. output redirection is done via a simple callback with a string per line]

interesting stuff, would be cool if Jules can come up to a cross platform unified version of the ideas here, obviously in the usual juce style :slight_smile:

i never bothered to checked the forum, i just knew how to do this so i just wrote this myself.

i agree that it would nice to have something that can read output from commands in juce, perhpas write to stdin of parent process, each platform has the means to do that.