Подтвердить что ты не робот

Как я могу реализовать свой собственный внешний вид?

В нашем продукте мы называем "сервисы", которые являются основным средством коммуникации между различными частями продукта (и особенно между языками - внутренним языком, 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, ...);
}

Но это довольно многословно, и не так легко управлять, когда делаете это для множества услуг, как я намереваюсь делать.

Увидев, как работают extern и DllImportAttribute, я надеюсь, что с ним можно будет связать это следующим образом:

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

Но я не знаю, как добиться этого вообще и не могу найти никакой документации для него (extern кажется довольно неопределенно определенным вопросом). Самое близкое, что я нашел, - это несколько связанный с этим вопрос, Как предоставить пользовательскую реализацию для extern-методов в .NET?, которая на самом деле не отвечала на мой вопрос и несколько отличается, так или иначе. Спецификация языка С# (особенно в версии 4.0, раздел 10.6.7, "Внешние методы" ) не помогает.

Итак, я хочу предоставить пользовательскую реализацию внешних методов; может ли это быть достигнуто? И если да, то как?

4b9b3361

Ответ 1

Ключевое слово С# extern делает очень мало, он просто сообщает компилятору, что в объявлении метода не будет тела. Компилятор делает минимальную проверку, он настаивает на том, что вы также предоставляете атрибут, все идет. Таким образом, этот примерный код будет скомпилирован просто:

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

        class FooBar : Attribute { }

        [FooBar]
        static extern void foo();
    }

Но, конечно, это не сработает, дрожь поднимает руки вверх в объявлении. Это то, что требуется для фактического запуска этого кода, это задание джиттера для создания для него правильного исполняемого кода. Требуется, чтобы джиттер распознал атрибут.

Это можно увидеть в исходном коде для джиттера в SSCLI20 distribution, clr/src/md/compiler/custattr. cpp исходный код, RegMeta:: _ HandleKnownCustomAttribute(). Этот код, который точен для .NET 2.0, мне неизвестны дополнения к нему, которые влияют на вызов метода. Вы увидите, что он обрабатывает следующие атрибуты, относящиеся к генерации кода для вызовов методов, тип, который будет использовать ключевое слово extern:

  • [DllImport], вы, несомненно, знаете его

  • [MethodImpl (MethodImplOptions.InternalCall)] - атрибут, который используется для методов, реализованных в среде CLR, вместо структуры. Они написаны на С++, CLR имеет внутреннюю таблицу, которая ссылается на функцию С++. Канонический пример - метод Math.Pow(), я описал подробности реализации в этом ответе. Таблица не является расширяемой, она жестко испечена в исходном коде CLR.

  • [ComImport] - атрибут, который отмечает интерфейс, реализованный в другом месте, неизменно на COM-сервере. Вы редко программируете этот атрибут напрямую, вместо этого вы должны использовать библиотеку interop, сгенерированную Tlbimp.exe. Этот атрибут также требует, чтобы атрибут [Guid] выдавал требуемый указатель интерфейса. Это в остальном аналогично атрибуту [DllImport], он генерирует вызов типа pinvoke для неуправляемого кода, но с использованием соглашений вызова COM. Разумеется, это нормально, если у вас действительно есть требуемый COM-сервер на вашем компьютере, в противном случае он будет бесконечно расширяемым.

В этой функции распознается множество атрибутов, но они не относятся к вызывающему коду, который определен в другом месте.

Поэтому, если вы не напишете свой собственный джиттер, использование extern не является жизнеспособным способом получить то, что вы хотите. Вы можете рассмотреть проект Mono, если хотите продолжить это.

Общие решения расширяемости, которые являются чистыми управляемыми, - это в значительной степени забытое пространство имен System.AddIn, очень популярная структура MEF и решения AOP, такие как Postsharp.

Ответ 2

Мне нужно было сделать что-то очень похожее (вызовы метода ретрансляции) в последнее время. Я закончил создание типа, который пересылаемый метод вызывает динамически во время выполнения.

Для вашего варианта использования реализация будет выглядеть примерно так. Сначала создайте интерфейс, описывающий вашу службу. Вы будете использовать этот интерфейс там, где вы хотите вызвать службу в своем коде.

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, который позволяет генерировать код во время компиляции. Это платное коммерческое решение.

Ответ 3

Даже если это не совсем то, о чем вы просили, я бы рекомендовал создать собственный T4 template, который будет генерировать эти вспомогательные методы. Это особенно полезно, если у вас есть программный API для получения списка имен служб и применимых типов параметров.