C# 向 Windows 进程(不是其主窗口)发送消息

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1777668/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 20:43:48  来源:igfitidea点击:

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

c#.netwindowsinterop

提问by chitza

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 exits.

我有一个应用程序,该应用程序在随后启动时检测是否有同名进程已在运行,如果是,则激活正在运行的应用程序的窗口,然后退出。

The problem is that the main window could be hidden (only a notification area icon visible), thus leaving me with no window handle.

问题是主窗口可能被隐藏(只有一个通知区域图标可见),从而使我没有窗口句柄。

At startup, previous instance's MainWindowHandleproperty is 0, so I can't send ShowWindowor PostMessage.

在启动时,前一个实例的MainWindowHandle属性是 0,所以我不能发送ShowWindowPostMessage

Is there any way I can send a message that can be intercepted by the running app, thus allowing it to display its main window?

有什么方法可以发送可以被正在运行的应用程序拦截的消息,从而允许它显示其主窗口?

The application is written in C#, the code I'm using to achieve this below.

该应用程序是用 C# 编写的,下面是我用来实现此目的的代码。

[STAThread]
static void Main()
{
    bool createdNew = true;
    using (Mutex mutex = new Mutex(true, "MyMutexName", out createdNew))
    {
        if (createdNew)
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
        else
        {
            Process current = Process.GetCurrentProcess();
            foreach (Process process in Process.GetProcessesByName(current.ProcessName))
            {
                if (process.Id != current.Id)
                {
                    Interop.WINDOWINFO pwi = new Interop.WINDOWINFO();
                    IntPtr handle = process.MainWindowHandle;
                    var isVisible = Interop.GetWindowInfo(handle, ref pwi);
                    if (!isVisible)
                    {
                        MessageBox.Show(Constants.APP_NAME + " is already running, check the notification area (near the clock).", 
                                        Constants.APP_NAME, MessageBoxButtons.OK, MessageBoxIcon.Information);//temporary message, until I find the solution
                        //Interop.ShowWindow(handle, Interop.WindowShowStyle.ShowNormal);
                        //Interop.PostMessage(handle, Interop.WM_CUSTOM_ACTIVATEAPP, IntPtr.Zero, IntPtr.Zero);
                    }
                    else
                        Interop.SetForegroundWindow(handle);//this works when the window is visible
                        break;
                    }
                }
            }
        }
    }
}

采纳答案by Matt Davis

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.

我希望我已经正确地转录了这个。我不得不遗漏很多其他东西,但我认为我得到了必要的东西。我所拥有的对我有用。如果你有问题,让我知道,我会看看我错过了什么。

回答by xpda

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.

命名管道可用于此目的。.net 可能是更可接受的方法。您可以在主应用程序中定义一个服务,该服务接受来自调用应用程序的消息。这是服务的示例,在 vb 中。它调用主应用程序并向其传递一个字符串,在本例中为文件名。它还返回一个字符串,但这里可以使用任何参数。

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

回答by chitza

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

对于其他想要实现此目标的人,我将使用 Matt Davis 的解决方案在我的实现下方发布。

In Program.cs

在 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

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