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.