Intercepting Window Messages for another Window

2019-08-07 12:14发布

问题:

I'm using CefGlue to make an application with an embedded webkit browser in it, and I need to listen for mousemovements in the browser window. The winforms control doesn't pass down mouse events to the control so I can't listen to them.

However, I found a bug/feature request with a solution in it, but it's beyond me on how to implement it, I'm not familiar with working directly in the WinAPI. The developer says I need to:

2. OS-specific (windows) - after browser created (CefLifeSpanHandler.OnAfterCreated) get window handle and subclass them (window subclassing technique). Actually now we have native window with class CefBrowserWindow (returned by CefBrowser.GetHost().GetWindowHandle()), then child window Chrome_WidgetWin_0, and then Chrome_RenderWidgetHostHWND. For intercepting WM_MOUSEMOVE you are interesting in Chrome_WidgetWin_0 window, which can be easily obtained via CefBrowserWindow. Just play with Spy++ to see precisely.

https://bitbucket.org/xilium/xilium.cefglue/issue/4/mouse-events-unaccessible

I've done some googling, but I'm not sure how to hook into this. I have this function on my form:

    [System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
    protected override void WndProc(ref Message m) {
        base.WndProc(ref m);

        switch (m.Msg) {
            case WM_MOUSEMOVE:
                Console.WriteLine("Mouse move!");
                break;
            default:
                Console.WriteLine(m.ToString());
                break;
        }
    }

But I never see a mouse move when I'm over the control. I suspect I need to be listening on the WndProc of CefBrowser.GetHost().GetWindowHandle() But I'm not sure how to do that.

回答1:

I found a solution. I call this function in the OnAfterCreated event from the WebLifeSpanHandler.

        browser.Created += (sender, eventargs) => {
            Console.WriteLine("Created.");
            BrowserWindowPointer = browser.CefBrowser.GetHost().GetWindowHandle();
            Console.WriteLine("BrowserWindowPointer: " + BrowserWindowPointer);
            uint BrowserThreadId = GetWindowThreadProcessId(BrowserWindowPointer, IntPtr.Zero);
            Console.WriteLine("Browser PID: " + BrowserThreadId);

            MouseHookProcedure = new HookProc(this.MouseHookProc);

            hHookMouse = SetWindowsHookEx(WH_MOUSE,
                        MouseHookProcedure,
                        (IntPtr)0,
                        BrowserThreadId);

            if (hHookMouse == 0) {
                Console.WriteLine("MouseHook Failed. Making cursor always visible.");
                Cursor.Show();
            }

            KeyboardHookProcedure = new HookProc(this.KeyboardHookProc);
            hHookKeyboard = SetWindowsHookEx(WH_KEYBOARD,
                        KeyboardHookProcedure,
                        (IntPtr)0,
                        BrowserThreadId);
        };

The function I was looking for is GetWindowThreadProcessId which I can use the window handle provided by browser.CefBrowser.GetHost().GetWindowHandle()

One thing to note is that the Hook Procedure needs to have some sort of larger scope. C# doesn't see it hooked into the native process and will Garbage Collect it if you let it go out of scope. To fix this, I made them class properties.

Supporting Documents:

  • http://support.microsoft.com/kb/318804 How to set a Windows hook in Visual C# .NET Provided the basis for hooking into the mouse events. Modified to use the browser thread id
  • http://msdn.microsoft.com/en-us/library/ms644988(v=vs.85).aspx MouseProc callback function How to handle the mouse windows hook
  • http://msdn.microsoft.com/en-us/library/ms644984(VS.85).aspx KeyboardProc callback function How to handle the keyboard windows hook. Includes structure of lParam. Important note: wParam contains the Virtual Key Code of the key entered.
  • http://msdn.microsoft.com/en-us/library/dd375731(v=vs.85).aspx Virtual-Key Codes List of keys and their Virtual Key Code, so I could tell what button the user was pressing.

My two hooks (not good code, I don't do anything with the marshaled data):

    private int MouseHookProc(int nCode, IntPtr wParam, IntPtr lParam) {
        //Marshall the data from the callback.
        MouseHookStruct MyMouseHookStruct = (MouseHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseHookStruct));

        if (nCode < 0) {
            return CallNextHookEx(hHookMouse, nCode, wParam, lParam);
        } else {
            if (wParam.ToInt32() == WM_MOUSEMOVE) {
                Screensaver_OnMouseMove(this, null);
            }
            return CallNextHookEx(hHookMouse, nCode, wParam, lParam);
        }
    }

    private int KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam) {
        //Marshall the data from the callback.
        KeyboardHookStruct keyInfo = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
        int VK_ESCAPE = 0x1B;

        if (nCode < 0) {
            return CallNextHookEx(hHookKeyboard, nCode, wParam, lParam);
        } else {
            int keyCode = wParam.ToInt32();

            if (keyCode == VK_ESCAPE) {
                Application.Exit();
            }

            return CallNextHookEx(hHookKeyboard, nCode, wParam, lParam);
        }

    }

This is the externs and the code needed to expose the hooks, this is class level scope:

    public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);

    //Declare the hook handle as an int.
    static int hHookMouse = 0;
    static int hHookKeyboard = 0;

    //Declare the mouse hook constant.
    //For other hook types, you can obtain these values from Winuser.h in the Microsoft SDK.
    private const int WH_KEYBOARD = 2;
    private const int WH_MOUSE = 7;
    private const int WM_MOUSEMOVE = 0x0200;

    //Declare the wrapper managed POINT class.
    [StructLayout(LayoutKind.Sequential)]
    public class POINT {
        public int x;
        public int y;
    }

    //Declare the wrapper managed MouseHookStruct class.
    [StructLayout(LayoutKind.Sequential)]
    public class MouseHookStruct {
        public POINT pt;
        public int hwnd;
        public int wHitTestCode;
        public int dwExtraInfo;
    }
    public struct KeyboardHookStruct {
        public int vkCode;
        public int scanCode;
        public int flags;
        public int time;
        public int dwExtraInfo;
    }

    //This is the Import for the SetWindowsHookEx function.
    //Use this function to install a thread-specific hook.
    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, uint threadId);

    //This is the Import for the UnhookWindowsHookEx function.
    //Call this function to uninstall the hook.
    [DllImport("user32.dll", CharSet = CharSet.Auto,
     CallingConvention = CallingConvention.StdCall)]
    public static extern bool UnhookWindowsHookEx(int idHook);

    //This is the Import for the CallNextHookEx function.
    //Use this function to pass the hook information to the next hook procedure in chain.
    [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll")]
    static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);

    public HookProc KeyboardHookProcedure { get; set; }

    public HookProc MouseHookProcedure { get; set; }

I'm not sure this is entirely required, especially since the thread being hooked to is going away on close, but just for good measure, don't forget to cleanup your hook after you're done listening. I do this on my form's dispose method:

    protected override void Dispose(bool disposing) {
        base.Dispose();

        if (disposing) {
            if (hHookKeyboard != 0) {
                UnhookWindowsHookEx(hHookKeyboard);
            }
            if (hHookMouse != 0) {
                UnhookWindowsHookEx(hHookMouse);
            }
        }
    }