如何让C#类库依赖后续由最终应用指定的非托管DLL?
在C#中实现与非托管DLL的解耦依赖
方法1:动态P/Invoke(手动加载DLL)
核心思路是绕过静态的[DllImport],直接调用Win32 API动态加载DLL并获取函数指针,以此实现运行时指定DLL名称的效果。
实现步骤:
- 定义与非托管函数签名完全匹配的委托类型
- 在类库中暴露初始化入口,允许最终应用传入DLL路径/名称
- 通过
LoadLibrary加载目标DLL,再用GetProcAddress获取函数地址,转换为委托实例供类库内部调用
示例代码(类库部分):
using System; using System.Runtime.InteropServices; // 匹配非托管函数签名的委托 public delegate int MyApiFunction(int param); public class MyApiWrapper : IDisposable { private MyApiFunction _myFunction; private IntPtr _dllHandle = IntPtr.Zero; // 由最终应用调用,传入具体DLL名称/路径 public void Initialize(string dllPath) { _dllHandle = LoadLibrary(dllPath); if (_dllHandle == IntPtr.Zero) throw new System.ComponentModel.Win32Exception(); IntPtr funcPtr = GetProcAddress(_dllHandle, "MyApiFunction"); if (funcPtr == IntPtr.Zero) throw new System.ComponentModel.Win32Exception(); _myFunction = Marshal.GetDelegateForFunctionPointer<MyApiFunction>(funcPtr); } // 类库对外暴露的业务方法,内部调用动态获取的非托管函数 public int CallMyFunction(int param) { return _myFunction?.Invoke(param) ?? throw new InvalidOperationException("API未完成初始化"); } // Win32 API声明 [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); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool FreeLibrary(IntPtr hModule); // 释放已加载的DLL资源 public void Dispose() { if (_dllHandle != IntPtr.Zero) { FreeLibrary(_dllHandle); _dllHandle = IntPtr.Zero; } } }
最终应用中的调用示例:
using (var wrapper = new MyApiWrapper()) { wrapper.Initialize("MySpecificImpl.dll"); // 在此指定具体的非托管DLL var result = wrapper.CallMyFunction(42); // 处理业务结果 }
方法2:抽象接口+依赖注入
通过定义抽象API接口让类库依赖,最终应用负责实现接口(包含指定具体DLL的P/Invoke逻辑),再通过依赖注入将实现注入到类库中,完全解耦类库与具体DLL的绑定。
实现步骤:
- 类库中定义抽象API接口:
public interface IMyApi { int MyApiFunction(int param); }
- 类库的业务逻辑依赖该抽象接口:
public class BusinessLogic { private readonly IMyApi _api; public BusinessLogic(IMyApi api) { _api = api; } public void ProcessData(int input) { var result = _api.MyApiFunction(input); // 执行具体业务逻辑 } }
- 最终应用中实现接口,指定目标DLL:
public class MyApiImpl : IMyApi { [DllImport("MySpecificImpl.dll")] private static extern int MyApiFunction(int param); public int MyApiFunction(int param) { return MyApiFunction(param); } }
- 应用中注入实现并使用:
var apiImpl = new MyApiImpl(); var businessLogic = new BusinessLogic(apiImpl); businessLogic.ProcessData(42);
这种方式符合C#面向对象设计原则,解耦彻底,还能轻松替换不同的DLL实现。
方法3:反射修改DllImport属性(不推荐)
如果想保留[DllImport]的形式,可以通过反射动态修改其DLL名称属性,但这属于hack手段,稳定性较差,仅作参考:
public class MyApiWrapper { private static string _dllName; // 由应用传入DLL名称 public static void SetDllName(string name) { _dllName = name; } [DllImport("", EntryPoint = "MyApiFunction", SetLastError = true)] private static extern int MyApiFunctionImpl(int param); public int CallMyFunction(int param) { // 反射修改DllImport的DLL名称 var methodInfo = typeof(MyApiWrapper).GetMethod("MyApiFunctionImpl", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); var dllImportAttr = (DllImportAttribute)Attribute.GetCustomAttribute(methodInfo, typeof(DllImportAttribute)); var field = typeof(DllImportAttribute).GetField("_dllName", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); field.SetValue(dllImportAttr, _dllName); Marshal.Prelink(methodInfo); return MyApiFunctionImpl(param); } }
内容的提问来源于stack exchange,提问作者curiousdannii
相关产品推荐
相关产品推荐

