当前位置: 首页 > news >正文

通过子类化窗口(SubClass)来为现有的某个窗口添加新的窗口处理程序(或者叫钩子,Hook)

创建窗口的时候,可以传一个消息处理函数。然而如果窗口不是自己创建的,还能增加消息处理函数吗?答案是可以的,除了 SetWindowsHookEx 来添加钩子之外,更推荐用子类化的方式来添加。

本文介绍如何通过子类化(SubClass)的方式来为窗口添加额外的消息处理函数。


@TOC

子类化

子类化的本质是通过 SetWindowLong 传入 GWL_WNDPROC 参数。

SetWindowLong 的 API 如下:

LONG SetWindowLongA(
  HWND hWnd,
  int  nIndex,
  LONG dwNewLong
);

nIndex 指定为 GWL_WNDPROC,在此情况下,后面的 dwNewLong 就可以指定为一个函数指针,返回值就是原始的消息处理函数。

对于 .NET/C# 来说,我们需要拿到窗口句柄,拿到一个消息处理函数的指针。

窗口句柄在不同的 UI 框架拿的方法不同,WPF 是通过 HwndSource 或者 WindowInteropHelper 来拿。而将委托转换成函数指针则可通过 Marshal.GetFunctionPointerForDelegate 来转换。

你可别吐槽 WPF 另有它法来加消息处理函数啊!本文说的是 Win32,方法需要具有普适性。特别是那种你只能拿到一个窗口句柄,其他啥也不知道的窗口。

var hWnd = new WindowInteropHelper(this).EnsureHandle();
var wndProc = Marshal.GetFunctionPointerForDelegate<WndProc>(OnWndProc);
_originalWndProc = SetWindowLongPtr(hWnd, GWL_WNDPROC, wndProc);

IntPtr OnWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
    // 在这里处理消息。
}

将完整的代码贴下来,大约是这样:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        SourceInitialized += MainWindow_SourceInitialized;
    }

    private void MainWindow_SourceInitialized(object sender, EventArgs e)
    {
        var hWnd = new WindowInteropHelper(this).EnsureHandle();
        _wndProc = OnWndProc;
        var wndProc = Marshal.GetFunctionPointerForDelegate<WndProc>(_wndProc);
        _originalWndProc = SetWindowLongPtr(hWnd, GWL_WNDPROC, wndProc);
    }

    private WndProc _wndProc;
    private IntPtr _originalWndProc;

    private IntPtr OnWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
    {
        switch (msg)
        {
            case WM_NCHITTEST:
                return CallWindowProc(_originalWndProc, hWnd, msg, wParam, lParam);
            default:
                return CallWindowProc(_originalWndProc, hWnd, msg, wParam, lParam);
        }
    }
}

其中,我将委托存成了一个字段,这样可以避免 GC 回收掉这个委托对象造成崩溃。

在示例的消息处理函数中,我示例处理了一下 WM_NCHITTEST(虽然依然什么都没做)。最后,必须调用 CallWindowProc 以调用此前原来的那个消息处理函数。

最后,如果你又不希望处理这个消息了,那么使用以下方法注销掉这个委托:

// 嗯,没错,就是前面更换消息处理函数时返回的那个指针。
SetWindowLongPtr(hWnd, GWL_WNDPROC, _originalWndProc);

上面需要的所有的 P/Invoke 我都贴到了下面,需要的话放到你的代码当中。

private static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
{
    if (IntPtr.Size == 8)
    {
        return SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
    }
    else
    {
        return new IntPtr(SetWindowLong32(hWnd, nIndex, dwNewLong.ToInt32()));
    }
}

[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
private static extern int SetWindowLong32(IntPtr hWnd, int nIndex, int dwNewLong);

[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

[DllImport("user32.dll")]
static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

private delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);

private const int GWL_WNDPROC = -4;
private const int WM_NCHITTEST = 0x0084;
private const int HTTRANSPARENT = -1;

其他方法

本文一开始说到了使用 SetWindowsHookEx 的方式来添加钩子,具体你可以阅读我的另一篇博客来了解如何实现:

  • .NET/C# 使用 SetWindowsHookEx 监听鼠标或键盘消息以及此方法的坑 - walterlv

参考资料

  • Using Window Procedures - Win32 apps - Microsoft Docs

我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。

相关文章:

  • .NET Framework 和 .NET Core 在默认情况下垃圾回收(GC)机制的不同(局部变量部分)
  • .NET Windows:删除文件夹后立即判断,有可能依然存在
  • .NET 将混合了多个不同平台(Windows Mac Linux)的文件 目录的路径格式化成同一个平台下的路径
  • git 乱改你的换行符?一句话设置让 git 不再碰你某个文件的换行符
  • Linux Shell 中需要转义的字符
  • Unity3D 入门:Unity Editor 编辑器常用快捷键
  • Unity3D 入门:Unity 项目版本管理建议使用的 .gitignore 忽略文件和 .gitattributes 文件(2020年4月更新)
  • Unity3D 入门:让 C# 脚本公开可在 Unity 编辑器中设置的属性
  • Unity3D 入门:如何管理 Unity 项目中的 NuGet 包?使用第三方 NuGet 包管理器——NuGetForUnity
  • Unity3D 入门:如何在脚本中找到游戏对象的父子级 祖孙级对象和它们的组件
  • Unity3D 入门:如何制作天空效果?天空盒的使用
  • Unity3D 入门:使用 Visual Studio 开发 Unity C# 脚本,说说根目录的那些 sln 和 csproj 文件
  • Unity3D 入门:最简单的控制视角,以及控制角色前进、转向的脚本
  • 比较 Windows 上四种不同的文件(夹)链接方式(NTFS 的硬链接、目录联接、符号链接,和大家熟知的快捷方式)
  • 了解 Windows Linux 下命令行 Shell 启动程序传参的区别,这下不用再担心 Windows 下启动程序传参到 Linux 下挂掉了
  • 【108天】Java——《Head First Java》笔记(第1-4章)
  • 【mysql】环境安装、服务启动、密码设置
  • ECMAScript6(0):ES6简明参考手册
  • java8 Stream Pipelines 浅析
  • JS创建对象模式及其对象原型链探究(一):Object模式
  • Linux Process Manage
  • Objective-C 中关联引用的概念
  • Python学习之路13-记分
  • React-redux的原理以及使用
  • windows下mongoDB的环境配置
  • Zepto.js源码学习之二
  • 笨办法学C 练习34:动态数组
  • 第十八天-企业应用架构模式-基本模式
  • 高度不固定时垂直居中
  • 好的网址,关于.net 4.0 ,vs 2010
  • 京东美团研发面经
  • 聊聊spring cloud的LoadBalancerAutoConfiguration
  • 前嗅ForeSpider教程:创建模板
  • 数据仓库的几种建模方法
  • 新版博客前端前瞻
  • 新手搭建网站的主要流程
  • scrapy中间件源码分析及常用中间件大全
  • ​520就是要宠粉,你的心头书我买单
  • ​马来语翻译中文去哪比较好?
  • $HTTP_POST_VARS['']和$_POST['']的区别
  • (09)Hive——CTE 公共表达式
  • (2)STL算法之元素计数
  • (27)4.8 习题课
  • (42)STM32——LCD显示屏实验笔记
  • (java版)排序算法----【冒泡,选择,插入,希尔,快速排序,归并排序,基数排序】超详细~~
  • (k8s中)docker netty OOM问题记录
  • (MIT博士)林达华老师-概率模型与计算机视觉”
  • (三)docker:Dockerfile构建容器运行jar包
  • (三)终结任务
  • (十八)三元表达式和列表解析
  • (十七)Flask之大型项目目录结构示例【二扣蓝图】
  • (推荐)叮当——中文语音对话机器人
  • (万字长文)Spring的核心知识尽揽其中
  • (一)python发送HTTP 请求的两种方式(get和post )
  • (一)基于IDEA的JAVA基础12