Click here to Skip to main content
15,861,168 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 215.4K   2.9K   166   87
Capture Console Output

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

Image 1

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:

C++
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:
C++
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: <nobr>"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#

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

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++

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

VB.NET
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
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)


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

Comments and Discussions

 
Questionfollowing exit code generated. no stderr, stdout. Pin
manoj madushantha14-Aug-21 23:32
manoj madushantha14-Aug-21 23:32 
AnswerRe: following exit code generated. no stderr, stdout. Pin
Elmue16-Aug-21 4:57
Elmue16-Aug-21 4:57 
GeneralRe: following exit code generated. no stderr, stdout. Pin
manoj madushantha20-Aug-21 2:32
manoj madushantha20-Aug-21 2:32 
GeneralRe: following exit code generated. no stderr, stdout. Pin
manoj madushantha20-Aug-21 3:14
manoj madushantha20-Aug-21 3:14 
PraiseIts working man. Horaay!!!! Pin
manoj madushantha12-Aug-21 2:12
manoj madushantha12-Aug-21 2:12 
GeneralThanks Pin
psmkb11101-Sep-14 15:25
psmkb11101-Sep-14 15:25 
GeneralRe: Thanks Pin
Elmue6-Sep-14 7:06
Elmue6-Sep-14 7:06 
There is no need to recompile a DLL that is already compiled in the ZIP file.
QuestionAlternative for realtime logging: errout.exe Pin
Jens Bornemann15-Dec-13 8:28
Jens Bornemann15-Dec-13 8:28 
AnswerRe: Alternative for realtime logging: errout.exe Pin
Elmue16-Dec-13 15:33
Elmue16-Dec-13 15:33 
GeneralMy vote of 5 Pin
Member 966550122-Mar-13 3:53
Member 966550122-Mar-13 3:53 
QuestionSuggestion Pin
Davide Zaccanti30-Nov-12 15:56
Davide Zaccanti30-Nov-12 15:56 
AnswerRe: Suggestion Pin
Elmue3-Dec-12 11:03
Elmue3-Dec-12 11:03 
GeneralRe: Suggestion Pin
Davide Zaccanti3-Dec-12 18:47
Davide Zaccanti3-Dec-12 18:47 
GeneralMy vote of 5 Pin
rob_toutant31-Mar-12 22:47
rob_toutant31-Mar-12 22:47 
GeneralThank You Pin
rob_toutant31-Mar-12 22:24
rob_toutant31-Mar-12 22:24 
QuestionEasy and cool is good; - Extended and powerful it is sometimes necessary.... Pin
Pincopanco9-Aug-11 8:55
Pincopanco9-Aug-11 8:55 
AnswerRe: Easy and cool is good; - Extended and powerful it is sometimes necessary.... Pin
Elmue9-Aug-11 10:18
Elmue9-Aug-11 10:18 
GeneralRe: Easy and cool is good; - Extended and powerful it is sometimes necessary.... Pin
Pincopanco13-Aug-11 3:57
Pincopanco13-Aug-11 3:57 
Questionoh man thought it was ok Pin
catchit200019-Jul-11 13:55
catchit200019-Jul-11 13:55 
AnswerRe: oh man thought it was ok Pin
Elmue25-Jul-11 14:41
Elmue25-Jul-11 14:41 
QuestionAWESOME, THANKS! Pin
catchit200019-Jul-11 13:21
catchit200019-Jul-11 13:21 
GeneralError? No such file or directory. [modified] Pin
RAND 4558661-Feb-11 21:08
RAND 4558661-Feb-11 21:08 
AnswerRe: Error? No such file or directory. Pin
Elmue2-Feb-11 4:41
Elmue2-Feb-11 4:41 
GeneralRe: Error? No such file or directory. [modified] Pin
RAND 4558662-Feb-11 7:23
RAND 4558662-Feb-11 7:23 
GeneralRe: Error? No such file or directory. Pin
Elmue4-Feb-11 2:39
Elmue4-Feb-11 2:39 

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

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