Send message to a Windows process (not its main window)

后端 未结 3 1067
执念已碎
执念已碎 2020-12-02 18:07

I have an application that on a subsequent start detects if there\'s a process with the same name already running and, if so, activates the running app\'s window and then ex

相关标签:
3条回答
  • 2020-12-02 18:19

    Named Pipes can be used for this. It might be the more acceptable method with .net.You can define a service in the main application that accepts a message from the calling application. Here's a sample of the service, in vb. It calls the main app and passes a string to it, in this case, a filename. It also returns a string, but any parameters can be used here.

    Public Class PicLoadService : Implements IMainAppPicLoad
    
    Public Function LoadPic(ByVal fName As String) As String Implements IMainAppPicLoad.LoadPic
    ' do some stuff here.
    LoadPic = "return string"
    End Function
    
    End Class
    

    The setup in the calling application is a little more involved. The calling and main application can be the same application.

    Imports System.Diagnostics
    Imports System.ServiceModel
    Imports System.IO
    Imports vb = Microsoft.VisualBasic
    
    Module MainAppLoader
    
    Sub Main()
    
    Dim epAddress As EndpointAddress
    Dim Client As picClient
    Dim s As String
    Dim loadFile As String
    Dim procs() As Process
    Dim processName As String = "MainApp"
    
    loadFile = "" ' filename to load
    
    procs = Process.GetProcessesByName(processName)
    
    If UBound(procs) >= 0 Then
      epAddress = New EndpointAddress("net.pipe://localhost/MainAppPicLoad")
      Client = New picClient(New NetNamedPipeBinding, epAddress)
      s = Client.LoadPic(loadFile)
    End If
    
    End Sub
    
    <System.Diagnostics.DebuggerStepThroughAttribute(), _
     System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")> _
    Partial Public Class picClient
        Inherits System.ServiceModel.ClientBase(Of IMainAppPicLoad)
        Implements IMainAppPicLoad
    
        Public Sub New(ByVal binding As System.ServiceModel.Channels.Binding, ByVal remoteAddress As System.ServiceModel.EndpointAddress)
            MyBase.New(binding, remoteAddress)
        End Sub
    
        Public Function LoadPic(ByVal fName As String) As String Implements IMainAppPicLoad.LoadPic
            Return MyBase.Channel.LoadPic(fName)
        End Function
    
    End Class
    
    ' from here down was auto generated by svcutil.
    ' svcutil.exe /language:vb /out:generatedProxy.vb /config:app.config http://localhost:8000/MainAppPicLoad
    ' Some has been simplified after auto code generation.
    <System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0"), _
     System.ServiceModel.ServiceContractAttribute(ConfigurationName:="IMainAppPicLoad")> _
    Public Interface IMainAppPicLoad
      <System.ServiceModel.OperationContractAttribute(Action:="http://tempuri.org/IMainAppPicLoad/LoadPic", ReplyAction:="http://tempuri.org/IMainAppPicLoad/LoadPicResponse")> _
      Function LoadPic(ByVal fName As String) As String
    End Interface
    
    <System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")> _
    Public Interface IMainAppPicLoadChannel
      Inherits IMainAppPicLoad, System.ServiceModel.IClientChannel
    End Interface
    
    <System.Diagnostics.DebuggerStepThroughAttribute(), _
    System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")> _
    Partial Public Class IMainAppPicLoadClient
      Inherits System.ServiceModel.ClientBase(Of IMainAppPicLoad)
      Implements IMainAppPicLoad
    
      Public Sub New(ByVal binding As System.ServiceModel.Channels.Binding, ByVal remoteAddress As System.ServiceModel.EndpointAddress)
        MyBase.New(binding, remoteAddress)
      End Sub
    
      Public Function LoadPic(ByVal fName As String) As String Implements IMainAppPicLoad.LoadPic
        Return MyBase.Channel.LoadPic(fName)
      End Function
    End Class
    
    End Module
    
    <ServiceContract()> Public Interface IMainAppPicLoad
    <OperationContract()> Function LoadPic(ByVal fName As String) As String
    End Interface
    
    0 讨论(0)
  • 2020-12-02 18:26

    Here's how I've done this:

    using System;
    using System.Runtime.InteropServices;
    using System.Threading;
    using System.Windows.Forms;
    public partial class MainForm : Form
    {
        #region Dll Imports
        private const int HWND_BROADCAST = 0xFFFF;
    
        private static readonly int WM_MY_MSG = RegisterWindowMessage( "WM_MY_MSG" );
    
        [DllImport( "user32" )]
        private static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
    
        [DllImport( "user32" )]
        private static extern int RegisterWindowMessage(string message);
        #endregion Dll Imports
        static Mutex _single = new Mutex(true, "{4EABFF23-A35E-F0AB-3189-C81203BCAFF1}");
        [STAThread]
        static void Main()
        {
            // See if an instance is already running...
            if (_single.WaitOne(TimeSpan.Zero, true)) {
                // No...start up normally.
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                try {
                    Application.Run(new MainForm());
                } catch (Exception ex) {
                    // handle exception accordingly
                } finally {
                    _single.ReleaseMutex();
                }
            } else {
                // Yes...Bring existing instance to top and activate it.
                PostMessage(
                    (IntPtr) HWND_BROADCAST,
                    WM_MY_MSG,
                    new IntPtr(0xCDCD),
                    new IntPtr(0xEFEF));
            }
        }
    
        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_MY_MSG) {
                if ((m.WParam.ToInt32() == 0xCDCD) && (m.LParam.ToInt32() == 0xEFEF)) {
                    if (WindowState == FormWindowState.Minimized) {
                        WindowState = FormWindowState.Normal;
                    }
                    // Bring window to front.
                    bool temp = TopMost;
                    TopMost = true;
                    TopMost = temp;
                    // Set focus to the window.
                    Activate();
                }
            } else {
                base.WndProc(ref m);
            }
        }
    }
    

    I hope I've transcribed this correctly. I had to leave out a lot of other stuff, but I think I got what is necessary. What I have works for me without fail. If you have a problem, let me know, and I'll see what I've missed.

    0 讨论(0)
  • 2020-12-02 18:27

    For other people wanting to achieve this, I'm posting below my implementation, using Matt Davis' solution.

    In Program.cs

    static class Program
    {
        #region Dll Imports
        public const int HWND_BROADCAST = 0xFFFF;
    
        [DllImport("user32.dll")]
        public static extern bool SetForegroundWindow(IntPtr hWnd);
    
        [DllImport("user32")]
        public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
    
        [DllImport("user32")]
        public static extern int RegisterWindowMessage(string message);
        #endregion Dll Imports
    
        public static readonly int WM_ACTIVATEAPP = RegisterWindowMessage("WM_ACTIVATEAPP");
    
        [STAThread]
        static void Main()
        {
            bool createdNew = true;
            //by creating a mutex, the next application instance will detect it
            //and the code will flow through the "else" branch 
            using (Mutex mutex = new Mutex(true, "MyMutexName", out createdNew))//make sure it's an unique identifier (a GUID would be better)
            {
                if (createdNew)
                {
                    Application.EnableVisualStyles();
                    Application.SetCompatibleTextRenderingDefault(false);
                    Application.Run(new MainForm());
                }
                else
                {
                    //we tried to create a mutex, but there's already one (createdNew = false - another app created it before)
                    //so there's another instance of this application running
                    Process currentProcess = Process.GetCurrentProcess();
    
                    //get the process that has the same name as the current one but a different ID
                    foreach (Process process in Process.GetProcessesByName(currentProcess.ProcessName))
                    {
                        if (process.Id != currentProcess.Id)
                        {
                            IntPtr handle = process.MainWindowHandle;
    
                            //if the handle is non-zero then the main window is visible (but maybe somewhere in the background, that's the reason the user started a new instance)
                            //so just bring the window to front
                            if (handle != IntPtr.Zero)
                                SetForegroundWindow(handle);
                            else
                                //tough luck, can't activate the window, it's not visible and we can't get its handle
                                //so instead notify the process that it has to show it's window
                                PostMessage((IntPtr)HWND_BROADCAST, WM_ACTIVATEAPP, IntPtr.Zero, IntPtr.Zero);//this message will be sent to MainForm
    
                            break;
                        }
                    }
                }
            }
        }
    }
    

    In MainForm.cs

    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);
                //someone (another process) said that we should show the window (WM_ACTIVATEAPP)
        if (m.Msg == Program.WM_ACTIVATEAPP)
            this.Show();
    }
    
    0 讨论(0)
提交回复
热议问题