12

私たちの製品には「サービス」と呼ばれるものがあります。これは、製品のさまざまな部分間 (特に言語間 (特に社内言語、C、Python、および .NET)) の通信の基本的な手段です。

現在、コードは次のようになっています (Services.Execute利用params object[] args):

myString = (string)Services.Execute("service_name", arg1, arg2, ...);

私はむしろ、このようなコードを記述して、型チェックとあまり冗長でないコードの利点を得られるようにしたいと考えています。

myString = ServiceName(arg1, arg2, ...);

これは、単純な関数で実現できます。

public static string ServiceName(int arg1, Entity arg2, ...)
{
    return (string)Services.Execute("service_name", arg1, arg2, ...);
}

しかし、これはかなり冗長であり、多数のサービスに対して実行する場合、私が意図しているように管理するのはそれほど簡単ではありません。

方法externDllImportAttribute作業を見て、次のような方法でこれを接続できることを願っています。

[ServiceImport("service_name")]
public static extern string ServiceName(int arg1, Entity arg2, ...);

しかし、これを達成する方法がまったくわからず、それに関するドキュメントが見つからないようです(externかなり漠然と定義されているようです)。私が見つけた最も近いものは、やや関連する質問です。.NET で extern メソッドのカスタム実装を提供するにはどうすればよいですか? とにかく、これは私の質問に実際には答えませんでした。C# 言語仕様 (特に、バージョン 4.0、セクション 10.6.7、外部メソッド) は役に立ちません。

そこで、外部メソッドのカスタム実装を提供したいと思います。これは達成できますか?もしそうなら、どのように?

4

3 に答える 3

5

C# のexternキーワードはほとんど何もしません。メソッド宣言に本体がないことをコンパイラに伝えるだけです。コンパイラは最小限のチェックを行い、属性も提供することを主張します。何でも構いません。したがって、このサンプル コードは正常にコンパイルされます。

   class Program {
        static void Main(string[] args) {
            foo();
        }

        class FooBar : Attribute { }

        [FooBar]
        static extern void foo();
    }

しかし、もちろん実行されません。ジッターは宣言に手を上げます。このコードを実際に実行するには、これが必要です。このための適切な実行可能コードを生成するのがジッターの仕事です。必要なのは、ジッタが属性を認識することです。

これは、 SSCLI20 ディストリビューションのジッターのソース コード、clr/src/md/compiler/custattr.cpp ソース コード ファイル、RegMeta::_HandleKnownCustomAttribute() 関数で確認できます。これは .NET 2.0 に対して正確なコードです。メソッドの呼び出しに影響するコードへの追加については知りません。externキーワードを使用する種類のメソッド呼び出しのコード生成に関連する次の属性を処理することがわかります。

  • [DllImport]、あなたは間違いなくそれを知っています

  • [MethodImpl(MethodImplOptions.InternalCall)]、フレームワークではなく CLR に実装されているメソッドで使用される属性。それらは C++ で書かれており、CLR には C++ 関数にリンクする内部テーブルがあります。標準的な例は Math.Pow() メソッドです。この回答で実装の詳細を説明しました。それ以外の場合、テーブルは拡張可能ではなく、CLR ソース コードでハードベークされています。

  • [ComImport] は、インターフェイスが別の場所 (常に COM サーバー) に実装されていることを示す属性です。この属性を直接プログラムすることはめったになく、代わりに Tlbimp.exe によって生成された相互運用ライブラリを使用します。この属性には、必要なインターフェイスの GUID を指定するための [Guid] 属性も必要です。それ以外の点では [DllImport] 属性に似ています。アンマネージ コードへの pinvoke のような呼び出しを生成しますが、COM 呼び出し規則を使用します。もちろん、これはマシンに必要な COM サーバーが実際にある場合にのみ適切に機能しますが、それ以外の場合は無限に拡張可能です。

この関数ではさらに多くの属性が認識されますが、他の場所で定義されたコードの呼び出しには関係しません。

そのため、独自のジッターを作成しない限り、externを使用しても、必要なものを取得するための実行可能な方法はありません。とにかくこれを追求したい場合は、Mono プロジェクトを検討できます。

純粋に管理された一般的な拡張ソリューションは、ほとんど忘れられている System.AddIn 名前空間、非常に人気のある MEF フレームワーク、および Postsharp のような AOP ソリューションです。

于 2012-12-06T14:57:46.233 に答える
1

それはあなたが求めていたものではありませんが、これらのヘルパーメソッドを生成する独自のT4テンプレートを作成することをお勧めします。これは、サービス名とその適用可能なパラメータータイプのリストを取得するためのプログラムAPIがある場合に特に役立ちます。

于 2012-12-06T15:20:17.547 に答える
1

最近、非常に似たようなこと (メソッド呼び出しの中継) を行う必要がありました。実行時にメソッド呼び出しを動的に転送する型を生成することになりました。

あなたのユースケースでは、実装は次のようになります。最初に、サービスを記述するインターフェースを作成します。コード内でサービスを呼び出したい場合は、このインターフェイスを使用します。

public interface IMyService
{
    [ServiceImport("service_name")]
    string ServiceName(int arg1, string arg2);
}

次に、コードを実行して、このインターフェイスを動的に実装するクラスを生成します。

// Get handle to the method that is going to be called.
MethodInfo executeMethod = typeof(Services).GetMethod("Execute");

// Create assembly, module and a type (class) in it.
AssemblyName assemblyName = new AssemblyName("MyAssembly");
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run, (IEnumerable<CustomAttributeBuilder>)null);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MyModule");
TypeBuilder typeBuilder = moduleBuilder.DefineType("MyClass", TypeAttributes.Class | TypeAttributes.Public, typeof(object), new Type[] { typeof(IMyService) });
typeBuilder.DefineDefaultConstructor(MethodAttributes.Public);

// Implement each interface method.
foreach (MethodInfo method in typeof(IMyService).GetMethods())
{
    ServiceImportAttribute attr = method
        .GetCustomAttributes(typeof(ServiceImportAttribute), false)
        .Cast<ServiceImportAttribute>()
        .SingleOrDefault();

    var parameters = method.GetParameters();

    if (attr == null)
    {
        throw new ArgumentException(string.Format("Method {0} on interface IMyService does not define ServiceImport attribute."));
    }
    else
    {
        // There is ServiceImport attribute defined on the method.
        // Implement the method.
        MethodBuilder methodBuilder = typeBuilder.DefineMethod(
            method.Name,
            MethodAttributes.Public | MethodAttributes.Virtual,
            CallingConventions.HasThis,
            method.ReturnType,
            parameters.Select(p => p.ParameterType).ToArray());

        // Generate the method body.
        ILGenerator methodGenerator = methodBuilder.GetILGenerator();

        LocalBuilder paramsLocal = methodGenerator.DeclareLocal(typeof(object[])); // Create the local variable for the params array.
        methodGenerator.Emit(OpCodes.Ldc_I4, parameters.Length); // Amount of elements in the params array.
        methodGenerator.Emit(OpCodes.Newarr, typeof(object)); // Create the new array.
        methodGenerator.Emit(OpCodes.Stloc, paramsLocal); // Store the array in the local variable.

        // Copy method parameters to the params array.
        for (int i = 0; i < parameters.Length; i++)
        {
            methodGenerator.Emit(OpCodes.Ldloc, paramsLocal); // Load the params local variable.
            methodGenerator.Emit(OpCodes.Ldc_I4, i); // Value will be saved in the index i.
            methodGenerator.Emit(OpCodes.Ldarg, (short)(i + 1)); // Load value of the (i + 1) parameter. Note that parameter with index 0 is skipped, because it is "this".
            if (parameters[i].ParameterType.IsValueType)
            {
                methodGenerator.Emit(OpCodes.Box, parameters[i].ParameterType); // If the parameter is of value type, it needs to be boxed, otherwise it cannot be put into object[] array.
            }

            methodGenerator.Emit(OpCodes.Stelem, typeof(object)); // Set element in the array.
        }

        // Call the method.
        methodGenerator.Emit(OpCodes.Ldstr, attr.Name); // Load name of the service to execute.
        methodGenerator.Emit(OpCodes.Ldloc, paramsLocal); // Load the params array.
        methodGenerator.Emit(OpCodes.Call, executeMethod); // Invoke the "Execute" method.
        methodGenerator.Emit(OpCodes.Ret); // Return the returned value.
    }
}

Type generatedType = typeBuilder.CreateType();

// Create an instance of the type and test it.
IMyService service = (IMyService)generatedType.GetConstructor(new Type[] { }).Invoke(new object[] { });
service.ServiceName(1, "aaa");

このソリューションはおそらく少し面倒ですが、自分でコードを作成する手間を省きたい場合は、非常にうまく機能します。動的型の作成に関連するパフォーマンス ヒットがあることに注意してください。ただし、これは通常、初期化中に行われ、ランタイムにあまり影響を与えるべきではありません。

あるいは、コンパイル時にコードを生成できるPostSharpを検討することをお勧めします。ただし、有料の商用ソリューションです。

于 2012-12-06T13:59:58.193 に答える