Click here to Skip to main content
Click here to Skip to main content

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

By , 28 Oct 2010
Rate this:
Please Sign up or sign in to vote.

Introduction

You find a lot of code on the internet to capture Console output.
But normally, you find only a class written for a specific programming language.
The big advantage of this easy to use DLL is that you can use it in ALL projects independent of the programming language or compiler.

With 2 lines of code in your application, you can load this DLL which:

  1. executes a Console Application or DOS Script invisible in the background
  2. waits until the Console has finished
  3. returns all printed output via stdout and stderr as Strings to the calling application

Features

  1. This DLL is ultra easy to use for any application which wants to start a Console Process/Script and needs the output written by the Console.
  2. The only exported function "Execute" in this DLL uses the same calling convention as all Windows DLLs. (WINAPI = __stdcall)
  3. You can use this DLL in ANY compiler for ANY programming language which supports API calls. (If you can call Kernel32.dll, you can also call CaptureConsole.dll)
  4. This download contains a Demo Application for C++, Visual Basic 6, VB.NET and C# which demonstrate how to load the DLL and call the function "Execute".
  5. The DLL is thread-safe: You can execute multiple Console applications at the same time from different threads.
  6. You can choose if you want stdout and stderr separated or as mixed output.
  7. The Exit Code of the Console Application is returned to the caller.
  8. You can define the Working Directory (GetCurrentDirectory) for the console application.
  9. You can pass additional Environment Variables (GetEnvironmentVariable) to the Console Application or replace existing ones.
  10. The DLL can be compiled as Unicode (exports ExecuteW) or ANSI (exports ExecuteA). The DLL can be compiled as 32 Bit and as 64 Bit.
  11. The DLL is a MFC C++ project, but no external MFCxx.DLL is required because MFC is statically linked. (CaptureConsole.DLL depends only on standard Windows DLLs)
  12. The DLL internally uses CStrings and exports BSTR, so a buffer overflow is impossible. If your console prints 50 MB text output, that's no problem.
  13. All API errors that may happen while starting the Console process are handled and returned as a human readable error message.
  14. You can specify an optional timeout. If it elapses, the Console process will be killed. This avoids dead processes hanging around when used on a server.
  15. Special characters (like äöüáéú) are converted to the DOS Codepage when passed as commandline parameters.
  16. Special characters are converted back to the ANSI codepage when returned to the calling application.

Capturing in Real Time

If you need the Console Output in REALTIME, this is not the correct project for you:
CaptureConsole.DLL waits until the Console process has exited and THEN returns stdout and stderr.

If you need Console Output in real time, please read Oliver's article, which directly reads from the Console buffer.
But Oliver's technique has severe disadvantages: 

  1. The code is weak: You may lose characters if the console application prints faster than you read the buffer or the console is scrolled
  2. You don't get stdout and stderr separated
  3. You need an additional EXE file which calls the Console process
  4. You have to implement the receiving pipe code into your main application. This code is very complex and has a lot of traps. It is much simpler to call a function in a DLL which does all the dirty work for you.

How It Works

CaptureConsole.DLL uses CreateProcess() to start the Console Process.
The outputs stdout and stderr are redirected to one or two pipes which send the printed characters to the calling application.
While the Console application is running, the pipe(s) is (are) read by the calling process and output is stored in a string.
When the Console application has exited, the string(s) is (are) returned to the calling application.

A Really Stupid Microsoft Design

When a Console application writes its output using printf() and perror() a STUPID design in the CRT defines that the printed characters are NOT immediately sent to the pipe.
This does not matter as long as the Console does not write to stderr.
It also does not matter if you are not interested in the original order of stdout and stderr.

If your Console application does this:

printf("Text  1");
perror("Error 1");
printf("Text  2");
perror("Error 2");

You see the output in the Console window in the same order:

Text  1
Error 1
Text  2
Error 2

But if the output is redirected to a pipe, Microsoft writes the printed characters to an internal buffer and they are sent to the pipe all together when the Console exits.
Although you capture stdout and stderr with only one and the SAME pipe, you get a wrong order:

Text  1
Text  2
Error 1
Error 2
In the calling process which starts the Console application, you have NO CHANCE to influence this behaviour!
If you have the source code of the Console process, you can include the following commands at the start of main() to turn off this stupid buffering:
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);

Another option is to use _write() instead of printf() or call fflush().

How to Use this DLL

If the DLL is compiled as Unicode, it exports the function ExecuteW.
If the DLL is compiled as MBCS, it exports the function ExecuteA.

ExecuteA/W Parameters

  • s_CommandLine = The entire commandline to be executed. e.g. "C:\Test\Test.bat Param1 Param2"
  • u32_FirstConvert = 0 -> Commandline parameter codepage conversion is turned off
  • u32_FirstConvert > 0 -> The first commandline parameter to be converted to the DOS codepage (see next chapter for more details)
  • s_CurrentDir = The current working directory for the Console Application or null if not used.
  • s_Environment = Additional Environment Variables to be passed to the Console Application: "UserVar1=Value1\nUserVar2=Value2\n"
    You can also override the system variables with your own values. Pass null if not used.
  • b_SeparatePipes = true -> Capture stdout and stderr with two separate pipes and return them in s_StdOut and s_StdErr
  • b_SeparatePipes = false -> Capture stdout and stderr with one common pipe and return them in s_StdOut
  • u32_Timeout = 0 -> No timeout
  • u32_Timeout > 0 -> Timeout in milliseconds after which the Console process will be killed

ExecuteA/W Returns

Returns the Exit Code of the Console application and the strings s_ApiError, s_StdOut, s_StdErr.
If s_ApiError is not empty, this means that an error has occurred while creating the console process or the communication pipe. You get a human readable error message.

IMPORTANT: You must ALWAYS check s_ApiError. If this string is not empty, the other return values are invalid!

IMPORTANT: Do not forget to free the BSTR afterwards with SysFreeString() to avoid a memory leak!
In the demo projects, you see how to do it correctly.
This is not necessary in .NET where the Marshalling frees the strings automatically for you!

C#

[DllImport("CaptureConsole.dll", EntryPoint="ExecuteW", CharSet=CharSet.Unicode)]
static extern UInt32 ExecuteW(string   s_Commandline, 
                              UInt32 u32_FirstConvert,
                              string   s_CurrentDir, 
                              string   s_Environment, 
                              bool     b_SeparatePipes,
                              UInt32 u32_Timeout,
                              [MarshalAs(UnmanagedType.BStr)] out string s_ApiError, 
                              [MarshalAs(UnmanagedType.BStr)] out string s_StdOut, 
                              [MarshalAs(UnmanagedType.BStr)] out string s_StdErr);

string s_ApiError, s_StdOut, s_StdErr;
UInt32 u32_ExitCode = ExecuteW(@"C:\Test\Console.exe Hello Wörld", 1, null, null, 
                               true, 120000, out s_ApiError, out s_StdOut, out s_StdErr);

VB .NET

<DllImport("CaptureConsole.dll", EntryPoint:="ExecuteW", CharSet:=CharSet.Unicode)> _
Public Shared Function ExecuteW(ByVal   s_Commandline   As String,  _
                                ByVal u32_FirstConvert  As Int32,   _
                                ByVal   s_CurrentDir    As String,  _
                                ByVal   s_Environment   As String,  _
                                ByVal   b_SeparatePipes As Boolean, _
                                ByVal u32_Timeout       As Int32,   _
                                <MarshalAs(UnmanagedType.BStr)> ByRef s_ApiError As String, _
                                <MarshalAs(UnmanagedType.BStr)> ByRef s_StdOut   As String, _
                                <MarshalAs(UnmanagedType.BStr)> ByRef s_StdErr   As String) As UInt32

Dim s_ApiError, s_StdOut, s_StdErr As String
Dim u32_ExitCode As UInt32 = ExecuteW("C:\Test\Console.exe Hello Wörld", 1, Nothing, Nothing, 
                                      True, 120000, s_ApiError, s_StdOut, s_StdErr)

C++

typedef DWORD (WINAPI* tExecute)(const WCHAR*, DWORD, const WCHAR*, const WCHAR*, BOOL, DWORD, BSTR*, BSTR*, BSTR*);
  
HMODULE h_Dll = LoadLibraryW(L"CaptureConsole.dll"); 
tExecute f_Execute = (tExecute)GetProcAddress(h_Dll, "ExecuteW");

BSTR s_ApiError, s_StdOut, s_StdErr;
DWORD u32_ExitCode = f_Execute(L"C:\\Test\\Console.exe Hello Wörld", 1, NULL, NULL, 
                               TRUE, 120000, &s_ApiError, &s_StdOut, &s_StdErr);

VB 6

Private Declare Function ExecuteW Lib "CaptureConsole" (
        ByVal s_CommandLine As Long, 
        ByVal s32_FirstConvert As Long,
        ByVal s_CurrentDir As Long, 
        ByVal s_Environment As Long, 
        ByVal b_SeparatePipes As Boolean, 
        ByVal s32_Timeout As Long,
        ByRef s_ApiError As Long, 
        ByRef s_StdOut As Long, 
        ByRef s_StdErr As Long) As Long
  
Dim bs_ApiError, bs_StdOut, bs_StdErr, s32_ExitCode As Long
s32_ExitCode = ExecuteW(StrPtr("C:\Test\Console.exe Hello Wörld"), 1, 0, 0, _
                        True, 120000, bs_ApiError, bs_StdOut, bs_StdErr)

Dim s_ApiError, s_StdOut, s_StdErr As String
s_ApiError = ConvertBSTR(bs_ApiError)
s_StdOut   = ConvertBSTR(bs_StdOut)
s_StdErr   = ConvertBSTR(bs_StdErr)

You find the definition of the function ConvertBSTR() in the demo project.

Codepage Conversions

Console applications and DOS scripts use the OEM codepage to display characters above ASCII code 127.
To display characters like äöüáéú correctly, it is necessary to convert them.

CaptureConsole.dll converts all output (stdout and stderr) from OEM (DOS codepage) to ANSI / Unicode before it is returned to the calling application.

But the input to the Console application or DOS script is more complicated:
If you call a Console.exe application, the commandline parameters must be converted in CaptureConsole.dll.
But if you call a Console.bat script, this will be executed by CMD.EXE which already does this conversion automatically.
So when you call a DOS script, the conversion in CaptureConsole.dll must be turned off. (u32_FirstConvert = 0)

But there are also other cases where the conversion is not desired:

java -cp "C:\Programación\Transacción.jar" Classname Hello Wörld

If you pass a commandline parameter which contains a path or filename with special characters, this path/filename must NOT be converted.
In this case, the conversion must start at the third parameter, because the first "-cp" and the second "C:\Programación\Transacción.jar" must not be converted.
So here you must set u32_FirstConvert = 3.

If the case is more complicated, turn off the conversion in CaptureConsole.dll (u32_FirstConvert = 0) and convert the commandline in your calling application:

C:\Железнодо\Console.exe Param1 Param2

The first part of the commandline is always the EXE, BAT or CMD file to be executed.
If you use ExecuteW in the Unicode compiled CaptureConsole.dll this path may contain Chinese, Russian or Greek Unicode characters.
But all the following parameters must never exceed ASCII code 255 !!

History

  • 31st January, 2009: Initial post
  • 3rd February, 2009: Added a code sample for Visual Basic .NET
  • 6th February, 2009: Added Environment Variables, Current Directory and fixed a Pipe problem
  • 30th October, 2009: Updated source code
  • 19th November, 2009: Updated source code

Elmü

License

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

About the Author

Elmue
Software Developer (Senior) ElmüSoft
Chile Chile
Software Engineer since 27 years.

Comments and Discussions

 
QuestionAlternative for realtime logging: errout.exe PinmemberJens Bornemann15-Dec-13 8:28 
AnswerRe: Alternative for realtime logging: errout.exe [modified] PinmemberElmue16-Dec-13 15:33 
GeneralMy vote of 5 PinmemberMember 966550122-Mar-13 3:53 
QuestionSuggestion PinmemberDavide Zaccanti30-Nov-12 15:56 
AnswerRe: Suggestion PinmemberElmue3-Dec-12 11:03 
GeneralRe: Suggestion PinmemberDavide Zaccanti3-Dec-12 18:47 
GeneralMy vote of 5 Pinmemberrob_toutant31-Mar-12 22:47 
GeneralThank You Pinmemberrob_toutant31-Mar-12 22:24 
QuestionEasy and cool is good; - Extended and powerful it is sometimes necessary.... PinmemberPincopanco9-Aug-11 8:55 
AnswerRe: Easy and cool is good; - Extended and powerful it is sometimes necessary.... PinmemberElmue9-Aug-11 10:18 
GeneralRe: Easy and cool is good; - Extended and powerful it is sometimes necessary.... PinmemberPincopanco13-Aug-11 3:57 
Questionoh man thought it was ok Pinmembercatchit200019-Jul-11 13:55 
AnswerRe: oh man thought it was ok PinmemberElmue25-Jul-11 14:41 
QuestionAWESOME, THANKS! Pinmembercatchit200019-Jul-11 13:21 
GeneralError? No such file or directory. [modified] PinmemberMember 4558661-Feb-11 21:08 
AnswerRe: Error? No such file or directory. PinmemberElmue2-Feb-11 4:41 
GeneralRe: Error? No such file or directory. [modified] PinmemberMember 4558662-Feb-11 7:23 
GeneralRe: Error? No such file or directory. PinmemberElmue4-Feb-11 2:39 
GeneralThank! PinmemberMember 4558664-Feb-11 11:55 
GeneralMy vote of 5 Pinmemberjohannesnestler28-Oct-10 23:37 
GeneralHRESULT: 0x8007000B on x64 compilation [modified] Pinmemberclaus.dieter27-Oct-10 1:51 
GeneralRe: HRESULT: 0x8007000B on x64 compilation PinmvpElmue27-Oct-10 2:53 
GeneralRe: HRESULT: 0x8007000B on x64 compilation Pinmemberclaus.dieter27-Oct-10 4:38 
NewsCaptureConsole Version 3.3b released PinmvpElmue27-Oct-10 14:50 
GeneralSlow output (not your implementation actually) Pinmemberd1879-Sep-10 23:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140415.2 | Last Updated 28 Oct 2010
Article Copyright 2009 by Elmue
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid