I have used the built in C# methods to write a compiler, like the following:
CodeDomProvider codeProvider = CodeDomProvider.CreateProvider("CSharp");
string Output = "Out.exe";
Button ButtonObject = (Button)sender;
this.RadTextBox1.Text = string.Empty;
System.CodeDom.Compiler.CompilerParameters parameters = new CompilerParameters();
//Make sure we generate an EXE, not a DLL
parameters.GenerateExecutable = true;
parameters.OutputAssembly = Output;
CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, RadTextBox1.Text);
if (results.Errors.Count > 0)
{
RadTextBox2.ForeColor = Color.Red;
foreach (CompilerError CompErr in results.Errors)
{
RadTextBox2.Text = RadTextBox2.Text +
"Line number " + CompErr.Line +
", Error Number: " + CompErr.ErrorNumber +
", '" + CompErr.ErrorText + ";" +
Environment.NewLine + Environment.NewLine;
}
}
else
{
//Successful Compile
RadTextBox2.ForeColor = Color.Blue;
Guid guid = Guid.NewGuid();
string PathToExe = Server.MapPath(Path.Combine(#"\Compiled" , Output));
FileStream fs = System.IO.File.Create(PathToExe);
using (StreamWriter sw = new StreamWriter(fs))
{
sw.Write(RadTextBox1.Text);
}
Response.WriteFile(PathToExe);
When I run this code and write a Main method (such as the code sample in http://msdn.microsoft.com/en-us/library/ms228506(VS.80).aspx, I get this error:
Line number 0, Error Number: CS5001, 'Program 'c:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\Out.exe' does not contain a static 'Main' method suitable for an entry point;
The code above is used as the basis of a compiler on my site (not yet live). So you type in code and generate an .exe assembly. But when I enter code into the textbox for code writing (Radtextbox1), even with a main method, I get the error.
What gives?
Thanks
The entry point function is special: you can't just add a method called "main" to the assembly. Instead you must add an instance of the CodeEntryPointMethod type to one of your classes.
See http://blogs.msdn.com/bclteam/archive/2005/10/01/475768.aspx for more information on some of the limitations of using the CodeEntryPointMethod.
Related
I am creating a /kind of/ custom compiler for a project. What I am doing is having users enter lines of code into either a textbox, or they can import some from text files.
I've been trying to test this for the last few days, with no results. I have a class, called Pressure, where I have a public method called 'emit' which simply shows a text box, like so...
public void emit()
{
MessageBox.Show("HOORA!");
}
and I have a text file named "testCompile.txt" as follows:
PressureTransducer pt = new PressureTransducer(0,0);
pt.emit();
which, when inserted into VS compiles just fine, as it should. Afterwards, I try to compile the file like so...
String sourceName = #"C:\Users\Devic\Desktop\CompileTester\testCompile.txt";
CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
CompilerParameters cp = new CompilerParameters();
cp.GenerateExecutable = true;
//cp.OutputAssembly = null;
cp.GenerateInMemory = true;
cp.TreatWarningsAsErrors = false;
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);
}
but VS gives me the error:
c:\Users\Devic\Desktop\CompileTester\testCompile.txt(1,29) : error CS1518: Expected class, delegate, enum, interface, or struct
The thread 0x1290 has exited with code 0 (0x0).
Any ideas as to what is going on?
You need to encapsulate the code from your text file into a usable class and method.
Below is code I've been using for a few years that allows C# scripts to run within my app and it even passes in a user defined variable. I had other parameters being passed in within my code to let the script writer have full access to other existing class instances, but I stripped those out as they are unique to my software. You could do the same if you want to provide access to any existing classes or forms in your app.
To use your class PressureTransducer you will need to ensure the DLL which declares that type is properly referenced and the namespace is included in the using section of the Fake code encapsulation. However I have a section built in to automatically reference all assemblies currently referenced by your running program, so that usually takes care of everything automatically.
Also, this takes the code in as a string for the source code and generates the assembly into memory, so there is no disk access - it runs very fast.
NOTE: There is use of an obsolete function in there, codeProvider.CreateCompiler();, but it's still working for me. I probably should update it eventually though.
private static object RunCSharpCode(string CSharpCode, bool ShowErrors, string StringParameter)
{
try
{
#region Encapsulate Code into a single Method
string Code =
"using System;" + Environment.NewLine +
"using System.Windows.Forms;" + Environment.NewLine +
"using System.IO;" + Environment.NewLine +
"using System.Text;" + Environment.NewLine +
"using System.Collections;" + Environment.NewLine +
"using System.Data.SqlClient;" + Environment.NewLine +
"using System.Data;" + Environment.NewLine +
"using System.Linq;" + Environment.NewLine +
"using System.ComponentModel;" + Environment.NewLine +
"using System.Diagnostics;" + Environment.NewLine +
"using System.Drawing;" + Environment.NewLine +
"using System.Runtime.Serialization;" + Environment.NewLine +
"using System.Runtime.Serialization.Formatters.Binary;" + Environment.NewLine +
"using System.Xml;" + Environment.NewLine +
"using System.Reflection;" + Environment.NewLine +
"public class UserClass" + Environment.NewLine +
"{" + Environment.NewLine +
"public object UserMethod( string StringParameter )" + Environment.NewLine +
"{" + Environment.NewLine +
"object Result = null;" + Environment.NewLine +
Environment.NewLine +
Environment.NewLine +
CSharpCode +
Environment.NewLine +
Environment.NewLine +
"return Result;" + Environment.NewLine +
"}" + Environment.NewLine +
"}";
#endregion
#region Compile the Dll to Memory
#region Make Reference List
Assembly[] FullAssemblyList = AppDomain.CurrentDomain.GetAssemblies();
System.Collections.Specialized.StringCollection ReferencedAssemblies_sc = new System.Collections.Specialized.StringCollection();
foreach (Assembly ThisAssebly in FullAssemblyList)
{
try
{
if (ThisAssebly is System.Reflection.Emit.AssemblyBuilder)
{
// Skip dynamic assemblies
continue;
}
ReferencedAssemblies_sc.Add(ThisAssebly.Location);
}
catch (NotSupportedException)
{
// Skip other dynamic assemblies
continue;
}
}
string[] ReferencedAssemblies = new string[ReferencedAssemblies_sc.Count];
ReferencedAssemblies_sc.CopyTo(ReferencedAssemblies, 0);
#endregion
Microsoft.CSharp.CSharpCodeProvider codeProvider = new Microsoft.CSharp.CSharpCodeProvider();
System.CodeDom.Compiler.ICodeCompiler CSharpCompiler = codeProvider.CreateCompiler();
System.CodeDom.Compiler.CompilerParameters parameters = new System.CodeDom.Compiler.CompilerParameters(ReferencedAssemblies);
parameters.GenerateExecutable = false;
parameters.GenerateInMemory = true;
parameters.IncludeDebugInformation = false;
parameters.OutputAssembly = "ScreenFunction";
System.CodeDom.Compiler.CompilerResults CompileResult = CSharpCompiler.CompileAssemblyFromSource(parameters, Code);
#endregion
if (CompileResult.Errors.HasErrors == false)
{ // Successful Compile
#region Run "UserMethod" from "UserClass"
System.Type UserClass = CompileResult.CompiledAssembly.GetType("UserClass");
object Instance = Activator.CreateInstance(UserClass, false);
return UserClass.GetMethod("UserMethod").Invoke(Instance, new object[] { StringParameter });
#endregion
}
else // Failed Compile
{
if (ShowErrors)
{
#region Show Errors
StringBuilder ErrorText = new StringBuilder();
foreach (System.CodeDom.Compiler.CompilerError Error in CompileResult.Errors)
{
ErrorText.Append("Line " + (Error.Line - 1) +
" (" + Error.ErrorText + ")" +
Environment.NewLine);
}
MessageBox.Show(ErrorText.ToString());
#endregion
}
}
}
catch (Exception E)
{
if (ShowErrors)
MessageBox.Show(E.ToString());
}
return null;
}
You might consider looking at the new Roslyn compiler. You pass a string to the Execute method in the script engine class and it will execute the code on-the-fly.
public class CSharpScriptEngine
{
private static Script _previousInput;
private static Lazy<object> _nextInputState = new Lazy<object>();
public static object Execute(string code)
{
var script = CSharpScript.Create(code, ScriptOptions.Default).WithPrevious(_previousInput);
var endState = script.Run(_nextInputState.Value);
_previousInput = endState.Script;
_nextInputState = new Lazy<object>(() => endState);
return endState.ReturnValue;
}
}
See this article for credit and a complete implementation.
So when i generate a C# program using CodeDom and scan it online it comes up as being a virus. How do I stop this?
This is the code i am using to generate it:
string Output = "Out.exe";
string[] fileArray = { "source.cs", "AssemblyInfo.cs" };
CodeDomProvider codeProvider = CodeDomProvider.CreateProvider("CSharp");
CompilerParameters parameters = new CompilerParameters();
parameters.GenerateExecutable = true;
parameters.OutputAssembly = Output;
parameters.ReferencedAssemblies.Add("System.dll");
CompilerResults results = codeProvider.CompileAssemblyFromFile(parameters, fileArray);
if (results.Errors.Count > 0)
{
foreach (CompilerError CompErr in results.Errors)
{
MessageBox.Show(
"Line number " + CompErr.Line +
", Error Number: " + CompErr.ErrorNumber +
", '" + CompErr.ErrorText + ";" +
Environment.NewLine + Environment.NewLine);
}
}
else
{
MessageBox.Show("Success!");
}
This is the code i'm using for the generated exe
using System;
using System.Text;
namespace Out
{
class Program
{
static void Main(string[] args)
{
System.Text.StringBuilder sbMessage = new System.Text.StringBuilder();
Console.WriteLine(sbMessage.Append("the result of adding (1+2) is " + (1 + 2).ToString()));
System.Console.ReadLine();
}
}
}
So when I scan the generated file, its getting detected as a virus as showen in the VirusTotal link https://www.virustotal.com/en/file/d19cb8ad9c9de9da50a29acab91b53d10327b3023aa1a32c367d17c0c50fd28c/analysis/1434772258/
I found this program ( http://support.microsoft.com/kb/304655 ) where i compile the code during runtime, It works for code that uses the reference,
using System;
Following is the the code for the program that compiles code during runtime,
CSharpCodeProvider codeProvider = new CSharpCodeProvider();
ICodeCompiler icc = codeProvider.CreateCompiler();
string Output = "Out.exe";
Button ButtonObject = (Button)sender;
textBox2.Text = "";
System.CodeDom.Compiler.CompilerParameters parameters = new CompilerParameters();
//Make sure we generate an EXE, not a DLL
parameters.GenerateExecutable = true;
parameters.OutputAssembly = Output;
CompilerResults results = icc.CompileAssemblyFromSource(parameters, textBox1.Text);
if (results.Errors.Count > 0)
{
textBox2.ForeColor = Color.Red;
foreach (CompilerError CompErr in results.Errors)
{
textBox2.Text = textBox2.Text +
"Line number " + CompErr.Line +
", Error Number: " + CompErr.ErrorNumber +
", '" + CompErr.ErrorText + ";" +
Environment.NewLine + Environment.NewLine;
}
}
else
{
//Successful Compile
textBox2.ForeColor = Color.Blue;
textBox2.Text = "Success!";
//If we clicked run then launch our EXE
if (ButtonObject.Text == "Run") Process.Start(Output);
}
And Following is the code i need to compile at runtime,
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Text.RegularExpressions;
using Newtonsoft.Json.Linq;
namespace Tsubame
{
class Program
{
static void Main(string[] args)
{
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(#"url");
// Create Client
WebClient client = new WebClient();
// Assign Credentials
client.Credentials = new NetworkCredential("user", "pass");
//Grab Data
var data = client.DownloadString(#"url");
JObject o = JObject.Parse(data);
string getFristRow = Convert.ToString(o["Body"][0]["RowId"]);
string encaplulateStart = "\\\"";
string encaplulateEnd = "\\\":";
List<string> _matches = new List<string>();
_matches = Regex.Matches(getFristRow, #"(?<=" + encaplulateStart + ").*(?=" + encaplulateEnd + ")")
.Cast<Match>()
.Select(m => m.Value)
.ToList();
foreach (string head in _matches)
{
Console.WriteLine(head);
}
Console.ReadLine();
}
}
}
But when I input this gives the error code,
Error Number: CS0234
For the references other than System. May I please know how to add additional references during runtime so that it can compile sucessfully :) Thank you very much :)
You need to add the references in CompilerParameters using CompilerParameters.ReferencedAssemblies:
var parameters = CompilerParameters
{
GenerateExecutable = true,
OutputAssembly = Output,
ReferencedAssemblies = {
"System.dll",
"System.Core.dll",
// etc
}
};
(Of course you don't have to use object initializer syntax to set this up, but it makes it neater IMO.)
I have a Compiler codes in my form Like this
Assembly BuildAssembly(string code)
{
List<string> SKParams = new List<string>();
string Caption = #"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client\";
SKParams.Add(Caption + "System" + ".dll");
SKParams.Add(Caption + "System.Windows.Forms" + ".dll");
SKParams.Add(Caption + "System.Data" + ".dll");
SKParams.Add(Caption + "System.Core" + ".dll");
SKParams.Add(Caption + "System.Drawing" + ".dll");
SKParams.Add(Caption + "System.Drawing" + ".dll");
SKParams.Add(#"D:\SK\Projelerim\ZxProject\MySDK\MySDK\bin\Debug\MySDK.dll");
SKParams.Add(Caption + "System.XML" + ".dll");
Microsoft.CSharp.CSharpCodeProvider provider = new CSharpCodeProvider();
ICodeCompiler compiler = provider.CreateCompiler();
CompilerParameters compilerparams = new CompilerParameters(SKParams.ToArray());
compilerparams.GenerateExecutable = false;
compilerparams.GenerateInMemory = true;
CompilerResults results = compiler.CompileAssemblyFromSource(compilerparams, code);
if (results.Errors.HasErrors)
{
StringBuilder errors = new StringBuilder("Compiler Errors :\r\n");
foreach (CompilerError error in results.Errors)
{
errors.AppendFormat("Line {0},{1}\t: {2}\n",
error.Line, error.Column, error.ErrorText);
MessageBox.Show(error.ErrorText);
}
throw new Exception(errors.ToString());
}
else
{
return results.CompiledAssembly;
}
}
I am getting assembly with this MEthod from string code.Now I want to add a void this assemble and change the assembly and source code (string code) How can I Make this?
The only way I know for adding methods to an existing type is using the DynamicMethod class that you find in System.Reflection.Emit. You can get some help and an example here: http://msdn.microsoft.com/en-us/library/system.reflection.emit.dynamicmethod.aspx, but it is not too user friendly. If you want to create this method starting from a string, you have to parse the string and translate it in a series of calls to Emit with the appropriate OpCodes. I think that you could much more easily recreate your assembly with the updated/added code.
If you should have to apply the newly added method to an existing instance of your class, I would create the new assembly, create a new instance of the new class and then copy the old values for the properties via reflection.
I've just got my own little custom c# compiler made, using the article from MSDN.
But, when I create a new Windows Forms application using my sample compiler, the MSDOS window also appears, and if I close the DOS window, my WinForms app closes too. How can I tell the Compiler? not to show the MSDOS window at all?
Thank you :)
Here's my code:
using System;
namespace JTS
{
public class CSCompiler
{
protected string ot,
rt,
ss, es;
protected bool rg, cg;
public string Compile(String se, String fe, String[] rdas, String[] fs, Boolean rn)
{
System.CodeDom.Compiler.CodeDomProvider CODEPROV = System.CodeDom.Compiler.CodeDomProvider.CreateProvider("CSharp");
ot =
fe;
System.CodeDom.Compiler.CompilerParameters PARAMS = new System.CodeDom.Compiler.CompilerParameters();
// Ensure the compiler generates an EXE file, not a DLL.
PARAMS.GenerateExecutable = true;
PARAMS.OutputAssembly = ot;
PARAMS.CompilerOptions = "/target:winexe"; PARAMS.ReferencedAssemblies.Add(typeof(System.Xml.Linq.Extensions).Assembly.Location);
PARAMS.LinkedResources.Add("this.ico");
foreach (String ay in rdas)
{
if (ay.Contains(".dll"))
PARAMS.ReferencedAssemblies.Add(ay);
else
{
string refd = ay;
refd = refd + ".dll";
PARAMS.ReferencedAssemblies.Add(refd);
}
}
System.CodeDom.Compiler.CompilerResults rs = CODEPROV.CompileAssemblyFromFile(PARAMS, fs);
if (rs.Errors.Count > 0)
{
foreach (System.CodeDom.Compiler.CompilerError COMERR in rs.Errors)
{
es = es +
"Line number: " + COMERR.Line +
", Error number: " + COMERR.ErrorNumber +
", '" + COMERR.ErrorText + ";" +
Environment.NewLine + Environment.NewLine;
}
}
else
{
// Compilation succeeded.
es = "Compilation Succeeded.";
if (rn) System.Diagnostics.Process.Start(ot);
}
return es;
}
}
}
In C# compiler Console window is shown when /target switch is exe. When /target=winexe, Console window is not shown.
http://msdn.microsoft.com/en-us/library/6h25dztx.aspx
Try this:
System.CodeDom.Compiler.CompilerParameters PARAMS = new System.CodeDom.Compiler.CompilerParameters();
PARAMS->CompilerOptions = "/target:winexe";
See:
http://msdn.microsoft.com/en-us/library/system.codedom.compiler.compilerparameters.compileroptions.aspx
I don't know which MSDN Article you are referring to, but if you use the AssemblyBuilder then the "magic" is in the call to SetEntryPoint.
If you have a Windows Forms application, you need to specify PEFileKinds.WindowApplication:
var asm = AppDomain.CurrentDomain.DefineDynamicAssembly(
new AssemblyName(assemblyName), AssemblyBuilderAccess.Save);
var mod = asm.DefineDynamicModule(assemblyName, fileName);
var type = mod.DefineType("Program",
TypeAttributes.Class | TypeAttributes.Sealed | TypeAttributes.Public);
var mainMethod = type.DefineMethod("Main",
MethodAttributes.Public | MethodAttributes.Static);
// ... Code for Main method and the rest ...
type.CreateType();
asm.SetEntryPoint(mainMethod,PEFileKinds.WindowApplication);
asm.Save(fileName);
Other PEFileKinds are ConsoleApplication and Dll, although I think the AssemblyBuilder automatically assumes it's a Dll if you don't specify an EntryPoint.