Click here to Skip to main content
15,884,628 members
Articles / Programming Languages / Visual Basic

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 220.3K   338   37  
Demonstrates a single-instance application which can pass command-line parameters to a previous instance.
Imports System.Windows.Forms
Imports System.Diagnostics
Imports System.Runtime.InteropServices
Imports System.Text
Imports System.Runtime.Serialization.Formatters.Binary
Imports System.IO

Module mMain
    Public Class SerialHelper
        '///Class mercilessly ripped-off from Dr GUI.Net #3 :)
        Public Shared Function SerializeToBase64String(ByVal o As Object) As String
            Dim formatter As New BinaryFormatter()
            Dim serialMemoryStream As New MemoryStream()
            formatter.Serialize(serialMemoryStream, o)
            Dim bytes() As Byte = serialMemoryStream.ToArray()
            Return Convert.ToBase64String(bytes).Trim()
        End Function

        Public Shared Function DeserializeFromBase64String(ByVal base64String As String) As Object
            Dim formatter As New BinaryFormatter()
            base64String = base64String.Trim(ControlChars.NullChar)
            Dim bytes() As Byte = Convert.FromBase64String(base64String)
            Dim serialMemoryStream As New MemoryStream(bytes)
            Return formatter.Deserialize(serialMemoryStream)
        End Function
    End Class

    Public Class SingleInstance
        Public Interface ISingleInstanceForm
            Delegate Sub _WndProc(ByVal m As Message, ByRef Cancel As Boolean)
            Event WndProc As _WndProc
            ReadOnly Property Handle() As IntPtr
            Sub HandleCommand(ByVal strCmd As String)
        End Interface
#Region "API"
        Private Const WM_COPYDATA As Integer = &H4A

        <StructLayout(LayoutKind.Sequential)> _
        Private Structure COPYDATASTRUCT
            Public dwData As Integer
            Public cbData As Integer
            Public lpData As Integer
        End Structure

        Private Declare Auto Function GetProp Lib "user32" (ByVal hWnd As Integer, ByVal lpString As String) As Integer
        Private Declare Auto Function SetProp Lib "user32" (ByVal hWnd As Integer, ByVal lpString As String, ByVal hData As Integer) As Integer

        Private Delegate Function EnumWindowsProc(ByVal hWnd As Integer, ByVal lParam As Integer) As Integer
        Private Declare Function EnumWindows Lib "user32" (ByVal lpEnumFunc As EnumWindowsProc, ByVal lParam As Integer) As Integer

        Private Declare Auto Function SendMessage Lib "user32" (ByVal hWnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, ByVal lParam As IntPtr) As Integer
#End Region

#Region "EnumWindows"
        Private Shared _EWP As New EnumWindowsProc(AddressOf EWP)
        Private Shared Function EWP(ByVal hWnd As Integer, ByVal lParam As Integer) As Integer
            ' Customised windows enumeration procedure.  Stops
            ' when it finds another application with the Window
            ' property set, or when all windows are exhausted.
            Try
                If IsThisApp(hWnd) Then
                    _hWnd = hWnd
                    Return 0
                Else
                    Return 1
                End If
            Catch
                Return 0
            End Try
        End Function
        Private Shared Function IsThisApp(ByVal hWnd As Long) As Boolean
            ' Check if the windows property is set for this
            ' window handle:
            If GetProp(hWnd, _mcThisAppID & "_APPLICATION") = 1 Then
                Return True
            End If
        End Function

        Private Shared Function FindWindow() As Boolean
            If _hWnd = -1 Then
                EnumWindows(_EWP, 0)
                If _hWnd = -1 Then
                    Return False
                Else
                    Return True
                End If
            Else
                Return True
            End If
        End Function

        Private Shared Function SendCDSToWindow(ByVal CD As COPYDATASTRUCT) As Boolean
            Try
                Dim lpCD As IntPtr = Marshal.AllocHGlobal(Len(CD))
                Marshal.StructureToPtr(CD, lpCD, False)
                SendMessage(_hWnd, WM_COPYDATA, _hWnd, lpCD)
                Marshal.FreeHGlobal(lpCD)

                Return True
            Catch
                Return False
            End Try
        End Function

        Private Shared Function SendMessageToWindow(ByVal strCmd As String) As Boolean
            If _hWnd = -1 Then Return False
            If Len(strCmd) = 0 Then
                Try
                    Dim CD As COPYDATASTRUCT
                    With CD
                        .dwData = 0
                        .cbData = 0
                        .lpData = 0
                    End With
                    Return SendCDSToWindow(cd)
                Catch
                    Return False
                End Try
            Else
                Try
                    Dim B() As Byte = Encoding.Default.GetBytes(strCmd)
                    Dim lpB As IntPtr = Marshal.AllocHGlobal(B.Length)
                    Marshal.Copy(B, 0, lpB, B.Length)

                    Dim CD As COPYDATASTRUCT
                    With CD
                        .dwData = 0
                        .cbData = B.Length
                        .lpData = lpB.ToInt32
                    End With
                    Erase B

                    Try
                        If SendCDSToWindow(cd) Then
                            Return True
                        Else
                            Return False
                        End If
                    Catch
                        Return False
                    Finally
                        Marshal.FreeHGlobal(lpB)
                    End Try

                Catch
                    Return False
                End Try
            End If
        End Function
        Private Shared Function SendMessageToWindow(ByVal oCmd As Object) As Boolean
            Try
                Dim strCmd As String = SerialHelper.SerializeToBase64String(oCmd)
                Return SendMessageToWindow(strCmd)
            Catch
                Return False
            End Try
        End Function
#End Region

        Private Shared _hWnd As Integer = -1
        Private Shared _mcThisAppID As String
        Private Shared oMutex As Threading.Mutex
        Private Shared _MutexOwned As Boolean = False
        Private Shared WithEvents MainForm As ISingleInstanceForm

        Shared Sub New()
            _mcThisAppID = Reflection.Assembly.GetExecutingAssembly().FullName
            oMutex = New Threading.Mutex(True, _mcThisAppID & "_APPLICATION_MUTEX", _MutexOwned)
            If Not _MutexOwned Then
                If Not FindWindow() Then
                    _MutexOwned = True
                End If
            End If
            AddHandler AppDomain.CurrentDomain.ProcessExit, AddressOf OnExit
        End Sub
        Private Shared Sub OnExit(ByVal sender As Object, ByVal e As EventArgs)
            Try
                If Not oMutex Is Nothing Then
                    oMutex.ReleaseMutex()
                    CType(oMutex, IDisposable).Dispose()
                    oMutex = Nothing
                End If
            Catch
                'Do Nothing
            End Try
        End Sub

        Public Shared ReadOnly Property IsFirstInstance() As Boolean
            Get
                Return _MutexOwned
            End Get
        End Property

        Public Shared Function NotifyPreviousWindow() As Boolean
            Return SendMessageToWindow(vbNullString)
        End Function
        Public Shared Function NotifyPreviousWindow(ByVal strText As String) As Boolean
            Return SendMessageToWindow(strText)
        End Function
        Public Shared Function NotifyPreviousWindow(ByVal oCmd As Object) As Boolean
            Return SendMessageToWindow(oCmd)
        End Function

        Public Shared Sub SetMainForm(ByVal frm As ISingleInstanceForm)
            MainForm = frm
            Try
                Dim hWnd As Integer = frm.Handle.ToInt32
                SetProp(hWnd, _mcThisAppID & "_APPLICATION", 1)
            Catch
                MainForm = Nothing
            End Try
        End Sub

        Private Shared Sub MainForm_WndProc(ByVal m As System.Windows.Forms.Message, ByRef Cancel As Boolean) Handles MainForm.WndProc
            Select Case m.Msg
                Case WM_COPYDATA
                    Dim B() As Byte
                    Try
                        Dim CD As COPYDATASTRUCT = m.GetLParam(GetType(COPYDATASTRUCT))
                        ReDim B(CD.cbData)
                        Dim lpData As IntPtr = New IntPtr(CD.lpData)
                        Marshal.Copy(lpData, B, 0, CD.cbData)
                        Dim strData As String = Encoding.Default.GetString(B)
                        MainForm.HandleCommand(strData)
                        Cancel = True

                    Catch
                        Cancel = False
                    Finally
                        Erase B
                    End Try

                Case Else
                    Cancel = False
            End Select
        End Sub
    End Class

    Private MainForm As frmMain

    <STAThread()> Public Function Main(ByVal CmdArgs() As String) As Integer
        If SingleInstance.IsFirstInstance Then
            'No previous instance
            MainForm = New frmMain()
            SingleInstance.SetMainForm(MainForm)
            MainForm.HandleCommand(CmdArgs)
            Application.Run(MainForm)
        Else
            SingleInstance.NotifyPreviousWindow(CmdArgs)
        End If
    End Function
End Module

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

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