How to use code generation to dynamically create C# methods?

后端 未结 4 1059
忘了有多久
忘了有多久 2021-02-05 17:11

In order to define a method in C that is callable by Lua it has to match a given signature and use the Lua API to retrieve parameters and return results. I\'m writing a C# wrapp

4条回答
  •  北恋
    北恋 (楼主)
    2021-02-05 17:47

    If I understand what you want, it seems you have 2 options:

    1. use the CodeDOM to generate and dynamically compile code, at runtime.
    2. emit actual C# source code, and dynamically compile it into a callable assembly at runtime.

    CodeDom is sort of hairy, very low-level code to write. The idea is there's an object model for the C# language. You start by instantiating a CodeTypeDeclaration - this will generate a type or class. Then you add properties and fields - here you would likely add DllImport declarations for your p/invoke functions. Then you use different CodeDOM add methods to the type - this would be where you'd insert the generated method. You could make it public, static, whatever you like.

    CodeDOM looks like this:

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

    it goes on. and on. As you can see, it gets pretty ugly. Then later you compile it, which I did not show. I'm assuming you're not gonna want to take this approach.


    I found CodeDom to be pretty crufty to use; instead, now when I need dynamically-generated assemblies, I will emit actual C# code, normally via templates, into a string in memory, and compile that. It's much simpler for my purposes. The compilation looks like this:

    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
    

    In the above code, LiteralSource contains the source code to be compiled. As I said, I generate this by reading a template and filling in the blanks.

提交回复
热议问题