如何声明与指定方法签名匹配的委托类型,避免重复维护?
这个需求太接地气了——谁都不想因为API签名改了,还要同时改原生接口、托管实现、委托声明三处,简直是维护噩梦!好在.NET里确实有几种办法能帮你摆脱手动声明委托的麻烦,下面给你逐个拆解:
方案1:用Delegate.CreateDelegate动态创建委托(无需提前声明委托类型)
你完全不用手动写APIDel这种委托类型,直接从目标方法动态生成匹配的委托就行。比如你的例子可以改成这样:
class Foo { public void APIFunction() { /* 你的API实现 */ } } IntPtr GetAPIFunctionPointer() { var foo = new Foo(); // 根据方法签名选择对应的预定义委托(无参无返回值用Action,带参数/返回值用Func<T>系列) Action del = (Action)Delegate.CreateDelegate(typeof(Action), foo, nameof(Foo.APIFunction)); IntPtr ptr = Marshal.GetFunctionPointerForDelegate(del); // 关键提醒:必须保留委托的引用!如果del是局部变量,GC可能会回收它,导致指针失效 // 最好把委托存在类的字段里(比如一个List<Delegate>),而不是方法内的临时变量 GC.KeepAlive(del); return ptr; }
如果你的API方法带参数或返回值,只需要换成对应的Func泛型委托就行——比如int APIFunc(string arg)就用Func<string, int>作为委托类型。
方案2:C# 9+ 函数指针语法(直接跳过委托,获取原生指针)
如果你用的是C# 9及以上版本,可以直接用函数指针语法,连委托都不用创建,直接拿到原生可调用的指针,效率更高:
class Foo { public void APIFunction() { /* 你的API实现 */ } } // 需要开启unsafe编译选项(项目属性→构建→允许不安全代码) unsafe IntPtr GetAPIFunctionPointer() { var foo = new Foo(); // 直接获取实例方法的函数指针,签名匹配就用对应的delegate*<>类型 // 无参无返回值用delegate*<void>,如果是int参数+string返回值就是delegate*<int, string> delegate*<void> funcPtr = &foo.APIFunction; return (IntPtr)funcPtr; }
这个方式省去了委托的包装,直接和原生指针对接,不过需要注意开启unsafe模式,而且静态方法的话更简单,直接&Foo.APIFunction就行。
方案3:源生成器(批量自动生成委托,适合大规模API)
如果你的插件API有一堆方法,手动用上面的方案还是有点麻烦,可以用.NET的源生成器自动生成所有匹配的委托类型。
比如你有一个镜像C API的托管接口:
public interface IPluginAPI { void APIFunction(); int AnotherAPIFunc(string param); }
写一个简单的源生成器,遍历这个接口的所有方法,自动生成对应的委托类型:
// 源生成器自动输出的代码(无需手动写) public delegate void APIFunctionDelegate(); public delegate int AnotherAPIFuncDelegate(string param);
这样每次你修改接口的方法签名,源生成器会自动同步更新委托类型,彻底消除手动维护的成本。
针对你的插件场景的适配建议
结合你托管+非托管插件的场景,最优的做法是遍历托管接口的所有方法,动态创建委托并封送指针,同时把委托实例存起来防止GC回收:
// 类内的字段,用来保存所有注册的委托,防止被GC回收 private List<Delegate> _registeredDelegates = new(); public void RegisterPluginAPI(IPluginAPI pluginInstance) { var apiMethods = typeof(IPluginAPI).GetMethods(); foreach (var method in apiMethods) { // 根据方法签名自动匹配对应的委托类型 Type delegateType = GetMatchingDelegateType(method); Delegate apiDelegate = Delegate.CreateDelegate(delegateType, pluginInstance, method); // 封送为原生函数指针,传给原生侧 IntPtr funcPtr = Marshal.GetFunctionPointerForDelegate(apiDelegate); NativePluginLoader.RegisterFunction(method.Name, funcPtr); // 保存委托引用,避免GC回收 _registeredDelegates.Add(apiDelegate); } } // 辅助方法:根据MethodInfo生成对应的Action/Func委托类型 private Type GetMatchingDelegateType(MethodInfo method) { var paramTypes = method.GetParameters().Select(p => p.ParameterType).ToArray(); if (method.ReturnType == typeof(void)) { return paramTypes.Length == 0 ? typeof(Action) : typeof(Action<>).MakeGenericType(paramTypes); } else { var allTypes = paramTypes.Concat(new[] { method.ReturnType }).ToArray(); return typeof(Func<>).MakeGenericType(allTypes); } }
这样不管你的API接口怎么改,这段代码都不需要手动调整,完美解决“三处同步更新”的痛点。
内容的提问来源于stack exchange,提问作者Adam
相关产品推荐
相关产品推荐

