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.
Related
I'm using Mangement class to create a process, but That starts a UI console - I want to console to run in background.
public uint LaunchProcess(string sIPAddress, string sPort)
{
uint iPid = 0;
try
{
logger.AddLog("LaunchProcess : " + sIPAddress + " " + sPort);
object[] PlugInRunnerInfo = { StaticUtils.GetLocation(AgilentPluginCommonConstants.PlugInRunnerPath) + "\\" + "PlugInRunner.exe" + " " + sIPAddress + " " + sPort, null, null, 0 };
//ManagementClass is a part of Windows Management Intrumentation,namespaces. One of its use is to provides access to manage applications.
//Here this class is used to launch PlugInRunner as detached process.By setting the ManagementClass object's property 'CreateFlags' to value 0x00000008
//we can start the PlugInRunner as detached one.
using (var mgmtObject = new ManagementClass("Win32_Process"))
{
var processStartupInfo = new ManagementClass("Win32_ProcessStartup",null);
processStartupInfo.Properties["CreateFlags"].Value = 0x00000008;//DETACHED_PROCESS.
var result = mgmtObject.InvokeMethod("Create", PlugInRunnerInfo);
if (result != null)
{
logger.AddLog("Process id " + Convert.ToUInt32(PlugInRunnerInfo[3]));
iPid = Convert.ToUInt32(PlugInRunnerInfo[3]);
}
}
}
catch (Exception ex)
{
logger.AddLog("Exception " + ex.Message);
}
return iPid;
}
The above code what I have got. Please help me.
I made script task that's downloading and saving on disk two spreadsheets from Google Drive using file ID and prepared URL address.
This is main() from my C# code, there are no things outside of it:
public void Main()
{
string m_FileId = Dts.Variables["User::varFileId"].Value.ToString();
string m_RemoteUrl = "https://docs.google.com/spreadsheets/d/" + m_FileId + "/export?format=xlsx";
string m_FilePath = null;
WebClient client = new WebClient();
try
{
m_FilePath = Dts.Variables["User::varFilePath"].Value.ToString() + Dts.Variables["User::varFileName"].Value.ToString();
client.DownloadFile(new System.Uri(m_RemoteUrl), m_FilePath);
m_FilePath = "";
m_FileId = Dts.Variables["User::varFileId2"].Value.ToString();
m_RemoteUrl = "https://docs.google.com/spreadsheets/d/" + m_FileId + "/export?format=xlsx";
m_FilePath = Dts.Variables["User::varFilePath"].Value.ToString() + Dts.Variables["User::varFileName2"].Value.ToString();
client.DownloadFile(new System.Uri(m_RemoteUrl), m_FilePath);
}
catch(Exception e)
{
Dts.Events.FireError(0, "FileDownload", e.Message
+ "\r" + e.StackTrace
+ " \rUrl: " + m_RemoteUrl
+ " \rFilePath: " + m_FilePath
+ " \rPath: " + Dts.Variables["User::varFilePath"].Value.ToString()
+ " \rFileName2: " + Dts.Variables["User::varFileName2"].Value.ToString()
, string.Empty, 0);
Dts.TaskResult = (int)ScriptResults.Failure;
}
Dts.TaskResult = (int)ScriptResults.Success;
}
Problem occurs exactly on every second time I run this code and I don't know how to get rid of it. There's just exception in my script task. I'm printing all variables that are used in this code, and as you can see there's something wrong with m_FilePath, it's like multiplied despite of being printed just once.
[FileDownload] Error: An exception occurred during a WebClient request.
at System.Net.WebClient.DownloadFile(Uri address, String fileName)
at ST_84b63d1593dd449886eb2b32dff40b2d.ScriptMain.Main()
Url: https://docs.google.com/spreadsheets/d/----------/export?format=xlsx
FilePath: C:\Google Drive extract\ga_manual_cost_file.xlsxC:\Google Drive extract\ga_manual_cost_file.xlsx
Path: C:\Google Drive extract\ga_manual_cost_file.xlsx
FileName2: ga_manual_cost_file.xlsx
SSIS variables that I'm using are ReadOnly, and are used only in this script task(I tried running only this part of control flow), and their values are as follows:
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/
Let's say a create an (executable) assembly in memory by
compiling a code string. Then I want to serialize this assembly
object into a byte array and then store it in a database. Then later
on I want to retrieve the byte array from the database and deserialize
the byte array back into an assembly object, then invoke the entry
point of the assembly.
At first I just tried to do this serialization like I would any other simple object in .net, however apparently that won't work with an assembly object. The assembly object contains a method called GetObjectData which gets serialization data necessary to reinstantiate the assembly. So I'm somewhat confused as to how I piece all this together for my scenario.
The answer only needs to show how to take an assembly object, convert it into a byte array, convert that back into an assembly, then execute the entry method on the deserialized assembly.
A dirty trick to get the assembly bytes using reflection:
MethodInfo methodGetRawBytes = assembly.GetType().GetMethod("GetRawBytes", BindingFlags.Instance | BindingFlags.NonPublic);
object o = methodGetRawBytes.Invoke(assembly, null);
byte[] assemblyBytes = (byte[])o;
Explanation: at least in my sample (assembly was loaded from byte array) the assembly instance was of type "System.Reflection.RuntimeAssembly". This is an internal class, so it can be accessed only using reflection. "RuntimeAssembly" has a method "GetRawBytes", which return the assembly bytes.
An assembly is more conveniently represented simply as a binary dll file. If you think of it like that, the rest of the problems evaporate. In particlar, look at Assembly.Load(byte[]) for loading an Assembly from binary. To write it as binary, use CompileAssemblyFromSource and look at the result's PathToAssembly - then File.ReadAllBytes(path) to obtain the binary from the file.
System.Reflection.Assembly is ISerializable and can simply be serialized like so:
Assembly asm = Assembly.GetExecutingAssembly();
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream stream = new MemoryStream();
formatter.Serialize(stream, asm);
and deserialization is just as simple but call BinaryFormatter.Deserialize instead.
Here is my example:
public static byte[] SerializeAssembly()
{
var compilerOptions = new Dictionary<string, string> { { "CompilerVersion", "v4.0" } };
CSharpCodeProvider provider = new CSharpCodeProvider(compilerOptions);
CompilerParameters parameters = new CompilerParameters()
{
GenerateExecutable = false,
GenerateInMemory = false,
OutputAssembly = "Examples.dll",
IncludeDebugInformation = false,
};
parameters.ReferencedAssemblies.Add("System.dll");
ICodeCompiler compiler = provider.CreateCompiler();
CompilerResults results = compiler.CompileAssemblyFromSource(parameters, StringClassFile());
return File.ReadAllBytes(results.CompiledAssembly.Location);
}
private static Assembly DeserializeAssembyl(object fromDataReader)
{
byte[] arr = (byte[])fromDataReader;
return Assembly.Load(arr);
}
private string StringClassFile()
{
return "using System;" +
"using System.IO;" +
"using System.Threading;" +
"namespace Examples" +
"{" +
" public class FileCreator" +
" {" +
" private string FolderPath { get; set; }" +
" public FileCreator(string folderPath)" +
" {" +
" this.FolderPath = folderPath;" +
" }" +
" public void CreateFile(Guid name)" +
" {" +
" string fileName = string.Format(\"{0}.txt\", name.ToString());" +
" string path = Path.Combine(this.FolderPath, fileName);" +
" if (!File.Exists(path))" +
" {" +
" using (StreamWriter sw = File.CreateText(path))" +
" {" +
" sw.WriteLine(\"file: {0}\", fileName);" +
" sw.WriteLine(\"Created from thread id: {0}\", Thread.CurrentThread.ManagedThreadId);" +
" }" +
" }" +
" else" +
" {" +
" throw new Exception(string.Format(\"duplicated file found {0}\", fileName));" +
" }" +
" }" +
" }" +
"}";
}
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.