遇到一个技术点,记一下,.net 有一个 Delegate Marshall.GetDelegateForFunctionPointer(IntPtr ptr, Type t) 用来将内存地址映射为一个 delegate,转为 delegate 后就可以对内存段的二进制代码进行 .net 内的调用了。例如 ptr 是 VirtualAlloc 的地址,t 是具有 IntPtr(IntPtr lpStartAddr, UInt32 size, UInt32 flAllocationType, UInt32 flProtect) 签名的代理类型,那么获得的就是一个这个 delegate 的实例:

delegate IntPtr VirtualAllocDelegate(IntPtr lpStartAddr, UInt32 size, UInt32 flAllocationType, UInt32 flProtect);

VirtualAllocDelegate d = Marshall.GetDelegateForFunctionPointer(ptr, typeof(VirtualAllocDelegate))

d(IntPtr.Zero, 200, 1, 0)

这是把 API 映射为 .net 的一个重要方法。

现在我要做的是拦截它,抓取调用。我要提供一个万能函数,然后所有调用都先回调到这个函数:

class Hook{
void Invoke(params object[] args){...}
}

我需要实现两个环节:

  1. 把这个函数适配到任意的 Type t,把参数发到 params object[] args
  2. 要先调用我这个钩子函数

下面是实现:

            var h = Marshal.GetDelegateForFunctionPointer(entrance, delegateType);   // 指针对应的 delegate
// 构造一个头和 delegateType 对齐的 lambda expression
var invoke = delegateType.GetMethod("Invoke");
var parameters = invoke.GetParameters().Select(p => ParameterExpression.Variable(p.ParameterType)).ToArray();
var args = Expression.NewArrayInit(typeof(object),
parameters.Select(p => Expression.TypeAs(p, typeof(object))).ToArray());
MethodCallExpression callExpression = Expression.Call(Expression.Constant(new Hook()), "Invoke", null, args); // 参数转发到 new Hook().Invoke
LambdaExpression lambdaExpression;
if (invoke.ReturnType == typeof(void))
{
lambdaExpression = Expression.Lambda(t, callExpression, parameters); // t 是 void 类型,lambda 无需返回
}
else
{
var block = Expression.Block(callExpression, Expression.Default(invoke.ReturnType)); // 否则返回默认值,注意这里只要加一个 Expression.Default(..) 就行,不需要 Expression.Return()...
lambdaExpression = Expression.Lambda(t, block, parameters);
}
dynamic h2 = Delegate.Combine(lambdaExpression.Compile(), h); // Combine 在一起,先调用我再调用原来的指针,目标达到!

至于怎么替掉原来的 Marshall.GetDelegateForFunctionPointer 就不展开了。

总体来说,.net 的 delegate 对应的级别很低,它是内存函数指针的类型化,其 DynamicInvoke Invoke 等方法既不暴露也不能覆盖,是一个魔法原子。相比来说 Java 面向对象的更彻底。

class Hook {
void invoke(object... args)
}
class VirtualAllocDelegate {
IntPrt invoke(IntPtr a, IntPtr b, IntPtr c)
}
// 这里也要用到 Java 的动态技术,生成一个临时类 MyDel extends VirtualAllocDelegate
dynamic class MyDel extends VirtualAllocDelegate{
MyDel(VirtualAllocDelegate inner, Hook h){
}
override IntPtr invoke(IntPtr a, IntPtr b, IntPtr c){
hook.invoke(a,b,c);
return inner.invoke(a,b,c);
}
}
var d = new MyDel(new VirtualAllocDelegate(offset), new Hook());
d.invoke(...)

Java 里 invoke 会是一个符合 OO 思想的 override,而不是 Delegate.Combine 这种更 c-style 还带点 fp 的玩意儿。

和 java 相比 c# 的面向对象程度不够彻底,有时像是 C 和 FP 结合的怪胎,一个典型的特点是 c# 从一开始就没有匿名内部类。

.net delegate 万能适配的更多相关文章

  1. Android TV开发总结(五)TV上屏幕适配总结

    前言:前面几篇总结一些TV上的小Sample,开源到GitHub:https://github.com/hejunlin2013/TVSample, 点击链接,可以持续关注.今天总结下TV上屏幕适配. ...

  2. 一行代码搞定所有屏幕适配AbViewUtil

    适配原理:抛弃google提供的dip理论与多套图片与布局方案,采用与UI设计师通用的px作为标准单位,原理是将UI设计师的设计图与当前查看的手机或其他设备的屏幕像素尺寸进行换算,得到缩放比例,在Ac ...

  3. Android Andbase应用开发框架

    [运行说明]运行AndbaseDemo需要将文件中的Andbase库Add进demo中.1.andbase中包含了大量的开发常用手段.如网络下载,多线程与线程池的管理,数据库ORM,图片缓存管理,图片 ...

  4. android开源框架之 andbase

    andbase开发框架介绍:andbase是为Android开发人员量身打造的一款开源类库产品,您能够在本站中获取到最新的代码,演示样例以及开发文档. 下载地址:http://download.csd ...

  5. 一个漂亮而强大的RecyclerView

    代码地址如下:http://www.demodashi.com/demo/13470.html 简介 主要提供了简单易用强大的RecyclerView库,包括自定义刷新加载效果.极简通用的万能适配器A ...

  6. 9款Android经常使用的高速开发框架

    1.Afinal框架 项目地址:https://github.com/yangfuhai/afinal 项目地址:http://www.oschina.net/p/afinal 主要有四大模块:  ( ...

  7. Android 开源控件与常用开发框架开发工具类

    Android的加载动画AVLoadingIndicatorView 项目地址: https://github.com/81813780/AVLoadingIndicatorView 首先,在 bui ...

  8. iOS设计模式之适配器模式

    一,适配器的定义 定义 将一个类的接口转换成客户希望的另外一个接口.适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作 需求场景 需要使用以前开发的“一些现存的对象”,但是新环境中要求 ...

  9. iOS---iOS10适配iOS当前所有系统的远程推送

    一.iOS推送通知简介 众所周知苹果的推送通知从iOS3开始出现, 每一年都会更新一些新的用法. 譬如iOS7出现的Silent remote notifications(远程静默推送), iOS8出 ...

  10. Android—万能ListView适配器

    ListView是开发中最常用的控件了,但是总是会写重复的代码,浪费时间又没有意义. 最近参考一些资料,发现一个万能ListView适配器,代码量少,节省时间,总结一下分享给大家. 首先有一个自定义的 ...

随机推荐

  1. 017 Python 流程控制之 if 判断

    博客配套视频链接: https://space.bilibili.com/383551518?spm_id_from=333.1007.0.0 b 站直接看 配套 github 链接:https:// ...

  2. SQLServer数据库日志太大处理方式

    SQLServer数据库日志太大处理方式 1.1 如下图,点击连接登陆数据库 1.2 如下图,打开数据库属性窗口 1.3 如下图,更改数据库恢复模式 1.4 如下图,收缩数据库日志 到这里已经完成了, ...

  3. URL是什么

    URL是什么 URL(Uniform Resource Locator,统一资源定位器) URL的组成: 协议://{域名|主机名|IP}:端口/路径/文件名?参数#锚点 协议 Scheme/Prot ...

  4. vue-cli 跳转到页面指定位置

    原文关注公众号,后台里留言可进行提问,可在后台留言向作者提问解答问题! https://mp.weixin.qq.com/s?__biz=Mzg3NTAzMzAxNA==&mid=224748 ...

  5. html 根据配置项统一检查文本框数据规范

    <div> 中文名:<input id="txtName" type="text" /><br /> 身份证号:<in ...

  6. Flink window

    窗口计算 我们经常需要在一个时间窗口维度上对数据进行聚合,窗口是流处理应用中经常需要解决的问题.Flink的窗口算子为我们提供了方便易用的API,我们可以将数据流切分成一个个窗口,对窗口内的数据进行处 ...

  7. 2.16 Linux挂载详解

    前面讲过,Linux 系统中"一切皆文件",所有文件都放置在以根目录为树根的树形目录结构中.在 Linux 看来,任何硬件设备也都是文件,它们各有自己的一套文件系统(文件目录结构) ...

  8. 题解:CF1551D1 Domino (easy version)

    题解:CF1551D1 Domino (easy version) 分析 题目中保证 \(n\times m\) 为偶数,下面进行分类讨论. 情况一 如果 \(n\) 和 \(m\) 都是偶数,那么可 ...

  9. 使用技巧 | 红米 Redmi Note 12 Turbo优化记录(去广告等)

    原文链接:https://engapi.com/article/7569 原文也是我写的. 我的红米Redmi note8 pro 6+128已有些卡顿,遂在K70推出之际下单了Redmi Note ...

  10. php 超过64位进制和10进制的转换

    有时候想把一个很大的数尽量用更少的空间存储起来,那么就可以采用很大的进制来存储它,比如说,一个大于等于10小于等于16数字使用10进制就需要两位,使用16进制就只需要1位,那就等于帮程序省了一位的空间 ...