Click here to Skip to main content
Click here to Skip to main content

Single Instance App with Command-Line Parameters

, 13 May 2002 CPOL
Rate this:
Please Sign up or sign in to vote.
Demonstrates a single-instance application which can pass command-line parameters to a previous instance.
<!-- Download Links --> <!-- Add the rest of your HTML here -->

The Problem

In a recent posting on the GotDotNet forums, a user asked "Is there a way to support a 'ddeexec'-enabled file association in a app, so when the app is already running, and a file is double-clicked from Windows Explorer, the app can handle it?". DDE has not been recommended for a long time, and .NET has no support for it.

A Solution

Personally, I could never be bothered to get DDE working. Instead, I used a trick from the old vbAccelerator site:

  1. Create a mutex with a name specific to your app;
  2. If the mutex doesn't exist, your app is not running:
    1. Start the app;
    2. Mark the main form with a property to make it easily identifiable (SetProp);
    3. Subclass the main form, and wait for the WM_COPYDATA message. The data will contain the command-line from a new instance;
  3. If the mutex already exists, your app is already running:
    1. Enumerate all windows to find the one with your property (GetProp);
    2. If you don't find it, proceed as step 2;
    3. If you do find it, send the WM_COPYDATA message to it, with the command-line from this instance;

This may seem very complicated, but the App.PrevInstance property was not always reliable, and didn't provide any means to transfer data to the other instance.


When I first read the question, I thought there must be a better solution in .NET - this kind of application is so common, I was sure the functionality would be built-in. Some of it - such as Mutex - is. Some of the classes in System.Diagnostics look very promising, but don't seem to work. For example, there doesn't seem to be a way to get a NativeWindow object from a Process.MainWindowHandle - the FromHandle method returns Nothing. There is a COPYDATASTRUCT in System.Windows.Forms, but although the Object Browser thinks it's Public, the compiler thinks it's Private, so that can't be used.

In the end, I have had to resort to a lot of the same Win32 API calls I was using in VB6. Subclassing is much easier in VB.NET, and the Mutex object helps, but I can't see a pure managed-code solution to this problem. On the plus-side, the Marshal class provides a very easy way to transfer data between managed objects and un-managed APIs, although some of the calls may not be obvious to a VB6 programmer. Object serialization also makes it possible to pass objects between processes as well.

Using the code

To use this in your own code, you will need to add the mMain.vb file to your project. Set the startup object to be Sub Main within this module, and change the references to frmMain to reflect your application's main form. You will need to make your main form implement mMain.SingleInstance.ISingleInstanceForm, using the following code as an example:

// frmMain.vb

Public Event WndProc2(ByVal m As System.Windows.Forms.Message, _
                      ByRef Cancel As Boolean) _
    Implements mMain.SingleInstance.ISingleInstanceForm.WndProc

'//Subclassing the form - so much easier than VB6! :)
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
    Dim bCancel As Boolean = False
    RaiseEvent WndProc2(m, bCancel)
    'If this message wasn't handled (i.e. WM_COPYDATA), pass it on
    If Not bCancel Then MyBase.WndProc(m)
End Sub

'//The reference to the handle is required for SetProp
Public ReadOnly Property hWnd() As System.IntPtr _
Implements mMain.SingleInstance.ISingleInstanceForm.Handle
        Return Handle
    End Get
End Property

'//This is where the string passed in the COPYDATASTRUCT is handled
'//In this case, we have a Base64 serialization of the CmdArgs() array
Public Sub HandleCommand(ByVal strCmd As String) _
    Implements mMain.SingleInstance.ISingleInstanceForm.HandleCommand
    Dim arrCmd() As String
        arrCmd = SerialHelper.DeserializeFromBase64String(strCmd)
    Catch ex As Exception
        HandleCommand(New String() {strCmd})
        Erase arrCmd
    End Try
End Sub

'//This is where we handle the command-line arguments
'//In this sample, I simply add them to the list-box
'//You would probably want to open the files, etc.
Public Sub HandleCommand(ByVal strArgs() As String)
    Dim strCmd As String
    For Each strCmd In strArgs
        lstEvents.Items.Add("CMD: " & strCmd)
End Sub


VB.NET and Pointers

One interesting thing to come out of this code - I have discovered how to deal with un-managed pointers. For example, the COPYDATASTRUC structure contains the lpData member, which is a pointer to a Byte Array. If the member is defined as an IntPtr, the structure cannot be passed to an API, so it has to be declared as an Integer.

There are two operations required - set the lpData member to point to a String, and retrieve a String from it. In VB6, you would use VarPtr and the RtlMoveMemory API (a.k.a. CopyMemory) to do this. In .Net, VarPtr no longer exists, but the Marshal class provides much better alternatives.

To set the pointer to the value of a String:

'//Get an array of bytes for the string
Dim B() As Byte = Encoding.Default.GetBytes(StringVarToSend)
    '//Allocate enough memory on the global heap
    Dim lpB As IntPtr = Marshal.AllocHGlobal(B.Length)
    '//Copy the managed array to the un-managed pointer
    Marshal.Copy(B, 0, lpB, B.Length)
    With CD
        .dwData = 0
        .cbData = B.Length
        '//The ToInt32 function converts an IntPtr to an Integer pointer
        .lpData = lpB.ToInt32
    End With
    'Do something with CD here...
    '//Clean up
    Erase B
End Try

To retrieve a String from the pointer:

Dim B() As Byte
    '//Get the COPYDATASTRUCT from the message
    '//Allocate enough space in B
    ReDim B(CD.cbData)
    '//Create an IntPtr from the Integer pointer
    Dim lpData As IntPtr = New IntPtr(CD.lpData)
    '//Copy the data into the array
    Marshal.Copy(lpData, B, 0, CD.cbData)
    '//Get the string from the Byte Array
    Dim strData As String = Encoding.Default.GetString(B)
    '//Do something with the String here...
    '//Clean up
    Erase B
End Try    


If anyone finds a better way to accomplish this, or a way to get a NativeWindow object from a Process.MainWindowHandle, please let me know!


The SerialHelper class was "borrowed" from Dr GUI.Net #3.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Richard Deeming
Software Developer Nevalee Business Solutions
United Kingdom United Kingdom
No Biography provided
Follow on   Twitter   Google+

Comments and Discussions

QuestionVB BETA 2005 version PinmemberJamesthebod4-Oct-05 9:27 
AnswerRe: VB BETA 2005 version PinmemberRichard Deeming4-Oct-05 9:37 
Generalstuck in loop - MainForm_WndProc Pinmemberdjdidge12-Sep-05 1:48 
GeneralRe: stuck in loop - MainForm_WndProc PinmemberRichard Deeming12-Sep-05 2:22 
If you have set a break-point in the MainForm_WndProc procedure outside of the Case WM_COPYDATA block (lines 217 to 231), you may get stuck in a loop. Every time a message is sent to the form, the procedure gets called, and the debugger window is activated. This sends a deactivation message to the form, which calls the procedure again. When you try to switch back to the form, it gets an activation message, which switches you back to the debugger again.
I would suggest that you switch to the Microsoft solution, as that is more likely to be compatible with the new application framework in VB 2005. This article was a very simple translation of a VB6 kludge, and should be considered obsolete.[^][^]

"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer
GeneralRe: stuck in loop - MainForm_WndProc Pinmemberdjdidge12-Sep-05 2:49 
QuestionRe: stuck in loop - MainForm_WndProc PinmemberStijn Courtheyn19-Aug-09 0:22 
AnswerRe: stuck in loop - MainForm_WndProc PinmemberRichard Deeming19-Aug-09 2:28 
GeneralRe: stuck in loop - MainForm_WndProc PinmemberStijn Courtheyn21-Aug-09 1:05 
Questionhow return the focus on the old istance ? Pinmembergerico.one17-May-05 4:15 
AnswerRe: how return the focus on the old istance ? PinmemberRichard Deeming18-May-05 6:01 
GeneralDependency the .exe launched PinmemberVitoto4-May-05 11:27 
GeneralRe: Dependency the .exe launched PinmemberRichard Deeming5-May-05 0:39 
GeneralRe: Dependency the .exe launched PinmemberVitoto5-May-05 5:25 
GeneralRe: Dependency the .exe launched PinmemberRichard Deeming6-May-05 8:03 
QuestionHow about a Form-less app? PinmemberIcingDeath24-Mar-05 2:17 
AnswerRe: How about a Form-less app? PinmemberIcingDeath24-Mar-05 2:20 
AnswerRe: How about a Form-less app? PinmemberRichard Deeming24-Mar-05 2:24 
GeneralSingle Instance and Tray Icon PinmemberSTSC5-Nov-04 4:13 
GeneralRe: Single Instance and Tray Icon PinmemberRichard Deeming8-Nov-04 9:00 
GeneralSingleInstanceApp PinmemberPhilippe Hollmuller6-Jul-04 2:18 
GeneralRe: SingleInstanceApp PinmemberRichard Deeming19-Jul-04 8:48 
GeneralFilename truncated PinmemberJasonHo23-Jun-03 1:38 
GeneralRe: Filename truncated PinmemberRichard Deeming23-Jun-03 2:34 
Generalno focus .. Pinmembermichiganian8-Apr-03 4:07 
GeneralRe: no focus .. PinmemberRichard Deeming11-Apr-03 3:19 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150327.1 | Last Updated 14 May 2002
Article Copyright 2002 by Richard Deeming
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid