C#中如何动态加载非托管DLL并动态指定函数调用?
我需要在C#中动态加载非托管DLL,并动态指定函数调用及参数。以往找到的示例和我之前使用的方法都是在类顶部静态声明委托,不确定是否可以实现动态指定。以下是包含静态委托的可用示例,以及尝试动态定义委托但无法运行的代码:
using System; using System.Runtime.InteropServices; using System.IO; class Program { // 声明委托以匹配函数签名,我希望能动态完成这一步 [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int MyFunctionDelegate(int a, int b); static void Main() { try { // DLL路径 string dllPath = @"C:\Projects_VS\Tests\DynamicLoadDLLTest\simpleCDLL.dll"; // 要调用的函数名 string functionName = "Add"; if (!File.Exists(dllPath)) { Console.WriteLine("缺少DLL文件。"); } // 动态加载指定DLL IntPtr dllHandle = LoadLibrary(dllPath); if (dllHandle == IntPtr.Zero) { Console.WriteLine("加载DLL失败。"); return; } // 获取函数地址 IntPtr functionAddress = GetProcAddress(dllHandle, functionName); if (functionAddress == IntPtr.Zero) { Console.WriteLine("获取函数地址失败。"); return; } // 当前可行的写法 MyFunctionDelegate myFunction = Marshal.GetDelegateForFunctionPointer<MyFunctionDelegate>(functionAddress); int result = myFunction(10, 20); // 我想要实现的写法 IntPtr functionPtr = GetProcAddress(dllHandle, "Add"); delegate int DynamicFunctionDelegate(int parameter1, int parameter2); DynamicFunctionDelegate myFunction = Marshal.GetDelegateForFunctionPointer<DynamicFunctionDelegate>(functionPtr); int result = myFunction(10, 20); Console.WriteLine("结果: " + result); } catch (Exception ex) { Console.WriteLine("发生错误: " + ex.Message); } } // 加载DLL的辅助方法 [DllImport("kernel32.dll")] private static extern IntPtr LoadLibrary(string dllToLoad); // 获取函数地址的辅助方法 [DllImport("kernel32.dll")] private static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); }
更新:我找到另一个示例并修改,但无法调用DLL,请问这个方向是否可行?
static void Main() { // DLL路径 string dllPath = @"E:\Projects_VS\Tests\DynamicLoadDLLTest\SimpleCDLL.dll"; // 要调用的函数名 string functionName = "Add"; object[] parameterValues = { 5, 10 }; object r = DynamicDllFunctionInvoke(dllPath, functionName, parameterValues); } public static object DynamicDllFunctionInvoke(string DllPath, string entrypoint, object[] funcPrams) { // 定义DLL函数的返回类型 Type returnType = typeof(int); // 获取函数参数类型 int n = funcPrams.Length; Type[] parameterTypes = new Type[n]; for (int i =0; i<n; ++i) { parameterTypes[i] = funcPrams[i].GetType(); } string entryPoint = entrypoint; // 创建动态程序集和模块 AssemblyName asmName = new AssemblyName(); asmName.Name = "tempDll"; AssemblyBuilder dynamicAsm = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run); ModuleBuilder dynamicMod = dynamicAsm.DefineDynamicModule("tempModule"); // 动态构造全局PInvoke签名 MethodBuilder dynamicMethod = dynamicMod.DefinePInvokeMethod( entryPoint, DllPath, MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.PinvokeImpl, CallingConventions.Standard, returnType, parameterTypes, CallingConvention.Winapi, CharSet.Ansi); // 完成全局方法定义 dynamicMod.CreateGlobalFunctions(); // 获取PInvoke方法的MethodInfo MethodInfo mi = dynamicMod.GetMethod(entrypoint); // 调用静态方法并返回结果 object retval = mi.Invoke(null, funcPrams); return retval; }
问题解答
1. 动态定义委托的代码为何无法运行?
你在方法内部直接定义委托类型(delegate int DynamicFunctionDelegate(int parameter1, int parameter2);)是C#不允许的语法——委托类型只能在命名空间或类级别声明,不能在方法内部定义,这是代码编译失败的直接原因。
若要实现动态匹配函数签名的需求,不能直接在方法内定义委托,需通过反射动态生成委托类型,或使用你尝试的动态程序集方案。
2. 动态程序集的方向可行,但代码存在关键问题
动态生成P/Invoke方法的思路是正确的,但你的代码有以下错误需要修正:
问题1:调用约定不匹配
你的非托管DLL函数Add如果是C/C++默认的cdecl调用约定(与第一个示例中的[UnmanagedFunctionPointer(CallingConvention.Cdecl)]对应),但你在DefinePInvokeMethod中指定的是CallingConvention.Winapi(通常对应stdcall),会导致调用时栈不平衡,引发异常。
问题2:获取MethodInfo可能失败
dynamicMod.GetMethod(entrypoint)可能找不到方法,因为动态生成的P/Invoke方法名称可能因重载或命名规则被修改,建议直接使用dynamicMethod获取MethodInfo,而非通过名称查找。
问题3:缺少错误处理
未处理DLL不存在、函数找不到等场景,易出现未捕获异常。
修正后的代码
using System; using System.Reflection; using System.Reflection.Emit; using System.Runtime.InteropServices; class Program { static void Main() { string dllPath = @"E:\Projects_VS\Tests\DynamicLoadDLLTest\SimpleCDLL.dll"; string functionName = "Add"; object[] parameterValues = { 5, 10 }; try { object r = DynamicDllFunctionInvoke(dllPath, functionName, typeof(int), new[] { typeof(int), typeof(int) }, parameterValues); Console.WriteLine($"结果: {r}"); } catch (Exception ex) { Console.WriteLine($"错误: {ex.Message}"); } } public static object DynamicDllFunctionInvoke(string dllPath, string entryPoint, Type returnType, Type[] parameterTypes, object[] funcParams) { // 检查DLL是否存在 if (!System.IO.File.Exists(dllPath)) throw new System.IO.FileNotFoundException("DLL文件不存在", dllPath); // 创建动态程序集和模块 AssemblyName asmName = new AssemblyName("TempDllAssembly"); AssemblyBuilder dynamicAsm = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run); ModuleBuilder dynamicMod = dynamicAsm.DefineDynamicModule("TempModule"); // 定义P/Invoke方法,确保调用约定与非托管函数一致 MethodBuilder dynamicMethod = dynamicMod.DefinePInvokeMethod( entryPoint, dllPath, MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.PinvokeImpl, CallingConventions.Standard, returnType, parameterTypes, CallingConvention.Cdecl, // 改为匹配非托管函数的调用约定 CharSet.Ansi); // 完成方法定义 dynamicMethod.SetImplementationFlags(dynamicMethod.GetImplementationFlags() | MethodImplAttributes.PreserveSig); dynamicMod.CreateGlobalFunctions(); // 获取MethodInfo并调用 MethodInfo mi = dynamicMod.GetMethod(entryPoint) ?? throw new MissingMethodException($"找不到函数 {entryPoint}"); return mi.Invoke(null, funcParams); } }
3. 更简洁的方案:反射动态生成委托类型
若不想使用动态程序集,可通过System.Reflection.Emit动态生成委托类型,结合Marshal.GetDelegateForFunctionPointer调用:
using System; using System.Reflection; using System.Reflection.Emit; using System.Runtime.InteropServices; public static class DynamicDllHelper { public static Delegate CreateDynamicDelegate(IntPtr functionPtr, Type returnType, Type[] parameterTypes, CallingConvention callingConvention) { // 创建动态程序集、模块和委托类型 AssemblyName asmName = new AssemblyName("DynamicDelegateAssembly"); AssemblyBuilder asmBuilder = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run); ModuleBuilder modBuilder = asmBuilder.DefineDynamicModule("DynamicDelegateModule"); // 定义委托类型:签名需与非托管函数一致 TypeBuilder delegateTypeBuilder = modBuilder.DefineType( $"DynamicDelegate_{Guid.NewGuid():N}", TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class, typeof(MulticastDelegate)); // 定义委托构造函数 ConstructorBuilder ctorBuilder = delegateTypeBuilder.DefineConstructor( MethodAttributes.RTSpecialName | MethodAttributes.HideBySig | MethodAttributes.Public, CallingConventions.Standard, new[] { typeof(object), typeof(IntPtr) }); ctorBuilder.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed); // 定义委托的Invoke方法 MethodBuilder invokeBuilder = delegateTypeBuilder.DefineMethod( "Invoke", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, returnType, parameterTypes); invokeBuilder.SetImplementationFlags(MethodImplAttributes.Runtime | MethodImplAttributes.Managed); // 完成委托类型创建 Type delegateType = delegateTypeBuilder.CreateType(); // 应用UnmanagedFunctionPointer特性,指定调用约定 ConstructorInfo attrCtor = typeof(UnmanagedFunctionPointerAttribute).GetConstructor(new[] { typeof(CallingConvention) }); CustomAttributeBuilder attrBuilder = new CustomAttributeBuilder(attrCtor, new object[] { callingConvention }); delegateType.SetCustomAttribute(attrBuilder); // 创建委托实例 return Marshal.GetDelegateForFunctionPointer(functionPtr, delegateType); } } // 使用示例 class Program { [DllImport("kernel32.dll")] private static extern IntPtr LoadLibrary(string dllToLoad); [DllImport("kernel32.dll")] private static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); static void Main() { string dllPath = @"C:\Projects_VS\Tests\DynamicLoadDLLTest\simpleCDLL.dll"; string functionName = "Add"; IntPtr dllHandle = LoadLibrary(dllPath); if (dllHandle == IntPtr.Zero) { Console.WriteLine("加载DLL失败"); return; } IntPtr funcPtr = GetProcAddress(dllHandle, functionName); if (funcPtr == IntPtr.Zero) { Console.WriteLine("获取函数地址失败"); return; } // 创建动态委托 Delegate dynamicDelegate = DynamicDllHelper.CreateDynamicDelegate(funcPtr, typeof(int), new[] { typeof(int), typeof(int) }, CallingConvention.Cdecl); // 调用函数 int result = (int)dynamicDelegate.DynamicInvoke(10, 20); Console.WriteLine($"结果: {result}"); } }
内容的提问来源于stack exchange,提问作者runfastman

