I'm looking to move a scripting solution that I currently have over to C# as I believe as this will solve some of the issues which I am currently facing when it comes to running on different platforms. I can call functions which are within the script and access their variables, however, one thing that I would like to be able to do is call a function from the class that the script resides in. Does anyone know how I would be able to do this?
Here is my code at the minute which is working for calling and access objects within the script, but I would like to be able to call the method "Called" from within the script, but cannot:
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.CSharp;
namespace scriptingTest
{
class MainClass
{
public static void Main (string[] args)
{
var csc = new CSharpCodeProvider ();
var res = csc.CompileAssemblyFromSource (
new CompilerParameters ()
{
GenerateInMemory = true
},
#"using System;
public class TestClass
{
public int testvar = 5;
public string Execute()
{
return ""Executed."";
}
}"
);
if (res.Errors.Count == 0) {
var type = res.CompiledAssembly.GetType ("TestClass");
var obj = Activator.CreateInstance (type);
var output = type.GetMethod ("Execute").Invoke (obj, new object[] { });
Console.WriteLine (output.ToString ());
FieldInfo test = type.GetField ("testvar");
Console.WriteLine (type.GetField ("testvar").GetValue (obj));
} else {
foreach (var error in res.Errors)
Console.WriteLine(error.ToString());
}
Console.ReadLine ();
}
static void Called() // This is what I would like to be able to call
{
Console.WriteLine("Called from script.");
}
}
}
I am attempting to do this in Mono, however, I don't believe this should affect how this would be resolved.
There are a handful of things you need to change.
MainClass and Called need to be accessible to other assemblies so make them public. Additionally, you need to add a reference to the current assembly to be able to access it in your script code. So essentially your code will end up looking like:
public class MainClass
public static void Called()
var csc = new CSharpCodeProvider();
var ca = Assembly.GetExecutingAssembly();
var cp = new CompilerParameters();
cp.GenerateInMemory = true;
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("mscorlib.dll");
cp.ReferencedAssemblies.Add(ca.Location);
var res = csc.CompileAssemblyFromSource(
cp,
#"using System;
public class TestClass
{
public int testvar = 5;
public string Execute()
{
scriptingTest.MainClass.Called();
return ""Executed."";
}
}"
);
The output of running the test looks like:
Called from script.
Executed.
5
Related
I try to make a simple console modding project in C# where I have my program that contain a list of an abstract class named ElementInGame. I want to be able to create others class that inherit ElementInGame from a .txt file. The class ElementInGame will contain some basic methods (virtual and not virtual). But I don't want these other modded class execute malicious code, I would like that they can only access the methods/properties from the inherited class. Here is my ElementInGame code :
(My C# program #1)
using System;
namespace Modding
{
//The class itself inherit from MarshalByRefObject to be available in 2 differents Domains
public abstract class ElementInGame : MarshalByRefObject
{
public ElementInGame()
{
Console.WriteLine("ElementInGame class created");
}
public virtual int GetNumber()
{
return 10;
}
public void CountToTen()
{
for (int i = 0; i <= 10; i++)
{
Console.WriteLine(i);
}
}
}
}
Then I have my .txt file stored at "C:\program.txt"
(My original .txt file)
using System;
namespace Test
{
public class HelloWorld
{
public HelloWorld()
{
Console.WriteLine("Called Constructor() !");
}
public static int TestMethod()
{
Console.WriteLine("Called TestMethod() !");
return 11;
}
}
}
So I code the main program to read the .txt file, compile it with restrictions, and execute it :
(My C# program #2 in a second .cs file, long code warning)
using System;
using System.CodeDom.Compiler;
using System.IO;
using Microsoft.CSharp;
using System.Reflection;
using System.Security.Permissions;
using System.Security;
using System.Security.Policy;
using System.Runtime.Remoting;
using System.Collections.Generic;
namespace Modding
{
public class Program : MarshalByRefObject
{
public static void Main(string[] args)
{
string assemblyPath = #"C:\program.txt"; // Where the .txt file is stored
string code = File.ReadAllText(assemblyPath); //The code to compile
CompilerResults compile = CompileFromCode(code); //Compile the code in the temporary files
string fullPath = compile.PathToAssembly; //sample : C:\Users\MY_USER_NAME\AppData\Local\Temp\5v2p3qki.dll
string pathWithoutFile = Path.GetDirectoryName(fullPath); //sample : C:\Users\MY_USER_NAME\AppData\Local\Temp
string pathNameOnly = Path.GetFileNameWithoutExtension(fullPath); //sample : 5v2p3qki
Program newDomainInstance = GetOtherProtectedDomainInstance(pathWithoutFile);
newDomainInstance.CallMethod(pathNameOnly, "Test.HelloWorld", "TestMethod", null, null);
newDomainInstance.CreateObject(pathNameOnly,"Test.HelloWorld");
List<ElementInGame> allElement = new List<ElementInGame>();
//allElement.Add ***?***
Console.ReadKey();
}
public static Program GetOtherProtectedDomainInstance(string pathWithoutFile)
{
AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = pathWithoutFile;
//Set some permissions to avoid malicious code
PermissionSet permSet = new PermissionSet(PermissionState.None);
permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
StrongName fullTrustAssembly = new StrongName(
new StrongNamePublicKeyBlob(typeof(Program).Assembly.GetName().GetPublicKey()),
typeof(Program).Assembly.GetName().Name,
typeof(Program).Assembly.GetName().Version);
AppDomain newDomain = AppDomain.CreateDomain("Sandbox", null, adSetup, permSet, fullTrustAssembly);
ObjectHandle handle = Activator.CreateInstanceFrom(
newDomain, typeof(Program).Assembly.ManifestModule.FullyQualifiedName,
typeof(Program).FullName
);
Program newDomainInstance = (Program)handle.Unwrap();
return newDomainInstance;
}
public static CompilerResults CompileFromCode(string code)
{
//Compile the code in a .dll locate in the temporary files
//The following code is based on https://stackoverflow.com/questions/10314815/trying-to-compile-and-execute-c-sharp-code-programmatically
CompilerParameters CompilerParams = new CompilerParameters();
string outputDirectory = Directory.GetCurrentDirectory();
CompilerParams.GenerateInMemory = false;
CompilerParams.TreatWarningsAsErrors = false;
CompilerParams.GenerateExecutable = false;
CompilerParams.CompilerOptions = "/optimize";
//Adding a reference to the current project to allow the .txt file to inherit the class "ElementInGame" later
string[] references = { "System.dll", Assembly.GetEntryAssembly().Location };
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);
}
return compile;
}
public static void DisplaySomething()//Useful for later
{
Console.WriteLine("This isn't supposed to be display");
}
//Calling a method from the restricted Domain
public void CallMethod(string assemblyName, string typeName, string entryPoint, object objectToExecute = null, object[] parameters = null)
{
MethodInfo target = Assembly.Load(assemblyName).GetType(typeName).GetMethod(entryPoint);
try
{
target.Invoke(objectToExecute, parameters);
}
catch
{
Console.WriteLine("Security Error with Method " + assemblyName + " namespace : " + typeName + " method : " + entryPoint);
}
}
//Create an instance from the restricted Domain
public void CreateObject(string assemblyName, string typeName)
{
try
{
object o = Assembly.Load(assemblyName).CreateInstance(typeName);
}
catch
{
Console.WriteLine("Security Error with Constructor " + assemblyName + " namespace : " + typeName);
}
}
}
}
For the moment the .txt file don't have any link at all with my C# program. The code work properly and I got the following output :
Called TestMethod() !
Called Constructor() !
Then I edit my code in my .txt file to inherit from Modding.ElementInGame :
(My edited .txt file)
using System;
namespace Test
{
public class HelloWorld : Modding.ElementInGame
{
public HelloWorld() : base()
{
Console.WriteLine("Called Constructor() !");
}
public static int TestMethod()
{
Console.WriteLine("Called TestMethod() !");
return 11;
}
}
}
So I expected an output like :
Called TestMethod() !
ElementInGame class created
Called Constructor() !
But after this change, the program crash with a System.NullReferenceException at when calling the TestMethod : newDomainInstance.CallMethod(pathNameOnly, "Test.HelloWorld", "TestMethod", null, null);
However creating an instance of HelloWorld (the .txt file): newDomainInstance.CreateObject(pathNameOnly,"Test.HelloWorld"); seem to works (no crash, the code stay in the try part when doing the try/catch), but my there is nothings print in my console, so it doesn't work I guess ?
Changing the permission of the AppDomain change nothing.
PermissionSet permSet = new PermissionSet(PermissionState.Unrestricted);
permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.AllFlags));
So my question is : How can I create and store an instance of the .txt file in my program that inherit from ElementInGame (and add it to the list of ElementInGame) ?
That way I can use from my program the virtual method GetNumber(). I don't want the .txt file have access to the program itself (like calling the method DisplaySomething()), just communicate with ElementInGame.
You are generating and loading reference assemblies from different locations. You did set the current directory for output but forgot to assign it to compiler parameters.
string outputDirectory = Directory.GetCurrentDirectory();
CompilerParams.OutputAssembly = Path.Combine(outputDirectory, "Test.dll");
This should fix the issue.
I'm very new to C#. Coming from pure C/C++ background. Hence please go easy on me, if my question is basic.
I'm trying to declare an object of struct Abc which is complaining a compilation error as below which means that, the object obj is NOT recognized.
string mains1 = obj.s1;
error CS0120: An object reference is required for the non-static
field, method, or property 'csharp1.Program.obj'
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace csharp1
{
class Program
{
public struct Abc
{
public string s1;
};
Abc obj = new Abc();;
static void Main(string[] args)
{
System.Console.WriteLine("start main");
string cwd = Directory.GetCurrentDirectory();
string fileName = "Myfile.xml";
string destFile = System.IO.Path.Combine(cwd, fileName);
string inpFile = "MyfileINP.xml";
string inpDir = System.IO.Path.Combine(cwd, "SubDir");
string srcfile = System.IO.Path.Combine(inpDir,inpFile);
File.Copy(srcfile, destFile, false);
string mains1 = obj.s1;
}
}
}
Not sure what is the error here.
This is complaining because you are trying to access a non static instance "obj" from a static context the Main method so there are multiple ways to resolve it you can make that instance static too like "static Abc obj=new Abc()" or you can shift that declaration inside Main method or you can create an instance of Program class and then use that instance.
METHOD 1:
static Abc obj = new Abc();
static void Main(string[] args)
{
System.Console.WriteLine("start main");
//
string mains1 = obj.s1;
}
METHOD 2:
static void Main(string[] args)
{
Abc obj = new Abc();
System.Console.WriteLine("start main");
//
string mains1 = obj.s1;
}
METHOD 3:
Abc obj = new Abc();
static void Main(string[] args)
{
System.Console.WriteLine("start main");
//
string mains1 = new Program().obj.s1;
}
Its always not a good practice to make "static" everywhere until
there is no other option you have, making static allow this object to
be shared across all the instances of this class so its not good thing
until we really need that.
You need to create an instance of the containing class Program before accessing it's member(s) like
string mains1 = new Program().obj.s1;
You are trying to access obj (a non-static method) from within Main (a static method). Because of this, your are getting CS0120. I advise changing Abc obj = new Abc() to static ABC obj = new Abc().
I have the following code, using Mono.Cecil:
{
ModuleDefinition module = ModuleDefinition.ReadModule("library.dll");
TypeDefinition type1 = module.Types.Where(t => "Namespace.Destination".Equals(t.FullName)).Single();
TypeDefinition type2 = module.Types.Where(t => "Namespace.Target".Equals(t.FullName)).Single();
MethodDefinition method1 = type1.Methods.Where(m => "Test".Equals(m.Name)).Single();
MethodDefinition method2 = type2.Methods.Where(m => "Test".Equals(m.Name)).Single();
var processor = methodTesta1.Body.GetILProcessor();
var newInstruction = processor.Create(OpCodes.Call, methodTesta2);
var firstInstruction = methodTesta1.Body.Instructions[0];
processor.Replace(firstInstruction, newInstruction);
}
namespace Namespace
{
public class Destination
{
public String Test()
{
Console.Write("Destination method");
}
}
public class Target
{
public String Test()
{
Console.Write("Target Method");
}
}
}
I'd not like to create a new "dll" file or overwrite the current, I want to modify class only at runtime.
How can I "persist" the modification and create a new instance of Destination class with modified method?
Is there a way to do it?
EDIT: The objective is execute a different method body, when certain method is called, wich return a certain type.
I am trying to add support for System.Web.Mvc.HtmlHelper to a CLI app for compiling Razor templates, but although it compiles it fails at runtime with:
System.TypeLoadException: Could not load type 'HtmlHelper`1' from assembly '/Users/oligofren/src/razor-cli/build/System.Web.Mvc.dll'.
How should I proceed in fixing this?
I am not well versed in the core of .NET (here in Mono version), so I can't say if I have done anything wrong here. I have added all the assemblies to the build folder (where the exe ends up) and I also try to manually load the required assemblies before RazorEngine tries to compile the assemblies.
How can I resolve this?
Full source code
// See also tips on building cli apps with razorengine: https://github.com/Antaris/RazorEngine/blob/master/src/source/RazorEngine.Hosts.Console/RazorEngine.Hosts.Console.csproj
using System;
using System.Web;
using System.Web.Routing;
using System.Web.Mvc;
using Moq;
using System.IO;
using Newtonsoft.Json.Linq;
using RazorEngine;
using RazorEngine.Templating; // For extension methods.
using RazorEngine.Configuration;
using RazorEngine.Text;
public class RazorCli
{
static public void Main (string[] args)
{
CheckCommandLine(args);
string template = ReadFile(args[0]);
JObject model = ParseModel(args[1]);
// try to load the required assemblies
//http://stackoverflow.com/a/23496144/200987
System.Reflection.Assembly.Load("System.Web");
System.Reflection.Assembly.Load("System.Web.Mvc");
var result = CompileTemplate(template, model);
Console.WriteLine (result);
}
private static string CompileTemplate (string template, JObject model)
{
string res = "";
var config = new TemplateServiceConfiguration();
// You can use the #inherits directive instead (this is the fallback if no #inherits is found).
config.BaseTemplateType = typeof(MyClassImplementingTemplateBase<>);
try
{
using (var service = RazorEngineService.Create(config))
{
res = service.RunCompile(template, "templateKey", null, model);
}
}
catch( RazorEngine.Templating.TemplateCompilationException ex )
{
Console.WriteLine (ex);
System.Environment.Exit(1);
}
return res;
}
/* Cannot dispatch a dynamic object to extension methods */
private static JObject ParseModel(string fileName){
string json = ReadFile(fileName);
return JObject.Parse(json);
}
private static void CheckCommandLine(string[] args){
if(args.Length != 2){
Usage();
System.Environment.Exit(1);
}
}
private static void Usage(){
string usage = "Usage: razor-cli <partial.cshtml> <model.json>\n";
Console.WriteLine(usage);
}
private static String ReadFile(string filename)
{
string result;
using (StreamReader sr = new StreamReader(filename))
{
result = sr.ReadToEnd();
}
return result;
}
}
public class MyHtmlHelper
{
public IEncodedString Raw(string rawString)
{
return new RawString(rawString);
}
}
// https://antaris.github.io/RazorEngine/TemplateBasics.html
public abstract class MyClassImplementingTemplateBase<T> : TemplateBase<T>
{
public MyClassImplementingTemplateBase()
{
Html = MvcHelpers.CreateHtmlHelper<Object>();
}
public HtmlHelper Html { get; set; }
}
// Ripped straight from a SO Q/A
// http://stackoverflow.com/questions/17271688/mocking-viewcontext-to-test-validation-error-messages
public class MvcHelpers {
public static HtmlHelper<TModel> CreateHtmlHelper<TModel>(ViewDataDictionary dictionary = null)
{
if (dictionary == null)
dictionary = new ViewDataDictionary { TemplateInfo = new TemplateInfo() };
var mockViewContext = new Mock<ViewContext>(
new ControllerContext(
new Mock<HttpContextBase>().Object,
new RouteData(),
new Mock<ControllerBase>().Object),
new Mock<IView>().Object,
dictionary,
new TempDataDictionary(),
new Mock<TextWriter>().Object);
var mockViewDataContainer = new Mock<IViewDataContainer>();
mockViewDataContainer.Setup(v => v.ViewData).Returns(dictionary);
return new HtmlHelper<TModel>(mockViewContext.Object, mockViewDataContainer.Object);
}
}
Details on how I run this can be seen in the Makefile, if that helps.
Further details
Installed Mono 4.2.2.0 using Homebrew on OS X 10.11.4.
I'm trying to call a method from another class within a service, however it's saying that the method I'm trying to call doesn't exist and would like some help if possible.
the program is a work project, which logs user inactivity as we've had issues with people not picking up the phone, code is below, this is a topshelf service that consumes messages from rabbitMQ and I want it to consume the messages and forward them to a database =]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using NLog;
using IWshRuntimeLibrary;
using Topshelf;
using System.Data.Odbc;
using EasyNetQ;
using RabbitMQ;
using EasyNetQ.Topology;
using System.Threading.Tasks;
using System.Windows.Forms;
using AccessEye;
namespace LogService
{
public class WindowsServiceHost : ServiceControl, ServiceShutdown
{
public static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public bool Start(HostControl hostControl)
{
Program.bus = RabbitHutch.CreateBus("host=as01.access.local;virtualHost=DEV-Reece;username=reece;password=reece").Advanced;
//var bus = RabbitHutch.CreateBus("host=as01.access.local;virtualHost=DEV-Reece;username=reece;password=reece").Advanced;
var queue = Queue.Declare(true, false, true, null);
var exchange = Exchange.DeclareFanout("UserActivityFanout", true, false, null);
var exchangeTopic = Exchange.DeclareTopic("UserActivity", true, false, null);
queue.BindTo(exchange, "#");
exchange.BindTo(exchangeTopic, "#");
Program.bus.Subscribe<AccessEye.LogData>(queue, (msg, messageRecInfo) => Task.Factory.StartNew(() =>
{
WriteLogDataToDb();
Console.WriteLine(msg.Body.UserName + " -- " + msg.Body.ComputerName + " -- " + msg.Body.EventType + " -- " + msg.Body.TeamviewerId);
}));
return true;
}
And this is the method I'm trying to call
public partial class AppForm : Form
{
public static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private Screensaver watcher;
public Inactivity inactivity;
IAdvancedBus bus;
IExchange exchange;
public void WriteLogDataToDb(LogData data)
{
using (var db = new LogService.UserActivityDataContext())
{
DbLogData logData = AutoMapper.Mapper.Map<LogData, DbLogData>(data);
int t = (int)data.EventType;
EventType eventType = db.EventTypes.FirstOrDefault(r => r.Id == t);
if (eventType == null)
{
eventType = db.EventTypes.Add(new EventType
{
Event = GetEnumDescriptionAttributeValue(data.EventType),
Id = (int)data.EventType
});
db.SaveChanges();
}
logData.EventTypeId = eventType.Id;
db.LogEvents.Add(logData);
db.SaveChanges();
}
}
If your class with the WriteLogDataToDb() declared is called ClassA, then do two things. Make the method static, and you actually have to pass some LogData data through it.
public class AppForm
{
public static void WriteLogDataToDb(LogData data)
{
using (var db = new LogService.UserActivityDataContext())
{
DbLogData logData = AutoMapper.Mapper.Map<LogData, DbLogData>(data);
int t = (int)data.EventType;
EventType eventType = db.EventTypes.FirstOrDefault(r => r.Id == t);
if (eventType == null)
{
eventType = db.EventTypes.Add(new EventType
{
Event = GetEnumDescriptionAttributeValue(data.EventType),
Id = (int)data.EventType
});
db.SaveChanges();
}
logData.EventTypeId = eventType.Id;
db.LogEvents.Add(logData);
db.SaveChanges();
}
}
}
Then in your Start code, you have to call AppForm.WriteLogDataToDb(data)
Edit:
Now that these classes are in two different projects, you need to add reference so your WindowsServiceHost can use AppForm. To do this:
Right-click > Properties on the project containing AppForm. On the Application tab, take note of the Assembly name:
Right-click the References item in WindowsServiceHost and choose Add reference
Go to the Projects tab
Add the Assembly name: noted in step #1
Right click AppForm in WindowsSerivceHost and Resolve by adding your using statement.