Programming Topic of the Week
Using the Process Class in C#
From the forthcoming book: Microsoft Visual C# Programming Essentials by Souleiman Valiev.
Coding the Process Class to Run External Applications
In your project you may need to execute another application in a silent or hidden mode.
For example, to execute a SQLPLUS.exe, SQLLDR.exe, FTP.exe or csc.exe.
This can be done relatively easy using .NET classes: Process and ProcessStartInfo.
But when you try to do so, you should be aware of two issues that may prevent you from
achieving the best results and creating error free code.
Listing 1:
private bool Compile(string strClassFileName, string strOutputFileName, ref string ErrMsg)
{
const string strCompiler35 = @"C:\Windows\Microsoft.NET\Framework\v3.5\csc.exe";
const string strCompiler20 = @"C:\WINNT\Microsoft.NET\Framework\v2.0.50727\csc.exe";
const string strCompiler12 = @"C:\WINNT\Microsoft.NET\Framework\v1.1.4322\csc.exe";
Process objProcess = null;
ProcessStartInfo objProcStartInfo = null;
string strCompilerToUse = "";
string strArgs = "";
Int32 intReturnValue;
bool blnValue = false;
//Default return value to false try
{
if (File.Exists(strCompiler35))
strCompilerToUse = strCompiler35;
else if (File.Exists(strCompiler20)) strCompilerToUse = strCompiler20;
else if (File.Exists(strCompiler12)) strCompilerToUse = strCompiler12;
else
{
ErrMsg = "C# compiler file not found";
return false;
}
//If you need reference dependencies use /r: swith as follows:
//strArgs = @"/t:library /r:" + strDllNameNPath + " /out:" + strOutputFileName + " " + strClassFileName;
strArgs = @"/t:library /out:" + strOutputFileName + " " + strClassFileName;
objProcStartInfo = new ProcessStartInfo(strCompilerToUse, strArgs);
objProcStartInfo.Verb = "Open";
objProcess = Process.Start(objProcStartInfo);
intReturnValue = objProcess.ExitCode;
if (intReturnValue != 0)
ErrMsg = "CompileA method failed.";
else
blnValue = true;
return blnValue;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return false;
}
finally
{
// Cleanup
if (objProcess != null)
{
objProcess.Close();
objProcess.Dispose();
objProcess = null;
}
}
}
//You can run this code from any procedure. For example,
//from Form_Load event as follows:
private void Form1_Load(object sender, EventArgs e)
{ string ErrMsg = "";
Compile(@"C:\temp\classA.cs", @"C:\Temp\ClassA.dll", ref ErrMsg );
}
Listing 1 present a method that is used to compile a C# class code.
There is one bug and one issue in the above code.
In this implementation you ignore the fact that you need to wait for the external application
to complete its work and return an ExitCode, which will tell you if the application
execution was successful or failed. Notice that in this method you read the ExitCode property
immediately after you strted the external process. It may work only if the execution is instantanious.
But in real-world situation it will always return 0, which is a success exit code. Also there is an issue
with the process creating a console window and showing it for a short period of time or until the
called application exits. In most cases you would like to hide the console window.
The ExitCode problem can be easily fixed by calling the WaitForExit method of the Process class, which effectively
delay the execution of the next line of code until the external process completes execution. As for the console window you can silence
it by simply turning on the ProcStartInfo.CreateNoWindow property or by setting the ProcStartInfo.WindowStyle to ProcessWindowStyle.Hidden.
Both fixes are shown in Listing 2.
Listing 2:
{
const string strCompiler35 = @"C:\Windows\Microsoft.NET\Framework\v3.5\csc.exe";
const string strCompiler20 = @"C:\WINNT\Microsoft.NET\Framework\v2.0.50727\csc.exe";
const string strCompiler12 = @"C:\WINNT\Microsoft.NET\Framework\v1.1.4322\csc.exe";
Process objProcess = null;
ProcessStartInfo objProcStartInfo = null;
string strCompilerToUse = "";
string strArgs = "";
Int32 intReturnValue;
bool blnValue = false;
//Default return value to false try
{
if (File.Exists(strCompiler35))
strCompilerToUse = strCompiler35;
else if (File.Exists(strCompiler20)) strCompilerToUse = strCompiler20;
else if (File.Exists(strCompiler12)) strCompilerToUse = strCompiler12;
else
{
ErrMsg = "C# compiler file not found";
return false;
}
//If you need reference dependencies use /r: swith as follows:
//strArgs = @"/t:library /r:" + strDllNameNPath + " /out:" + strOutputFileName + " " + strClassFileName;
strArgs = @"/t:library /out:" + strOutputFileName + " " + strClassFileName;
objProcStartInfo = new ProcessStartInfo(strCompilerToUse, strArgs);
objProcStartInfo.Verb = "Open";
objProcess = Process.Start(objProcStartInfo);
intReturnValue = objProcess.ExitCode;
if (intReturnValue != 0)
ErrMsg = "CompileA method failed.";
else
blnValue = true;
return blnValue;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
return false;
}
finally
{
// Cleanup
if (objProcess != null)
{
objProcess.Close();
objProcess.Dispose();
objProcess = null;
}
}
}
//You can run this code from any procedure. For example,
//from Form_Load event as follows:
private void Form1_Load(object sender, EventArgs e)
{ string ErrMsg = "";
Compile(@"C:\temp\classA.cs", @"C:\Temp\ClassA.dll", ref ErrMsg );
}
strArgs = @"/t:library /out:" + strOutputFileName + " " + strClassFileName;
objProcStartInfo = new ProcessStartInfo(strCompilerToUse, strArgs);
objProcStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
objProcStartInfo.Verb = "Open";
objProcess = Process.Start(objProcStartInfo);
//To get a valid external application exit code;
//we need to wait until the application completes its work;
objProcess.WaitForExit();
intReturnValue = objProcess.ExitCode;
if (intReturnValue != 0)
ErrMsg = "CompileB method failed.";
else
blnValue = true;
return blnValue;
Now you can get a valid exit code and run the external application in a silent mode.
But if the external application fails you need to know what caused the failure
or at least get the error description.
To get the error detail you need to sweat a little more. Again this is because your main
application runs in one process boundary and the external application runs in another one.
To get the external application's output you need to redirect it to the main process.
This is done by turning the ProcStartInfo.RedirectStandardOutput and
ProcStartInfo.RedirectStandardError on. Then you just need to read the content of the
StandardOutput stream object into your error message string variable. Also you should turn
off the UseShellExecute feature. Notice when you set UseShellExecute to false the console window will be shown if you use
ProcessWindowStyle.Hidden. In this case use CreateNoWindow property instead. These modifications
are shown in Listing 3.
Listing 3:
objProcStartInfo = new ProcessStartInfo(strCompilerToUse, strArgs);
objProcStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
objProcStartInfo.Verb = "Open";
objProcess = Process.Start(objProcStartInfo);
//To get a valid external application exit code;
//we need to wait until the application completes its work;
objProcess.WaitForExit();
intReturnValue = objProcess.ExitCode;
if (intReturnValue != 0)
ErrMsg = "CompileB method failed.";
else
blnValue = true;
return blnValue;
strArgs = @"/t:library /out:" + strOutputFileName + " " + strClassFileName;
objProcStartInfo = new ProcessStartInfo(strCompilerToUse, strArgs);
//objProcStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
objProcStartInfo.CreateNoWindow = true;
objProcStartInfo.Verb = "Open";
//The following is necessary to import the external application
//to your ProcessStartInfo object.
objProcStartInfo.UseShellExecute = false;
objProcStartInfo.RedirectStandardError = true;
objProcStartInfo.RedirectStandardOutput = true;
objProcess = Process.Start(objProcStartInfo);
objProcess.WaitForExit();
intReturnValue = objProcess.ExitCode;
if (intReturnValue != 0)
//Here we read the error description.
ErrMsg = objProcess.StandardOutput.ReadToEnd();
else
blnValue = true;
return blnValue;
�Copyright 2008 by Souleiman Valiev
objProcStartInfo = new ProcessStartInfo(strCompilerToUse, strArgs);
//objProcStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
objProcStartInfo.CreateNoWindow = true;
objProcStartInfo.Verb = "Open";
//The following is necessary to import the external application
//to your ProcessStartInfo object.
objProcStartInfo.UseShellExecute = false;
objProcStartInfo.RedirectStandardError = true;
objProcStartInfo.RedirectStandardOutput = true;
objProcess = Process.Start(objProcStartInfo);
objProcess.WaitForExit();
intReturnValue = objProcess.ExitCode;
if (intReturnValue != 0)
//Here we read the error description.
ErrMsg = objProcess.StandardOutput.ReadToEnd();
else
blnValue = true;
return blnValue;