参数化DllImport用于C#应用程序

我们有一个供应商,提供访问其硬件的库。 不幸的是,如果您有多个设备,则需要多次导入其库,并使用不同的dll名称。 因此,我们有一吨重复的代码,我担心它很快就会成为维护的噩梦。

我们现在所拥有的是:

namespace MyNamespace { public static class Device01 { public const string DLL_NAME = @"Device01.dll"; [DllImport(DLL_NAME, EntryPoint = "_function1")] public static extern int Function1(byte[] param); 

  [DllImport(DLL_NAME, EntryPoint = "_function99")] public static extern int Function99(int param); } 

….

  public static class Device16 { public const string DLL_NAME = @"Device16.dll"; [DllImport(DLL_NAME, EntryPoint = "_function1")] public static extern int Function1(byte[] param); 

  [DllImport(DLL_NAME, EntryPoint = "_function99")] public static extern int Function99(int param); } } 

如果我使用的是C或C ++,我只会在静态类中定义一个文件和#include它们多次,不是很好但是比替代方案好,但在C#中我没有那个选项。

如果有人对如何有效地定义工厂有任何聪明的想法,这将允许我们生成尽可能多的静态设备类,我会非常感兴趣。

谢谢,

编辑:函数原型是非常变化的,所以任何依赖它们相同的方法都是不合适的。 感谢到目前为止的建议,我并没有如此迅速地阐述这么多想法。

只是一些考虑:

替代#one

编辑:这种方法需要改变编译方法,这很难,需要注入,assembly修改或AOP-land中常用的其他方法。 考虑下面的方法二,这更容易。

  1. 删除所有具有相同签名的函数,保留其中一个
  2. 使用GetIlAsByteArray创建DllImport方法的动态方法
  3. 使用此处描述的技术来操作函数的IL,在这里您可以更改DllImport属性等。
  4. 创建这些function的委托并缓存您的呼叫
  5. 返回代表

替代#two:

编辑:这种替代方法起初似乎有点涉及,但有人已经为你做了工作。 查看这篇优秀的CodeProject文章 ,只需下载并使用其代码即可动态创建DllImport样式方法。 基本上,它归结为:

  1. 删除所有DllImport
  2. 创建自己的DllImport包装器:获取dll名称和函数名称(假设所有签名都相同)
  3. 包装器使用dllimport API函数执行带有LoadLibraryLoadLibraryEx的“手动”DllImport
  4. 包装器使用MethodBuilder为您创建一个方法。
  5. 返回可用作函数的方法的委托。

另类#three

编辑:进一步看,有一个更简单的方法:只需使用DefinePInvokeMethod ,它DefinePInvokeMethod您的所有需求。 MSDN链接已经提供了一个很好的示例,但是在此CodeProject文章中提供了可以基于DLL和函数名创建任何本机DLL的完整包装器。

  1. 删除所有DllImport样式签名
  2. DefinePInvokeMethod周围创建一个简单的包装器方法
  3. 确保添加简单的缓存(字典?)以防止在每次调用时构建整个方法
  4. 从包装器返回一个委托。

以下是这种方法在代码中的外观,您可以根据需要重用返回的委托,每个方法只需要进行一次动态方法的昂贵构建。

编辑:更新代码示例以使用任何委托,并自动反映委托签名中的正确返回类型和参数类型。 通过这种方式,我们将实现与签名完全分离,即根据您当前的情况,我们可以做到最好。 优点:您具有类型安全性和单点变化,这意味着:非常容易管理。

 // expand this list to contain all your variants // this is basically all you need to adjust (!!!) public delegate int Function01(byte[] b); public delegate int Function02(); public delegate void Function03(); public delegate double Function04(int p, byte b, short s); // TODO: add some typical error handling public T CreateDynamicDllInvoke(string functionName, string library) { // create in-memory assembly, module and type AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly( new AssemblyName("DynamicDllInvoke"), AssemblyBuilderAccess.Run); ModuleBuilder modBuilder = assemblyBuilder.DefineDynamicModule("DynamicDllModule"); // note: without TypeBuilder, you can create global functions // on the module level, but you cannot create delegates to them TypeBuilder typeBuilder = modBuilder.DefineType( "DynamicDllInvokeType", TypeAttributes.Public | TypeAttributes.UnicodeClass); // get params from delegate dynamically (!), trick from Eric Lippert MethodInfo delegateMI = typeof(T).GetMethod("Invoke"); Type[] delegateParams = (from param in delegateMI.GetParameters() select param.ParameterType).ToArray(); // automatically create the correct signagure for PInvoke MethodBuilder methodBuilder = typeBuilder.DefinePInvokeMethod( functionName, library, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.PinvokeImpl, CallingConventions.Standard, delegateMI.ReturnType, /* the return type */ delegateParams, /* array of parameters from delegate T */ CallingConvention.Winapi, CharSet.Ansi); // needed according to MSDN methodBuilder.SetImplementationFlags( methodBuilder.GetMethodImplementationFlags() | MethodImplAttributes.PreserveSig); Type dynamicType = typeBuilder.CreateType(); MethodInfo methodInfo = dynamicType.GetMethod(functionName); // create the delegate of type T, double casting is necessary return (T) (object) Delegate.CreateDelegate( typeof(T), methodInfo, true); } // call it as follows, simply use the appropriate delegate and the // the rest "just works": Function02 getTickCount = CreateDynamicDllInvoke ("GetTickCount", "kernel32.dll"); Debug.WriteLine(getTickCount()); 

我想其他方法也是可能的(就像这个post中其他人提到的模板方法一样)。

更新:添加了一个优秀的codeproject文章的链接。
更新:增加了第三种更容易的方法。
更新:添加代码示例
更新:更新的代码示例,可与任何函数原型无缝协作
更新:修复了可怕的错误: typeof(Function02)当然应该是typeof(T)

如何使用T4 (文本模板转换工具包)。 使用以下内容创建.tt文件:

 <#@ template language="C#" #> using System.Runtime.InteropServices; namespace MyNamespace { <# foreach(string deviceName in DeviceNames) { #> public static class <#= deviceName #> { public const string DLL_NAME = @"<#= deviceName #>.dll"; <# foreach(string functionName in FunctionNames) { #> [DllImport(DLL_NAME, EntryPoint = "<#= functionName #>")] public static extern int <#= functionName.Substring(1) #>(byte[] param); <# } #> } <# } #> } <#+ string[] DeviceNames = new string[] { "Device01", "Device02", "Device03" }; string[] FunctionNames = new string[] { "_function1", "_function2", "_function3" }; #> 

然后Visual Studio将其转换为:

 using System.Runtime.InteropServices; namespace MyNamespace { public static class Device01 { public const string DLL_NAME = @"Device01.dll"; [DllImport(DLL_NAME, EntryPoint = "_function1")] public static extern int function1(byte[] param); [DllImport(DLL_NAME, EntryPoint = "_function2")] public static extern int function2(byte[] param); [DllImport(DLL_NAME, EntryPoint = "_function3")] public static extern int function3(byte[] param); } public static class Device02 { public const string DLL_NAME = @"Device02.dll"; [DllImport(DLL_NAME, EntryPoint = "_function1")] public static extern int function1(byte[] param); [DllImport(DLL_NAME, EntryPoint = "_function2")] public static extern int function2(byte[] param); [DllImport(DLL_NAME, EntryPoint = "_function3")] public static extern int function3(byte[] param); } public static class Device03 { public const string DLL_NAME = @"Device03.dll"; [DllImport(DLL_NAME, EntryPoint = "_function1")] public static extern int function1(byte[] param); [DllImport(DLL_NAME, EntryPoint = "_function2")] public static extern int function2(byte[] param); [DllImport(DLL_NAME, EntryPoint = "_function3")] public static extern int function3(byte[] param); } } 

我还建议使用本机LoadLibraryGetProcAddress

对于后者,您只需使用与pinvoke方法签名匹配的委托类型调用Marshal.GetDelegateForFunctionPointer