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

后端 未结 4 1069
忘了有多久
忘了有多久 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.

    0 讨论(0)
  • 2021-02-05 17:48

    You could expose your C# as COM, which would allow all the (public) methods to be called be external apps.

    Or, expose a single C# function which would call the appropriate other function, perhaps hard-coded for the list of actual functions in C#, or perhaps using reflection. It might take an arbitrary sized array for the parameters.

    0 讨论(0)
  • 2021-02-05 17:50

    Try looking into T4. Because it natively part of Visual Studio, you are able to use the reflection framework to find all the methods as per your questions. Search on google and I'm sure you can find some sample code or template of people using reflection with T4 already to generate wrapper classes or methods.

    0 讨论(0)
  • 2021-02-05 17:53

    I'm not sure that I'm interpreting your question correctly but you might want to have a look at Castle.Dynamic proxy. It allows you to create proxies for classes and interfaces and then intercept certain method calls (anything at all on an interface and anything virtual on a real class). When you intercept the call you can just look at the arguments and forward the call to the lua API via P-Invoke. There's a great tutorial here.

    0 讨论(0)
提交回复
热议问题