2010-03-01 5 views
10

Para definir un método en C que sea invocado por Lua, debe coincidir con una firma determinada y usar la API de Lua para recuperar parámetros y obtener resultados. Estoy escribiendo un contenedor C# de Lua y estoy interesado en poder llamar a métodos C# arbitrarios sin hacer que sigan estas convenciones. Cuando envuelve algo como D, uno puede usar el sistema de plantillas para crear dinámicamente este código de pegamento para cualquier método dado. Estaba pensando que esto también podría ser posible en C#, pero usando generación de código dinámico.¿Cómo usar la generación de código para crear dinámicamente métodos C#?

La API C se ve más o menos así, y el código generado podría manipular esto a través de una parte de nivel inferior de mi biblioteca que P/invoca la biblioteca 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 */ 
} 

Así que, básicamente, la idea es tomar un método de C#, reflejar sus parámetros y valores de retorno, generar (o recuperar de la memoria caché) un método que utiliza la API de Lua como la de arriba a pasar esos parámetros y devolver los tipos de retorno y finalmente empujar ese método a Lua. Entonces, cuando se llama a la función C# desde Lua, se parece a lua -> función de envoltura mágica -> función C# normal.

Gracias.

Respuesta

14

Si entiendo lo que quiere, parece que tienes 2 opciones:

  1. use the CodeDOM para generar y compilar código de forma dinámica, en tiempo de ejecución.
  2. emiten el código fuente real de C# y lo compilan dinámicamente en un ensamblado que se puede llamar en el tiempo de ejecución.

CodeDom es una especie de codigo de escritura de muy bajo nivel. La idea es que hay un modelo de objetos para el lenguaje C#. Comienza creando una instancia de CodeTypeDeclaration: esto generará un tipo o clase. A continuación, agrega propiedades y campos: aquí probablemente agregue declaraciones DllImport para sus funciones p/invoke. Luego, utiliza diferentes métodos de adición de CodeDOM para el tipo: aquí sería donde insertaría el método generado. Podrías hacerlo público, estático, lo que quieras.

CodeDOM se ve así:

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

... 

que sigue. y en. Como puedes ver, se pone bastante feo.Luego, luego lo compila, lo cual no se muestra. Supongo que no querrás tomar este enfoque.


Encontré CodeDom bastante complicado de usar; en su lugar, ahora cuando necesito conjuntos generados dinámicamente, voy a emitir el código de C# real, normalmente a través de plantillas, en una cadena en la memoria, y compilar que. Es mucho más simple para mis propósitos. La compilación es el siguiente:

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 

En el código anterior, LiteralSource contiene el código fuente para ser compilado. Como dije, genero esto leyendo una plantilla y completando los espacios en blanco.

+0

Este es un enfoque interesante, nunca pensé en escribir C# real y luego compilar eso. Probablemente lo que terminaré haciendo. Gracias. – jsimmons

+0

¿Cómo se generan las clases en el disco en primer lugar? – jcolebrand

+0

@jcolebrand - No estoy seguro de lo que estás preguntando, pero creo que deberías publicar una nueva pregunta. – Cheeso

0

Puede exponer su C# como COM, lo que permitiría que todos los métodos (públicos) se denominen aplicaciones externas.

O, exponga una sola función C# que llamaría a la otra función apropiada, tal vez codificada para la lista de funciones reales en C#, o tal vez utilizando la reflexión. Puede tomar una matriz de tamaño arbitrario para los parámetros.

+0

Utilizar el reflejo así es más o menos lo que quería evitar. – jsimmons

1

No estoy seguro de interpretar correctamente tu pregunta, pero es posible que desees echar un vistazo a Castle.Dynamic proxy. Le permite crear proxies para clases e interfaces y luego interceptar ciertas llamadas a métodos (cualquier cosa en una interfaz y cualquier cosa virtual en una clase real). Cuando interceptas la llamada, puedes mirar los argumentos y reenviar la llamada a la API de lua a través de P-Invoke. Hay un gran tutorial here.

+0

Eso es interesante, sin duda investigaré eso. Creo que sería más útil para llamar a los métodos Lua desde C# donde quiero llamar a los métodos C# de Lua. – jsimmons

0

Intente buscar en T4. Debido a que forma parte de Visual Studio, puede utilizar el marco de reflexión para buscar todos los métodos según sus preguntas. Busque en google y estoy seguro de que puede encontrar algún código de muestra o plantilla de personas que usen la reflexión con T4 para generar clases o métodos de envoltura.

+0

Esto no soluciona la posibilidad de invocar dinámicamente una función C# arbitraria en tiempo de ejecución desde LUA (o cualquier idioma) –

+0

Puede usar T4 para generar clases contenedoras y finalmente obtendrá el mismo resultado que usará CodeDOM como en la respuesta aceptada. No se trata de hacer que Lua pueda invocar dinámicamente cualquier método arbitrario. ¡Así que no entiendo su preocupación y voto negativo! –

+0

"... Me interesa poder llamar a métodos C# arbitrarios sin hacer que sigan estas convenciones ..." –

Cuestiones relacionadas