Not so long ago I started to learn to use System.Reflection.Emit namespace. I'm now trying to translate this code to use of ILGenerator:
MyClass c = new MyClass("MyClass");
c.Do(":D");
For this piece of code I have three questions: how to create object? how to call contructor and how to call method of class? Please help.
Here's a complete example that shows the necessary IL code.
You can test this in LINQPad:
void Main()
{
// Manual test first
MyClass c = new MyClass("MyClass");
c.Do(":D");
var method = new DynamicMethod("dummy", null, Type.EmptyTypes);
var il = method.GetILGenerator();
// <stack> = new MyClass("MyClass");
il.Emit(OpCodes.Ldstr, "MyClass");
il.Emit(OpCodes.Newobj, typeof(MyClass).GetConstructor(new[] { typeof(string) }));
// <stack>.Do(":D");
il.Emit(OpCodes.Ldstr, ":D");
il.Emit(OpCodes.Call, typeof(MyClass).GetMethod("Do", new[] { typeof(string) }));
// return;
il.Emit(OpCodes.Ret);
var action = (Action)method.CreateDelegate(typeof(Action));
action();
}
public class MyClass
{
public MyClass(string text)
{
Console.WriteLine("MyClass(" + text + ")");
}
public void Do(string text)
{
Console.WriteLine("Do(" + text + ")");
}
}
Output:
MyClass(MyClass)
Do(:D)
MyClass(MyClass)
Do(:D)
Incidentally, you can use LINQPad to get hold of the IL code for a particular example. Let me cut out the IL-part of the above example, like this (I removed the class as well, it's the same class):
void Main()
{
MyClass c = new MyClass("MyClass");
c.Do(":D");
}
By executing this code, and then using the IL tab of the output, you can see the generated code:
The two instructions stloc.0 and ldloc.0 is the variable in the code.
The IL I emitted first is akin to this piece of code:
new MyClass("MyClass").Do(":D");
ie. no variable, just temporary storage on the stack, and indeed:
Related
I want to make a machine learning api for use with a web application, the field names will be passed to the api with their data types.
Currently I am making a class at runtime with the code provided in this answer: https://stackoverflow.com/a/3862241
The problem arises when I need to call the ML.NET PredictionFunction, I can't pass in the types for the generic function since they are made at runtime.
I've tried using reflection to call it however it seems to not be able to find the function.
NOTE: Right now the docs for ML.NET is being updated for 0.9.0 so it is unavailable.
What I've tried is this (minimal):
Type[] typeArgs = { generatedType, typeof(ClusterPrediction) };
object[] parametersArray = { mlContext }; // value
MethodInfo method = typeof(TransformerChain).GetMethod("MakePredictionFunction");
if (method == null) { // Using PredictionFunctionExtensions helps here
Console.WriteLine("Method not found!");
}
MethodInfo generic = method.MakeGenericMethod(typeArgs);
var temp = generic.Invoke(model, parametersArray);
The full (revised and trimmed) source (for more context):
Program.cs
namespace Generic {
class Program {
public class GenericData {
public float SepalLength;
public float SepalWidth;
public float PetalLength;
public float PetalWidth;
}
public class ClusterPrediction {
public uint PredictedLabel;
public float[] Score;
}
static void Main(string[] args) {
List<Field> fields = new List<Field>() {
new Field(){ name="SepalLength", type=typeof(float)},
new Field(){ name="SepalWidth", type=typeof(float)},
new Field(){ name="PetalLength", type=typeof(float)},
new Field(){ name="PetalWidth", type=typeof(float)},
};
var generatedType = GenTypeBuilder.CompileResultType(fields);
var mlContext = new MLContext(seed: 0);
TextLoader textLoader = mlContext.Data.TextReader(new TextLoader.Arguments() {
Separator = ",",
Column = new[]
{
new TextLoader.Column("SepalLength", DataKind.R4, 0),
new TextLoader.Column("SepalWidth", DataKind.R4, 1),
new TextLoader.Column("PetalLength", DataKind.R4, 2),
new TextLoader.Column("PetalWidth", DataKind.R4, 3)
}
});
IDataView dataView = textLoader.Read(Path.Combine(Environment.CurrentDirectory, "Data", "flowers.txt"););
var pipeline = mlContext.Transforms
.Concatenate("Features", "SepalLength", "SepalWidth", "PetalLength", "PetalWidth")
.Append(mlContext.Clustering.Trainers.KMeans("Features", clustersCount: 3));
var model = pipeline.Fit(dataView);
Type[] typeArgs = { generatedType, typeof(ClusterPrediction) };
object[] parametersArray = { mlContext }; // value
MethodInfo method = typeof(TransformerChain).GetMethod("MakePredictionFunction");
if (method == null) { // Using PredictionFunctionExtensions helps here
Console.WriteLine("Method not found!");
}
MethodInfo generic = method.MakeGenericMethod(typeArgs);
var temp = generic.Invoke(model, parametersArray);
var prediction = temp.Predict(new GenericData {SepalLength = 5.6f, SepalWidth = 2.5f,
PetalLength = 3.9f, PetalWidth = 1.1f});
}
}
}
Try reading your test data in an IDataView, than pass that IDataView to model.Transform();
That should insert the Score and the PredictedLabel as separate columns in your test data.
It seems, when trying to reflect for the MakePredictionFunction, you confused the TransformerChain<TLastTransformer> type (which is an instantiable generic type) with the static class TransformerChain.
But even reflecting upon TransformerChain<TLastTransformer> will not succeed, because MakePredictionFunction is not a method declared by this type. Rather, MakePredictionFunction is an extension method declared in the static class PredictionFunctionExtensions⁽¹⁾.
Thus, to get the MethodInfo for MakePredictionFunction, try this:
MethodInfo method = typeof(PredictionFunctionExtensions).GetMethod("MakePredictionFunction");
⁽¹⁾
I am not 100% certain which namespace PredictionFunctionExtensions resides in. Searching the ML.NET 0.9.0 API documentation, it seems it resides in the Microsoft.ML.Runtime.Data namespace. But trying to visit the actual documentation page for MakePredictionFunction currently only results in a 404 error, so there is a chance that this information might perhaps not be accurate (i am no ML.NET user, so i can't verify) :-(
So what i am trying to do is ;
1) I am getting a string input from user.
2) I am searching the system if project contains a function with the same name of user input.
3) If i find a function with the same name of input i am trying to execute / invoke it.
4) Usually this function is placed into another class , so i tried to create instance of class using Activators but invoke function still fails.
5) Invoke function gives me error ;
Can not invoke method : (methodName) method could not be called !
Here is the code that i am currently working on ;
public void Execute()
{
// If we are only looking for function inputs.
if (!m_canReadCls)
{
// If there is already a class linked into Developer Console.
if (s_linkedType != null)
{
MethodInfo[] tmp = ReflectionExtensions.GetFunctions(s_linkedType);
// Using linear search algorithm for executing functions.
// Need to optimize it !
if (tmp!= null)
{
string funcName = m_uProps.m_inptField.text;
int i;
for (i = 0 ;i < tmp.Length;i++)
{
if ( tmp[i].Name == funcName)
{
var instance = Activator.CreateInstance( s_linkedType);
MethodInfo m = instance.GetType().GetMethod( funcName);
Invoke(m.Name, 0.0f);
Reset();
}
}
}
}
}
}
Any help is great , thanks :-)
See Microsoft help here You can call m.Invoke. or see this post
In more details
public object Invoke(
object obj,
object[] parameters
)
and
Type magicType = Type.GetType("MagicClass");
ConstructorInfo magicConstructor = magicType.GetConstructor(Type.EmptyTypes);
object magicClassObject = magicConstructor.Invoke(new object[]{});
// Get the ItsMagic method and invoke with a parameter value of 100
MethodInfo magicMethod = magicType.GetMethod("ItsMagic");
object magicValue = magicMethod.Invoke(magicClassObject, new object[]{100});
I have an HelloWorld method with some logic in it. I have to generate many methods with different names that have same logic implemantation as HelloWorld. If HelloWorld method don't have any parameters it's easy to do. Problem starts when I have to pass some parameters to HelloWorld using DynamicMethod. Here is a pice of code to help you understand.
public string HelloWorld(string Greeting)
{
return Greeting;
}
public void MethodGenerator()
{
MethodInfo HelloWorldMethod = typeof(MyClass).GetMethod("HelloWorld");
DynamicMethod DM = new DynamicMethod("HelloWorld", typeof(string), new Type[] { typeof(MyClass) });
ILGenerator IL = DM.GetILGenerator();
IL.Emit(OpCodes.Ldarg_0);
IL.Emit(OpCodes.Call, HelloWorldMethod);
IL.Emit(OpCodes.Ret);
Func<MyClass, string> DMDelegate = (Func<MyClass, string>)DM.CreateDelegate(typeof(Func<MyClass, string>));
string Result = DMDelegate(MyObject);
}
Please help me to modify this code for my case. Thanks in advance.
P.S. I've already googled it, and didn't found anything for my case. Here are some google results that I used
Used Code example
Another example
I need to convert an existing code that uses Reflection.Emit to Roslyn.
The code I have currently is basically this:
var assemblyName = new AssemblyName("AssemblyName");
var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Save);
var builder = assemblyBuilder.DefineDynamicModule("test", "test.dll");
var type = builder.DefineType("Entry", TypeAttributes.Public, typeof(object), null);
var method = type.DefineMethod("###Start_v1.4.3.0", MethodAttributes.Public | MethodAttributes.HideBySig);
method.SetReturnType(typeof(void));
var generator = method.GetILGenerator();
generator.Emit(OpCodes.Nop);
generator.Emit(OpCodes.Ret);
type.CreateType();
assemblyBuilder.Save(#"test.dll");
As you can see, there is a class named Entry with a method called ###Start_v1.4.3.0.
We're using this for more than 7 years now but evereytime we need to change anything, it's a pain because we need to use those Emits and it's not trivial.
It would be great if we could just have Roslyn to compile the code:
public class Entry
{
public void ###Start_v1.4.3.0()
{
}
}
But it doesn't work due to the method name being invalid.
The compiled dll is used by a third party component and it looks for this class and method name to execute. We tried to reach the developers to have a new version but no luck.
I think Roslyn won't compile this at all, but I believe there might be a way to rename the method name later from let's say just Start() to ###Start_v1.4.3.0()... I just don't know how to do this.
Any help will be very welcome.
If the only problem is the illegal method name, you can easily resolve that issue.
Compile the dll with a legal name, and then you have several ways to change the method name.
With mono.cecil its pretty simple.
public void ChangeMethodName()
{
//Before changing the method name
var assem = Assembly.LoadFile(#"C:\temp\ClassLibrary1.dll");
Console.WriteLine(
assem.GetType("ClassLibrary1.Class1").
GetMethod("Start", BindingFlags.Static | BindingFlags.Public).
Invoke(null, null));
// Change the name
var module = ModuleDefinition.ReadModule(#"C:\temp\ClassLibrary1.dll");
TypeDefinition myType =
module.Types.First(type => type.Name == "Class1");
var method = myType.Methods.First(m => m.Name == "Start");
method.Name = "###Start_v1.4.3.0";
module.Write(#"C:\temp\ClassLibrary1_new.dll");
//After changing the method name
assem = Assembly.LoadFile(#"C:\temp\ClassLibrary1_new.dll");
Console.WriteLine(
assem.GetType("ClassLibrary1.Class1").
GetMethod("###Start_v1.4.3.0",
BindingFlags.Static|BindingFlags.Public).
Invoke(null, null));
}
public class Class1
{
public static string Start()
{
return $"my name is {MethodBase.GetCurrentMethod().Name}";
}
}
I have the following method:
public void DoSomething()
{
Console.WriteLine("");
}
I want to modify this code with Mono Cecil. I want to create an instance of a custom class within the method:
public void DoSomething()
{
MyClass instance = new MyClass();
Console.WriteLine("");
}
Currently I use the following code:
var constructorInfo = typeof(MyClass).GetConstructor(new Type[] { });
MethodReference myClassConstructor = targetAssembly.MainModule.Import(constructorInfo);
var processor = targetMethod.Body.GetILProcessor();
var firstInstruction = targetMethod.Body.Instructions[1];
var instructions = new List<Instruction>() {
processor.Create(OpCodes.Newobj, myClassConstructor),
processor.Create(OpCodes.Stloc_0)
};
foreach (var instruction in instructions)
{
processor.InsertBefore(firstInstruction, instruction);
}
After applying those changes, the program is invalid and cannot be executed.
If i use 'IL DASM' to look at the generated code the following statement is missing:
.locals init ([0] class [MyAssembly]MyNamespace.MyClass instance)
The rest of the IL is the same, as if I directly compile the full code.
Any ideas what I am doing wrong?
I have not tried it but by looking at the Cecil Source Code you should first create the local variable which is part of your MethodBody.
MethodBody has a Variables collection which can be filled via
body.Variables.Add( new VariableDefinition(typedef) )
Then you need to call processor.Create(xxx,indexto local variable);
That should do the trick. The only thing I did not see yet how you can get a TypeDefinition out from a Type. I think you need to load the assembly into Mono Cecil first before you can use this approach.