我用
DefineDynamicAssembly(myAssembly, AssemblyBuilderAccess.Save);没有看到创建出来的.dll文件

解决方案 »

  1.   

    http://www.rainsts.net/article.asp?id=283里面 Emit、CodeDom 相关的文章就是你要的答案。
      

  2.   

    微软社区文章作者:阎宏2003-4
    第1页
    如何在.NET 中实现eval 函数
    —源代码的动态编译和执行
    撰文/阎宏
    (用户名jeffyan77 电子邮件地址[email protected]
    目录
    摘要............................ 1
    引言............................ 1
    需求和用户界面........ 2
    对象化的代码生成.... 2
    动态的代码编译........ 3
    eval 函数.................... 5
    用户脚本代码的编译和执行.................................................................................................................... 6
    小节............................ 7
    附录............................ 8
    参考文献...................10
    摘要
    本文讨论了在.NET 框架中动态地编译源代码和执行源代码的技术,并给出了两个简单的例子,以
    及它们在的VB.NET 和C#.NET 语言中的源代码。
    引言
    曾经有很多朋友问过我这样一个问题:怎样动态地执行一段运行时间输入的代码?比如用户可以在
    系统运行的时候,以字符串方式输入一段计算式:1+2*4+4*5-6,程序得到上面这个计算式之后,
    将计算式的结果算出。使用过dBase,Foxpro 和Clipper 的读者可能会联想起eval 这个函数(有时
    叫做evaluator)。这些以解释方式运行的语言,允许系统以字符串方式传入一个计算式,这个eval
    函数会把计算结果返回。计算式里面含有什么内容,可以由程序在运行时间动态地直接或者间接地
    根据用户所输入的数据决定。在应用程序编译的时候,这个计算式的内容还没有确定,因此也不可
    能被编译。编译或者解释编译必然是在程序运行时间完成的。
    更为高级的做法,是允许一个系统的用户编写自己的脚本代码,由系统在运行时间进行编译、执
    行。一些高级应用系统,比如Crystal Reports(水晶报表)就允许用户自行定义一些函数,由系统
    将之存储起来,并在合适的时间执行这些函数。这些函数在水晶报表系统被编译的时候,尚不存
    在,它们是动态地被编译的。
    很遗憾,Visual Basic 6.0 以及以前的版本并不提供类似于eval 函数这样的功能;而除非使用某种
    ActiveScripting 控件,使用Visual Basic 写成的系统往往也不能提供用户定义的脚本功能。
    令人高兴的是,.NET 框架提供了非常强大的代码编译、动态的类加载功能,这些功能只要善加利
    用,不仅仅可以提供简单得eval 函数的功能,而且完全可以提供用户脚本功能。本文拟就简单的
    eval 函数的实现问题作一详尽的讨论。对实现用户脚本功能的读者来说,本文仅仅是一个开始。
    微软社区文章作者:阎宏2003-4
    第2页
    需求和用户界面
    首先需要更为精确地定义出需要完成的任务:
    第一、用户可以在运行时间给出一个计算式(比如1+2*4+4*5-6)
    第二、这个计算式将以字符串的方式传入到系统中
    第三、系统计算出这个计算式的结果
    第四、系统将计算结果返还给用户
    第五、如果系统出现异常,应当将异常的内容显示出来
    根据上面的需求,可以设计一个简易的用户界面如下。可以看出,这个用户界面的左上方是一个可
    供用户输入计算式的文字框,在用户点击“Eval”按键之后,系统将计算式的计算结果显示在
    “=”后面,并将可能的出错信息显示在下面的Label
    对象化的代码生成
    VB.NET 和C#.NET 均是纯粹的面向对象的编程语言,在这样的语言中,代码都是以类为单位的。
    换言之,能够进行动态编译的不可能是一个计算式或者函数,而必须是一个类。为了把这个计算式
    封装到一个类里面,本文采取下面的一个简单的类:
    代码清单1、对计算式进行封装后得到的MyNewClass 类
    Imports System
    NameSpace MyNamespace
    Public Class MyNewClass
    Public Shared Function MySub()
    Return 1+2*4+4*5-6
    End Function
    End Class
    End NameSpace
    注意其中使用了一个示意性的计算式“1+2*4+4*5-6”,这个计算式必须替换成为用户自己输入的
    计算式。
    换句话说,当用户输入一个计算式的时候,本系统会将这个计算式封装到上面这个类中,然后将这
    个类送到编译器进行编译。假设这个封装计算式的功能都在一个叫做EvalClassFactory 的类中
    实现,那么这个类中需要如下的Create 方法对输入的计算式进行封装:
    微软社区文章作者:阎宏2003-4
    第3页
    代码清单2、对计算式进行封装的EvalClassFactory类
    Public Class EvalClassFactory : Implements IClassFactory
    Public Function Create(ByVal formula As String) As String _
    Implements IClassFactory.Create
    Dim ret As New StringBuilder("")
    ret.Append("Imports System").Append(vbCrLf)
    ret.Append("NameSpace MyNamespace").Append(vbCrLf)
    ret.Append("Public Class MyNewClass").Append(vbCrLf)
    ret.Append(" Public Shared Function MySub()").Append(vbCrLf)
    ret.Append(" Return ").Append(formula).Append(vbCrLf)
    ret.Append(" End Function").Append(vbCrLf)
    ret.Append("End Class").Append(vbCrLf)
    ret.Append("End NameSpace")
    Debug.WriteLine(ret.ToString)
    Return ret.ToString
    End Function
    End Class
    其中使用了一个接口作为抽象类型,因为本文在后面还会需要一个实现这个接口的另一个具体类
    ScriptClassFactory 提供一个简单的用户脚本的编译和讨论。
    我们把EvalClassFactory 类称为代码工厂类。熟悉设计模式的读者里可以可以看出,这里使用
    了工厂方法模式,参见下图:
    动态的代码编译
    下面来研究本文的核心部分,也就是代码如何动态地编译。这部分功能封装在Evaluator 类中。
    首先系统需要创建一个VB.NET 编译器的实例
    微软社区文章作者:阎宏2003-4
    第4页
    代码清单3.1
    Dim compiler As ICodeCompiler
    Dim compilerParams As CompilerParameters
    compiler = New VBCodeProvider().CreateCompiler()
    注意如果被动态编译的语言不是VB.NET而是C#,那么就应当将上面的VBCodeProvider改为
    CSharpCodeProvider。
    然后加载编译所需要的库集(Assembly)。一般来说单单为了eval函数,我们并不需要Forms库
    集;但是后面讨论用户脚本代码编译的时候,为了支持操纵Windows控件,就需要
    System.Windows.Forms库集。
      

  3.   

    代码清单3.2
    compilerParams = New CompilerParameters()
    compilerParams.ReferencedAssemblies.Add("System.dll")
    compilerParams.ReferencedAssemblies.Add("System.Windows.Forms.dll")
    然后将库集加载到内存空间:
    代码清单3.3
    compilerParams.GenerateInMemory = True
    假设这个时候我们已经得到了完整的封装类代码code,现在就可以将它传递给编译器进行编译:
    代码清单3.4
    Dim compiled As CompilerResults
    compiled = compiler.CompileAssemblyFromSource(compilerParams, code)
    如果编译失败的话,就应当终止执行,并记录下所有的错误信息,以便提供给用户:
    代码清单3.5
    If (Not compiled.Errors.HasErrors) Then
    errorMsg = "No error."
    Else
    'Create Error String
    errorMsg = compiled.Errors.Count.ToString() + " error(s):"
    Dim iCount As Integer
    For iCount = 0 To compiled.Errors.Count - 1
    errorMsg = errorMsg & vbCrLf & "Line: " _
    & compiled.Errors(iCount).Line.ToString & " - " _
    & compiled.Errors(iCount).ErrorText
    Return Nothing
    Next iCount
    End If
    假设编译是成功的,现在就应当加载封装类了,记住我们的封装类位于MyNamespace 名空间,类
    名是.MyNewClass:
    代码清单3.6
    Dim myAssembly As System.Reflection.Assembly
    myAssembly = compiled.CompiledAssembly
    Dim loadedObject As Object
    loadedObject = myAssembly.CreateInstance("MyNamespace.MyNewClass ")
    微软社区文章作者:阎宏2003-4
    第5页
    系统必须检验加载是否成功,如果不成功,就应当终止执行:
    代码清单3.7
    If (loadedObject Is Nothing) Then
    MessageBox.Show("Couldn't load class.")
    Return Nothing
    End If
    最后如果加载是成功的,就应当调用MySub()方法,并将结果返还给调用端。但是因为这个类是动
    态加载的,所以只能通过反身映射(Reflection)调用:
    代码清单3.8
    Try
    Dim retValue As Object = loadedObject.GetType().InvokeMember(
    "MySub", BindingFlags.InvokeMethod, Nothing,
    loadedObject, Nothing)
    Return retValue
    Catch e As Exception
    MessageBox.Show(e.Message, "Compiler Demo",
    MessageBoxButtons.OK, MessageBoxIcon.Information)
    End Try
    本小节所谈论的代码不仅仅适用于eval 函数,也同样适用于用户脚本功能,因为两者的区别只限于
    MySub()方法的内容不同。
    为了在两种情况下都能使用本小节的代码,我们将这些代码放到一个Private 方法runcode
    中,作为参数接受封装类的代码:
    代码清单3.9
    Private Function runcode(ByVal code As String)
    eval 函数
    实现这个eval 函数功能的就是Evaluator.eval()方法:
    代码清单4、Evaluator.eval()函数
    Public Function eval(ByVal code As String) As Object
    factory = New EvalClassFactory()
    Return runcode(factory.Create(code))
    End Function
    这个方法调用代码生成工厂EvalClassFactory 生成封装了计算式的封装类,然后调用私有方法
    runcode()执行这个封装类的MySub()方法。
    下面是系统在运行时的情况:
    微软社区文章作者:阎宏2003-4
    第6页
    如果用户输入的计算式不能通过编译的话,系统就会将出错信息打印到界面上。
    用户脚本代码的编译和执行
    正如前面所指出的,用户脚本代码的编译和执行与eval 函数的主要区别在于封装类的定义不同。下
    面就提供一种最为简单的脚本代码编译执行方案。本文所说的脚本语言,其实就是标准的.NET 语
    言。
    首先,我们必须给出一个用于脚本代码的工厂类。当然,这个类也应当实现IClassFactory 接
    口。
    代码清单5、ScriptClassFactory 类的源代码
    Public Class ScriptClassFactory : Implements IClassFactory
    Public Function Create(ByVal script As String) As String _
    Implements IClassFactory.Create
    Dim ret As New StringBuilder("")
    ret.Append("Imports System").Append(vbCrLf)
    ret.Append("Imports System.IO").Append(vbCrLf)
    ret.Append("Imports System.Windows.Forms").Append(vbCrLf)
    ret.Append("NameSpace MyNamespace").Append(vbCrLf)
    ret.Append("Public Class MyNewClass").Append(vbCrLf)
    ret.Append(" Public Shared Function MySub()").Append(vbCrLf)
    ret.Append(script).Append(vbCrLf)
    ret.Append(" End Function").Append(vbCrLf)
    ret.Append("End Class").Append(vbCrLf)
    ret.Append("End NameSpace")
    Debug.WriteLine(ret.ToString)
      

  4.   

    微软社区文章作者:阎宏2003-4
    第7页
    Return ret.ToString
    End Function
    End Class
    同时,在Evaluator 类中设置一个exec 方法,用于调用新的工厂对象,以及前面讨论过的runcode()
    方法:
    代码清单5、Evaluator.exec()函数
    Public Function exec(ByVal code As String) As Object
    factory = New ScriptClassFactory()
    Return runcode(factory.Create(code))
    End Function
    本文给出一个简单的脚本代码编辑器,见下图:
    可以看出,用户界面上有一个文字框,用于接受用户的输入。当单击“Exec”按键的时候,系统首
    先调用Evaluator.exec()方法,这个方法接着调用工厂对象,产生出封装类代码,然后编译这段代
    码,并加载、运行。
    小节
    由此可见,.NET 技术提供了非常强大的动态代码编译和执行的功能,这些功能完全可以利用来实
    现eval 函数功能,并且可以进一步用来开发高级的脚本代码的动态编译和执行。
    用户常常会发现,一个复杂的系统需要某种公式计算的能力,这种公式计算能力曾经在一些经典的
    解释式语言如dBase,Foxpro,Clipper 等中发挥过重要的作用,也在报表工具中广泛使用。现在读
    者完全可以将本文给出的例子加以改造,放到自己的系统中当作eval 函数使用。
    一个复杂的、提供动态修改能力的软件往往还给用户提供某种脚本语言的编辑、存储、编译、执
    行。这样的系统可以应付未来软件需求的某些变化,使得系统可以在不修改系统代码的情况下,以
    脚本编程的方式将系统功能加以修改和扩展。本文的读者可以从这里给出的简单例子出发,通过进
    一步研究,开发出具有完全脚本功能的软件。
    微软社区文章作者:阎宏2003-4
    第8页
    本文前面提到了工厂方法模式,如果读者想进一步研究脚本语言的话,还有可能会与解释器模式发
    生关系。关于设计模式,建议读者阅读文献[GOF95] [YAN02]。
    附录
    在这里笔者给出前面所有的VB.NET 代码所对应的C#代码,以便具有不同语言背景的读者阅读
    额。
    代码清单1 所对应的C#代码
    using System;
    namespace MyNamespace{
    public class MyNewClass{
    public Object MySub(){
    return 1+2*4+4*5-6;
    }
    }
    }
    代码清单2 所对应的C#代码
    public class EvalClassFactory : IClassFactory {
    public String create(String formula) {
    StringBuilder ret =new StringBuilder("");
    ret.Append("using System;\r\n");
    ret.Append("namespace MyNamespace{\r\n");
    ret.Append("public class MyNewClass{\r\n");
    ret.Append(" public Object MySub(){\r\n");
    ret.Append(" return ").Append(formula).Append(";\r\n");
    ret.Append(" }\r\n");
    ret.Append("}\r\n");
    ret.Append("}\r\n");
    Debug.WriteLine(ret.ToString());
    return ret.ToString();
    }
    }
    代码清单3 所对应的C#代码
    public Object runcode(String code) {
    ICodeCompiler compiler;
    CompilerParameters compilerParams ;
    // Create an instance of the compiler
    compiler = new CSharpCodeProvider().CreateCompiler();
    //Create an instance of CompilerParameters to load libraries
    compilerParams = new CompilerParameters();
    // Start by adding any referenced libraries
    compilerParams.ReferencedAssemblies.Add("System.dll");
    compilerParams.ReferencedAssemblies.Add("System.Windows.Forms.dll");
    微软社区文章作者:阎宏2003-4
    第9页
    //Load the result into memory
    compilerParams.GenerateInMemory = true;
    //Now wrap the code and compile the whole thing
    CompilerResults compiled = compiler.CompileAssemblyFromSource(
    compilerParams, code);
    if (!compiled.Errors.HasErrors) {
    errorMsg = "No error.";
    }
    else{
    //Create Error String
    errorMsg = compiled.Errors.Count.ToString() + " error(s):";
    for (int iCount = 0 ; iCount<compiled.Errors.Count ; iCount++) {
    errorMsg = errorMsg + "\n\rLine: "
    + compiled.Errors[iCount].Line.ToString() + " - "
    + compiled.Errors[iCount ].ErrorText;
    return null;
    }
    }
    System.Reflection.Assembly myAssembly = compiled.CompiledAssembly;
    Object loadedObject = myAssembly.CreateInstance(
    "MyNamespace.MyNewClass");
    if (loadedObject == null) {
    MessageBox.Show("Couldn't load class.");
    return null;
    }
    try {
    Object retValue = loadedObject.GetType().InvokeMember(
    "MySub", BindingFlags.InvokeMethod,
    null, loadedObject, null);
    return retValue;
    }catch (Exception e){
    MessageBox.Show(e.Message, "Compiler Demo",
    MessageBoxButtons.OK, MessageBoxIcon.Information);
    return null;
    }
    }
    注意上面假设了“脚本语言”也一同改为了C#。如果脚本语言是VB.NET,就应当将
    CSharpCodeProvider 类改为VBCodeProvider。当然,系统也可以提供用户一个选择,也决
    定使用哪一个作为脚本语言。
    代码清单4 所对应的C#代码
    public Object eval(String code) {
    factory = new EvalClassFactory();
    return runcode(factory.create(code));
    }
    代码清单5 所对应的C#代码
    微软社区文章作者:阎宏2003-4
    第10页
    public class ScriptClassFactory : IClassFactory {
    public String create(String formula) {
    StringBuilder ret =new StringBuilder("");
    ret.Append("using System;\r\n");
    ret.Append("using System.IO;\r\n");
    ret.Append("using System.Windows.Forms;\r\n");
    ret.Append("namespace MyNamespace{\r\n");
    ret.Append("public class MyNewClass{\r\n");
    ret.Append(" public Object MySub(){\r\n");
    ret.Append(formula).Append(";\r\n");
    ret.Append(" }\r\n");
    ret.Append("}\r\n");
    ret.Append("}\r\n");
    Debug.WriteLine(ret.ToString());
    return ret.ToString();
    }
    }
    VB.NET 的代码可以在下面的网址下载:
    http://www.WebEndsHere.com/vbpatterns/dyna/dynacodevb.zip
    C#.NET 的代码可以在下面的网址下载:
    http://www.WebEndsHere.com/vbpatterns/dyna/dynacodecs.zip
    参考文献
    [STRAHL] Rick Strahl, Dynamically Executing Code in .NET, http://www.devx.com, 2002
    [GOF95] E.Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Patterns - Elements of Reusable Object-
    Oriented Software, 1995, Addison-Wesley.
    [YAN02] 阎宏,Java 与模式,中国电子工业出版社2002 年10 月1 日出版
    作者简介:
    阎宏,微软MVP,1964 年出生于天津市。1987 年毕业于中国科技大学近代物理
    系,1990 年于中科院理论物理所获硕士学位,1992 年获博士学位,翌年赴日本京
    都大学进行博士后研究工作。
    作者曾于美国花旗银行(Citibank)、美国汤臣金融(Thomson Financial)、美国奥
    本海默基金(Oppenheimer)等处供职,进行了多年的软件开发、架构设计和技术管
    理工作,著有《Java 与模式》一书。