11

我们有一个供应商提供一个库来访问他们的硬件。不幸的是,如果您有多个设备,则需要使用不同的 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# 中我没有那个选项。

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

谢谢,

编辑:函数原型变化很大,因此任何依赖它们相同的方法都不合适。到目前为止,感谢您的建议,我并没有这么快就提出这么多想法。

4

3 回答 3

15

只是一些考虑:

替代#one

编辑:这种方法需要更改编译方法,这很困难,需要注入、修改程序集或其他 AOP 领域常用的方法。考虑下面的方法二,这更容易。

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

备选方案二:

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

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

替代#三

编辑:进一步看,有一种更简单的方法:只需使用DefinePInvokeMethodwhich 即可满足您的所有需求。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<T>(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<Function02>
    ("GetTickCount", "kernel32.dll");

Debug.WriteLine(getTickCount());

我猜其他方法是可能的(就像其他人在这个线程中提到的模板方法)。

更新:添加了优秀代码项目文章的链接。
更新:添加了第三种更简单的方法。
更新:添加了代码示例
更新:更新了代码示例以与任何函数原型无缝协作
更新:修复了可怕的错误:当然typeof(Function02)应该typeof(T)

于 2009-11-02T11:44:52.187 回答
4

如何使用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);

    }
}
于 2009-11-02T11:59:55.143 回答
3

我也只是建议使用本机LoadLibraryGetProcAddress.

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

于 2009-11-02T12:07:51.340 回答