views:

358

answers:

4

I've got a WinForms app, where if there is already an instance running & the user tries to spin up another one, I stop it by checking against a Mutex before calling Application.Run(). That part works just fine. What I would like to do is pass a message from the new instance of the app (along with a piece of data in string form) to the existing instance before killing the new process.

I've tried calling PostMessage, and I do receive the message on the running app, but the string I pass along in the lparam is failing (yes, I have checked to make sure I'm passing in a good string to begin with). How can I best do this?

static class Program
{
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern bool PostMessage(int hhwnd, uint msg, IntPtr wparam, IntPtr lParam);

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    static extern uint RegisterWindowMessage(string lpString);

    private const int HWND_BROADCAST = 0xffff;
    static uint _wmJLPC = unchecked((uint)-1);

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        _wmJLPC = RegisterWindowMessage("JumpListProjectClicked");
        if (_wmJLPC == 0)
        {
            throw new Exception(string.Format("Error registering window message: \"{0}\"", Marshal.GetLastWin32Error().ToString()));
        }

        bool onlyInstance = false;
        Mutex mutex = new Mutex(true, "b73fd756-ac15-49c4-8a9a-45e1c2488599_ProjectTracker", out onlyInstance);

        if (!onlyInstance) {
            ProcessArguments();
            return;
        }

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm());

        GC.KeepAlive(mutex);
    }

    internal static void ProcessArguments()
    {
        if (Environment.GetCommandLineArgs().Length > 1)
        {
            IntPtr param = Marshal.StringToHGlobalAuto(Environment.GetCommandLineArgs()[1]);
            PostMessage(HWND_BROADCAST, _wmJLPC, IntPtr.Zero, param);
        }
    }
}

Elsewhere, in my Form...

protected override void WndProc(ref Message m)
{
    try
    {
        if (m.Msg == _wmJLPC)
        {
             // always returns an empty string
             string param = Marshal.PtrToStringAnsi(m.LParam);

             // UI code omitted
        }
    }
    catch (Exception ex)
    {
        HandleException(ex);
    }

    base.WndProc(ref m);
}
A: 

Check your code again. You're using StringToHGlobalAuto to create the string (likely ending up with Unicode). Then, you're calling PtrToStringAnsi, which is not using unicode.

If you can't get this solution working, there are several options. You can read about them by looking for IPC (InterProcess Communication.)

One method I've used, since it was quick and easy, is to create a well-known file with the needed content. You lock use of that file with named events, and tell the "owner" app that the file has changed by setting another named event. The "owner" checkes the event occasionally, or launches a worker thread to watch for it.

Again, IPC has many flavors, if these ideas don't work, keep looking.

John Fisher
A: 

There are much simpler and modern methods for doing cross-process communication these days. in particular, check out WCF.

Although I will admit that there is a teensy bit of a learning curve. Once you figure it out it truly is very easy. You can even do it all programmatically so you don't have to worry about any configuration confusions.

Joel Martinez
A: 

Here is a simple way. I haven't run the code but you get the idea

class Program
{
    static Thread listenThread;
    static void Main(string[] args)
    {
        try
        {
            using (Mutex mutex = new Mutex(true, "my mutex"))
            {
                listenThread = new Thread(Listen);
                listenThread.IsBackground = true;
                listenThread.Start();
            }
        }
        catch (ApplicationException)
        {
            using (Mutex mutex = Mutex.OpenExisting("my mutex"))
            {
                mutex.WaitOne();
                try
                {
                    using (NamedPipeClientStream client = new NamedPipeClientStream("some pipe"))
                    {
                        using (StreamWriter writer = new StreamWriter(client))
                        {
                            writer.WriteLine("SomeMessage");
                        }
                    }
                }
                finally
                {
                    mutex.ReleaseMutex();
                }
            }
        }
    }
    static void Listen()
    {
        using (NamedPipeServerStream server = new NamedPipeServerStream("some pipe"))
        {
            using (StreamReader reader = new StreamReader(server))
            {
                for (; ; )
                {
                    server.WaitForConnection();
                    string message = reader.ReadLine();
                    //Dispatch the message, probably onto the thread your form 
                    //  was contructed on with Form.BeginInvoke

                }
            }
        }
    }
csaam
+1  A: 

Greg,

The unmanaged pointer created by StringToHGlobalAuto is only valid in the process space that created it. The memory it references can't be accessed from the other process.

To pass data from one app to another, use SendMessage() with the WM_COPYDATA message.

Scott

ScottTx