Как использовать генерацию кода для динамического создания методов С#?

Чтобы определить метод в C, вызываемый Lua, он должен соответствовать заданной сигнатуре и использовать Lua API для извлечения параметров и возврата результатов. Я пишу обертку С# Lua, и я заинтересован в возможности вызова произвольных методов С#, не заставляя их следовать этим соглашениям. При обертывании чего-то типа D, можно использовать систему шаблонов для динамического создания этого кода клея для любого данного метода. Я думал, что это возможно и в С#, но с использованием динамического генерации кода.

API C выглядит примерно так, и сгенерированный код будет манипулировать этим через часть более низкого уровня моей библиотеки, которая P/вызывает библиотеку Lua C.

static int foo (lua_State *L)
{
    int n = lua_gettop(L);    /* number of arguments */
    lua_Number sum = 0;
    int i;
    for (i = 1; i <= n; i++)
    {
        if (!lua_isnumber(L, i)) 
        {
            lua_pushstring(L, "incorrect argument");
            lua_error(L);
        }
        sum += lua_tonumber(L, i);
    }
    lua_pushnumber(L, sum/n);        /* first result */
    lua_pushnumber(L, sum);         /* second result */
    return 2;                   /* number of results */
}

Таким образом, в основном идея состоит в том, чтобы взять метод С#, отразить его параметры и вернуть значения, сгенерировать (или извлечь из кеша) метод, который использует API Lua, как описано выше, для передачи этих параметров и возврата этих возвращаемых типов и, наконец, метод для Lua. Поэтому, когда функция С# вызывается из Lua, она выглядит как lua ​​- > magic wrapper function → обычная функция С#.

Спасибо.

Ответ 1

Если я понимаю, что вы хотите, кажется, у вас есть 2 варианта:

  • использовать CodeDOM для генерации и динамической компиляции кода во время выполнения.
  • испускать фактический исходный код С# и динамически компилировать его в вызываемую сборку во время выполнения.

CodeDom - это своего рода волосатый, очень низкоуровневый код для написания. Идея есть объектная модель для языка С#. Вы начинаете с создания экземпляра CodeTypeDeclaration - это создаст тип или класс. Затем вы добавляете свойства и поля - здесь вы, вероятно, добавите объявления DllImport для своих функций p/invoke. Затем вы используете разные методы добавления CodeDOM к типу - это будет то место, где вы должны вставить сгенерированный метод. Вы можете сделать его общедоступным, статическим, каким бы вы ни хотели.

CodeDOM выглядит следующим образом:

System.Type mt= a[0].GetType();

System.CodeDom.CodeTypeDeclaration class1 = new System.CodeDom.CodeTypeDeclaration(mt.Name);
class1.IsClass=true;
class1.TypeAttributes = System.Reflection.TypeAttributes.Public;
class1.Comments.Add(new System.CodeDom.CodeCommentStatement("Wrapper class for " + mt.Name));

System.CodeDom.CodeConstructor ctor;
ctor= new System.CodeDom.CodeConstructor();
ctor.Attributes = System.CodeDom.MemberAttributes.Public;
ctor.Comments.Add(new System.CodeDom.CodeCommentStatement("the null constructor"));
class1.Members.Add(ctor);
ctor.Statements.Add(new System.CodeDom.CodeAssignStatement(new System.CodeDom.CodeVariableReferenceExpression("m_wrapped"), new System.CodeDom.CodeObjectCreateExpression(mt)));

ctor= new System.CodeDom.CodeConstructor();
ctor.Attributes = System.CodeDom.MemberAttributes.Public;
ctor.Comments.Add(new System.CodeDom.CodeCommentStatement("the 'copy' constructor"));
class1.Members.Add(ctor);
ctor.Parameters.Add(new System.CodeDom.CodeParameterDeclarationExpression(mt,"X"));
ctor.Statements.Add(new System.CodeDom.CodeAssignStatement(new System.CodeDom.CodeVariableReferenceExpression("m_wrapped"), new System.CodeDom.CodeVariableReferenceExpression("X")));

// embed a local (private) copy of the wrapped type
System.CodeDom.CodeMemberField field1;
field1= new System.CodeDom.CodeMemberField();
field1.Attributes = System.CodeDom.MemberAttributes.Private;
field1.Name= "m_wrapped";
field1.Type=new System.CodeDom.CodeTypeReference(mt);
class1.Members.Add(field1);

...

он продолжается. и дальше. Как вы можете видеть, это становится довольно уродливым. Затем вы скомпилируете его, что я не показывал. Я предполагаю, что вы не захотите принять этот подход.


Я нашел CodeDom довольно крутым для использования; вместо этого, теперь, когда мне нужны динамически сгенерированные сборки, я буду испускать фактический код С#, обычно через шаблоны, в строку в памяти и компилировать это. Это намного проще для моих целей. Компиляция выглядит следующим образом:

var cp = new System.CodeDom.Compiler.CompilerParameters {
  ReferencedAssemblies.Add(filesystemLocation), // like /R: option on csc.exe
  GenerateInMemory = true,    // you will get a System.Reflection.Assembly back
  GenerateExecutable = false, // Dll
  IncludeDebugInformation = false,
  CompilerOptions = ""
};

var csharp = new Microsoft.CSharp.CSharpCodeProvider();

// this actually runs csc.exe:
System.CodeDom.Compiler.CompilerResults cr = 
      csharp.CompileAssemblyFromSource(cp, LiteralSource);


// cr.Output contains the output from the command

if (cr.Errors.Count != 0)
{
    // handle errors
}

System.Reflection.Assembly a = cr.CompiledAssembly;

// party on the type here, either via reflection...
System.Type t = a.GetType("TheDynamicallyGeneratedType");

// or via a wellknown interface

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

Ответ 2

Я не уверен, что правильно интерпретирую ваш вопрос, но вы можете взглянуть на Castle.Dynamic proxy. Он позволяет создавать прокси для классов и интерфейсов, а затем перехватывать определенные вызовы методов (что-то вообще на интерфейсе и что-то виртуальное на реальном классе). Когда вы перехватываете вызов, вы можете просто просмотреть аргументы и перенаправить вызов в API lua через P-Invoke. Там отличный учебник здесь.

Ответ 3

Вы можете представить свой С# как COM, который позволит всем (общедоступным) методам вызываться внешними приложениями.

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

Ответ 4

Попробуйте найти T4. Поскольку он изначально является частью Visual Studio, вы можете использовать структуру отражения, чтобы найти все методы в соответствии с вашими вопросами. Поиск в google и я уверен, что вы можете найти пример кода или шаблона людей, использующих отражение с T4 уже для создания классов-оболочек или методов.