11

ハードウェアにアクセスするためのライブラリを提供するサプライヤがあります。残念ながら、複数のデバイスがある場合は、異なるdll名でライブラリを複数回インポートする必要があります。結果として、重複したコードが1メートルトンあり、まもなくメンテナンスの悪夢になるのではないかと心配しています。

現在私たちが持っているのは次のようなものです。

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++を使用している場合は、関数を1つのファイルで定義し、静的クラスに複数回#includeします。これは、他の方法よりも優れていますが、C#ではそのオプションがありません。

必要な数の静的デバイスクラスを生成できるファクトリを効果的に定義する方法について誰かが賢いアイデアを持っているなら、私は非常に興味があります。

ありがとう、

編集:関数プロトタイプは非常に多様であるため、それらが同じであることに依存する方法は適切ではありません。これまでの提案に感謝しますが、私はそれほど多くのアイデアをそれほど迅速に説明していませんでした。

4

3 に答える 3

15

いくつかの考慮事項:

代替#one

編集:このアプローチでは、コンパイルされたメソッドを変更する必要があります。これは困難であり、注入、アセンブリの変更、またはAOPランドで一般的に使用されるその他のメソッドが必要です。以下のアプローチ2を検討してください。これは簡単です。

  1. 同じ署名を持つすべての関数を削除し、それぞれ1つを残します
  2. GetIlAsByteArrayメソッドの動的メソッドを作成するためにDllImport使用します
  3. ここで説明する手法を使用して、関数のILを操作します。ここで、DllImport属性などを変更できます。
  4. これらの関数のデリゲートを作成し、通話をキャッシュします
  5. 代理人を返す

代替案#2:

編集:この代替アプローチは最初は少し複雑に見えますが、誰かがすでにあなたのために仕事をしてくれました。この優れたCodeProjectの記事を検索し、そのコードをダウンロードして使用するだけで、DllImportスタイルのメソッドを動的に作成できます。基本的に、それは次のようになります。

  1. すべてのDllImportを削除します
  2. 独自のDllImportラッパーを作成します:dll名と関数名を取ります(すべての署名が等しいと仮定します)
  3. ラッパーは、dllimportAPI関数を使用してLoadLibraryまたは使用して「手動」DllImportを実行しますLoadLibraryEx
  4. ラッパーは、を使用してメソッドを作成しますMethodBuilder
  5. 関数として使用できるそのメソッドへのデリゲートを返します。

代替案#3

編集:さらに見てみると、より簡単なアプローチがDefinePInvokeMethodあります:必要なすべてを実行するだけです。MSDNリンクはすでに良い例を示していますが、DLLと関数名に基づいてネイティブDLLを作成できる完全なラッパーは、このCodeProjectの記事で提供されています。

  1. すべてのDllImportスタイルの署名を削除します
  2. 簡単なラッパーメソッドを作成するDefinePInvokeMethod
  3. 呼び出しごとにメソッド全体が構築されないように、必ず単純なキャッシュ(辞書?)を追加してください
  4. ラッパーからデリゲートを返します。

このアプローチがコードでどのように見えるかを次に示します。返されたデリゲートを好きなだけ再利用できます。動的メソッドのコストのかかる構築は、メソッドごとに1回だけ実行する必要があります。

編集:コードサンプルを更新して、任意のデリゲートで機能し、デリゲート署名からの正しいリターンタイプとパラメータータイプを自動的に反映するようにしました。このようにして、実装を署名から完全に切り離しました。これは、現在の状況を考慮して、私たちができる最善のことです。利点:型安全性と単一の変更点があります。つまり、非常に簡単に管理できます。

// 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());

他のアプローチも可能だと思います(このスレッドで他の誰かが言及したテンプレートアプローチのように)。

更新:優れたcodeproject記事へのリンクを追加しました。
更新: 3番目のはるかに簡単なアプローチが追加されました。
更新:コードサンプルを追加更新
関数プロトタイプとシームレスに連携するようにコードサンプルを
更新更新:恐ろしいエラーを修正:もちろん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" };
#>

VisualStudioはこれを次のように変換します。

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

また、ネイティブLoadLibraryとを使用することをお勧めしGetProcAddressます。

後者の場合Marshal.GetDelegateForFunctionPointer、pinvokeメソッドのシグネチャに一致するデリゲート型で呼び出すだけです。

于 2009-11-02T12:07:51.340 に答える