Click here to Skip to main content
15,894,825 members
Articles / Programming Languages / Java

CaptureConsole.DLL - A Universal Console Output Redirector for all Compilers

Rate me:
Please Sign up or sign in to vote.
4.70/5 (41 votes)
28 Oct 2010CPOL7 min read 217.8K   3K   166  
Capture Console Output
// ---------------------------------------------------------------------------------------------------------
//
// CRunProcess class
// 
// Purpose: Starts a Console Application and captures its stdout and stderr output while the app is running.
//
// Author: Elm�Soft (www.netcult.ch/elmue)
//
// ---------------------------------------------------------------------------------------------------------

/*
----------------------------------------------------------------------------------
Using these conventions results in better readable code and less coding errors !
----------------------------------------------------------------------------------

     cName  for generic class definitions
     CName  for MFC     class definitions
     tName  for type    definitions
     eName  for enum    definitions
     kName  for struct  definitions

    e_Name  for enum variables
    E_Name  for enum constant values

    i_Name  for instances of classes
    h_Name  for handles

    T_Name  for Templates
    t_Name  for TCHAR or LPTSTR

    s_Name  for strings
   sa_Name  for Ascii strings
   sw_Name  for Wide (Unicode) strings
   bs_Name  for BSTR
    f_Name  for function pointers
    k_Name  for contructs (struct)

    b_Name  bool,BOOL 1 Bit

   s8_Name    signed  8 Bit (char)
  s16_Name    signed 16 Bit (SHORT, WCHAR)
  s32_Name    signed 32 Bit (LONG, int)
  s64_Name    signed 64 Bit (LONGLONG)

   u8_Name  unsigned  8 Bit (BYTE)
  u16_Name  unsigned 16 bit (WORD)
  u32_Name  unsigned 32 Bit (DWORD, UINT)
  u64_Name  unsigned 64 Bit (ULONGLONG)

    d_Name  for double

  ----------------

    m_Name  for member variables of a class (e.g. ms32_Name for int member variable)
    g_Name  for global (static) variables   (e.g. gu16_Name for global WORD)
    p_Name  for pointer                     (e.g.   ps_Name  *pointer to string)
   pp_Name  for pointer to pointer          (e.g.  ppd_Name **pointer to double)
*/

#include "stdafx.h"
#include "RunProcess.h"

// Constructor
CRunProcess::CRunProcess()
{
	Init();
}

// Destructor
CRunProcess::~CRunProcess()
{
	CloseHandles();
}

void CRunProcess::Init()
{
	mh_Process = 0;
	mh_OutPipe = 0;
	mh_ErrPipe = 0;
	mh_OutFile = 0;
	mh_ErrFile = 0;

	memset(&mk_OverOut, 0, sizeof(OVERLAPPED));
	memset(&mk_OverErr, 0, sizeof(OVERLAPPED));
}

void CRunProcess::CloseHandles()
{
	CloseHandle(mh_Process);
	CloseHandle(mh_OutPipe);
	CloseHandle(mh_ErrPipe);
	CloseHandle(mh_OutFile);
	CloseHandle(mh_ErrFile);
}

// Parameter description see ExecuteAW() in CaptureConsole.cpp !
// runs the process which is specified in s_Commandline
// waits until the process has terminated
// fills ps_StdOutput and ps_StdError with the stdout and stderr output of the Console application or batch script
// returns "" if OK, otherwise an error message
CString CRunProcess::Execute(CString         s_Commandline,   // IN
                             DWORD         u32_FirstConvert,  // IN
                             TCHAR*          t_CurrentDir,    // IN
                             CString         s_UserVariables, // IN
                             BOOL            b_SeparatePipes, // IN
                             DWORD         u32_TimeOut,       // IN
                             CFastString*   ps_StdOutput,     // OUT
                             CFastString*   ps_StdError,      // OUT
                             DWORD*       pu32_ExitCode)      // OUT
{
	// Execute may be called multiple times from the same thread -> Close any open handles
	CloseHandles();
	Init();

	*pu32_ExitCode = 0;
	ps_StdOutput->Empty();
	ps_StdError ->Empty();

	CString s_Err = CreatePipe(&mh_OutPipe, &mh_OutFile, &mk_OverOut);
	if (s_Err.GetLength() > 0)
		return s_Err;

	if (b_SeparatePipes)
	{
		s_Err = CreatePipe(&mh_ErrPipe, &mh_ErrFile, &mk_OverErr);
		if (s_Err.GetLength() > 0)
			return s_Err;
	}

	DWORD u32_Flags = CREATE_NO_WINDOW;
	void* p_Environ = NULL; // Inherit the environment variables from the parent process

	CString s_Environ;
	if (s_UserVariables.GetLength())
	{
		#ifdef UNICODE
		u32_Flags |= CREATE_UNICODE_ENVIRONMENT;
		#endif

		// Merge the user variables with the environment variables of the parent process
		MergeUserEnvironmentStrings(s_UserVariables, &s_Environ);
		p_Environ = (void*)s_Environ.GetBuffer(0);
	}

	PROCESS_INFORMATION k_Proc;
	memset(&k_Proc,  0, sizeof(PROCESS_INFORMATION));

	STARTUPINFO k_Start;
	memset(&k_Start, 0, sizeof(STARTUPINFO));

	k_Start.cb         = sizeof(STARTUPINFO);
	k_Start.hStdError  = (b_SeparatePipes) ? mh_ErrFile : mh_OutFile;
	k_Start.hStdOutput = mh_OutFile;
	k_Start.dwFlags    = STARTF_USESTDHANDLES;

	// Convert special characters in the commandline parameters to DOS codepage
	s_Commandline = CommandLineParamsToOEM(s_Commandline, u32_FirstConvert);

	if (!CreateProcess(0, s_Commandline.GetBuffer(0), 0, 0, TRUE, u32_Flags, p_Environ, t_CurrentDir, &k_Start, &k_Proc))
	{
		return _T("CaptureConsole.dll: Error creating Process. ") + GetLastErrorMsg();
	}

	mh_Process = k_Proc.hProcess; // closed in destructor

	// wait until the process has exited
	DWORD u32_Event   = WAIT_TIMEOUT;
	DWORD u32_EndTime = GetTickCount() + u32_TimeOut;
	do
	{
		if (u32_TimeOut && GetTickCount() > u32_EndTime)
		{
			TerminateProcess(k_Proc.hProcess, 0);
			return _T("CaptureConsole.dll: Timeout elapsed.");
		}

		// WaitForSingleObject will return immediately when the process has exited otherwise wait 200 ms
		u32_Event = WaitForSingleObject(k_Proc.hProcess, 200);
		if (u32_Event == WAIT_FAILED)
		{
			return _T("CaptureConsole.dll: Error from WaitForSingleObject. ") + GetLastErrorMsg();
		}

		if (!GetExitCodeProcess(k_Proc.hProcess, pu32_ExitCode))
		{
			return _T("CaptureConsole.dll: Error getting ExitCode from Process. ") + GetLastErrorMsg();
		}
		
		s_Err = ReadPipe(mh_OutPipe, &mk_OverOut, ps_StdOutput);
		if (s_Err.GetLength() > 0)
			return s_Err;

		if (b_SeparatePipes)
		{
			s_Err = ReadPipe(mh_ErrPipe, &mk_OverErr, ps_StdError);
			if (s_Err.GetLength() > 0)
				return s_Err;
		}
	}
	while (u32_Event != WAIT_OBJECT_0);

	return CString();
}

// Creates a named pipe and a file handle which is used to write to the pipe
CString CRunProcess::CreatePipe(HANDLE* ph_Pipe, HANDLE* ph_File, OVERLAPPED* pk_Overlapped)
{
	// Create a threadsafe unique name for the Pipe
	static int s32_Counter = 0;

	CString s_PipeName;
	s_PipeName.Format(_T("\\\\.\\pipe\\CRunProcessPipe_%X_%X_%X_%X"),
	                  GetCurrentProcessId(), GetCurrentThreadId(), GetTickCount(), s32_Counter++);

	SECURITY_ATTRIBUTES k_Secur;
	k_Secur.nLength              = sizeof(SECURITY_ATTRIBUTES);
	k_Secur.lpSecurityDescriptor = 0;
	k_Secur.bInheritHandle       = TRUE;

	*ph_Pipe = CreateNamedPipe(s_PipeName, PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED, 0, 1, 4096, 4096, 0, &k_Secur);

	if (*ph_Pipe == INVALID_HANDLE_VALUE)
	{
		return _T("CaptureConsole.dll: Error creating Named Pipe. ") + GetLastErrorMsg();
	}

	*ph_File = CreateFile(s_PipeName, GENERIC_READ|GENERIC_WRITE, 0, &k_Secur, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);

	if (*ph_File == INVALID_HANDLE_VALUE)
	{
		return _T("CaptureConsole.dll: Error creating Pipe Reader. ") + GetLastErrorMsg();
	}

	if (!ConnectNamedPipe(*ph_Pipe, pk_Overlapped))
	{
		if (GetLastError() != ERROR_PIPE_CONNECTED)
		{
			return _T("CaptureConsole.dll: Error connecting Pipe. ") + GetLastErrorMsg();
		}
	}

	SetHandleInformation(*ph_Pipe, HANDLE_FLAG_INHERIT, 0);
	SetHandleInformation(*ph_File, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
	return CString();
}

// Reads from the Pipe and appends the read data to ps_Data
CString CRunProcess::ReadPipe(HANDLE h_Pipe, OVERLAPPED* pk_Overlapped, CFastString* ps_Data)
{
	// IMPORTANT: Check if there is data that can be read.
	// The first console output will be lost if ReadFile() is called before data becomes available!
	// It does not make any sense but the following 5 lines are indispensable!!
	DWORD u32_Avail = 0;
	if (!PeekNamedPipe(h_Pipe, 0, 0, 0, &u32_Avail, 0))
		return _T("CaptureConsole.dll: Error peeking Pipe. ") + GetLastErrorMsg();

	if (!u32_Avail)
		return CString();

	const BLOCK_SIZE = 4095;
	char   s8_Buf[BLOCK_SIZE+1];
	WCHAR u16_Buf[BLOCK_SIZE+1];
	DWORD u32_Read = 0;
	do
	{
		if (!ReadFile(h_Pipe, s8_Buf, BLOCK_SIZE, &u32_Read, pk_Overlapped))
		{
			if (GetLastError() != ERROR_IO_PENDING)
			{
				return _T("CaptureConsole.dll: Error reading Pipe. ") + GetLastErrorMsg();
			}
		}

		// ATTENTION: The Console always prints ANSI to the pipe independent if compiled as UNICODE or MBCS!
		s8_Buf[u32_Read] = 0;
		OemToCharA(s8_Buf, s8_Buf); // convert DOS codepage -> ANSI

		#ifdef UNICODE
			mbstowcs(u16_Buf, s8_Buf, u32_Read); // convert ANSI -> UNICODE
			ps_Data->AppendBuffer(u16_Buf, u32_Read);
		#else
			ps_Data->AppendBuffer(s8_Buf, u32_Read);
		#endif
	}
	while (u32_Read == BLOCK_SIZE);

	return CString();
}

// returns the last error as string message
CString CRunProcess::GetLastErrorMsg()
{
	DWORD u32_Error = GetLastError();
	if (!u32_Error)
		return CString();

	CString s_Err;
	s_Err.Format(_T("API Error %d: "), u32_Error);

	const DWORD BUF_LEN = 2048;
	TCHAR t_Msg[BUF_LEN];
	if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, u32_Error, 0, t_Msg, BUF_LEN, 0))
		s_Err += t_Msg;

	return s_Err;
}

// The commandline must be splitted into the application path+filename and the parameters
// The first parameter (path+filename) must NEVER be converted, it may contain Unicode characters
// ONLY the parameters must be converted into the DOS codepage!
// Attention: The filename and the parameters may be enclosed in parenthesis.
// Example:
// "C:\Test\HelloW�rld.exe Hello W�rld"  will be converted into
// "C:\Test\HelloW�rld.exe Hello W�rld"
// u32_FirstConvert = 0 -> DOS Conversion completely turned off
// u32_FirstConvert = 1 -> convert the first  and all following parameters
// u32_FirstConvert = 2 -> convert the second and all following parameters
CString CRunProcess::CommandLineParamsToOEM(CString s_CommandLine, DWORD u32_FirstConvert)
{
	// NEVER EVER convert the path+filename!!!
	if (!u32_FirstConvert)
		return s_CommandLine;

	s_CommandLine.TrimLeft();

	BOOL b_Parentesis = FALSE;
	for (int i=0; i<s_CommandLine.GetLength(); i++)
	{
		// Toggle parenthesis flag
		if (s_CommandLine[i] == '\"')
		{
			b_Parentesis = !b_Parentesis;
			continue;
		}

		// Decrement u32_FirstConvert for each space delimited param found in the commandline
		if (s_CommandLine[i] == ' ' && !b_Parentesis)
		{
			// There may be multiple spaces
			while (s_CommandLine[i+1] == ' ')
				i++;

			u32_FirstConvert--;
			continue;
		}

		if (!u32_FirstConvert)
		{
			CString s_Leave = s_CommandLine.Left(i);
			CString s_Conv  = s_CommandLine.Mid (i);

			char* s8_OEM = new char[s_Conv.GetLength()+1];
			
			// If Unicode compiled: Unicode -> DOS codepage
			// If MBCS    compiled: ANSI    -> DOS codepage
			CharToOem(s_Conv, s8_OEM);

			// If Unicode compiled: DOS codepage -> Unicode
			s_CommandLine = s_Leave + s8_OEM;
			
			delete s8_OEM;
			break;
		}
	}
	return s_CommandLine;
}


// Fills the CMap with the data in the zero terminated string "Key1=Value1\0Key2=Value2\0"
// If the map already contains a key it will be overriden
// returns the length of all added string data
int CRunProcess::ZeroStringToMap(const TCHAR* t_String, CMapStringToString *pi_Map)
{
	int s32_TotLen = 0;
	while (1)
	{
		CString s_String = t_String;
		int  s32_Len = s_String.GetLength();
		if (!s32_Len)
			break;

		int s32_Equal = s_String.Find('=');
		if (s32_Equal > 0)
		{
			CString s_Key = s_String.Left(s32_Equal);
			CString s_Val = s_String.Mid (s32_Equal+1);

			s_Key.MakeUpper();
			pi_Map->SetAt(s_Key, s_Val);
		}

		t_String   += s32_Len +1;
		s32_TotLen += s32_Len +1;
	};
	return s32_TotLen;
}

// Fills the buffer t_String with zero terminated strings "Key1=Value1\0Key2=Value2\0"
// which are obtained from the Map
void CRunProcess::MapToZeroString(CMapStringToString *pi_Map, TCHAR* t_String)
{
	CString s_Key, s_Val;
	for (POSITION P=pi_Map->GetStartPosition(); P!=NULL; )
	{
		pi_Map->GetNextAssoc(P, s_Key, s_Val);

		_tcscpy(t_String, s_Key);
		t_String += s_Key.GetLength();

		t_String[0] = '=';
		t_String ++;

		_tcscpy(t_String, s_Val);
		t_String += s_Val.GetLength();

		t_String[0] = 0;
		t_String ++;
	}
	t_String[0] = 0;
}

// s_UserVar = "Key1=Value1\nKey2=Value2\n"
// Merges the User Environment Variables with the current System Environment variables
// and returns them in the buffer ps_Out as zero terminated strings
void CRunProcess::MergeUserEnvironmentStrings(CString s_UserVar, CString* ps_Out)
{
	TCHAR* t_Environ = GetEnvironmentStrings();

	CMapStringToString i_Map;
	int s32_TotLen = ZeroStringToMap(t_Environ, &i_Map);

	FreeEnvironmentStrings(t_Environ);

	if (s_UserVar.GetLength())
	{
		if (s_UserVar.Right(1) != _T("\n"))
			s_UserVar += _T("\n");

		s_UserVar.Replace('\n', '\0');

		s32_TotLen += ZeroStringToMap(s_UserVar, &i_Map);
	}

	TCHAR* t_Buf = ps_Out->GetBuffer(s32_TotLen);
	MapToZeroString(&i_Map, t_Buf);
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) ElmüSoft
Chile Chile
Software Engineer since 40 years.

Comments and Discussions