【问题标题】:How can I send a message to a specific process by process id rather than by window handle?如何通过进程 ID 而不是窗口句柄向特定进程发送消息?
【发布时间】:2013-04-15 22:27:53
【问题描述】:

要解决 GenerateConsoleCtrlEvent 的限制,我必须创建一个中间“中间人”进程来处理启动一些控制台应用程序。该进程的主要目的是在其自身上调用 GenerateConsoleCtrlEvent,导致自身和所有子进程干净地关闭以响应 ctrl+break 信号(而不是使用 Process.Kill)。这种需求源于 GenerateConsoelCtrlEvent 基本上没有效果,除非进程组 id 为零,这意味着它只对调用进程组本身有效。见:https://stackoverflow.com/a/2431295/88409

无论如何...我已经创建了这个中间进程,它启动了一个线程,该线程在处理特定用户定义消息的表单上调用 Application.Run。

我的问题是……如何向这个进程发送消息来控制它?

我有 Process 对象及其进程 ID,但仅此而已。 Process.MainWindowHandle 为零。

所以我需要一种方法来向特定进程发送消息或将消息广播到特定进程中的所有窗口。

FindWindow 不是一个选项,因为它试图在任何进程上通过名称和类来识别窗口,这是不可靠的。我想明确地向特定进程发送消息。

【问题讨论】:

    标签: c# .net sendmessage inter-process-communicat


    【解决方案1】:

    在以下 3 种情况下,可以将消息认为发送或发布到进程:

    1. 我可以将 [window] 消息“发送”或“发布”到特定进程,方法是将其发送到该进程中的第一个枚举窗口
    2. 我可以将 [thread] 消息“发布”到特定进程,方法是将其发布到进程中的第一个枚举线程。
    3. 我可以将 [thread] 消息“发布”到特定进程,方法是将其发布到拥有该进程的第一个枚举窗口的线程。

    方法 1 可能过于具体,因为它针对的是一个具体但任意的窗口。 方法 2 可能不够具体,因为第一个枚举线程是任意的并且可能没有消息循环。 方法 3 是一种混合方法,首先识别一个窗口,然后将线程消息发布到该窗口的线程,因此它不是针对特定窗口(即它是“线程消息”),而是针对一个线程由于线程至少拥有一个窗口,因此可能有消息循环。

    以下是支持所有三种方法以及“send”和“post”两种方法的实现。方法 1 包含在下面的方法 SendMessage 和 PostMessage 中。方法 2 和 3 包含在下面的 PostThreadMessage 方法中,具体取决于您是否设置了可选参数 ensureTargetThreadHasWindow(true = 方法 3,false = 方法 2)。

    public static class ProcessExtensions
    {
        private static class Win32
        {
            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
    
            [return: MarshalAs(UnmanagedType.Bool)]
            [DllImport("user32.dll", SetLastError = true)]
            public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
    
            [return: MarshalAs(UnmanagedType.Bool)]
            [DllImport("user32.dll", SetLastError = true)]
            public static extern bool PostThreadMessage(uint threadId, uint msg, IntPtr wParam, IntPtr lParam);
    
            public delegate bool EnumThreadDelegate (IntPtr hWnd, IntPtr lParam);
            [DllImport("user32.dll")]
            public static extern bool EnumThreadWindows(uint dwThreadId, EnumThreadDelegate lpfn, IntPtr lParam);
    
            [DllImport("user32.dll", SetLastError=true)]
            public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
        }
    
        //Sends a message to the first enumerated window in the first enumerated thread with at least one window, and returns the handle of that window through the hwnd output parameter if such a window was enumerated.  If a window was enumerated, the return value is the return value of the SendMessage call, otherwise the return value is zero.
        public static IntPtr SendMessage( this Process p, out IntPtr hwnd, UInt32 msg, IntPtr wParam, IntPtr lParam )
        {
            hwnd = p.WindowHandles().FirstOrDefault();
            if (hwnd != IntPtr.Zero)
                return Win32.SendMessage( hwnd, msg, wParam, lParam );
            else
                return IntPtr.Zero;
        }
    
        //Posts a message to the first enumerated window in the first enumerated thread with at least one window, and returns the handle of that window through the hwnd output parameter if such a window was enumerated.  If a window was enumerated, the return value is the return value of the PostMessage call, otherwise the return value is false.
        public static bool PostMessage( this Process p, out IntPtr hwnd, UInt32 msg, IntPtr wParam, IntPtr lParam )
        {
            hwnd = p.WindowHandles().FirstOrDefault();
            if (hwnd != IntPtr.Zero)
                return Win32.PostMessage( hwnd, msg, wParam, lParam );
            else
                return false;
        }
    
        //Posts a thread message to the first enumerated thread (when ensureTargetThreadHasWindow is false), or posts a thread message to the first enumerated thread with a window, unless no windows are found in which case the call fails.  If an appropriate thread was found, the return value is the return value of PostThreadMessage call, otherwise the return value is false.
        public static bool PostThreadMessage( this Process p, UInt32 msg, IntPtr wParam, IntPtr lParam, bool ensureTargetThreadHasWindow = true )
        {
            uint targetThreadId = 0;
            if (ensureTargetThreadHasWindow)
            {
                IntPtr hwnd = p.WindowHandles().FirstOrDefault();
                uint processId = 0;
                if (hwnd != IntPtr.Zero)
                    targetThreadId = Win32.GetWindowThreadProcessId( hwnd, out processId );
            }
            else
            {
                targetThreadId = (uint)p.Threads[0].Id;
            }
            if (targetThreadId != 0)
                return Win32.PostThreadMessage( targetThreadId, msg, wParam, lParam );
            else
                return false;
        }
    
        public static IEnumerable<IntPtr> WindowHandles( this Process process )
        {
            var handles = new List<IntPtr>();
            foreach (ProcessThread thread in process.Threads)
                Win32.EnumThreadWindows( (uint)thread.Id, (hWnd, lParam) => { handles.Add(hWnd); return true; }, IntPtr.Zero );
            return handles;
        }
    }
    

    在 Process 对象上使用此扩展方法:

    Process process = Process.Start( exePath, args );
    IntPtr hwndMessageWasSentTo = IntPtr.Zero; //this will receive a non-zero value if SendMessage was called successfully
    uint msg = 0xC000; //The message you want to send
    IntPtr wParam = IntPtr.Zero; //The wParam value to pass to SendMessage
    IntPtr lParam = IntPtr.Zero; //The lParam value to pass to SendMessage
    
    IntPtr returnValue = process.SendMessage( out hwndMessageWasSentTo, msg, wParam, lParam );
    if (hwndMessageWasSentTo != IntPtr.Zero)
       Console.WriteLine( "Message successfully sent to hwnd: " + hwndMessageWasSentTo.ToString() + " and return value was: " + returnValue.ToString() );
    else
       Console.WriteLine( "No windows found in process.  SendMessage was not called." );
    

    【讨论】:

    • 我明白了,您回答了自己的问题。 :-) 问题的症结实际上是将中间进程的主线程 ID 传递给发送者。一旦你做到了,PostThreadMessage() 就很简单了。
    【解决方案2】:

    您不能向进程发送或发布消息,但可以向线程发布消息。当然,该线程必须启动一个消息循环来处理它。

    【讨论】:

    • 所以消息的主要目标是线程,这样的线程消息可以选择针对特定窗口。这体现在 Win32 方法 PostThreadMessage 和 PostMessage 上,它们的签名相似,只是其中一个的第一个参数是线程 id,而另一个是窗口句柄。由于主要目标是线程消息队列,因此 PostMessage 函数必须首先识别拥有窗口句柄的线程,并通过一些同时接受线程 ID 和窗口句柄的内部函数将消息发布到该线程的消息队列。
    • 由于“SendMessage”绕过了消息队列,将消息直接路由到了窗口过程,说你可以“发布”消息到线程,但不能“发送”可能更完整给线程的消息。这就是为什么没有“SendThreadMessage”这样的东西。顺便说一句,SendMessage 也会产生绕过 TranslateMessage 调用的副作用,因为它是在消息循环中调用的。如果你通过调用 SendMessage 绕过循环,你也将绕过 TranslateMessage 调用。
    • 我将此标记为答案,但我也将留下我自己的答案 (stackoverflow.com/a/16157666/88409),其中包含可用于定位消息的代码概念上 在进程中,不指定线程或窗口,通过枚举自动选择目标线程或窗口。这些方法的目的是更容易生成进程并向其发送控制消息,而无需识别特定线程或窗口句柄的额外步骤,这可以自动完成。
    【解决方案3】:

    使用EnumWindows 扫描顶级窗口,并使用GetProcessHandleFromHWnd 确定窗口是否在您的目标进程中。

    然后您可以向该 hwnd 发送消息,并且它们应该被您的目标进程接收(如果您的目标进程有消息循环)。

    【讨论】:

    • 我更喜欢使用 Process.Threads(返回 ProcessThread[]),然后在每个线程上调用 EnumThreadWindows 来获取窗口。如果改为调用 EnumWindows,它将从许多进程返回窗口,因此我必须通过在每个句柄上调用 GetProcessHandleFromHWnd 来过滤窗口。按照 Medinoc 的建议使用 GetWindowThreadProcessId 还需要我通过检查目标线程的 Process.Threads 集合是否包含具有窗口所有者线程 ID 的线程来过滤窗口。
    【解决方案4】:

    我刚刚回答了一个非常相似的问题(带有示例代码)here。 快速回答是PostThreadMessage()

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-19
      • 1970-01-01
      • 1970-01-01
      • 2010-12-25
      相关资源
      最近更新 更多