C# – How to send a message from one instance of a managed app to another

cinteropipcpostmessage

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);
}

Best Answer

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