I'm trying to use CodeDomProvider to make a C# compiler.
I managed to get the errors but i can't get the output.
This is what i have so far:
public List<string> Errors(CompilerResults compilerResults)
{
List<string> messages = new List<string>();
foreach (CompilerError error in compilerResults.Errors)
{
messages.Add(String.Format("Line {0} Error No:{1} - {2}", error.Line, error.ErrorNumber, error.ErrorText));
}
return messages;
}
public CompilerResults ProcessCompilation(string programText)
{
CodeDomProvider codeDomProvider = CodeDomProvider.CreateProvider("CSharp");
CompilerParameters parameters = new CompilerParameters();
parameters.GenerateExecutable = false;
StringCollection assemblies = new StringCollection();
return codeDomProvider.CompileAssemblyFromSource(parameters, programText);
}
CSharpCompiler is the class that contains the functions from above
public JsonResult Compiler(string code)
{
CSharpCompiler compiler = new CSharpCompiler();
CompilerResults compilerResults = compiler.ProcessCompilation(code);
Debug.WriteLine("OUTPUT----------------------------------------------");
foreach (var o in compilerResults.Output)
{
Debug.WriteLine(o);
}
List<string> compilerErrors = compiler.Errors(compilerResults);
if (compilerErrors.Count != 0)
return Json(new { success = false, errors = compilerErrors});
return Json(true);
}
compilerResults.Output is always empty.
If i run this piece of code:
using System;
public class HelloWorld
{
public static void Main()
{
Console.WriteLine("Hello world!");
}
}
What can i do to display the message "Hello world!"?
CompileAssemblyFromSource creates, as its name implies, an assembly. To get access to the compiled code, you can use the CompilerResults.CompiledAssembly property and then use reflection to find and invoke the Main method:
compilerResults.CompiledAssembly.GetType("HelloWorld").GetMethod("Main").Invoke(null, null);
Though if you set parameters.GenerateExecutable to true, you can simplify this to:
compilerResults.CompiledAssembly.EntryPoint.Invoke(null, null);
Related
I have a problem with compiling code dynamically using below code:
CSharpCodeProvider provider = new CSharpCodeProvider();
// Build the parameters for source compilation.
CompilerParameters cp = new CompilerParameters();
// Add an assembly reference.
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("System.Management.dll");
// Generate an executable instead of
// a class library.
cp.GenerateExecutable = true;
// Set the assembly file name to generate.
cp.OutputAssembly = "test.exe";
// Save the assembly as a physical file.
cp.GenerateInMemory = false;
// Invoke compilation.
CompilerResults cr = provider.CompileAssemblyFromFile(cp, "data.txt");
if (cr.Errors.Count > 0)
{
// Display compilation errors.
MessageBox.Show("Errors building {0} into {1}","data,txt");
foreach (CompilerError ce in cr.Errors)
{
MessageBox.Show(ce.ToString());
}
}
else
{
MessageBox.Show("Source {0} built into {1} successfully.","data.txt");
}
The code in data.txt file has line:
Properties.Resources.something
Now when I compile code dynamically it shows error:
Name "Properites" does not exist in current context.
I don't know how to fix it. There is no DLL named Properties.dll so I cannot refernce it.
I tried System.References.dll but It does not solve the problem.
Edit sample:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApp1
{
public static void Main()
{
byte[] resource = Properties.Resources.someresource;
}
}
Based on my search, I find that your source file is wrong. As usual, the source file should end with .cs instead of .txt.
I make a successful code example and you could have a look.
Code:
class Program
{
[STAThread]
static void Main(string[] args)
{
if (args.Length > 0)
{
// First parameter is the source file name.
if (File.Exists(args[0]))
{
CompileExecutable(args[0]);// Here args[0] is 'D:\\test.cs'
}
else
{
Console.WriteLine("Input source file not found - {0}",
args[0]);
}
}
else
{
Console.WriteLine("Input source file not specified on command line!");
}
Console.WriteLine("success");
Console.ReadKey();
}
public static bool CompileExecutable(String sourceName)
{
FileInfo sourceFile = new FileInfo(sourceName);
CodeDomProvider provider = null;
bool compileOk = false;
// Select the code provider based on the input file extension.
if (sourceFile.Extension.ToUpper(CultureInfo.InvariantCulture) == ".CS")
{
provider = CodeDomProvider.CreateProvider("CSharp");
}
else if (sourceFile.Extension.ToUpper(CultureInfo.InvariantCulture) == ".VB")
{
provider = CodeDomProvider.CreateProvider("VisualBasic");
}
else
{
Console.WriteLine("Source file must have a .cs or .vb extension");
}
if (provider != null)
{
// Format the executable file name.
// Build the output assembly path using the current directory
// and <source>_cs.exe or <source>_vb.exe.
String exeName = String.Format(#"{0}\{1}.exe",
System.Environment.CurrentDirectory,
sourceFile.Name.Replace(".", "_"));
CompilerParameters cp = new CompilerParameters();
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("System.Console.dll");
cp.ReferencedAssemblies.Add("System.Resources.ResourceManager.dll");
cp.ReferencedAssemblies.Add("System.Windows.Forms.dll");// I have changed here.
cp.GenerateExecutable = true;
// Specify the assembly file name to generate.
cp.OutputAssembly = exeName;
// Save the assembly as a physical file.
cp.GenerateInMemory = false;
// Set whether to treat all warnings as errors.
cp.TreatWarningsAsErrors = false;
// Invoke compilation of the source file.
CompilerResults cr = provider.CompileAssemblyFromFile(cp,
sourceName);
if (cr.Errors.Count > 0)
{
// Display compilation errors.
Console.WriteLine("Errors building {0} into {1}",
sourceName, cr.PathToAssembly);
foreach (CompilerError ce in cr.Errors)
{
Console.WriteLine(" {0}", ce.ToString());
Console.WriteLine();
}
}
else
{
// Display a successful compilation message.
Console.WriteLine("Source {0} built into {1} successfully.",
sourceName, cr.PathToAssembly);
}
// Return the results of the compilation.
if (cr.Errors.Count > 0)
{
compileOk = false;
}
else
{
compileOk = true;
}
}
return compileOk;
}
}
.cs file
using System;
using System.Collections;
using System.Collections.Generic;
using System.Resources;
namespace ConsoleApp1
{
public class Student
{
public static void Main()
{
using (ResXResourceWriter resx = new ResXResourceWriter(#".\CarResources.resx"))
{
resx.AddResource("Title", "Classic American Cars");
resx.AddResource("Name", "test1");
resx.AddResource("Age", "22");
}
Dictionary<string, string> pairs = new Dictionary<string, string>();
using (ResXResourceReader resxReader = new ResXResourceReader(#".\CarResources.resx"))
{
foreach (DictionaryEntry entry in resxReader)
{
pairs.Add(entry.Key.ToString(), entry.Value.ToString());
Console.WriteLine(entry.Value);
}
}
Console.WriteLine("success");
Console.ReadKey();
}
}
}
Result:
When you click the .exe file, you will get the following:
I have had success using this tutorial: http://www.codeproject.com/Tips/715891/Compiling-Csharp-Code-at-Runtime to set up a framework for runtime compilation and execution of C# code. Below is the code I currently have:
public static class CodeCompiler {
public static object InterpretString(string executable) {
string compilation_string =
#"
static class RuntimeCompilationCode {
public static void Main() {}
public static object Custom() {
/* CODE HERE */
}
}";
compilation_string = compilation_string.Replace("/* CODE HERE */", executable);
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters compiler_parameters = new CompilerParameters();
// True - memory generation, false - external file generation
compiler_parameters.GenerateInMemory = true;
// True - exe file generation, false - dll file generation
compiler_parameters.GenerateExecutable = true;
// Compile
CompilerResults results = provider.CompileAssemblyFromSource(compiler_parameters, compilation_string);
// Check errors
if (results.Errors.HasErrors) {
StringBuilder builder = new StringBuilder();
foreach (CompilerError error in results.Errors) {
builder.AppendLine(String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText));
}
throw new InvalidOperationException(builder.ToString());
}
// Execute
Assembly assembly = results.CompiledAssembly;
Type program = assembly.GetType("RuntimeCompilationCode");
MethodInfo execute = program.GetMethod("Custom");
return execute.Invoke(null, null);
}
}
I can pass a statement in the form of a string (ex. "return 2;") to InterpretString() and it will be compiled and executed as part of the Custom() function. However I am wondering if it is possible to use the same approach to execute a method that is in my original file. For instance, suppose the CodeCompiler class had another method returnsTwo() which returns the integer 2. Is there a way to call such a method by passing "CodeCompiler.returnsTwo();" or a similar string to InterpretString()?
Provided that the function is a static function this should not be a problem, as long as you add the appropriate reference to the compilation. I've done this short of thing on several projects.
If the CodeCompiler is in your current executable you have to include the references in this fashion:
string exePath = Assembly.GetExecutingAssembly().Location;
string exeDir = Path.GetDirectoryName(exePath);
AssemblyName[] assemRefs = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
List<string> references = new List<string>();
foreach (AssemblyName assemblyName in assemRefs)
references.Add(assemblyName.Name + ".dll");
for (int i = 0; i < references.Count; i++)
{
string localName = Path.Combine(exeDir, references[i]);
if (File.Exists(localName))
references[i] = localName;
}
references.Add(exePath);
CompilerParameters compiler_parameters = new CompilerParameters(references.ToArray())
I have an interface and a class that implements it. I am trying to create a unit test that will compile both the interface and the class and see that if there are any errors(if theres none then that would mean the class does implement the interface). I am able to build the interface with no errors but when I try to build the class that implements it it says that the class does not implement the interface. I made sure already that the class does implement the interface so i know that there is something wrong with the way I am trying to compile it on my unit test. I generate the inMemoryAssembly.dll and then try to add it to the parameters when trying to compile the implementation, but that is not working.Any suggestion?
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Text;
using System.IO;
namespace UnitTestProject2
{
[TestClass]
public class UnitTest1
{
[TestMethod]
[TestCategory("NumberOfFiles")]
public void TestMethod1()
{
var serviceReferancepath = System.IO.Path.GetFullPath(#"..\..\Service References\");
compileIterface(serviceReferancepath);
compileImplementation(serviceReferancepath);
}
static void compileIterface(string path)
{
CSharpCodeProvider codeProvider = new Microsoft.CSharp.CSharpCodeProvider();
ICodeCompiler compiler = codeProvider.CreateCompiler();
CompilerParameters parametersForInterface = new CompilerParameters();
parametersForInterface.GenerateInMemory = true;
parametersForInterface.ReferencedAssemblies.Add("System.dll");
parametersForInterface.ReferencedAssemblies.Add("System.Data.Services.Client.dll");
parametersForInterface.ReferencedAssemblies.Add("System.ComponentModel.dll");
parametersForInterface.OutputAssembly = "inMemoryAssembly.dll";
StringBuilder interfaceSC = new StringBuilder();
using (StreamReader sr = new StreamReader(path + #"alarm\IActiveEventSource.cs"))
{
string line;
// Read and display lines from the file until the end of
// the file is reached.
while ((line = sr.ReadLine()) != null)
{
interfaceSC.AppendLine(line);
}
}
CompilerResults results = compiler.CompileAssemblyFromSource(parametersForInterface, interfaceSC.ToString());
StringWriter sw = new StringWriter();
foreach (CompilerError ce in results.Errors)
{
if (ce.IsWarning) continue;
sw.WriteLine("{0}({1},{2}: error {3}: {4}", ce.FileName, ce.Line, ce.Column, ce.ErrorNumber, ce.ErrorText);
}
// If there were errors, raise an exception...
string errorText = sw.ToString();
}
static void compileImplementation(string path)
{
Microsoft.CSharp.CSharpCodeProvider codeProvider = new Microsoft.CSharp.CSharpCodeProvider();
ICodeCompiler compiler = codeProvider.CreateCompiler();
CompilerParameters Parameters = new CompilerParameters();
Parameters.ReferencedAssemblies.Add("inMemoryAssembly.dll");
Parameters.GenerateExecutable = false;
Parameters.GenerateInMemory = true;
StringBuilder implementationSC = new StringBuilder();
using (StreamReader sr = new StreamReader(path + #"alarm\implementation\TheActiveEventSource.cs"))
{
string line;
// Read and display lines from the file until the end of
// the file is reached.
while ((line = sr.ReadLine()) != null)
{
implementationSC.AppendLine(line);
}
}
CompilerResults results = compiler.CompileAssemblyFromSource(Parameters, implementationSC.ToString());
StringWriter sw = new StringWriter();
foreach (CompilerError ce in results.Errors)
{
if (ce.IsWarning) continue;
sw.WriteLine("{0}({1},{2}: error {3}: {4}", ce.FileName, ce.Line, ce.Column, ce.ErrorNumber, ce.ErrorText);
}
// If there were errors, raise an exception...
string errorText = sw.ToString();
}
}
}
The compileImplementation method, when I run it with a breakpoint I get the following error for the results
UnitTestProject2.alarm.ActiveEventSource' does not implement interface member 'UnitTestProject2.alarm.IActiveEventSource.EventSource
This the implementation code
namespace UnitTestProject2.alarm
{
partial class ActiveEventSource: IActiveEventSource
{
}
}
edit:
I was looking at the warnings but not the actual error that the compiling of the implementation was saying. the string errortext for the compileImplementatation method says that the inMemoryAssembly.dll could not be found
I'm trying to compile some unsafe code from an application using Codedom, but everytime I get an error saying I must use "/unsafe." I've googled the issue and added:
Parameters.CompilerOptions = "/unsafe";
To my codedom code. Are there any simple solutions for this?
Edit: if it wasn't already clear, my solution didn't work.
Edit: Here is the class.
public static bool Compile(string EXE_Name, string Source)
{
var Compiler = new CSharpCodeProvider();
var Parameters = new CompilerParameters
{
CompilerOptions = "/unsafe"
};
CompilerResults cResults = default(CompilerResults);
Parameters.GenerateExecutable = true;
Parameters.OutputAssembly = EXE_Name;
Parameters.ReferencedAssemblies.Add(typeof(System.Xml.Linq.Extensions).Assembly.Location);
Parameters.ReferencedAssemblies.Add("System.dll");
Parameters.ReferencedAssemblies.Add("System.Core.dll");
Parameters.ReferencedAssemblies.Add("System.Data.dll");
Parameters.ReferencedAssemblies.Add("System.Data.DataSetExtensions.dll");
Parameters.ReferencedAssemblies.Add("System.Deployment.dll");
Parameters.ReferencedAssemblies.Add("System.Windows.Forms.dll");
Parameters.ReferencedAssemblies.Add("System.Drawing.dll");
Parameters.ReferencedAssemblies.Add("System.Xml.dll");
Parameters.CompilerOptions = " /target:winexe";
Parameters.TreatWarningsAsErrors = false;
cResults = Compiler.CompileAssemblyFromSource(Parameters, Source);
if (cResults.Errors.Count > 0)
{
foreach (CompilerError CompilerError_loopVariable in cResults.Errors)
{
CompilerError error = CompilerError_loopVariable;
MessageBox.Show("Error: " + error.ErrorText, "", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
return false;
}
else if (cResults.Errors.Count == 0)
{
return true;
}
return true;
}
It works for me - perhaps you weren't setting the parameters correctly?
using System.CodeDom.Compiler;
using Microsoft.CSharp;
class Test
{
public static void Main(string[] args)
{
var compiler = new CSharpCodeProvider();
var parameters = new CompilerParameters {
CompilerOptions = "/unsafe"
};
var source = "unsafe struct Foo {}";
var result = compiler.CompileAssemblyFromSource(parameters, source);
// No errors are shown with the above options set
foreach (var error in result.Errors)
{
Console.WriteLine(error);
}
}
}
var Parameters = new CompilerParameters
{
CompilerOptions = "/unsafe"
};
and later:
Parameters.CompilerOptions = " /target:winexe";
You just replace "/unsafe" with " /target:winexe". Use:
Parameters.CompilerOptions += " /target:winexe";
Does anyone know how to get a CodeDomProvider in the new Microsoft.VisualStudio.TextTemplating.VSHost.BaseCodeGeneratorWithSite from the Visual Studio 2010 SDK? I used to get access to it just by in mere inheritance of the class Microsoft.CustomTool.BaseCodeGeneratorWithSite, but now with this new class it is not there. I see a GlobalServiceProvider and a SiteServiceProvider but I can't find any example on how to use them.
Microsoft.VisualStudio.TextTemplating.VSHost.BaseCodeGeneratorWithSite:
http://msdn.microsoft.com/en-us/library/bb932625.aspx
I was to do this:
public class Generator : Microsoft.VisualStudio.TextTemplating.VSHost.BaseCodeGeneratorWithSite {
public override string GetDefaultExtension() {
// GetDefaultExtension IS ALSO NOT ACCESSIBLE...
return this.InputFilePath.Substring(this.InputFilePath.LastIndexOf(".")) + ".designer" + base.GetDefaultExtension();
}
// This method is being called every time the attached xml is saved.
protected override byte[] GenerateCode(string inputFileName, string inputFileContent) {
try {
// Try to generate the wrapper file.
return GenerateSourceCode(inputFileName);
} catch (Exception ex) {
// In case of a faliure - print the exception
// as a comment in the source code.
return GenerateExceptionCode(ex);
}
}
public byte[] GenerateSourceCode(string inputFileName) {
Dictionary<string, CodeCompileUnit> oCodeUnits;
// THIS IS WHERE CodeProvider IS NOT ACCESSIBLE
CodeDomProvider oCodeDomProvider = this.CodeProvider;
string[] aCode = new MyCustomAPI.GenerateCode(inputFileName, ref oCodeDomProvider);
return Encoding.ASCII.GetBytes(String.Join(#"
", aCode));
}
private byte[] GenerateExceptionCode(Exception ex) {
CodeCompileUnit oCode = new CodeCompileUnit();
CodeNamespace oNamespace = new CodeNamespace("System");
oNamespace.Comments.Add(new CodeCommentStatement(MyCustomAPI.Print(ex)));
oCode.Namespaces.Add(oNamespace);
string sCode = null;
using (StringWriter oSW = new StringWriter()) {
using (IndentedTextWriter oITW = new IndentedTextWriter(oSW)) {
this.CodeProvider.GenerateCodeFromCompileUnit(oCode, oITW, null);
sCode = oSW.ToString();
}
}
return Encoding.ASCII.GetBytes(sCode );
}
}
Thanks for your help!
You can access the CodeDomProvider via the SiteServiceProvider by asking for the SVSMDCodeDomProvider service.
Something along the lines of:
IVSMDCodeDomProvider provider = SiteServiceProvider.
GetService(typeof(SVSMDCodeDomProvider)) as IVSMDCodeDomProvider;
if (provider != null)
{
codeDomProvider = provider.CodeDomProvider as CodeDomProvider;
}
The SiteServiceProvider is the limited scope service provider exposed by the site of a SingleFileGenerator, whereas the GlobalServiceProvider is VS' main service provider that you can ask for any globally-scoped interface.
Hope this helps.
Gareth