C# 或 VB.NET 代码动态编译和运行的工具






4.27/5 (17投票s)
2004年3月23日
2分钟阅读

218468

5813
一个用于在内存中动态编译和运行 C# 或 VB.NET 代码,而无需创建项目的工具
引言
该程序可以在内存中编译一段代码并动态运行它,因此您可以测试一些 C# 或 VB.NET 代码,而无需创建新的项目或解决方案。
背景
通常,我们需要测试一段代码(C# 或 VB.NET)。在 .NET 环境中,我们使用“csc
”或“vbc
”命令;在 VS.NET 中,我们创建一个新的项目或解决方案,并创建许多其他文件。 这两者都很繁琐,尤其是在我们测试一个简单的程序时。 jconwell 提出了一个方法:“Dot Net Script”。 他使用 XML 文件(命名为“dnml”)包含 C# 或 VB 代码。 在该方法中,dnml 文件被解析,代码在内存中编译。 这是一个好主意! 但是,这有点不方便,因为它需要构建一个新文件。 在这里,我们提供一个基于他的工作的工具。 该工具有以下特点:
- 它可以加载 C# 或 VB 代码文件,在内存中编译代码,并通过
static
方法(“入口点”)运行代码; - 对于 VB.NET 代码,如果它是 "
Form
",该工具可以添加 "Main()
" 来运行表单; - 它使用一个新线程来运行代码;
- 它有一个可视界面(见下文)。
使用工具
如下图所示,按 “打开...” 按钮可以打开 C# 或 VB.NET 文件;按 “粘贴” 按钮将代码从剪贴板粘贴到文本区域;勾选 “C#” 复选框表示代码是 C# 语言;文本框中填写入口点(C# 中的 static
方法或 VB.NET 中的 shared Sub
或 Function
);按 “Run!
” 按钮编译并运行代码。
工作原理
程序的主类是 CompileEngine
,它有这样的构造函数
public CompileEngine( string code, LanguageType language, string entry )
{
this.SourceCode = code;
this.Language = language;
this.EntryPoint = entry;
}
Run
是一个重要的方法。 它包括三个步骤:PrepareRealSourceCode
、CreateAssembly
和 CallEntry
。
public void Run()
{
ClearErrMsgs();
string strRealSourceCode = PrepareRealSourceCode();
Assembly assembly = CreateAssembly( strRealSourceCode );
CallEntry( assembly, EntryPoint );
DisplayErrorMsg();
}
PrepareRealSourceCode
有两个功能:添加 "Imports
" 语句,以及为 VB.NET Windows Forms 添加 "Sub Main
"。
private string PrepareRealSourceCode()
{
string strRealSourceCode = "";
// add some using(Imports) statements
string strUsingStatement = "";
if( Language == LanguageType.VB )
{
string [] basicImportsStatement = {
"Imports Microsoft.VisualBasic",
"Imports System",
"Imports System.Windows.Forms",
"Imports System.Drawing",
"Imports System.Data",
"Imports System.Threading",
"Imports System.Xml",
"Imports System.Collections",
"Imports System.Diagnostics",
};
foreach( string si in basicImportsStatement )
{
if( SourceCode.IndexOf( si ) < 0 )
strUsingStatement += si + "\r\n";
}
}
strRealSourceCode = strUsingStatement + SourceCode;
// for VB Prog, Add Main(), So We Can Run It
if( Language == LanguageType.VB && EntryPoint == "Main" &&
strRealSourceCode.IndexOf( "Sub Main(") < 0 )
{
try
{
int posClass = strRealSourceCode.IndexOf( "Public Class ")+
"Public Class ".Length;
int posClassEnd = strRealSourceCode.IndexOf( "\r\n", posClass );
string className = strRealSourceCode.Substring( posClass,
posClassEnd - posClass );
int pos = strRealSourceCode.LastIndexOf( "End Class");
if( pos > 0 )
strRealSourceCode = strRealSourceCode.Substring( 0, pos ) + @"
Private Shared Sub Main()
" + "Dim frm As New " + className + "()" + @"
If TypeOf frm Is Form Then frm.ShowDialog()
End Sub
" + strRealSourceCode.Substring( pos );
}
catch{}
}
return strRealSourceCode;
}
CreateAssembly
编译源代码并在内存中生成程序集。
// compile the source, and create assembly in memory
// this method code is mainly from jconwell,
// see https://codeproject.org.cn/dotnet/DotNetScript.asp
private Assembly CreateAssembly(string strRealSourceCode)
{
//Create an instance whichever code provider that is needed
CodeDomProvider codeProvider = null;
if (Language == LanguageType.CSharp )
codeProvider = new CSharpCodeProvider();
else if( Language == LanguageType.VB )
codeProvider = new VBCodeProvider();
//create the language specific code compiler
ICodeCompiler compiler = codeProvider.CreateCompiler();
//add compiler parameters
CompilerParameters compilerParams = new CompilerParameters();
compilerParams.CompilerOptions = "/target:library";
// you can add /optimize
compilerParams.GenerateExecutable = false;
compilerParams.GenerateInMemory = true;
compilerParams.IncludeDebugInformation = false;
// add some basic references
compilerParams.ReferencedAssemblies.Add( "mscorlib.dll");
compilerParams.ReferencedAssemblies.Add( "System.dll");
compilerParams.ReferencedAssemblies.Add( "System.Data.dll" );
compilerParams.ReferencedAssemblies.Add( "System.Drawing.dll" );
compilerParams.ReferencedAssemblies.Add( "System.Xml.dll" );
compilerParams.ReferencedAssemblies.Add(
"System.Windows.Forms.dll" );
if( Language == LanguageType.VB )
{
compilerParams.ReferencedAssemblies.Add(
"Microsoft.VisualBasic.dll" );
}
//actually compile the code
CompilerResults results = compiler.CompileAssemblyFromSource(
compilerParams,
strRealSourceCode );
//get a hold of the actual assembly that was generated
Assembly generatedAssembly = results.CompiledAssembly;
//return the assembly
return generatedAssembly;
}
CallEntry(Assembly...)
用于通过反射调用入口点。
// invoke the entry method
// this method code is mainly from jconwell,
// see https://codeproject.org.cn/dotnet/DotNetScript.asp
private void CallEntry(Assembly assembly, string entryPoint)
{
try
{
//Use reflection to call the static Main function
Module[] mods = assembly.GetModules(false);
Type[] types = mods[0].GetTypes();
foreach (Type type in types)
{
MethodInfo mi = type.GetMethod(entryPoint,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
if (mi != null)
{
if( mi.GetParameters().Length == 1 )
{
if( mi.GetParameters()[0].ParameterType.IsArray )
{
string [] par = new string[1]; // if Main has string [] arguments
mi.Invoke(null, par);
}
}
else
{
mi.Invoke(null, null);
}
return;
}
}
}
catch (Exception ex)
{
}
}
在 FormMain
中,创建一个新线程来创建一个 CompileEngine
实例并调用 Run
。
private void btnRun_Click(object sender, System.EventArgs e)
{
// 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。
许可证
本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。
作者可能使用的许可证列表可以在此处找到。