Compiling code with CodeProviders

Hello,

I've been working on a project that requires me to compile a VB or C# project at runtime. I create the appropriate CodeProvider based on language choice, and then parse the project file in order to populate the CompilerParameters with the information it needs to compile. Everything works wonderfully with the C# version, but when trying to compile a blank VB Windows Forms Application, I get the following 8 errors:

+ [7] {C:\Documents and Settings\brustd\My Documents\Visual Studio 2005\Projects\Atto\VBTestWindow\My Project\Application.Designer.vb(35,0) : error BC30691: 'Form1' is a type in 'VBTestWindow' and cannot be used as an expression.}

+ Devil {C:\Documents and Settings\brustd\My Documents\Visual Studio 2005\Projects\Atto\VBTestWindow\My Project\Application.Designer.vb(35,0) : error BC30456: 'MainForm' is not a member of 'VBTestWindow.My.MyApplication'.}

+ [5] {C:\Documents and Settings\brustd\My Documents\Visual Studio 2005\Projects\Atto\VBTestWindow\My Project\Application.Designer.vb(34,0) : error BC30284: sub 'OnCreateMainForm' cannot be declared 'Overrides' because it does not override a sub in a base class.}

+ [4] {C:\Documents and Settings\brustd\My Documents\Visual Studio 2005\Projects\Atto\VBTestWindow\My Project\Application.Designer.vb(30,0) : error BC30456: 'ShutDownStyle' is not a member of 'VBTestWindow.My.MyApplication'.}

+ [3] {C:\Documents and Settings\brustd\My Documents\Visual Studio 2005\Projects\Atto\VBTestWindow\My Project\Application.Designer.vb(29,0) : error BC30456: 'SaveMySettingsOnExit' is not a member of 'VBTestWindow.My.MyApplication'.}

+ [2] {C:\Documents and Settings\brustd\My Documents\Visual Studio 2005\Projects\Atto\VBTestWindow\My Project\Application.Designer.vb(28,0) : error BC30456: 'EnableVisualStyles' is not a member of 'VBTestWindow.My.MyApplication'.}

+ [1] {C:\Documents and Settings\brustd\My Documents\Visual Studio 2005\Projects\Atto\VBTestWindow\My Project\Application.Designer.vb(27,0) : error BC30456: 'IsSingleInstance' is not a member of 'VBTestWindow.My.MyApplication'.}

+ [0] {C:\Documents and Settings\brustd\My Documents\Visual Studio 2005\Projects\Atto\VBTestWindow\My Project\Application.Designer.vb(26,0) : error BC30057: Too many arguments to 'Public Sub New()'.}

Code:

public Assembly CompileProject(string workflowPath, string projectFile) {

Assembly assm = null;

CodeDomProvider provider = null;

FileInfo sourceFileInfo = null;

CompilerResults results = null;

String[] codeFilesArray = null;

string dllName = string.Empty;

ParseProjectFile(workflowPath, projectFile);

codeFilesArray = new String[codeFiles.Count];

codeFiles.Values.CopyTo(codeFilesArray, 0);

sourceFileInfo = new FileInfo(projectFile);

// Generate a name for the dll

dllName = String.Format(@"{0}\{1}.dll", sourceFileInfo.Directory, sourceFileInfo.Directory.Name.Replace(".", "_"));

assm = CheckIfAssemblyExists(dllName);

if (assm != null) {

return assm;

}

if (codeFiles.Count > 0) {

if (sourceFileInfo.Extension.ToUpper() == ".CSPROJ") {

provider = new Microsoft.CSharp.CSharpCodeProvider();

} else if (sourceFileInfo.Extension.ToUpper() == ".VBPROJ") {

provider = new Microsoft.VisualBasic.VBCodeProvider();

} else {

Console.WriteLine("The source file must be either C# or VB.");

}

} else {

Console.WriteLine("You need to specify either a file name or the language to be used!");

}

parameters.OutputAssembly = dllName;

// Generate a class library

parameters.GenerateExecutable = false;

// Keep the assembly in memory

parameters.GenerateInMemory = false;

// Don't treat warnings as errors

parameters.TreatWarningsAsErrors = false;

StringBuilder compilerOptions = new StringBuilder();

if (provider.FileExtension.Equals("cs")) {

// Add LIB stuff

} else if (provider.FileExtension.Equals("vb")) {

compilerOptions.Append(importCompilerOptions);

}

parameters.CompilerOptions = compilerOptions.ToString();

// Compile

try {

results = provider.CompileAssemblyFromFile(parameters, codeFilesArray);

} catch (Exception e) {

Console.WriteLine(e);

}

// Check the results for errors, and if they exist, print them out

if (results != null) {

if (results.Errors.Count > 0) {

Console.WriteLine("Errors building {0} into {1}",

codeFiles, results.PathToAssembly);

foreach (CompilerError error in results.Errors) {

Console.WriteLine(" {0}", error.ToString());

Console.WriteLine();

}

} else {

// If there were no errors, load the assembly.

assm = Assembly.LoadFrom(results.PathToAssembly);

Console.WriteLine("Source {0} built into {1} successfully.", codeFiles, results.PathToAssembly);

}

} else {

Console.WriteLine("There were no results, so the compilation failed.");

}

return assm;

}

All the errors are with Application.Designer.vb, and all of the files in the vb windows project are generated by the environment. I haven't even added a button. If any further code/examples/explanations are needed, I'll respond as quick as possible.

Thanks in advance for the help!


Darren

Thanks in advance for the help!



Answer this question

Compiling code with CodeProviders

  • Veera.c

    Hello Niels,

    Thanks for the response. I don't believe the problem is with the referenced assemblies, that is taken care of when I parse the .vbproj file. I added them manually just in case, and the results were the same. I have included a complete code listing below that will compile. I have it set up so that if you create a new VB windows project within the solution, in this case named TestApp, it will parse the .vbproj file and so forth. Similarly, if you create a CS windows project and do the same, it will work just fine.

    using System;
    using System.CodeDom.Compiler;
    using System.Collections.Specialized;
    using System.Collections;
    using System.Xml;
    using System.Diagnostics;
    using System.Reflection;
    using System.IO;
    using System.Text.RegularExpressions;
    using System.Text;

    namespace Utilties.Compiler {
    /// <summary>
    /// Generic compiler for compiling C# and VB applications dynamically.
    /// </summary>
    public class DotNetCompiler {
    #region Fields

    /// <summary>
    /// The parameters used to configure the compiler.
    /// </summary>
    private CompilerParameters parameters = new CompilerParameters();

    /// <summary>
    /// The files that will be compiled.
    /// </summary>
    //private StringCollection codeFiles = new StringCollection();
    private StringDictionary codeFiles = new StringDictionary();

    //private string dllName = string.Empty;
    private string importCompilerOptions = string.Empty;

    #endregion

    #region Constructors

    /// <summary>
    /// Initializes an instance of the DotNetCompiler class with default
    /// parameters.
    /// </summary>
    public DotNetCompiler() {
    }

    #endregion

    #region Properties

    /// <summary>
    /// Returns an ICollection of the referenced assemblies so they can be
    /// viewed but not edited.
    /// </summary>
    public ICollection References {
    get {
    return parameters.ReferencedAssemblies;
    }
    }

    /// <summary>
    /// Returns an ICollection of the code files so they can be viewed but
    /// not edited.
    /// </summary>
    public ICollection CodeFiles {
    get {
    return codeFiles.Values;
    }
    }

    #endregion

    #region Methods

    /// <summary>
    ///
    /// </summary>
    /// <param name="workflowPath"></param>
    /// <param name="projectFile"></param>
    /// <returns></returns>
    /// <exception cref="ArgumentException"></exception>
    public Assembly CompileProject(string workflowPath, string projectFile) {
    if (String.IsNullOrEmpty(workflowPath) || String.IsNullOrEmpty(projectFile)) {
    throw new ArgumentException("Argument Exception");
    }
    CodeDomProvider provider = null;
    FileInfo sourceFileInfo = null;
    CompilerResults results = null;
    String[] codeFilesArray = null;
    string dllName = string.Empty;

    ParseProjectFile(workflowPath, projectFile);
    codeFilesArray = new String[codeFiles.Count];
    codeFiles.Values.CopyTo(codeFilesArray, 0);
    sourceFileInfo = new FileInfo(projectFile);

    // Generate a name for the dll
    dllName = String.Format(@"{0}\{1}.dll", sourceFileInfo.Directory,
    sourceFileInfo.Directory.Name.Replace(".", "_"));

    if (AssemblyExists(dllName)) {
    return Assembly.LoadFrom(dllName);
    }

    if (codeFiles.Count > 0) {
    if (sourceFileInfo.Extension.ToUpper() == ".CSPROJ") {
    provider = new Microsoft.CSharp.CSharpCodeProvider();
    } else if (sourceFileInfo.Extension.ToUpper() == ".VBPROJ") {
    provider = new Microsoft.VisualBasic.VBCodeProvider();
    } else {
    Console.WriteLine("The source file must be either C# or VB.");
    }
    } else {
    Console.WriteLine("You need to specify either a file name or the language to be used!");
    }

    parameters.OutputAssembly = dllName;

    // Generate a class library
    parameters.GenerateExecutable = false;

    // Keep the assembly in memory
    parameters.GenerateInMemory = false;

    // Don't treat warnings as errors
    parameters.TreatWarningsAsErrors = false;

    StringBuilder compilerOptions = new StringBuilder();
    if (provider.FileExtension.Equals("cs")) {
    // Add LIB stuff
    } else if (provider.FileExtension.Equals("vb")) {
    compilerOptions.Append(importCompilerOptions);
    }
    parameters.CompilerOptions = compilerOptions.ToString();

    // Compile
    try {
    results = provider.CompileAssemblyFromFile(parameters,
    codeFilesArray);
    } catch (Exception e) {
    Console.WriteLine(e);
    }

    // Check the results for errors, and if they exist, print them out
    if (results != null) {
    if (results.Errors.Count > 0) {
    Console.WriteLine("Errors building {0} into {1}",
    codeFiles, results.PathToAssembly);
    foreach (CompilerError error in results.Errors) {
    Console.WriteLine(" {0}", error.ToString());
    Console.WriteLine();
    }
    } else {
    // If there were no errors, load the assembly.
    Console.WriteLine("Source {0} built into {1} successfully.",
    codeFiles, results.PathToAssembly);
    return Assembly.LoadFrom(results.PathToAssembly);
    }
    } else {
    Console.WriteLine(
    "There were no results, so the compilation failed.");
    }
    return null;
    }

    /// <summary>
    ///
    /// </summary>
    /// <param name="workflowPath"></param>
    /// <param name="projectFile"></param>
    private void ParseProjectFile(string workflowPath,
    string projectFile) {
    XmlTextReader xtr = new XmlTextReader(projectFile);
    XmlDocument doc = new XmlDocument();
    doc.Load(xtr);
    XmlNode docElement = doc.DocumentElement;
    XmlNamespaceManager nsmg = new XmlNamespaceManager(doc.NameTable);
    nsmg.AddNamespace("db", docElement.NamespaceURI);
    GetReferences(docElement, nsmg);
    GetSourceFiles(docElement, nsmg, workflowPath);
    GetImports(docElement, nsmg);
    }

    private void GetImports(XmlNode docElement,
    XmlNamespaceManager nsmg) {
    XmlNodeList result = docElement.SelectNodes(
    "//db:Import/@Include", nsmg);

    StringBuilder imports = new StringBuilder();
    imports.Append("/imports:");
    foreach (XmlNode node in result) {
    imports.Append(node.Value);
    imports.Append(",");
    }
    importCompilerOptions = imports.ToString().TrimEnd(',');

    }

    /// <summary>
    ///
    /// </summary>
    /// <param name="docElement"></param>
    /// <param name="nsmg"></param>
    private void GetReferences(XmlNode docElement,
    XmlNamespaceManager nsmg) {
    XmlNodeList result = docElement.SelectNodes(
    "//db:Reference/@Include", nsmg);

    foreach (XmlNode node in result) {
    parameters.ReferencedAssemblies.Add(String.Concat((
    new AssemblyName(node.Value).Name),
    ".dll"));
    }
    }

    /// <summary>
    ///
    /// </summary>
    /// <param name="docElement"></param>
    /// <param name="nsmg"></param>
    /// <param name="filePath"></param>
    private void GetSourceFiles(XmlNode docElement,
    XmlNamespaceManager nsmg, String filePath) {
    // Regular expression to remove the leading path and file extension
    // from a filename. The result is available with the 'filename'
    // key. The regex is anchored from the right ($) moves from right
    // to left first one or more word characters (\w+) then a dot (\.)
    // then any number of characters except a '\' ([^\\]). It will
    // terminate on the slash.
    Regex reg = new Regex(@"( <filename>[^\\]+)\.\w+$");
    XmlNodeList result = docElement.SelectNodes(
    "//db:Compile/@Include", nsmg);

    foreach (XmlNode node in result) {
    Match m = reg.Match(node.Value);
    if (m.Success && !string.IsNullOrEmpty(node.Value)) {
    codeFiles.Add(m.Groups["filename"].Value, String.Concat(
    filePath, "\\",
    node.Value));
    } else {
    throw new Exception(
    "Parse Exception");
    }
    }
    }

    /// <summary>
    ///
    /// </summary>
    public void Reset() {
    parameters.ReferencedAssemblies.Clear();
    codeFiles.Clear();
    }

    private Version GetVersionFromCode() {
    Version returnValue = null;
    //string returnText = string.Empty;
    if (codeFiles.ContainsKey("AssemblyInfo")) {
    string text = File.ReadAllText(codeFiles[
    "AssemblyInfo"]);
    Regex version = new Regex(
    @"AssemblyVersion\(""( <version>[^""]*)""\)");
    Match m = version.Match(text);
    returnValue = new Version(m.Groups["version"].Value);
    }
    return returnValue;
    }

    private bool AssemblyExists(string name) {
    Version fileVersion;
    AssemblyName assName;
    bool returnValue = false;
    if (File.Exists(name)) {
    fileVersion = GetVersionFromCode();
    assName = AssemblyName.GetAssemblyName(name);
    if (assName.Version == fileVersion) {
    returnValue = true;
    }
    }
    return returnValue;
    }

    #endregion

    static void Main(string[] args) {
    DotNetCompiler compiler = new DotNetCompiler();
    DirectoryInfo dir = new DirectoryInfo("..\\..\\..\\TestApp");
    FileInfo[] files = dir.GetFiles("*.vbproj");

    //DirectoryInfo dir = new DirectoryInfo("..\\..\\..\\CSTestApp");
    //FileInfo[] files = dir.GetFiles("*.csproj");

    Assembly vbAssembly = compiler.CompileProject(dir.FullName, files[0].FullName);
    }
    }
    }

    If I can provide any additional information, just ask. Thanks in advance for the help!


    Darren


  • netpicker9

    Hi,

    I could not compile your example, because you used some external functions.

    But I did an example which works (see below).

    I am not sure what was your problem, but maybe you needed to reference Syste.Windows.Forms.dll and System.Drawing.dll.

    Here is the example:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Reflection;
    using System.CodeDom;
    using System.CodeDom.Compiler;
    using System.IO;
    namespace ConsoleApplication1{
    class Program {
    public static Assembly CompileProject(string file)
    {
    CompilerParameters param = new CompilerParameters();
    param.GenerateExecutable = false;
    param.GenerateInMemory = false;
    param.OutputAssembly = Path.Combine(Path.GetTempPath(), "out.dll");
    param.ReferencedAssemblies.Add("System.dll");
    param.ReferencedAssemblies.Add("System.Windows.Forms.dll");
    param.ReferencedAssemblies.Add("System.Drawing.dll");
    param.TreatWarningsAsErrors = false;
    using (CodeDomProvider provider = new Microsoft.VisualBasic.VBCodeProvider())
    {
    CompilerResults results;
    results = provider.CompileAssemblyFromFile(param, file);
    if (results.Errors.Count > 0)
    {
    Console.WriteLine("Errors building {0} into {1}",
    file, results.PathToAssembly);
    foreach (CompilerError error in results.Errors)
    Console.WriteLine(" {0}", error);
    }
    // FIXME: you should also list the warnings.
    else
    {
    //Assembly assm = Assembly.LoadFrom(results.PathToAssembly);
    Assembly assm = results.CompiledAssembly;
    Console.WriteLine("Source {0} built into {1} successfully.",
    file, results.PathToAssembly);
    foreach(Type t in assm.GetTypes())
    Console.WriteLine("Found:{0}.", t.FullName);
    return assm;
    }
    return null;
    }
    }

    //C:\Form1.vb contains these 2 lines of code:
    // Public Class Form1
    // End Class
    static void Main(string[] args)
    {
    CompileProject(@"C:\Form1.vb");
    }
    }
    }

    There are tips on reflexion on a wiki page that I write:

    http://metasharp.net/index.php title=More_technical_Csharp#Reflexion



  • Compiling code with CodeProviders