December 10, 2009

Controlling External .Net Processes

I have a project where I wanted to be able to provide my users with a button that would open an external application. The idea is that the interaction would be seamless so that from the users perspective, the external application would seem to be apart of my application. To pull this off, I needed to be able to bring the main window(s) of the external application to the front if I had already launched it but the user clicked the button again.

For this implementation, I tried to use the following .Net implementation:

_process.WaitForInputIdle(1000);
IntPtr mainWindowsHandle = _process.MainWindowHandle;
//show the process's window if applicable else inform the user
if (!mainWindowsHandle.Equals(IntPtr.Zero))
{
//show the process's main window
ShowWindow(mainWindowsHandle, 1);
SetForegroundWindow(mainWindowsHandle);
}

Here we get the handle to the main window of the process and we call the win32 ShowWindow and SetForegroundWindow methods to the front.

I went ahead and tried it out with Windows' Calculator and everything seemed to work perfectly. As it turns out, however, .Net applications tend to return IntPtr.Zero as their MainWindowHandle. This would cause this implementation to fail. So it was back to the drawing board.

I found an alternative implementation that would work for .Net applications as well by jumping through a few more win32 hoops. Here is the new improved implementation:

DateTime start = DateTime.Now;
IntPtr windowHandle = IntPtr.Zero;

int waitTime = 500;

EnumWindows(
(hWnd, lParam) =>
{
if (windowHandle == IntPtr.Zero && DateTime.Now.Subtract(start).TotalMilliseconds <>
{
uint pId = 0;
GetWindowThreadProcessId(hWnd, out pId);
if (pId == _process.Id)
{
windowHandle = hWnd;
}
}

return true;
},
IntPtr.Zero);


while (windowHandle == IntPtr.Zero && DateTime.Now.Subtract(start).TotalMilliseconds <>

//show the process's window if applicable else inform the user
if(windowHandle != IntPtr.Zero)
{
//show the process's main window
ShowWindow(windowHandle, SW_SHOWNORMAL);
SetForegroundWindow(windowHandle);
}

Here we use EnumWindows to give us a list handles to open windows and find the first window that has the process ID that we are interested in. This technique seems to prevent getting a bad handle to the main window.

Here are the pInvoke signature used in C# for this implementation:

[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);

const uint SW_SHOWNORMAL = 1;

[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hwnd, uint windowStyle);

private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);

[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);