原文地址 源码下载
简介
这个程序在内存中动态编译代码片段并且运行它,所以你在测试C#或VB.NET代码时不必再创建新的工程或解决方案了。
背景
通常我们需要测试一些代码片段(C#或VB.NET),在.NET环境中,我们用"CSC"或"VBC"命令;在VS.NET中,我们创建新的工程或解决方案,并且创建很多其他文件。这两种方法都是沉闷乏味的,特别是当我们测试一个很简单的程序时。jconwell创作了一种方法:"Dot Net Script",他使用XML文件(命名为"dnml")来包涵C#或VB代码。在这种方法里,dnml文件被解析并且代码在内存中被编译,这是个好方法!但是,它仍然有些小麻烦,因为它需要创建一个新文件。现在我们来做一个基于他的工作之上的工具。这个工具具有以下特点:
1、可以加载C#或VB代码文件,在内存中编译代码,然后通过一个静态方法("入口点")来运行这些代码。
2、若为VB.NET代码,并且它是一个"Form"类,工具将会添加一个"Main()"方法来运行这个窗体。
3、通过一个新的线程来运行代码。
4、有一个可视化的界面(如下面的截图)。
使用这个工具
像下面截图那样,点击按钮"Open..."可以打开一个C#或VB.NET文件;点击按钮"Paste"可以从剪贴板中粘贴代码到文本区;复选框"C#"用来标记代码是不是C#语言;那个文本框用来输入入口点(在C#中为静态方法,在VB.NET中为公共的Sub或Function);点击按钮"Run!"将会编译并运行代码。
工作原理
这个程序的主要的类是CompileEngine,它有个构造函数:
2 {
3 this.SourceCode = code;
4 this.Language = language;
5 this.EntryPoint = entry;
6}
Run是一个很重要的方法,它包含三个工作步骤:PrepareRealSourceCode,CreateAssembly和CallEntry。
2 {
3 ClearErrMsgs();
4
5 string strRealSourceCode = PrepareRealSourceCode();
6
7 Assembly assembly = CreateAssembly( strRealSourceCode );
8
9 CallEntry( assembly, EntryPoint );
10
11 DisplayErrorMsg();
12
13}
PrepareRealSourceCode有两个功能:添加引用声明和为VB.NET窗体添加"Sub Main"。
2 {
3
4 string strRealSourceCode = "";
5
6 // add some using(Imports) statements
7 string strUsingStatement = "";
8
9 if( Language == LanguageType.VB )
10 {
11 string [] basicImportsStatement = {
12 "Imports Microsoft.VisualBasic",
13 "Imports System",
14 "Imports System.Windows.Forms",
15 "Imports System.Drawing",
16 "Imports System.Data",
17 "Imports System.Threading",
18 "Imports System.Xml",
19 "Imports System.Collections",
20 "Imports System.Diagnostics",
21 };
22 foreach( string si in basicImportsStatement )
23 {
24 if( SourceCode.IndexOf( si ) < 0 )
25 strUsingStatement += si + "\r\n";
26 }
27 }
28
29 strRealSourceCode = strUsingStatement + SourceCode;
30
31 // for VB Prog, Add Main(), So We Can Run It
32 if( Language == LanguageType.VB && EntryPoint == "Main" &&
33 strRealSourceCode.IndexOf( "Sub Main(") < 0 )
34 {
35 try
36 {
37 int posClass = strRealSourceCode.IndexOf( "Public Class ")+
38 "Public Class ".Length;
39 int posClassEnd = strRealSourceCode.IndexOf( "\r\n", posClass );
40 string className = strRealSourceCode.Substring( posClass,
41 posClassEnd - posClass );
42 int pos = strRealSourceCode.LastIndexOf( "End Class");
43 if( pos > 0 )
44 strRealSourceCode = strRealSourceCode.Substring( 0, pos ) + @"
45Private Shared Sub Main()
46" + "Dim frm As New " + className + "()" + @"
47 If TypeOf frm Is Form Then frm.ShowDialog()
48End Sub
49" + strRealSourceCode.Substring( pos );
50 }
51 catch{}
52 }
53
54 return strRealSourceCode;
55}
CreateAssembly在内存中编译代码并生成汇编集。
2 // this method code is mainly from jconwell,
3 // see http://www.codeproject.com/dotnet/DotNetScript.asp
4 private Assembly CreateAssembly( string strRealSourceCode)
5 {
6 //Create an instance whichever code provider that is needed
7 CodeDomProvider codeProvider = null;
8 if (Language == LanguageType.CSharp )
9 codeProvider = new CSharpCodeProvider();
10 else if( Language == LanguageType.VB )
11 codeProvider = new VBCodeProvider();
12
13 //create the language specific code compiler
14 ICodeCompiler compiler = codeProvider.CreateCompiler();
15
16 //add compiler parameters
17 CompilerParameters compilerParams = new CompilerParameters();
18 compilerParams.CompilerOptions = "/target:library";
19 // you can add /optimize
20 compilerParams.GenerateExecutable = false;
21 compilerParams.GenerateInMemory = true;
22 compilerParams.IncludeDebugInformation = false;
23
24 // add some basic references
25 compilerParams.ReferencedAssemblies.Add( "mscorlib.dll");
26 compilerParams.ReferencedAssemblies.Add( "System.dll");
27 compilerParams.ReferencedAssemblies.Add( "System.Data.dll" );
28 compilerParams.ReferencedAssemblies.Add( "System.Drawing.dll" );
29 compilerParams.ReferencedAssemblies.Add( "System.Xml.dll" );
30 compilerParams.ReferencedAssemblies.Add(
31 "System.Windows.Forms.dll" );
32
33 if( Language == LanguageType.VB )
34 {
35 compilerParams.ReferencedAssemblies.Add(
36 "Microsoft.VisualBasic.dll" );
37 }
38
39 //actually compile the code
40 CompilerResults results = compiler.CompileAssemblyFromSource(
41 compilerParams,
42 strRealSourceCode );
43
44 //get a hold of the actual assembly that was generated
45 Assembly generatedAssembly = results.CompiledAssembly;
46
47 //return the assembly
48 return generatedAssembly;
49}
CallEntry(Assembly...)用来通过映射调用入口点。
2 // this method code is mainly from jconwell,
3 // see http://www.codeproject.com/dotnet/DotNetScript.asp
4 private void CallEntry(Assembly assembly, string entryPoint)
5 {
6 try
7 {
8 //Use reflection to call the static Main function
9 Module[] mods = assembly.GetModules(false);
10 Type[] types = mods[0].GetTypes();
11
12 foreach (Type type in types)
13 {
14 MethodInfo mi = type.GetMethod(entryPoint,
15 BindingFlags.Public | BindingFlags.NonPublic
16
17 | BindingFlags.Static);
18 if (mi != null)
19 {
20 if( mi.GetParameters().Length == 1 )
21 {
22 if(
23
24 mi.GetParameters()[0].ParameterType.IsArray )
25 {
26 string [] par = new string[1]; // if
27
28 Main has string [] arguments
29 mi.Invoke(null, par);
30 }
31 }
32 else
33 {
34 mi.Invoke(null, null);
35 }
36 return;
37 }
38 }
39 }
40 catch (Exception ex)
41 {
42 }
43}
在FormMain里创建一个新线程来创建一个CompileEngine实例然后调用Run。
{
// we use a new thread to run it
new Thread( new ThreadStart( RunTheProg ) ).Start();
}
private void RunTheProg()
{
string code = txtSource.Text.Trim();
LanguageType language = chkIsCSharp.Checked ?
LanguageType.CSharp : LanguageType.VB;
string entry = txtEntry.Text.Trim();
if( code == "" ) return;
if( entry == "" ) entry = "Main";
CompileEngine engine = new CompileEngine( code, language, entry );
engine.Run();
}
一些可以改进的地方
在PrepareRealSourceCode,用正则表达式来查找类名会更好一些。
感谢
感谢jconwell,VictorV,George Orwell,Eric Astor和其他朋友,他们在他们的主题里展示了很多优秀的作品,比如Dot Net Script,SnippetCompiler,Runtime Compilation。
关于dstang 2000
一个专业的开发人员。他使用Java,C#,VB,C和一些其他语言。