65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.27/5 (17投票s)

2004年3月23日

2分钟阅读

viewsIcon

218468

downloadIcon

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 SubFunction);按 “Run!” 按钮编译并运行代码。

工作原理

程序的主类是 CompileEngine,它有这样的构造函数

  public CompileEngine( string code, LanguageType language, string entry )
  {
   this.SourceCode = code;
   this.Language = language;
   this.EntryPoint = entry;
  }

Run 是一个重要的方法。 它包括三个步骤:PrepareRealSourceCodeCreateAssemblyCallEntry

  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 ScriptSnippetCompilerRuntime Compilation

许可证

本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。

作者可能使用的许可证列表可以在此处找到。

© . All rights reserved.