Skip to content

Commit

Permalink
Make ChildProcess class more reusable
Browse files Browse the repository at this point in the history
Allow ChildProcess to be more easily reused by using ASSERT instead of
UIETWASSERT, and supporting both wchar_t and char (driven by _UNICODE).
  • Loading branch information
randomascii committed Mar 31, 2018
1 parent c682e8e commit 546ec0e
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 27 deletions.
42 changes: 23 additions & 19 deletions UIforETW/ChildProcess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ limitations under the License.

#include <vector>

static const wchar_t* kPipeName = L"\\\\.\\PIPE\\UIforETWPipe";
static const _TCHAR* const kPipeName = _T("\\\\.\\PIPE\\UIforETWPipe");

ChildProcess::ChildProcess(std::wstring exePath, bool printFailedExitCodes) noexcept
ChildProcess::ChildProcess(string_type exePath, bool printFailedExitCodes) noexcept
: exePath_(std::move(exePath)), printFailedExitCodes_(printFailedExitCodes)
{
// Create the pipe here so that it is guaranteed to be created before
Expand All @@ -47,7 +47,7 @@ ChildProcess::~ChildProcess()
// Always get the exit code since this also waits for the process to exit.
const DWORD exitCode = GetExitCode();
if (printFailedExitCodes_ && exitCode)
outputPrintf(L"Process exit code was %08x (%lu)\n", exitCode, exitCode);
outputPrintf(_T("Process exit code was %08x (%lu)\n"), exitCode, exitCode);
CloseHandle(hProcess_);
}
if (hOutputAvailable_)
Expand All @@ -68,11 +68,11 @@ bool ChildProcess::IsStillRunning() noexcept
return waitIndex != 0;
}

std::wstring ChildProcess::RemoveOutputText()
ChildProcess::string_type ChildProcess::RemoveOutputText()
{
CSingleLock locker(&outputLock_);
std::wstring result = processOutput_;
processOutput_ = L"";
string_type result = processOutput_;
processOutput_ = _T("");
return result;
}

Expand All @@ -98,14 +98,18 @@ DWORD ChildProcess::ListenerThread()
CSingleLock locker(&outputLock_);
buffer[dwRead] = 0;
OutputDebugStringA(buffer);
#ifdef _UNICODE
processOutput_ += AnsiToUnicode(buffer);
#else
processOutput_ += buffer;
#endif
}
SetEvent(hOutputAvailable_);
}
}
else
{
OutputDebugString(L"Connect failed.\n");
OutputDebugString(_T("Connect failed.\n"));
}

DisconnectNamedPipe(hPipe_);
Expand All @@ -114,12 +118,12 @@ DWORD ChildProcess::ListenerThread()
}

_Pre_satisfies_(!(this->hProcess_))
bool ChildProcess::Run(bool showCommand, std::wstring args)
bool ChildProcess::Run(bool showCommand, string_type args)
{
UIETWASSERT(!hProcess_);
ASSERT(!hProcess_);

if (showCommand)
outputPrintf(L"%s\n", args.c_str());
outputPrintf(_T("%s\n"), args.c_str());

SECURITY_ATTRIBUTES security = { sizeof(security), 0, TRUE };

Expand All @@ -145,8 +149,8 @@ bool ChildProcess::Run(bool showCommand, std::wstring args)
PROCESS_INFORMATION processInfo = {};
const DWORD flags = CREATE_NO_WINDOW;
// Wacky CreateProcess rules say args has to be writable!
std::vector<wchar_t> argsCopy(args.size() + 1);
wcscpy_s(&argsCopy[0], argsCopy.size(), args.c_str());
std::vector<_TCHAR> argsCopy(args.size() + 1);
_tcscpy_s(&argsCopy[0], argsCopy.size(), args.c_str());
const BOOL success = CreateProcess(exePath_.c_str(), &argsCopy[0], NULL, NULL,
TRUE, flags, NULL, NULL, &startupInfo, &processInfo);
if (success)
Expand All @@ -157,7 +161,7 @@ bool ChildProcess::Run(bool showCommand, std::wstring args)
}
else
{
outputPrintf(L"Error %lu starting %s, %s\n", GetLastError(), exePath_.c_str(), args.c_str());
outputPrintf(_T("Error %lu starting %s, %s\n"), GetLastError(), exePath_.c_str(), args.c_str());
}

return false;
Expand All @@ -174,10 +178,10 @@ DWORD ChildProcess::GetExitCode()
return result;
}

std::wstring ChildProcess::GetOutput()
ChildProcess::string_type ChildProcess::GetOutput()
{
if (!hProcess_)
return L"";
return _T("");
WaitForCompletion(false);
return RemoveOutputText();
}
Expand All @@ -193,8 +197,8 @@ void ChildProcess::WaitForCompletion(bool printOutput)
{
if (printOutput)
{
std::wstring output = RemoveOutputText();
outputPrintf(L"%s", output.c_str());
string_type output = RemoveOutputText();
outputPrintf(_T("%s"), output.c_str());
}
}
// This isn't technically needed, but removing it would make
Expand Down Expand Up @@ -240,8 +244,8 @@ void ChildProcess::WaitForCompletion(bool printOutput)
{
// Now that the child thread has exited we can finally read
// the last of the child-process output.
std::wstring output = RemoveOutputText();
string_type output = RemoveOutputText();
if (!output.empty())
outputPrintf(L"%s", output.c_str());
outputPrintf(_T("%s"), output.c_str());
}
}
23 changes: 15 additions & 8 deletions UIforETW/ChildProcess.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ limitations under the License.

#pragma once

#include <afxmt.h>

#include <string>

// This class encapsulates running a child thread and reading
// its output. Typical usage is:
// ChildProcess child(fullPathToExecutable);
// std::wstring args = L" -go";
// child.Run(bShowCommands_, L"exename.exe" + args);
// std::string args = _T(" -go");
// child.Run(bShowCommands_, _T("exename.exe") + args);
// Run returns immediately. The destructor, GetExitCode(), and
// GetOutput() will all wait for the process to exit. The
// destructor and GetExitCode() both print all output as it
Expand All @@ -32,15 +34,20 @@ limitations under the License.
class ChildProcess
{
public:
ChildProcess(std::wstring exePath, bool printFailedExitCodes = true) noexcept;
#ifdef _UNICODE
typedef std::wstring string_type;
#else
typedef std::string string_type;
#endif
ChildProcess(string_type exePath, bool printFailedExitCodes = true) noexcept;
// This waits for the child process to terminate, and prints
// output with outputPrintf as it arrives.
~ChildProcess();

// Returns true if the process started. This function returns
// immediately without waiting for process completion.
_Pre_satisfies_(!(this->hProcess_))
bool Run(bool showCommand, std::wstring args);
bool Run(bool showCommand, string_type args);

// This can be called even if the process doesn't start, but
// it will return zero. If the process is still running it
Expand All @@ -51,11 +58,11 @@ class ChildProcess
// Normally all output is printed as it is received. If this function
// is called after Run() then all output will be returned, and not
// printed.
std::wstring GetOutput();
string_type GetOutput();

private:
// Path to the executable to be run, and its process handle.
std::wstring exePath_;
string_type exePath_;

bool printFailedExitCodes_ = true;

Expand All @@ -70,7 +77,7 @@ class ChildProcess
// The processOutput_ string is written to by the listener thread.
// Don't modify processOutput_ without acquiring the lock.
CCriticalSection outputLock_;
std::wstring processOutput_;
string_type processOutput_;

// Output handles for the child process -- connected to the pipe.
HANDLE hStdOutput_ = INVALID_HANDLE_VALUE;
Expand All @@ -90,7 +97,7 @@ class ChildProcess
bool IsStillRunning() noexcept;
// Remove and return the accumulated output text. Typically
// this is called in an IsStillRunning() loop.
std::wstring RemoveOutputText();
string_type RemoveOutputText();

// This can be called even if the process doesn't start, but
// it will just return immediately. Otherwise it will wait
Expand Down

0 comments on commit 546ec0e

Please sign in to comment.