Click here to Skip to main content
15,860,859 members
Articles / Programming Languages / Visual Basic
Article

Single Instance App with Command-Line Parameters

Rate me:
Please Sign up or sign in to vote.
3.62/5 (15 votes)
13 May 2002CPOL3 min read 219.9K   337   37   34
Demonstrates a single-instance application which can pass command-line parameters to a previous instance.

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 vb.net 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.

.NET

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:

VB
//
// 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
        
    Get
        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
    Try
        arrCmd = SerialHelper.DeserializeFromBase64String(strCmd)
            HandleCommand(arrCmd)
    Catch ex As Exception
        MsgBox(ex.ToString)
        HandleCommand(New String() {strCmd})
    Finally
        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)
    Next
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:

VB
'//Get an array of bytes for the string
Dim B() As Byte = Encoding.Default.GetBytes(StringVarToSend)
    
Try
    '//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)
        
    Dim CD As COPYDATASTRUCT
    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...
        
Finally
    '//Clean up
    Erase B
    Marshal.FreeHGlobal(lpB)
End Try

To retrieve a String from the pointer:

VB
Dim B() As Byte
Try
    '//Get the COPYDATASTRUCT from the message
    Dim CD As COPYDATASTRUCT = m.GetLParam(GetType(COPYDATASTRUCT))
        
    '//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...
        
Finally
    '//Clean up
    Erase B
End Try    

Done!

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!

Credits

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

License

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


Written By
Software Developer CodeProject
United Kingdom United Kingdom
I started writing code when I was 8, with my trusty ZX Spectrum and a subscription to "Input" magazine. Spent many a happy hour in the school's computer labs with the BBC Micros and our two DOS PCs.

After a brief detour into the world of Maths, I found my way back into programming during my degree via free copies of Delphi and Visual C++ given away with computing magazines.

I went straight from my degree into my first programming job, at Trinet Ltd. Eleven years later, the company merged to become ArcomIT. Three years after that, our project manager left to set up Nevalee Business Solutions, and took me with him. Since then, we've taken on four more members of staff, and more work than you can shake a stick at. Smile | :)

Between writing custom code to integrate with Visma Business, developing web portals to streamline operations for a large multi-national customer, and maintaining RedAtlas, our general aviation airport management system, there's certainly never a dull day in the office!

Outside of work, I enjoy real ale and decent books, and when I get the chance I "tinkle the ivories" on my Technics organ.

Comments and Discussions

 
QuestionVB BETA 2005 version Pin
Jamesthebod4-Oct-05 8:27
Jamesthebod4-Oct-05 8:27 
AnswerRe: VB BETA 2005 version Pin
Richard Deeming4-Oct-05 8:37
mveRichard Deeming4-Oct-05 8:37 
Generalstuck in loop - MainForm_WndProc Pin
djdidge12-Sep-05 0:48
djdidge12-Sep-05 0:48 
Hi there,

Great article, i have learnt from it but still need to learn more!

I am having an issue where sometimes the system gets stuck in a loop hitting MainForm_WndProc

Is this a known issue?

Regards,

DiDGE

GeneralRe: stuck in loop - MainForm_WndProc Pin
Richard Deeming12-Sep-05 1:22
mveRichard Deeming12-Sep-05 1:22 
GeneralRe: stuck in loop - MainForm_WndProc Pin
djdidge12-Sep-05 1:49
djdidge12-Sep-05 1:49 
QuestionRe: stuck in loop - MainForm_WndProc Pin
Stijn Courtheyn18-Aug-09 23:22
Stijn Courtheyn18-Aug-09 23:22 
AnswerRe: stuck in loop - MainForm_WndProc Pin
Richard Deeming19-Aug-09 1:28
mveRichard Deeming19-Aug-09 1:28 
GeneralRe: stuck in loop - MainForm_WndProc Pin
Stijn Courtheyn21-Aug-09 0:05
Stijn Courtheyn21-Aug-09 0:05 
Questionhow return the focus on the old istance ? Pin
gerico.one17-May-05 3:15
gerico.one17-May-05 3:15 
AnswerRe: how return the focus on the old istance ? Pin
Richard Deeming18-May-05 5:01
mveRichard Deeming18-May-05 5:01 
GeneralDependency the .exe launched Pin
Vitoto4-May-05 10:27
Vitoto4-May-05 10:27 
GeneralRe: Dependency the .exe launched Pin
Richard Deeming4-May-05 23:39
mveRichard Deeming4-May-05 23:39 
GeneralRe: Dependency the .exe launched Pin
Vitoto5-May-05 4:25
Vitoto5-May-05 4:25 
GeneralRe: Dependency the .exe launched Pin
Richard Deeming6-May-05 7:03
mveRichard Deeming6-May-05 7:03 
QuestionHow about a Form-less app? Pin
John Korres24-Mar-05 1:17
John Korres24-Mar-05 1:17 
AnswerRe: How about a Form-less app? Pin
John Korres24-Mar-05 1:20
John Korres24-Mar-05 1:20 
AnswerRe: How about a Form-less app? Pin
Richard Deeming24-Mar-05 1:24
mveRichard Deeming24-Mar-05 1:24 
GeneralSingle Instance and Tray Icon Pin
STSC5-Nov-04 3:13
STSC5-Nov-04 3:13 
GeneralRe: Single Instance and Tray Icon Pin
Richard Deeming8-Nov-04 8:00
mveRichard Deeming8-Nov-04 8:00 
GeneralSingleInstanceApp Pin
Member 5340616-Jul-04 1:18
Member 5340616-Jul-04 1:18 
GeneralRe: SingleInstanceApp Pin
Richard Deeming19-Jul-04 7:48
mveRichard Deeming19-Jul-04 7:48 
GeneralFilename truncated Pin
JasonHo23-Jun-03 0:38
JasonHo23-Jun-03 0:38 
GeneralRe: Filename truncated Pin
Richard Deeming23-Jun-03 1:34
mveRichard Deeming23-Jun-03 1:34 
Generalno focus .. Pin
michiganian8-Apr-03 3:07
michiganian8-Apr-03 3:07 
GeneralRe: no focus .. Pin
Richard Deeming11-Apr-03 2:19
mveRichard Deeming11-Apr-03 2:19 

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

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