基于Net6.0的泛型委托测量:包装GetDelegateForFunctionPointer
实现带耗时统计的泛型Native函数加载器
需求说明
基于现有泛型Native函数加载逻辑,实现一个包装器:返回与原委托签名完全一致的委托,内部自动测量原Native函数的调用耗时并输出日志。要求兼容net6.0(Microsoft.NET.Sdk.Web SDK),仅限Windows平台。
核心计时逻辑示例:
var sw = Stopwatch.StartNew(); var rtn = 原委托调用(); sw.Stop(); Console.WriteLine($"elapsed: {sw.Elapsed}"); return rtn;
修改后的完整实现
using System; using System.Collections.Concurrent; using System.Diagnostics; using System.Linq.Expressions; using System.Reflection; using System.Runtime.InteropServices; public static class NativeFunctionLoader { private static readonly ConcurrentDictionary<string, IntPtr> LoadedLibraries = new(); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr LoadLibrary(string lpFileName); [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)] private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); public static T LoadFunction<T>(string dllPath, string functionName) where T : Delegate { // 标准化路径 if (!Path.IsPathFullyQualified(dllPath)) { dllPath = Path.GetFullPath(dllPath); } // 加载或复用已加载的库 IntPtr hModule = LoadedLibraries.GetOrAdd( dllPath, dll => { IntPtr loaded = LoadLibrary(dll); if (loaded == IntPtr.Zero) { throw new DllNotFoundException($"未找到指定路径的库:{dllPath}"); } return loaded; } ); // 获取函数地址 IntPtr functionAddress = GetProcAddress(hModule, functionName); if (functionAddress == IntPtr.Zero) { throw new EntryPointNotFoundException($"在{dllPath}中未找到函数:{functionName}"); } // 获取原始委托 var originalDelegate = Marshal.GetDelegateForFunctionPointer<T>(functionAddress); // 生成带耗时统计的包装委托 return CreateTimedWrapper(originalDelegate); } private static T CreateTimedWrapper<T>(T originalDelegate) where T : Delegate { Type delegateType = typeof(T); MethodInfo invokeMethod = delegateType.GetMethod("Invoke")!; // 定义参数:和原委托的Invoke方法参数一致 ParameterExpression[] parameters = invokeMethod.GetParameters() .Select(p => Expression.Parameter(p.ParameterType, p.Name)) .ToArray(); // 调用原委托的表达式 Expression invokeOriginal = Expression.Invoke(Expression.Constant(originalDelegate), parameters); // 构建计时逻辑的表达式 var stopwatchVar = Expression.Variable(typeof(Stopwatch), "sw"); BlockExpression block; // 区分有返回值和无返回值的委托 if (invokeMethod.ReturnType == typeof(void)) { block = Expression.Block( new[] { stopwatchVar }, Expression.Assign(stopwatchVar, Expression.Call(typeof(Stopwatch), "StartNew", Type.EmptyTypes)), invokeOriginal, Expression.Call(stopwatchVar, nameof(Stopwatch.Stop), Type.EmptyTypes), Expression.Call( typeof(Console), nameof(Console.WriteLine), Type.EmptyTypes, Expression.Add( Expression.Constant($"[{typeof(T).Name}] 调用耗时:"), Expression.Property(stopwatchVar, nameof(Stopwatch.Elapsed)) ) ) ); } else { var resultVar = Expression.Variable(invokeMethod.ReturnType, "result"); block = Expression.Block( new[] { stopwatchVar, resultVar }, Expression.Assign(stopwatchVar, Expression.Call(typeof(Stopwatch), "StartNew", Type.EmptyTypes)), Expression.Assign(resultVar, invokeOriginal), Expression.Call(stopwatchVar, nameof(Stopwatch.Stop), Type.EmptyTypes), Expression.Call( typeof(Console), nameof(Console.WriteLine), Type.EmptyTypes, Expression.Add( Expression.Constant($"[{typeof(T).Name}] 调用耗时:"), Expression.Property(stopwatchVar, nameof(Stopwatch.Elapsed)) ) ), resultVar ); } // 编译表达式为委托 var lambda = Expression.Lambda<T>(block, parameters); return lambda.Compile(); } }
关键说明
- 泛型约束增强:给
LoadFunction<T>添加where T : Delegate约束,明确T必须是委托类型,避免无效调用。 - 表达式树动态适配:通过表达式树自动适配任意签名的委托(带参数/无参数、有返回值/无返回值),生成的包装委托与原委托签名完全一致。
- net6.0兼容性:使用的表达式树API、强类型
Marshal.GetDelegateForFunctionPointer<T>均在net6.0中完全支持。 - Windows平台限定:依赖
kernel32.dll的LoadLibrary和GetProcAddress接口,仅适用于Windows系统。
使用示例
假设Native函数签名如下:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int AddNumbers(int a, int b);
加载并调用带计时的函数:
var addFunc = NativeFunctionLoader.LoadFunction<AddNumbers>("MyNativeLib.dll", "AddNumbers"); int result = addFunc(10, 20); // 控制台会输出类似:[AddNumbers] 调用耗时:00:00:00.0001234
内容的提问来源于stack exchange,提问作者Marek Sebera
相关产品推荐
相关产品推荐

