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:
- executes a Console Application or DOS Script invisible in the background
- waits until the Console has finished
- returns all printed output via
stdout
and stderr
as Strings to the calling application
Features
- 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.
- The only exported function "
Execute
" in this DLL uses the same calling convention as all Windows DLLs. (WINAPI
= __stdcall
) - 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)
- 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
". - The DLL is thread-safe: You can execute multiple Console applications at the same time from different threads.
- You can choose if you want
stdout
and stderr
separated or as mixed output. - The Exit Code of the Console Application is returned to the caller.
- You can define the Working Directory (
GetCurrentDirectory
) for the console application. - You can pass additional Environment Variables (
GetEnvironmentVariable
) to the Console Application or replace existing ones. - 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. - 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)
- The DLL internally uses
CString
s and exports BSTR, so a buffer overflow is impossible. If your console prints 50 MB text output, that's no problem. - All API errors that may happen while starting the Console process are handled and returned as a human readable error message.
- 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.
- Special characters (like äöüáéú) are converted to the DOS Codepage when passed as commandline parameters.
- 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:
- The code is weak: You may lose characters if the console application prints faster than you read the buffer or the console is scrolled
- You don't get stdout and stderr separated
- You need an additional EXE file which calls the Console process
- 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: <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 string
s 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 string
s 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ü