I use the CSharpCodeProvider to execute C# code from a string. But of course it creates a class for the code to run. Is there a way to call a method from the current app it is called from? For eg to call CallTest() from a parsed script in this (pseudo) example code
public class Test {
// lot of code
private void CallTest() {
Console.WriteLine("CallTest()");
}
public object OnMethodInvoke(string functionName, List<object> parameters) {
// lot of if statements
if(functionName == "exec") {
CSharpCodeProvider c = new CSharpCodeProvider();
ICodeCompiler icc = c.CreateCompiler();
CompilerParameters cp = new CompilerParameters();
cp.ReferencedAssemblies.Add("System.dll");
cp.CompilerOptions = "/t:library";
cp.GenerateInMemory = true;
System.Text.StringBuilder sb = new System.Text.StringBuilder("");
sb.Append("using System;\n");
sb.Append("namespace ScriptStack.Dynamic {\n");
sb.Append("public class Code {\n");
sb.Append("public object EvalCode(){\n");
sb.Append((string)parameters[0]);
sb.Append("}\n");
sb.Append("}\n");
sb.Append("}\n");
CompilerResults cr = icc.CompileAssemblyFromSource(cp, sb.ToString());
if (cr.Errors.Count > 0) return null;
System.Reflection.Assembly a = cr.CompiledAssembly;
object o = a.CreateInstance("ScriptStack.Dynamic.Code");
MethodInfo mi = o.GetType().GetMethod("EvalCode");
return mi.Invoke(o, null);
}
}
}
If I got your problem statement correctly, you want to be able to compile a statement like
t.CallTest(); //where t is an instance of your class
and run it.
As I hinted in the comments, Reflection might be way easier to do. But for a second, let's imagine you need to keep pressing on and generate some fancy code that you will then need to run (which is where LINQ Expression trees might be a better answer, but never mind - suppose we're still sticking to CodeDom).
There are a couple of issues I see to overcome here:
The unit of compilation in CodeDom is assembly, I don't believe you can have assembly nested in a class. Therefore you need to define at least a namespace and a class (which you seem to have done already)
I also don't believe CodeDom allows you to compile partial classes, therefore your generated code with direct references to Test will not compile even if you manage to overcome point #1
With the above in mind, I believe one option will be to basically have your generated code invoke reflection to obtain a reference to your desired method and invoke it dynamically (which kinda gets reduced to my suggestion #1):
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text;
using Microsoft.CSharp;
namespace ConsoleApp9
{
public class MainTestClass
{
private void CallTest(string value) => Console.WriteLine($"CallTest({value})");
public object OnMethodInvoke(string functionName, List<object> parameters)
{
if (functionName == "exec")
{
CSharpCodeProvider c = new CSharpCodeProvider();
CompilerParameters cp = new CompilerParameters();
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("ConsoleApp9.exe"); // note a reference to your main assembly here
cp.CompilerOptions = "/t:library";
cp.GenerateInMemory = true;
StringBuilder sb = new StringBuilder("");
sb.Append("using System;\n");
sb.Append("using System.Reflection;\n");
sb.Append("using ConsoleApp9;\n"); // reference your main assembly ConsoleApp9. this is my test console app
sb.Append("namespace ScriptStack.Dynamic\n");
sb.Append("{\n");
sb.Append(" public class Code\n");
sb.Append(" {\n");
sb.Append(" private MainTestClass t;\n"); // reference your
sb.Append(" public Code(MainTestClass t) { this.t = t; }\n"); // we're going to capture reference to calling MainTestClass instance and use it for reflection
sb.Append(" public object EvalCode()\n");
sb.Append(" {\n");
sb.Append(" // this is a very standard method of invoking private methods. see this SO answer: https://stackoverflow.com/a/135482/12339804 \n");
sb.Append(" var mi = typeof(MainTestClass).GetMethod(\"CallTest\", BindingFlags.NonPublic | BindingFlags.Instance);\n");
sb.Append(" return mi.Invoke(t, new object[]{ \"" + (string)parameters[0] + "\" });\n"); //suppose you want to pass a string to your generated C# source.
sb.Append(" }\n");
sb.Append(" }\n");
sb.Append("}\n");
var cr = c.CompileAssemblyFromSource(cp, sb.ToString());
if (cr.Errors.Count > 0) return null;
var a = cr.CompiledAssembly;
object o = a.CreateInstance("ScriptStack.Dynamic.Code", false, BindingFlags.Default, null, new object[] { this }, CultureInfo.InvariantCulture, null); //we have to opt for a way more overloaded CreateInstance method due to us requiring to pass a this into the Code class we're instantiating
var mi = o.GetType().GetMethod("EvalCode");
return mi.Invoke(o, null);
}
throw new NotImplementedException();
}
}
class Program
{
static void Main(string[] args)
{
var t = new MainTestClass();
t.OnMethodInvoke("exec", new List<object> { "parameter 1" });
Console.ReadKey();
}
}
}
Related
Recently I try to compile and run C# code stored somewhere else. My goal is to import a .txt file, compile it and run it. I followed this article on Simeon's blog about compiling and running C# code within the program, and everything work well.
Then I try making something a bit more complex by importing the C# code from my computer, so I created a .txt file with the following lines that is store for instance at "C:\program.txt" :
(the text file)
using System;
namespace Test
{
public class DynaCore
{
static public int Main(string str)
{
Console.WriteLine("Cool it work !");
return str.Length;
}
}
}
I do some coding based on the same article and that is my code :
(the C# program)
using System;
using System.Collections.Generic;
using System.Text;
using System.CodeDom.Compiler;
using System.IO;
using Microsoft.CSharp;
using System.Reflection;
namespace DynaCode
{
class Program
{
static void Main(string[] args)
{
string[] lines = System.IO.File.ReadAllLines(#"C:\program.txt");
string bigLine = string.Empty;
foreach(string s in lines)
{
bigLine += s;
}
string[] finalLine = new string[1] { bigLine };
CompileAndRun(finalLine);
Console.ReadKey();
}
static void CompileAndRun(string[] code)
{
CompilerParameters CompilerParams = new CompilerParameters();
string outputDirectory = Directory.GetCurrentDirectory();
CompilerParams.GenerateInMemory = true;
CompilerParams.TreatWarningsAsErrors = false;
CompilerParams.GenerateExecutable = false;
CompilerParams.CompilerOptions = "/optimize";
string[] references = { "System.dll" };
CompilerParams.ReferencedAssemblies.AddRange(references);
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerResults compile = provider.CompileAssemblyFromSource(CompilerParams, code);
if (compile.Errors.HasErrors)
{
string text = "Compile error: ";
foreach (CompilerError ce in compile.Errors)
{
text += "rn" + ce.ToString();
}
throw new Exception(text);
}
Module module = compile.CompiledAssembly.GetModules()[0];
Type mt = null;
MethodInfo methInfo = null;
if (module != null)
{
mt = module.GetType("Test.DynaCore");
}
if (mt != null)
{
methInfo = mt.GetMethod("Main");
}
if (methInfo != null)
{
Console.WriteLine(methInfo.Invoke(null, new object[] { "here in dyna code. Yes it work !!" }));
}
}
}
}
This work well, and I got the following output as expected :
Cool it work !
33
Note that I put all the code of the .txt file in one big line that I do myseft, because as Simeon said :
CompileAssemblyFromSource consumes is a single string for each block (file) worth of C# code, not for each line.
Even now this sentence still a bit obscure for me.
( I tried CompileAndRun(new string[1] { lines.ToString() }); before but there was an error when compiling the .txt file, that's why I do the big line myself. )
And here is my problem : I ask myself : "What if I add a comment in my .txt file ?", so I edit it and that how it look : (the text file)
using System;
namespace Test
{
//This is a super simple test
public class DynaCore
{
static public int Main(string str)
{
Console.WriteLine("Cool it work !");
return str.Length;
}
}
}
And of course I got an error (CS1513) because I convert the .txt file in one big string, so everything after the // is ignored. So how can I use comment using // inside my .txt file and got the program work ?
I also try CompileAndRun(lines);, but after launching the program it crash when compiling the .txt file because of the exception.
I do some search about it and I didn't find anythings about comment. I guess there is somethings wrong about passing only one big line in the CompileAndRun method, but passing several lines don't work as I say upper.
(Another note : Comment using /* insert comment */ works.)
Each element given to CompileAssemblyFromSource is supposed to be a file, not a single line of code. So read the whole file into a single string and give it to the method and it'll work just fine.
static void Main(string[] args)
{
var code = System.IO.File.ReadAllText(#"C:\program.txt");
CompileAndRun(code);
Console.ReadKey();
}
static void CompileAndRun(string code)
{
CompilerParameters CompilerParams = new CompilerParameters();
string outputDirectory = Directory.GetCurrentDirectory();
CompilerParams.GenerateInMemory = true;
CompilerParams.TreatWarningsAsErrors = false;
CompilerParams.GenerateExecutable = false;
CompilerParams.CompilerOptions = "/optimize";
string[] references = { "System.dll" };
CompilerParams.ReferencedAssemblies.AddRange(references);
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerResults compile = provider.CompileAssemblyFromSource(CompilerParams, code);
// ...
}
I'm developing some .Net application and I need to inject in any assembly new method with my own code. I'm using Mono.Cecil to get body of assembly and I found some samples, but they're old enough. Unfortunately, there's no info in migraton section on github wiki.
So, I have this code:
using System;
using Mono.Cecil;
using Mono.Cecil.Cil;
namespace CustomFieldsInjection
{
public partial class Injector
{
public static void MethodInjection(string assemblyFilename, string typeName, string methodName)
{
AssemblyDefinition assembly = AssemblyFactory.GetAssembly(assemblyFilename);
TypeReference returnTypeReference = assembly.MainModule.Import(typeof(void));
MethodDefinition methodDefinition = new MethodDefinition(methodName, MethodAttributes.Public | MethodAttributes.Static, returnTypeReference);
Instruction instruction1 = methodDefinition.Body.CilWorker.Create(OpCodes.Nop);
Instruction instruction2 = methodDefinition.Body.CilWorker.Create(OpCodes.Ldstr, methodName);
MethodReference writeline = assembly.MainModule.Import(typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }));
methodDefinition.Body.CilWorker.Append(instruction1);
methodDefinition.Body.CilWorker.Append(instruction2);
methodDefinition.Body.CilWorker.InsertAfter(instruction2, methodDefinition.Body.CilWorker.Create (OpCodes.Call, writeline));
methodDefinition.Body.CilWorker.Append (methodDefinition.Body.CilWorker.Create(OpCodes.Ret))
assembly.MainModule.Inject(methodDefinition, assembly.MainModule.Types[typeName]);
MethodReference methodReference = null;
foreach (MethodDefinition method in assembly.MainModule.Types[typeName].Methods)
{
if (method.Name == methodName)
{
methodReference = assembly.MainModule.Import(method);
break;
}
}
Instruction callTest = methodDefinition.Body.CilWorker.Create(OpCodes.Call, methodReference);
if (assembly.EntryPoint != null)
{
assembly.EntryPoint.Body.CilWorker.InsertBefore(assembly.EntryPoint.Body.Instructions[0], callTest);
}
AssemblyFactory.SaveAssembly(assembly, assemblyFilename);
}
}
}
It's old sample. Most features are up to date. I'm interesting in this construction:
assembly.MainModule.Inject(methodDefinition, assembly.MainModule.Types[typeName]);
I could not find a new analogues of this design. Someone can tell me what it can be replaced?
I'm not familiar with the construct you are referring to, but adding a MethodDefinition to an existing type is quite easy
using (var assemblyDefinition = AssemblyDefinition.ReadAssembly("assemblyPath")) {
var module = AssemblyDefinition.MainModule;
//Select the type you need to open for addition
var typeDef = module.Types.First(td => td.Name == "footer");
//Add your MethodDefinition
typeDef.Methods.Add(your_method_definition);
//Write the assembly back
assemblyDefinition.Write();
}
NOTE: If you don't use yet cecil 0.10.0.0 you'll use slightly different ReadAssembly() and Write() variants (without the using, and passing the assemblyPath to Write, mainly...)
I was wondering if there is any way to pass a variable value in a code that will be compiled One Time using CSharpCodeProvider .
for example :
string code = #"
using System;
namespace First
{
public class Program
{
public int Value; // pass this value
public static void Main()
{
" +
"Console.WriteLine(\"Hello + Value\");"
+ #"
}
}
}
";
Compile Method :
public void Compile(String Code)
{
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters();
parameters.ReferencedAssemblies.Add("System.Drawing.dll");
parameters.GenerateInMemory = true;
parameters.GenerateExecutable = false;
CompilerResults results = provider.CompileAssemblyFromSource(parameters, Code);
}
So i want to be able to pass a value of the Value example 2
and what i meant by ONE TIME is like compile time so the compiled code will always in its run-time will display the value : 2 whenever i executed the application .
I hope its clear !
Solved Using Mono.Cecil reference details Documentatin
Right now I'm working on a project, and the team wants a way to write code and edit it without having to recompile the whole project, so I've decided to try and implement a scripting engine.
Having implemented Lua into C++ before, I wasn't an entire newbie to implementing scripting capabilities into projects. However, we wanted to try and implement straight C# using the Microsoft.CSharp namespace, combined with System.Reflection that was already built in to C#.
So having hearing about this, I poked about in docs and I've come up with a prototype that ALMOST works - but doesn't quite.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;
namespace Scripting
{
class Program
{
static void Main(string[] args)
{
StringBuilder builder = new StringBuilder();
builder.Append("using System;");
builder.Append("using Scripting;");
builder.Append("class MyScript : IScript");
builder.Append("{");
builder.Append(" string ScriptName");
builder.Append(" {");
builder.Append(" get { return \"My Script\"; }");
builder.Append(" }");
builder.Append(" public bool Initialize()");
builder.Append(" {");
builder.Append(" Console.WriteLine(\"Hello, World!\");");
builder.Append(" return true;");
builder.Append(" }");
builder.Append("}");
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters param = new CompilerParameters(new string[] { "System.dll", "Scripting.dll" });
param.GenerateInMemory = true;
param.GenerateExecutable = true;
CompilerResults result = provider.CompileAssemblyFromSource(param, builder.ToString());
if (result.Errors.Count > 0)
{
foreach (CompilerError error in result.Errors)
Console.WriteLine(error);
Console.ReadKey();
return;
}
}
}
}
The issue I have at the moment is that I want to be able to reference my interface - IScript.cs (which is inside the Scripting namespace and thus, the current assembly) - so that scripts written and parsed in the compiler can access it. Obviously, I added Scripting.dll as a parameter, but it doesn't seem to be able to be accessed for some reason or another. I am running it in debug so this could be cause for some major facepalmage. What do?
Is there a way to reference the current assembly and pass it to CompilerParameters? Or am I royally screwed / should I rely on creating an assembly for script objects / etc?
It's probably looking in the wrong directory.
Pass typeof(Program).Assembly.CodeBase to pass the full path.
You can get the executable and pass it to the CompilerParameters:
string exeName = Assembly.GetEntryAssembly().Location;
param.ReferencedAssemblies.Add(exeName);
I have a string variable contain:
string classCode = "public class Person { public string Name{get;set;} }";
How can I create an instance of an object from the classCode ?
like
object obj = CreateAnInstanceAnObject(classCode);
You'll need to use CodeDom to compile an in-memory assembly, and then use reflection to create the type.
Here's a sample article on MSDN that walks through the process of code generation.
Once you've compiled the code, you can use Activator.CreateInstance to create an instance of it.
Building on the answers from above, here is a working demo to generate, compile and instantiate a class from an in-memory assembly:
namespace DynamicCompilation
{
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Reflection;
using Microsoft.CSharp;
internal static class Program
{
private static void Main()
{
var ccu = new CodeCompileUnit();
var cns = new CodeNamespace("Aesop.Demo");
cns.Imports.Add(new CodeNamespaceImport("System"));
var ctd = new CodeTypeDeclaration("Test")
{
TypeAttributes = TypeAttributes.Public
};
var ctre = new CodeTypeReferenceExpression("Console");
var cmie = new CodeMethodInvokeExpression(ctre, "WriteLine", new CodePrimitiveExpression("Hello World!"));
var cmm = new CodeMemberMethod
{
Name = "Hello",
Attributes = MemberAttributes.Public
};
cmm.Statements.Add(cmie);
ctd.Members.Add(cmm);
cns.Types.Add(ctd);
ccu.Namespaces.Add(cns);
var provider = new CSharpCodeProvider();
var parameters = new CompilerParameters
{
CompilerOptions = "/target:library /optimize",
GenerateExecutable = false,
GenerateInMemory = true
};
////parameters.ReferencedAssemblies.Add("System.dll");
var results = provider.CompileAssemblyFromDom(parameters, ccu);
if (results.Errors.Count == 0)
{
var t = results.CompiledAssembly.GetType("Aesop.Demo.Test");
var inst = results.CompiledAssembly.CreateInstance("Aesop.Demo.Test");
t.InvokeMember("Hello", BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod, null, inst, null);
}
Console.ReadLine();
}
}
}
Simple put you cannot do this in one line as you are attempting. It is possible to create an instance of an existing class via it's name and one of the overloads of Activator.CreateInstance.
What you are trying to achieve here though is quite different. You are attempting to both 1) define a new class type and 2) create an instance of it. Defining new metadata in the running process dynamically is very difficult to achieve with static languages like C#. It requires a significant amount of work that can't easily be put into a StackOverflow answer.
The following project should guide you in what your trying to accomplish:
RunTime Code Compilation
However, if you are attempting to write code at runtime, you may want to rethink your architecture. You may be creating more of a headache for yourself than you need to be.
What are you trying to accomplish by creating this object?