Skip to content

Instantly share code, notes, and snippets.

@floooh
Created January 27, 2026 08:20
Show Gist options
  • Select an option

  • Save floooh/e4a781854c381df1cedb000895275892 to your computer and use it in GitHub Desktop.

Select an option

Save floooh/e4a781854c381df1cedb000895275892 to your computer and use it in GitHub Desktop.
Nebula3 AppLauncher class
//------------------------------------------------------------------------------
// applauncher.cc
// (C) 2008 Radon Labs GmbH
//------------------------------------------------------------------------------
#include "stdneb.h"
#include "applauncher.h"
namespace ToolkitUtil
{
using namespace Util;
using namespace IO;
//------------------------------------------------------------------------------
/**
*/
AppLauncher::AppLauncher() :
isRunning(false),
noConsoleWindow(false),
stdoutRead(0),
stdoutWrite(0),
stderrRead(0),
stderrWrite(0),
exitCode(-1)
{
Memory::Clear(&this->processInfo, sizeof(this->processInfo));
Memory::Clear(this->captureBuffer, sizeof(this->captureBuffer));
}
//------------------------------------------------------------------------------
/**
*/
AppLauncher::~AppLauncher()
{
this->CloseOutputCapturing();
}
//------------------------------------------------------------------------------
/**
*/
bool
AppLauncher::CreateOutputCapturePipe(PHANDLE hRead, PHANDLE hWrite) const
{
// configure pipe security attributes
SECURITY_ATTRIBUTES security;
security.bInheritHandle = TRUE;
security.lpSecurityDescriptor = 0;
security.nLength = sizeof(SECURITY_ATTRIBUTES);
// create a pipe for the child process's stdout
if (!CreatePipe(hRead, hWrite, &security, pipeSize))
{
return false;
}
// ensure the read handle to the pipe for stdout is not inherited.
SetHandleInformation((*hRead), HANDLE_FLAG_INHERIT, 0);
return true;
}
//------------------------------------------------------------------------------
/**
*/
bool
AppLauncher::OpenOutputCapturing(STARTUPINFO& inOutStartupInfo)
{
if (this->stdoutCaptureStream.isvalid() || this->stderrCaptureStream.isvalid())
{
n_assert(0 == this->stdoutRead);
n_assert(0 == this->stdoutWrite);
n_assert(0 == this->stderrRead);
n_assert(0 == this->stderrWrite);
if (this->stdoutCaptureStream.isvalid())
{
this->stdoutCaptureStream->SetAccessMode(Stream::WriteAccess);
if (!this->stdoutCaptureStream->Open())
{
return false;
}
}
if (this->stderrCaptureStream.isvalid())
{
this->stderrCaptureStream->SetAccessMode(Stream::WriteAccess);
if (!this->stderrCaptureStream->Open())
{
return false;
}
}
inOutStartupInfo.dwFlags |= STARTF_USESTDHANDLES;
inOutStartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
if (this->stdoutCaptureStream.isvalid())
{
if (!this->CreateOutputCapturePipe((PHANDLE)&this->stdoutRead, (PHANDLE)&this->stdoutWrite))
{
return false;
}
inOutStartupInfo.hStdOutput = this->stdoutWrite;
}
else
{
inOutStartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
}
if (this->stderrCaptureStream.isvalid())
{
if (!this->CreateOutputCapturePipe((PHANDLE)&this->stderrRead, (PHANDLE)&this->stderrWrite))
{
return false;
}
inOutStartupInfo.hStdError = this->stderrWrite;
}
else
{
inOutStartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
}
}
return true;
}
//------------------------------------------------------------------------------
/**
*/
void
AppLauncher::CloseOutputCapturing()
{
if (0 != this->stdoutRead)
{
CloseHandle(this->stdoutRead);
this->stdoutRead = 0;
}
if (0 != this->stdoutWrite)
{
CloseHandle(this->stdoutWrite);
this->stdoutWrite = 0;
}
if (0 != this->stderrRead)
{
CloseHandle(this->stderrRead);
this->stderrRead = 0;
}
if (0 != this->stderrWrite)
{
CloseHandle(this->stderrWrite);
this->stderrWrite = 0;
}
if (this->stdoutCaptureStream.isvalid() && this->stdoutCaptureStream->IsOpen())
{
this->stdoutCaptureStream->Close();
this->stdoutCaptureStream->SetAccessMode(Stream::ReadAccess);
}
if (this->stderrCaptureStream.isvalid() && this->stderrCaptureStream->IsOpen())
{
this->stderrCaptureStream->Close();
this->stderrCaptureStream->SetAccessMode(Stream::ReadAccess);
}
}
//------------------------------------------------------------------------------
/**
Update capture data for a pipe and capture stream.
*/
void
AppLauncher::UpdateCaptureStream(HANDLE hRead, const Ptr<Stream>& captureStream)
{
n_assert(captureStream.isvalid());
n_assert(0 != hRead);
n_assert(0 != this->processInfo.hProcess);
DWORD numBytesRead = 0;
DWORD numBytesAvailable = 0;
Memory::Clear(this->captureBuffer, sizeof(this->captureBuffer));
// peek to check whether any data is on the stdout pipe
PeekNamedPipe(hRead, this->captureBuffer, sizeof(this->captureBuffer), &numBytesRead, &numBytesAvailable, NULL);
if (numBytesRead != 0)
{
if (numBytesAvailable > sizeof(this->captureBuffer))
{
while (numBytesRead >= sizeof(this->captureBuffer))
{
BOOL res = ::ReadFile(hRead, this->captureBuffer, sizeof(this->captureBuffer), &numBytesRead, NULL);
n_assert(res);
captureStream->Write(this->captureBuffer, numBytesRead);
}
}
else
{
BOOL res = ::ReadFile(hRead, this->captureBuffer, sizeof(this->captureBuffer), &numBytesRead, NULL);
n_assert(res);
captureStream->Write(this->captureBuffer, numBytesRead);
}
}
}
//------------------------------------------------------------------------------
/**
Reads all arrived data from stdout since the last call of this method and puts it to
the capture streams.
*/
void
AppLauncher::UpdateOutputCapturing()
{
if (this->stdoutCaptureStream.isvalid())
{
this->UpdateCaptureStream(this->stdoutRead, this->stdoutCaptureStream);
}
if (this->stderrCaptureStream.isvalid())
{
this->UpdateCaptureStream(this->stderrRead, this->stderrCaptureStream);
}
}
//------------------------------------------------------------------------------
/**
Gets the state of the application and also performs capturing.
Call this frequently in a loop (don't forget to n_sleep()).
*/
bool
AppLauncher::IsRunning()
{
if (this->isRunning)
{
this->UpdateOutputCapturing();
DWORD procExitCode = 0;
GetExitCodeProcess(this->processInfo.hProcess, &procExitCode);
if (procExitCode != STILL_ACTIVE)
{
this->exitCode = procExitCode;
this->CloseOutputCapturing();
CloseHandle(this->processInfo.hProcess);
CloseHandle(this->processInfo.hThread);
this->processInfo.hProcess = 0;
this->processInfo.hThread = 0;
this->isRunning = false;
}
}
return this->isRunning;
}
//------------------------------------------------------------------------------
/**
Launch the application process and returns immediately. The state of the launched
process must be checked by calling IsRunning() perdiodically. After
IsRunning() returns false, GetExitCode() will return the exit code of the process.
*/
bool
AppLauncher::Launch()
{
n_assert(this->exePath.IsValid());
n_assert(!this->isRunning);
DWORD creationFlags = 0;
if (this->noConsoleWindow)
{
creationFlags |= CREATE_NO_WINDOW;
}
this->exitCode = -1;
// build a command line
String cmdLine = this->exePath.LocalPath();
cmdLine.Append(" ");
cmdLine.Append(this->args);
// setup capturing if necessary
STARTUPINFO startupInfo = { sizeof(STARTUPINFO), 0 };
this->OpenOutputCapturing(startupInfo);
// create the process, return false if this fails
if (!CreateProcess(NULL, // lpApplicationName
(LPSTR) cmdLine.AsCharPtr(), // lpCommandLine
NULL, // lpProcessAttributes
NULL, // lpThreadAttributes
TRUE, // bInheritsHandle
creationFlags, // dwCreationFlags
NULL, // lpEnvironment
this->workingDir.LocalPath().AsCharPtr(), // lpCurrentDirectory
&startupInfo, // lpStartupInfo
(LPPROCESS_INFORMATION)&(this->processInfo))) // lpProcessInformation
{
this->CloseOutputCapturing();
return false;
}
this->isRunning = true;
return true;
}
//------------------------------------------------------------------------------
/**
*/
bool
AppLauncher::LaunchWait()
{
if (this->Launch())
{
// launching was successful, wait until process quits and capture data if necessary
while (this->IsRunning())
{
n_sleep(0.01);
}
return true;
}
return false;
}
} // namespace ToolkitUtil
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment