Send a message to a Single Instance Application

M

Mesan

Hello everyone,

Thanks to many useful posts in this newsgroup and others, I've been
able to come very close to something I've been wanting to do for a
very long time.

I've figured out how to create a new custom protocol handler in
Windows to handle locations like "myProtocol:", which lets me have a
shortcut pointing to "myProtocol:myPrimaryKey" and have my application
automatically open and display the given account. That's great. I
want to be able to have similar links within the program itself, and
rather than opening another instance of the program (as it does now)
the account would just be loaded within the current instance.

I've found out how to use a Mutex to make sure that there's only one
version of the program running, and I've even found out how to bring
the other instance to the Foreground - that's marvelous.

What I haven't figured out how to do is how do send a message to the
already running instance of the program telling it which account to
load.

Any ideas?

(ps - using a custom protocol to start your application is a pretty
neat trick for anyone who's interested).
 
T

the_grove_man

Try creating a class called AppMessenger

using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.IO;

namespace Messenger
{
/// <summary>
/// The problem with using command line parameters for any
applications is that a new
/// instance of the application is started. For some apps it would
be preferable if
/// there was only one instance of the app and the command line
could be forwarded
/// back to the previous instance of the app.
///
/// This can be done by looking for a previous instance of the
app, sending a
/// WM_COPYDATA message to that previous instance and exiting (if
it is found). If it
/// is not found then the application can start normally. This is
the technique used
/// by MS Word and other applications.
/// </summary>
public class AppMessenger
{
//Pick any number at random. This number will be used to
uniquely identify the message
private static int _messageID = -1163005939;
//this message will used to send the commandline to the
previous instance of the app
public const int WM_COPYDATA = 0x4A;
//API call to send WM_COPYDATA to the previous instance of the
app
[DllImport("user32", EntryPoint = "SendMessageA")]
private static extern int SendMessage(IntPtr hWnd, int wMsg,
int wParam, COPYDATASTRUCT lParam);

//all members of this class are static so do not allow an
instance to be created
private AppMessenger()
{
}

/// <summary>
/// Checks for a previous instance of this app and forwards
the
/// command line to this instance if found.
/// </summary>
/// <returns>
/// True if a previous instance was found.
/// </returns>
public static bool CheckPrevInstance()
{
IntPtr hWnd =
GetHWndOfPrevInstance(Process.GetCurrentProcess().ProcessName);
if (hWnd != IntPtr.Zero)
{
SendCommandLine(hWnd, Environment.CommandLine);
return true;
}
return false;
}

public static bool SendMessageToApp(string FileName, string
Message)
{
IntPtr hWnd =
GetHWndOfPrevInstance(GetFileNameFromFullName(FileName));
if (hWnd != IntPtr.Zero)
{
SendCommandLine(hWnd, Message);
return true;
}
//could not find process so start it
Process P = new Process();
P.StartInfo.FileName = FileName;
P.Start();
int t = Environment.TickCount + 30000;//30 second timeout
do
{
try
{
hWnd = P.MainWindowHandle;
}
catch//ignore errors
{
}
} while (hWnd == IntPtr.Zero && t <
Environment.TickCount);
if (hWnd != IntPtr.Zero)
{
SendCommandLine(hWnd, Message);
return true;
}
return false;
}

private static string GetFileNameFromFullName(string FullName)
{
int pos = FullName.LastIndexOf("\\");
if (pos >= 0)
{
return FullName.Substring(pos + 1);
}
else
{
return FullName;
}
}

/// <summary>
/// Searches for a previous instance of this app.
/// </summary>
/// <returns>
/// hWnd of the main window of the previous instance
/// or IntPtr.Zero if not found.
/// </returns>
private static IntPtr GetHWndOfPrevInstance(string
ProcessName)
{
//get the current process
Process CurrentProcess = Process.GetCurrentProcess();
//get a collection of the currently active processes with
the same name
Process[] Ps = Process.GetProcessesByName(ProcessName);
//if only one exists then there is no previous instance
if (Ps.Length > 1)
{
foreach (Process P in Ps)
{
if (P.Id != CurrentProcess.Id)//ignore this
process
{
//weed out apps that have the same exe name
but are started from a different filename.
if (P.ProcessName == ProcessName)
{
IntPtr hWnd = IntPtr.Zero;
try
{
//if process does not have a
MainWindowHandle then an exception will be thrown
//so catch and ignore the error.
hWnd = P.MainWindowHandle;
}
catch { }
//return if hWnd found.
if (hWnd.ToInt32() != 0) return hWnd;
}
}
}
}
return IntPtr.Zero;
}

/// <summary>
/// Sends command line to a previous instance of this app
/// </summary>
/// <param name="hWnd">Main Window handle of the previous
instance of this app. Found using the function
GetHWndOfPrevInstance()</param>
/// <param name="CommandLine">CommandLine or message to send</
param>
private static void SendCommandLine(IntPtr hWnd, string
CommandLine)
{
SendMessage(hWnd, WM_COPYDATA, _messageID, new
COPYDATASTRUCT(Environment.CommandLine));
}

/// <summary>
/// Processes WM_COPYDATA message sent to the main window of
this app
/// </summary>
/// <param name="m">Message sent to the main window of this
app</param>
/// <returns>Message received or null if messageID is not
valid</returns>
public static string
ProcessWM_COPYDATA(System.Windows.Forms.Message m)
{
if (m.WParam.ToInt32() == _messageID)
{
COPYDATASTRUCT st =
(COPYDATASTRUCT)Marshal.PtrToStructure(m.LParam,
typeof(COPYDATASTRUCT));
return st.lpData;
}
return null;
}

/// <summary>
/// Structure required to be sent with the WM_COPYDATA message
/// This structure is used to contain the CommandLine
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public class COPYDATASTRUCT
{
public int dwData = 0;//32 bit int to passed. Not used.
public int cbData = 0;//length of string. Will be one
greater because of null termination.
public string lpData;//string to be passed.

public COPYDATASTRUCT()
{
}

public COPYDATASTRUCT(string Data)
{
lpData = Data + "\0"; //add null termination
cbData = lpData.Length; //length includes null chr so
will be one greater
}
}
}
}


Then in your MainForm's constructor add this before
InitializeComponent:

if (Messenger.AppMessenger.CheckPrevInstance())
{
Application.Exit();
this.Close();
}
else
{
InitializeComponent();

//Command-Line
string[] Command = Environment.GetCommandLineArgs();
if (Command.Length > 1)
processCommandLine(Command);
}

protected override void WndProc(ref Message m)
{
if (m.Msg == Messenger.AppMessenger.WM_COPYDATA)
{
string command =
Messenger.AppMessenger.ProcessWM_COPYDATA(m);
if (command != null)
{

processCommandLine(command);
return;
}
}
base.WndProc(ref m);
}

private void processCommandLine(string CommandLine)
{


//do whatever you want to do..................

}


And be sure to add a try/catch in Program.cs like so:

static void Main()
{
try
{
Application.Run(new MainForm());
}catch (Exception ex) { }

}
 
M

Mesan

Try creating a class called AppMessenger

using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.IO;

namespace Messenger
{
/// <summary>
/// The problem with using command line parameters for any
applications is that a new
/// instance of the application is started. For some apps it would
be preferable if
/// there was only one instance of the app and the command line
could be forwarded
/// back to the previous instance of the app.
///
/// This can be done by looking for a previous instance of the
app, sending a
/// WM_COPYDATA message to that previous instance and exiting (if
it is found). If it
/// is not found then the application can start normally. This is
the technique used
/// by MS Word and other applications.
/// </summary>
public class AppMessenger
{
//Pick any number at random. This number will be used to
uniquely identify the message
private static int _messageID = -1163005939;
//this message will used to send the commandline to the
previous instance of the app
public const int WM_COPYDATA = 0x4A;
//API call to send WM_COPYDATA to the previous instance of the
app
[DllImport("user32", EntryPoint = "SendMessageA")]
private static extern int SendMessage(IntPtr hWnd, int wMsg,
int wParam, COPYDATASTRUCT lParam);

//all members of this class are static so do not allow an
instance to be created
private AppMessenger()
{
}

/// <summary>
/// Checks for a previous instance of this app and forwards
the
/// command line to this instance if found.
/// </summary>
/// <returns>
/// True if a previous instance was found.
/// </returns>
public static bool CheckPrevInstance()
{
IntPtr hWnd =
GetHWndOfPrevInstance(Process.GetCurrentProcess().ProcessName);
if (hWnd != IntPtr.Zero)
{
SendCommandLine(hWnd, Environment.CommandLine);
return true;
}
return false;
}

public static bool SendMessageToApp(string FileName, string
Message)
{
IntPtr hWnd =
GetHWndOfPrevInstance(GetFileNameFromFullName(FileName));
if (hWnd != IntPtr.Zero)
{
SendCommandLine(hWnd, Message);
return true;
}
//could not find process so start it
Process P = new Process();
P.StartInfo.FileName = FileName;
P.Start();
int t = Environment.TickCount + 30000;//30 second timeout
do
{
try
{
hWnd = P.MainWindowHandle;
}
catch//ignore errors
{
}
} while (hWnd == IntPtr.Zero && t <
Environment.TickCount);
if (hWnd != IntPtr.Zero)
{
SendCommandLine(hWnd, Message);
return true;
}
return false;
}

private static string GetFileNameFromFullName(string FullName)
{
int pos = FullName.LastIndexOf("\\");
if (pos >= 0)
{
return FullName.Substring(pos + 1);
}
else
{
return FullName;
}
}

/// <summary>
/// Searches for a previous instance of this app.
/// </summary>
/// <returns>
/// hWnd of the main window of the previous instance
/// or IntPtr.Zero if not found.
/// </returns>
private static IntPtr GetHWndOfPrevInstance(string
ProcessName)
{
//get the current process
Process CurrentProcess = Process.GetCurrentProcess();
//get a collection of the currently active processes with
the same name
Process[] Ps = Process.GetProcessesByName(ProcessName);
//if only one exists then there is no previous instance
if (Ps.Length > 1)
{
foreach (Process P in Ps)
{
if (P.Id != CurrentProcess.Id)//ignore this
process
{
//weed out apps that have the same exe name
but are started from a different filename.
if (P.ProcessName == ProcessName)
{
IntPtr hWnd = IntPtr.Zero;
try
{
//if process does not have a
MainWindowHandle then an exception will be thrown
//so catch and ignore the error.
hWnd = P.MainWindowHandle;
}
catch { }
//return if hWnd found.
if (hWnd.ToInt32() != 0) return hWnd;
}
}
}
}
return IntPtr.Zero;
}

/// <summary>
/// Sends command line to a previous instance of this app
/// </summary>
/// <param name="hWnd">Main Window handle of the previous
instance of this app. Found using the function
GetHWndOfPrevInstance()</param>
/// <param name="CommandLine">CommandLine or message to send</
param>
private static void SendCommandLine(IntPtr hWnd, string
CommandLine)
{
SendMessage(hWnd, WM_COPYDATA, _messageID, new
COPYDATASTRUCT(Environment.CommandLine));
}

/// <summary>
/// Processes WM_COPYDATA message sent to the main window of
this app
/// </summary>
/// <param name="m">Message sent to the main window of this
app</param>
/// <returns>Message received or null if messageID is not
valid</returns>
public static string
ProcessWM_COPYDATA(System.Windows.Forms.Message m)
{
if (m.WParam.ToInt32() == _messageID)
{
COPYDATASTRUCT st =
(COPYDATASTRUCT)Marshal.PtrToStructure(m.LParam,
typeof(COPYDATASTRUCT));
return st.lpData;
}
return null;
}

/// <summary>
/// Structure required to be sent with the WM_COPYDATA message
/// This structure is used to contain the CommandLine
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public class COPYDATASTRUCT
{
public int dwData = 0;//32 bit int to passed. Not used.
public int cbData = 0;//length of string. Will be one
greater because of null termination.
public string lpData;//string to be passed.

public COPYDATASTRUCT()
{
}

public COPYDATASTRUCT(string Data)
{
lpData = Data + "\0"; //add null termination
cbData = lpData.Length; //length includes null chr so
will be one greater
}
}
}

}

Then in your MainForm's constructor add this before
InitializeComponent:

if (Messenger.AppMessenger.CheckPrevInstance())
{
Application.Exit();
this.Close();
}
else
{
InitializeComponent();

//Command-Line
string[] Command = Environment.GetCommandLineArgs();
if (Command.Length > 1)
processCommandLine(Command);
}

protected override void WndProc(ref Message m)
{
if (m.Msg == Messenger.AppMessenger.WM_COPYDATA)
{
string command =
Messenger.AppMessenger.ProcessWM_COPYDATA(m);
if (command != null)
{

processCommandLine(command);
return;
}
}
base.WndProc(ref m);
}

private void processCommandLine(string CommandLine)
{

//do whatever you want to do..................

}

And be sure to add a try/catch in Program.cs like so:

static void Main()
{
try
{
Application.Run(new MainForm());
}catch (Exception ex) { }

}

Wow! Let me just say, that's amazing stuff. Thank you! Goodness
sakes, I hope you didn't write that all just for me.

That is an ideal way to handle what needs to happen, however my app is
a little funny in that it needs to have the ability to have multiple
instances, each instance running on a different database. How could I
diferentiate between my different databases? Is there some way
besides process name? Is a mutex what I want to use in that case? I
know what database to use based off of yet another command line
argument, so within the program I could know which mutex to check (or
so it seems to me).

What do you think?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top