Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsFree MagazinesWhite PapersSubmit Content
Discussion GroupsASP.NETWindows FormsLanguages.NET FrameworkVisual Studio.NET
Articles.NET FrameworkASP.NETToolsWindows Forms
.NET DirectoryOpen Source ProjectsUser GroupsWeb Resources
Related Topics
Visual Basic 6SQL ServerMS AccessOther DB ProductsMS Server ProductsMore Topics ...

.NET Forum / Languages / C# / February 2007

Tip: Looking for answers? Try searching our database.

Send a message to a Single Instance Application

Thread view: 
Enable EMail Alerts  Start New Thread
Thread rating: 
Mesan - 23 Feb 2007 21:42 GMT
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).
the_grove_man@yahoo.com - 23 Feb 2007 23:34 GMT
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) { }

       }
Mesan - 28 Feb 2007 22:05 GMT
On Feb 23, 4:34 pm, the_grove_...@yahoo.com wrote:
> Try creating a class called AppMessenger
>
[quoted text clipped - 268 lines]
>
>         }

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?

Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.