如何使用代码生成来动态创建 C# 方法?
Posted
技术标签:
【中文标题】如何使用代码生成来动态创建 C# 方法?【英文标题】:How to use code generation to dynamically create C# methods? 【发布时间】:2011-01-22 05:06:42 【问题描述】:为了在 C 中定义一个可由 Lua 调用的方法,它必须匹配给定的签名并使用 Lua API 来检索参数并返回结果。我正在编写 Lua 的 C# 包装器,并且我对能够调用任意 C# 方法而不使它们遵循这些约定感兴趣。当包装在 D 之类的东西中时,可以使用模板系统为任何给定的方法动态创建此胶合代码。我在想这在 C# 中也是可能的,但是通过使用动态代码生成。
C API 看起来像这样,生成的代码将通过我的库的较低级别部分来操作它,该部分 P/Invokes 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 */
所以基本上这个想法是采用一个 C# 方法,反映它的参数和返回值,生成(或从缓存中检索)一个使用 Lua API 的方法,就像上面一样,传递这些参数并返回这些返回类型,最后推送它Lua 的方法。因此,当从 Lua 调用 C# 函数时,它看起来像 lua -> 魔术包装函数 -> 普通 C# 函数。
谢谢。
【问题讨论】:
【参考方案1】:如果我明白你想要什么,看来你有两个选择:
-
use the CodeDOM 在运行时生成和动态编译代码。
发出实际的 C# 源代码,并在运行时将其动态编译为可调用程序集。
CodeDom 是一种毛茸茸的、非常低级的代码。这个想法是 C# 语言有一个对象模型。您首先实例化一个 CodeTypeDeclaration - 这将生成一个类型或类。然后您添加属性和字段 - 在这里您可能会为您的 p/invoke 函数添加 DllImport
声明。然后您使用不同的 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 使用起来非常笨拙;相反,现在当我需要动态生成的程序集时,我会将实际的 C# 代码(通常通过模板)发送到内存中的字符串中,然后编译 那个。就我的目的而言,这要简单得多。编译如下:
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
包含要编译的源代码。正如我所说,我通过阅读模板并填写空白来生成它。
【讨论】:
这是一种有趣的方法,我从没想过编写实际的 C# 然后编译它。大概我最终会做什么。谢谢。 首先如何在磁盘上生成类? @jcolebrand - 我不清楚你在问什么,但我认为你应该发布一个新问题。 是的,但这显然是一个棘手的对话。 chat.***.com/transcript/message/2889886#2889886我已经尝试过如何询问,如果你想看看我有什么,也许可以参与对话【参考方案2】:我不确定我是否正确解释了您的问题,但您可能想看看 Castle.Dynamic 代理。它允许您为类和接口创建代理,然后拦截某些方法调用(接口上的任何内容以及真实类上的任何虚拟内容)。当您拦截调用时,您只需查看参数并通过 P-Invoke 将调用转发到 lua API。有一个很棒的教程here。
【讨论】:
这很有趣,我一定会调查的。我认为这对于从 C# 调用 Lua 方法更有用,而我想从 Lua 调用 C# 方法。【参考方案3】:您可以将 C# 公开为 COM,这将允许将所有(公共)方法调用为外部应用程序。
或者,公开一个单独的 C# 函数,该函数将调用适当的其他函数,可能为 C# 中的实际函数列表硬编码,或者可能使用反射。它可能需要一个任意大小的参数数组。
【讨论】:
使用这样的反射几乎是我想要避免的。【参考方案4】:尝试查看T4。因为它本身是 Visual Studio 的一部分,所以您可以使用反射框架根据您的问题查找所有方法。在 google 上搜索,我相信您可以找到一些示例代码或模板,这些示例代码或模板已经使用 T4 反射来生成包装器类或方法。
【讨论】:
这不解决在运行时从 LUA(或任何语言)动态调用任意 C# 函数的问题 您可以使用 T4 生成包装器类,并最终获得与在接受的答案中使用 CodeDOM 相同的结果。这不是让 Lua 能够动态调用任意方法。所以我不理解你的关注和反对! "...我对能够调用任意 C# 方法而不使它们遵循这些约定很感兴趣..."以上是关于如何使用代码生成来动态创建 C# 方法?的主要内容,如果未能解决你的问题,请参考以下文章
如何从 CUDA C++ 创建和使用动态库“.so”并在 Linux 环境 (CentOS) 下的 C# 代码中使用它?