Wednesday, June 3, 2009

How to wait for a VB6-like executable

If you have written any VB6 and earlier programs for running in the Windows Command prompt as batch programs, you would have noticed that control is returned back to you or your calling script almost immediately after execution. There is no convenient way to know when the program execution has completed before executing the next line in your calling script. Global Mapper is an example executable that exhibits the same behavior but it has its batch execution engine that lets you get around this issue.

I have written a C# wrapper program that will wrap around executables that behave like VB6 executables. The wrapper program essentially works like this:
  1. Before starting up an instance of the executable, get a list of all running processes that have the same executable name.
  2. Start up an instance of the executable.
  3. Get a second list of all running processes that has the same executable name.
  4. If the second list has a process id that is not in the first list, then that is my executable instance.
  5. In a while loop, periodically check to see whether my executable instance is still running in the process list. If it is not in the running list, then exit from the loop and return to the calling method.
Example c# code snippets are shown below. The example requires the System.Collections, System.Threading, System.Runtime.InteropServices name spaces.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Collections;
using System.Threading;
using System.Runtime.InteropServices;

class VB6ExeWrapper
{
//This array list will store the list of running vb6exe processes before
//we run our own instance of vb6exe executable
private ArrayList _vb6exeProcesses = null;

//Declare the name of the vb6exe executable we want to run
private string _VB6ExeProcessName = "vb6exe";

#region public properties
public string Vb6ExeProcessName
{
get { return this._VB6ExeProcessName; }
set { this._VB6ExeProcessName = value; }
}
#endregion

#region public methods
public void Execute()
{
ProcessStartInfo Vb6ExeStartInfo = null;
Process theProc = null;
int myProcId = 0;
try
{
//Before invoking our vb6exe executable, get a list of vb6exe processes currently running
_vb6exeProcesses = new ArrayList();
foreach (Process proc in Process.GetProcessesByName(_VB6ExeProcessName))
_vb6exeProcesses.Add(proc.Id);

//Now we can startup our own instance of vb6exe
Vb6ExeStartInfo = new ProcessStartInfo(_VB6ExeProcessName);
Vb6ExeStartInfo.UseShellExecute = false;
theProc = Process.Start(Vb6ExeStartInfo);

//The vb6exe process has started. Now get a new list of vb6exe processes running.
//Compare that with the first list to determine the process id of our own
//vb6exe instance.
foreach (Process proc in Process.GetProcessesByName(_VB6ExeProcessName))
if (!_vb6exeProcesses.Contains(proc.Id))
{
//this is my vb6exe process!
myProcId = proc.Id;
}

//Use a threaded endless loop to periodically check whether our vbexe instance
//is still running. If it is no longer in the process list, then
//exit the loop
while (isProcessRunning(myProcId) == true)
{
//pause this program thread for 1500 milliseconds
Thread.Sleep(1500);
}
}
catch (Exception ex)
{
throw ex;
}
}
#endregion
#region private methods
private bool isProcessRunning(int procId)
{
bool status = true;
Process proc = null;
try
{
proc = Process.GetProcessById(procId);
if (proc == null)
status = false;
}
catch (Exception ex)
{
status = false;
}
return status;
}
#endregion
}

To use the wrapper, in the calling program code, the following lines of code are sufficient.

wrapper = new VB6ExeWrapper();
wrapper.Execute();
//The next line in the code will be executed only when the wrapper.Execute() method has finished.

No comments: