首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 呼叫 Win32 API SendMessage:P/Invoke 簽名、訊息攔截與 IMessageFilter 過濾實戰

     編輯:關於C語言

前言

雖然 WinForms 採用事件驅動模型,但底層仍是 Windows 訊息迴路。透過 P/Invoke 呼叫 `SendMessage`/`PostMessage`,可實現跨進程通訊、控制項行為修改、模擬輸入等進階功能。本文完整說明訊息定義、發送、接收攔截、與 `IMessageFilter` 全域過濾技術。

1. 訊息常數定義

public static class Win32Messages
{
    // 滑鼠
    public const int WM_LBUTTONDOWN = 0x0201;
    public const int WM_LBUTTONUP   = 0x0202;
    public const int WM_MOUSEMOVE   = 0x0200;
    public const int WM_MOUSEWHEEL  = 0x020A;
    
    // 鍵盤
    public const int WM_KEYDOWN     = 0x0100;
    public const int WM_KEYUP       = 0x0101;
    public const int WM_CHAR        = 0x0102;
    
    // 視窗
    public const int WM_CLOSE       = 0x0010;
    public const int WM_DESTROY     = 0x0002;
    public const int WM_PAINT       = 0x000F;
    
    // 自訂訊息範圍
    public const int WM_USER        = 0x0400; // 0x0400 ~ 0x7FFF 供視窗類別私用
    public const int WM_APP         = 0x8000; // 0x8000 ~ 0xBFFF 應用程式全域
}

2. SendMessage P/Invoke 簽名

using System.Runtime.InteropServices;

public static class NativeMethods
{
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr SendMessage(
        IntPtr hWnd,      // 目標視窗句柄
        int Msg,          // 訊息代碼
        IntPtr wParam,    // 參數 1
        IntPtr lParam     // 參數 2
    );

    // 針對特定訊息的強型別多載(如 WM_SETTEXT)
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SendMessage(
        IntPtr hWnd, int Msg, IntPtr wParam, string lParam);

    // 非同步發送(不等待處理完成)
    [DllImport("user32.dll")]
    public static extern bool PostMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
}

3. 發送訊息範例:模擬按鈕點擊

// 取得目標視窗句柄(可用 FindWindow/FindWindowEx/EnumWindows)
IntPtr hBtn = FindWindowEx(parentHandle, IntPtr.Zero, "Button", "確定");

// 發送 BM_CLICK (0x00F5) 訊息模擬點擊
NativeMethods.SendMessage(hBtn, 0x00F5, IntPtr.Zero, IntPtr.Zero);

4. 訊息接收與攔截:重寫 WndProc

public class MyForm : Form
{
    protected override void WndProc(ref Message m)
    {
        switch (m.Msg)
        {
            case Win32Messages.WM_LBUTTONDOWN:
                // 攔截左鍵按下
                Console.WriteLine($"滑鼠左鍵按下: X={m.LParam.ToInt32() & 0xFFFF}, Y={m.LParam.ToInt32() >> 16}");
                // 不呼叫 base.WndProc 即可「吃掉」訊息
                return; 
            case Win32Messages.WM_CLOSE:
                // 關閉前確認
                if (MessageBox.Show("確定關閉?", "確認", MessageBoxButtons.YesNo) == DialogResult.No)
                    return; // 取消關閉
                break;
        }
        base.WndProc(ref m); // 傳遞給基類處理
    }
}

5. 全域訊息過濾:IMessageFilter

應用程式層級攔截**所有表單**的訊息(如全域熱鍵、禁用滑鼠移動):

public class GlobalMouseFilter : IMessageFilter
{
    public bool PreFilterMessage(ref Message m)
    {
        if (m.Msg == Win32Messages.WM_MOUSEMOVE)
        {
            // 返回 true 表示已處理,訊息不再分發
            return true; 
        }
        return false; // 繼續正常分發
    }
}

// 在 Program.cs 主入口註冊
static void Main()
{
    Application.AddMessageFilter(new GlobalMouseFilter());
    Application.Run(new MainForm());
}

6. 進階:子類別化 監控特定控制項

針對單一控制項攔截訊息(類似 MFC SubclassWindow):

public class NativeWindowListener : NativeWindow
{
    private Control _target;
    public NativeWindowListener(Control target)
    {
        _target = target;
        AssignHandle(target.Handle); // 關聯 Handle
    }

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == Win32Messages.WM_KEYDOWN && (Keys)m.WParam == Keys.Enter)
        {
            Console.WriteLine("該控制項偵測到 Enter");
            // 可選:阻止進一步處理
            // return;
        }
        base.WndProc(ref m);
    }

    public void Release() => ReleaseHandle();
}

7. 常見訊息處理對照表

需求訊息/方法說明
設定控制項文字WM_SETTEXT (0x000C)SendMessage(hWnd, WM_SETTEXT, 0, "新文字")
取得控制項文字WM_GETTEXT (0x000D)需配合 StringBuilder 接收
啟用/停用控制項WM_ENABLE (0x000A)wParam=1 啟用, 0 停用
模擬鍵盤輸入WM_KEYDOWN/UP/CHAR需正確填入虛擬鍵碼
讓視窗置頂SetWindowPos HWND_TOPMOST非 SendMessage,需 P/Invoke SetWindowPos
跨進程傳資料WM_COPYDATA (0x004A)配合 COPYDATASTRUCT 結構

8. 64 位相容性關鍵

  • 所有 `IntPtr` 參數**必須**使用 `IntPtr` 型別,不可用 `int`/`long`,否則 64 位進程會截斷句柄導致崩潰。
  • 結構體對齊:`[StructLayout(LayoutKind.Sequential)]`,`IntPtr` 欄位自動適應 32/64 位。
  • 專案屬性 → Build → **取消勾選 "Prefer 32-bit"**,確保 64 位正確運行。

9. 現代替代方案

  • UI Automation:`System.Windows.Automation` 命名空間,跨進程、跨技術(WinForms/WPF/Win32)操作 UI 元素,無需訊息層級 Hack。
  • Windows Messages 封裝庫:Vanara.PInvoke.User32 (NuGet) 提供完整強型別簽名,避免手寫 DllImport 錯誤。
  • 全域熱鍵:`RegisterHotKey` / `UnregisterHotKey` API,配合 `WM_HOTKEY` (0x0312) 訊息,比鍵盤鉤子更標準。

總結

`SendMessage` + `WndProc`/`IMessageFilter` 是 WinForms 觸達 Windows 核心訊息機制的橋樑。掌握訊息代碼、`wParam`/`lParam` 解碼、64 位 P/Invoke 規範,即可實現控制項行為擴展、跨進程通訊、全域攔截等高階功能。**原則:能用 Managed API 解決時,優先用 Managed API;必須落入訊息層時,嚴謹處理型別與生命週期。**

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved