Friday, May 09, 2014

Launching a Single Instance of an Application

It is easy to launch a new process in C#. The following code sample launches the built-in Windows Calculator application:

{
    Process.Start("calc.exe");
}

However, the problem with this code is if it is called multiple times in an application (like on a button click), multiple copies of the Windows Calculator will be started. In some situations that may be desired, but often only one instance of the application is desired.

The following code demonstrates how to launch a new process in C# if it is not already running or set focus to the application if it is already running:

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool ShowWindow(IntPtr hWnd, uint nCmdShow);
 
private const uint SW_SHOWNORMAL = 1;
 
[DllImport("user32.dll", SetLastError = true)]
private static extern bool BringWindowToTop(IntPtr hWnd);
 
private void cmdLaunchOnce_Click(object sender, EventArgs e)
{
    try
    {
        Process calculator = Process.GetProcessesByName("calc").FirstOrDefault();
        if (calculator == null)
        {
            Process.Start("calc.exe");
        }
        else
        {
            ShowWindow(calculator.MainWindowHandle, SW_SHOWNORMAL);
            BringWindowToTop(calculator.MainWindowHandle);
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Launch Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

Things to note:

  • When searching for the process, you do not include the “.exe” part of the executable name.
  • The call to ShowWindow will restore the Calculator window if it has been minimized.
  • The call to BringWindowToTop causes the Calculator window to become the top-most window.

Monday, September 17, 2012

Easy way to debug a Windows Service at startup

The typical way of debugging a Windows Service, is to attach a debugger to it after it has started. This does not work very well if one needs to debug code that only executes during the startup of the Windows Service. A useful technique I have used to debug Windows Services is to launch the debugger from the Windows Service itself if a DEBUG parameter is specified. For example:

Windows Service Properties


protected override void OnStart(string[] args)
{
    // Only use this code in Debug mode.
    #if DEBUG
 
    List<string> arguments = new List<string>(args);
    if (arguments.Exists((arg) => arg.Equals("DEBUG",
        StringComparison.InvariantCultureIgnoreCase)))
    {
        // Launch Debugger if DEBUG argument is provided.
        System.Diagnostics.Debugger.Launch();
    }
 
    #endif
}

Wednesday, September 12, 2012

VB6 Binary Compatibility Issue on public COM fields

I discovered a very interesting VB6 ActiveX DLL issue the other day when I was trying to recompile some legacy code. Every time I would recompile a particular ActiveX DLL project, the “Break compatibility” dialog would be displayed even though the public field in question did not change.

Binary Compatibility Issue

The class that was causing this issue contained a public field that referenced an OCX control. I would never recommend doing that, but doing so should not cause Binary Compatibility to break every time it is rebuilt.

Using Microsoft’s OLE/COM Object Viewer, I discovered VB6 had declared this field (i.e. its associated methods) as IDispatch rather than IPicClip. This causes consumers of the library (including the VB6 compiler) to view the field as an Object rather than a PicClip.PictureClip.

OLEView IDispatch

After some trial an error, my colleague and I determined that if an OCX control is going to be available to consumers of the ActiveX DLL, the OCX library must FIRST be referenced using the References dialog before adding it to the Toolbar using the Components dialog.

References Dialog

Components Dialog

After adding the OCX library using the Reference dialog first, the “Binary Compatibility” dialog does not appear when rebuilding the ActiveX DLL and the class looks as expected in Microsoft’s OLE/COM Object Viewer.

OLEView IPicClip

NOTE: In some situations you may not be able to reference the OCX using both the References and the Components dialogs, and I would not recommend making OCX controls available using public class fields anyway.