How to send a message to an already running .NET application

May 7th, 2009 by Richard Leave a reply »

Here is a problem I came across over a year ago now and never got around to detailing.  Here is the scenario

Scenario

I want to create a URL handler in Windows and send that message to my already running .NET application.  For example I create a url handler called myapp: and I create a link in HTML to myapp://www.richardhyland.com/, I want to send that URL to my application, let’s call it MyApp.exe and do something with it.

Now I can quite easily create a URL handler in the Windows registry and call MyApp.exe, but that will create a new instance of the application, what I want  to do is detect to see if MyApp.exe is already running.  If it is then send the URL to that process and if it isn’t, load MyApp.exe.

My Solution

After many hours with Google, I discovered this cannot be done using managed code, so I had to delve into the scary arena of unmanaged code in .NET.

I first set up MyApp.exe to recieve the message.

protected override void WndProc(ref System.Windows.Forms.Message m)
{
  if (m.Msg == 0x400)
  {
    // We've recieved a message from handler.exe (we must now decode the IntPtr and deal with it)
    try
    {
      BS.BuildString(m.LParam);
    }
    catch
    {
    }
  }
  base.WndProc(ref m);
}

void BS_StringOK(string Result)
{
  if (Result.StartsWith("myapp://"))
  {
    Result = Result.Substring(8);
  }
  string strUrl = "http://" + Result.ToString();

  this.funcDoSomething(strUrl);
}

What we’ve done here is override the WndProc function in the application, examined it for a string and passed it to a function called funcDoSomething()

Next we can create a simple, small .exe for the URL handler to open.

Inside the main class of the handler.exe we need to declare

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

[DllImport("user32.dll")]
private static extern
bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);

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

private const int SW_HIDE = 0;
private const int SW_SHOWNORMAL = 1;
private const int SW_SHOWMINIMIZED = 2;
private const int SW_SHOWMAXIMIZED = 3;
private const int SW_SHOWNOACTIVATE = 4;
private const int SW_RESTORE = 9;
private const int SW_SHOWDEFAULT = 10;

[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{
   public int dwData;
   public int cbData;
   public int lpData;
}

Now you can get the string as an arguement in your form loader and process store it to a string

Now let’s detect to see if MyApp is running

Process[] myProcesses = Process.GetProcessesByName("MyApp");

if (myProcesses.Length >= 1) {
 int n = 0;        // assume the other process is at index 0
 // get the window handle
 IntPtr hWnd = myProcesses[n].MainWindowHandle;

 // if iconic, we need to restore the window
 if (IsIconic(hWnd))
 {
   ShowWindowAsync(hWnd, SW_RESTORE);
 }

 // bring it to the foreground
 SetForegroundWindow(hWnd);

 // Process the message through my builder DLL, see later
 RH.SendMessage.BuildString BS = new RH.SendMessage.BuildString();
 BS.PostString(hWnd, 0x400, 0, strUrl);

 // Message sent to exit
 Application.Exit();
}
else
{
 // Application not running so, start it
 string exeDir = System.Reflection.Assembly.GetExecutingAssembly().Location.Replace("\\handler.exe", "");
 System.Diagnostics.Process.Start(exeDir + "\\MyApp.exe", strURL);
 Application.Exit();
}

Finally we need to detail the String Parser DLL that is mentioned in the above code. This I found online, and it is in Visual Basic

Imports System.Text

Public Class BuildString

    Private Declare Function PostMessage Lib "user32.dll" _
Alias "PostMessageA" (ByVal HWnd As IntPtr, ByVal WMsg As _
Integer, ByVal WParam As Integer, ByVal LParam As Integer) _
As Integer
    Public Event StringOK(ByVal Result As String)
    Private HWnd As IntPtr
    Private WMsg As Integer = 0
    Private WParam As Integer = 0
    Private LParam As String = ""
    Private tempA(-1) As Byte
    Private enc As Encoding = Encoding.UTF8

    Public Property Encode() As Encoding
        Get
            Return enc
        End Get
        Set(ByVal value As Encoding)
            enc = value
        End Set
    End Property

    Public Sub BuildString(ByVal b As IntPtr)
        If b <> 0 Then
            'build temp array
            Dim tempB(tempA.Length) As Byte
            tempA.CopyTo(tempB, 0)
            tempB(tempA.Length) = b
            ReDim tempA(tempB.Length - 1)
            tempB.CopyTo(tempA, 0)
        Else
            'decode byte array to string
            Dim s As String
            If enc Is Encoding.UTF8 Then
                s = Encoding.UTF8.GetString(tempA)
            ElseIf enc Is Encoding.Unicode Then
                s = Encoding.Unicode.GetString(tempA)
            ElseIf enc Is Encoding.ASCII Then
                s = Encoding.ASCII.GetString(tempA)
            Else
                s = Encoding.Default.GetString(tempA)
            End If
            'send out result string via event
            RaiseEvent StringOK(s)
            ReDim tempA(-1)
        End If
    End Sub

    Public Sub PostString(ByVal HWnd As IntPtr, ByVal WMsg _
As Integer, ByVal WParam As Integer, ByVal LParam As String)
        Me.HWnd = HWnd
        Me.WMsg = WMsg
        Me.WParam = WParam
        Me.LParam = LParam
        'create a new thread to post window message
        Dim t As Threading.Thread
        t = New Threading.Thread(AddressOf SendString)
        t.Start()
    End Sub

    Private Sub SendString()
        'create byte array
        Dim ba() As Byte
        'encode string to byte array
        If enc Is Encoding.UTF8 Then
            ba = Encoding.UTF8.GetBytes(lParam)
        ElseIf enc Is Encoding.Unicode Then
            ba = Encoding.Unicode.GetBytes(lParam)
        ElseIf enc Is Encoding.ASCII Then
            ba = Encoding.ASCII.GetBytes(lParam)
        Else
            ba = Encoding.Default.GetBytes(lParam)
        End If
        Dim i As Integer
        For i = 0 To ba.Length - 1
            'start post message
            PostMessage(hwnd, wMsg, wParam, ba(i))
        Next
        'post a terminator message to destination window
        PostMessage(hwnd, wMsg, wParam, 0)
    End Sub
End Class

And voila, that is now I sent a message from one running .NET application to another without starting a new process of the recieving .NET application.

Advertisement

Leave a Reply

You must be logged in to post a comment.