10


5

ハードウェアにアクセスするためのライブラリを提供するサプライヤがあります。 残念ながら、あなたが複数のデバイスを持っている場合、あなたは異なる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を使用していた場合は、1つのファイルに関数を定義して静的クラスに複数回含めるだけで済みますが、それよりも優れていますが、C#ではそのオプションはありません。

必要なだけ多くの静的デバイスクラスを生成することを可能にするファクトリを効果的に定義する方法について誰かが巧妙なアイデアを持っているなら、私は非常に興味があるでしょう。

ありがとう、

編集:関数のプロトタイプはかなりバラエティに富んでいるので、それらを同じものに依存する方法は適していないでしょう。 これまでのところ提案をありがとう、私はそれほど多くのアイデアをそれほど早く拡張していませんでした。

3 Answer


14


いくつか考慮すべき点があります。

代替#one

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

  1. 同じシグネチャを持つすべての関数を削除し、それぞれ1つずつ残します

  2. GetIlAsByteArrayを使用して、DllImportの動的メソッドを作成します 方法

  3. Use http://blogs.msdn.com/haibo_luo/archive/2006/11/07/turn-methodinfo-to-dynamicmethod.aspx [ここで説明する手法]関数のILを操作するには、ここでDllImport属性を変更できます等

  4. これらの関数のデリゲートを作成してあなたの呼び出しをキャッシュしてください

  5. デリゲートを返す

代わりの#two:

編集:この代替アプローチは最初は少し複雑に思われますが、誰かがすでにあなたのために仕事をしました。 http://www.codeproject.com/KB/dotnet/DynamicDllImport.aspx [この優れたCodeProjectの記事]を参照し、そのコードをダウンロードして使用し、DllImportスタイルのメソッドを動的に作成してください。 基本的には、次のようになります。

  1. すべてのDllImportを削除

  2. 独自のDllImportラッパーを作成:dll名と関数を受け取ります 名前(すべての署名が等しいと仮定)

  3. ラッパーは「手動」のDllImportを実行します http://www.pinvoke.net/default.aspx/kernel32/LoadLibrary.html [LoadLibrary]またはhttp://www.pinvoke.net/default.aspx/kernel32/LoadLibraryEx.html [` LoadLibraryEx`]を使用してdllimport API関数

  4. ラッパーは `MethodBuilder`を使ってあなたのためのメソッドを作成します。

  5. 関数として使用できるそのメソッドへのデリゲートを返します。

代替の#three

編集:さらに見て、より簡単なアプローチがあります:単にあなたが必要とするすべてをするhttp://msdn.microsoft.com/en-us/library/hb2et051.aspx [DefinePInvokeMethod]を使う。 MSDNリンクは既に良い例を示していますが、DLLと関数名に基づいて任意のネイティブDLLを作成できるフルラッパーは、http://www.codeproject.com/KB/cs/dynamicinvokedll.aspx?msg = 1008849で提供されていますこの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(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());

他のアプローチも可能です、と私は思います(このスレッドで他の人によって言及されたテンプレート化アプローチのように)。

更新: http://www.codeproject.com/KB/dotnet/DynamicDllImport.aspx [優れたコードプロジェクトの記事]へのリンクを追加 更新: 3番目とさらに簡単な方法が追加されました。 *更新:*コードサンプルの追加*更新:*関数プロトタイプとシームレスに連携するようにコードサンプルを更新


4


http://www.hanselman.com/blog/T4TextTemplateTransformationToolkitCodeGenerationBestKeptVisualStudioSecret.aspx[T4](テキストテンプレート変換ツールキット)を使用する方法 次の内容の.ttファイルを作成します。
System.Runtime.InteropServicesを使用した<#@ template language = "C#"#> namespace MyNamespace {<#foreach(DeviceNameの文字列deviceName){#>パブリック静的クラス<#= deviceName#> {public const string DLL_NAME = @ "<#= deviceName#>。dll"; <#foreach(FunctionNameの文字列functionName){#> [DllImport(DLL_NAME、EntryPoint = "<#= functionName#>")] public static extern int <#= functionName.Substring(1)#>(byte []パラメータ; <#}#>} <#}#>} <#string [] DeviceNames =新しい文字列[] {"Device01"、 "Device02"、 "Device03"}; string [] FunctionNames = new string [] {"_function1"、 "_function2"、 "_function3"}; #>

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

System.Runtime.InteropServicesを使用します。ネームスペース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 = "_function 3")] public static extern int function 3(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 = "_function 3")] public static extern int function 3(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 = "_function 3")] public static extern int function 3(byte [] param);

}}


3


ネイティブの LoadLibrary`と GetProcAddress`を使うことをお勧めします

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