Calling a function with named arguments in a hosted application - c#

So I am hosting IronPython in my C# application. IronPhyton is used to implement a DSL for users. The DSL syntax should be something like this:
Ping(Message = "testOne1")
The hosting code looks like:
var engine = Python.CreateEngine();
var scope = engine.CreateScope();
Action<string> ping = (message) => Console.WriteLine(message.ToString());
scope.SetVariable("Ping", ping);
var script = #"
Ping(Message = ""testOne1"")
";
engine.Execute(script, scope);
But this does not work because Action<string> does not keep name of the argument. Calling it without the parameter name works as expected:
Ping("testOne1")
How do I store a function and call it with named arguments?

To use named arguments you'll have to define the method statically. For example, I'll just put all DSL operations into an Operations static class.
public static class Operations {
public static void Ping(string Message) {
Console.WriteLine(Message);
}
}
Then named arguments will work:
var engine = Python.CreateEngine();
var scope = engine.CreateScope();
// Load the assembly where the operations are defined.
engine.Runtime.LoadAssembly(Assembly.GetExecutingAssembly());
// Import the operations modules, settings their names as desired.
engine.Execute(#"
from Operations import Ping
", scope);
// Now named arguments will work...
var script = #"
Ping(Message = ""Ping!"")
";
engine.Execute(script, scope);
Now if I could give you some advise; I'd prefer to implement the actual Python API in Python, and have that call back into my .NET code as needed. For example, instead of having the "operations" defined in C#, you'd have an Operations.py file which defines your Python DSL:
# Get access to your .NET API
import clr
clr.AddReference("MyAPI")
import MyAPI
# Define the Ping call to call into your .NET API
def Ping(Message):
MyAPI.Ping(Message)
And your hosting code doesn't need to change at all.
Both are valid solutions, but the last one lets you iterate on your DSL easily.
Good luck!

The name of the parameter is defined by the name provided in the delegate type. In the case of Action<T>, the parameter name is obj.
public delegate void Action<in T>(
T obj
)
obj should work for you. Are you sure it isn't working? It works for me.
In an IronPython project I have a library:
namespace TestLibrary
{
public static class Test
{
public static readonly Action<string> WriteLine =
msg => Console.WriteLine(msg);
// the same works if I do this instead
//public static readonly Action<string> WriteLine = Console.WriteLine;
}
}
And this works:
from TestLibrary import Test
#Test.WriteLine(msg='foo') # error
Test.WriteLine(obj='foo') # works
Hosted, same deal:
var engine = Python.CreateEngine();
dynamic scope = engine.CreateScope();
Action<string> writeLine = msg => Console.WriteLine(msg);
// or even
//Action<string> writeLine = Console.WriteLine;
scope.writeLine = writeLine;
//engine.Execute("writeLine(msg='foo')", scope); // error
engine.Execute("writeLine(obj='foo')", scope); // works

Related

How to programmatically create a class library DLL using reflection?

Suppose my code possesses the knowledge about the metadata of a
nonexistent class library "mytest.dll", such as the types in this library, the functions of the types, the parameters and return types of the functions, etc.
How does my code manufacture this DLL using techniques such as reflection?
I know my code can generate the "mytest.cs" text file, then execute the compiler to produce the DLL, then delete the "mytest.cs" file. Just want to know if there are "more advanced" or "cooler" ways to do it.
Thanks.
There are 4 main steps in the process to compile and execute dynamic .net scripts from your application, even really complex scenarios can be simplified in this way:
Generate the code
Compile the script
Load the assembly
Execute the code
Lets generate a simple Hello Generated C# World App right now!:
Create a method that will generate an assembly that has 1 class called HelloWorldApp, this class has 1 method called GenerateMessage it will have X input parameters that will be integers, it will return a CSV string of the arguments that were passed in to it.
This solution requires the following package to be installed:
PM> Install-Package 'Microsoft.CodeAnalysis.CSharp.Scripting'
And will require the following using statements:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Emit;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
Orchestration
The following method encapsulates the above steps:
private static void GenerateAndExecuteApp(int numberOfParameters)
{
string nameSpace = "Dynamic.Example";
string className = "HelloWorldApp";
string methodName = "GenerateMessage";
// 1. Generate the code
string script = BuildScript(nameSpace, className, methodName, numberOfParameters);
// 2. Compile the script
// 3. Load the Assembly
Assembly dynamicAssembly = CompileScript(script);
// 4. Execute the code
int[] arguments = Enumerable.Range(1, numberOfParameters).ToArray();
string message = ExecuteScript(dynamicAssembly, nameSpace, className, methodName, arguments);
Console.Out.WriteLine(message);
}
Generate the code
You say you already have item 1 sorted out, you can use StringBuilder, T4 templates or other mechanisms to generate the code files.
generating the code itself is its own question if you need help with that.
However, for our demo app, the following would work:
private static string BuildScript(string nameSpace, string className, string methodName, int numberOfParameters)
{
StringBuilder code = new StringBuilder();
code.AppendLine("using System;");
code.AppendLine("using System.Linq;");
code.AppendLine();
code.AppendLine($"namespace {nameSpace}");
code.AppendLine("{");
code.AppendLine($" public class {className}");
code.AppendLine(" {");
var parameterNames = Enumerable.Range(0, numberOfParameters).Select(x => $"p{x}").ToList();
code.Append($" public string {methodName}(");
code.Append(String.Join(",", parameterNames.Select(x => $"int {x}")));
code.AppendLine(")");
code.AppendLine(" {");
code.Append(" return $\"");
code.Append(String.Join(",", parameterNames.Select(x => $"{x}={{{x}}}")));
code.AppendLine("\";");
code.AppendLine(" }");
code.AppendLine(" }");
code.AppendLine("}");
return code.ToString();
}
For an input value of 3, the following code is generated:
using System;
using System.Linq;
namespace Dynamic.Example
{
public class HelloWorldApp
{
public string GenerateMessage(int p0,int p1,int p2)
{
return $"p0={p0},p1={p1},p2={p2}";
}
}
}
Compile the script (and Load it)
These are two discrete steps, however it is easiest to code them together in the same method, for this example we will ignore the generated dll and load the assembly directly into memory, that is generally the more likely use case for this type of scripting scenario anyway.
The hardest element of this is usually the referencing of the relevant dlls. There are a number of ways to achieve this, including loading all the dlls that are in the current executing context, I find a simple way to do this is to access the Assembly reference from the Type reference for the types we want to use inside the dynamic script:
List<string> dlls = new List<string> {
typeof(object).Assembly.Location,
typeof(Enumerable).Assembly.Location
};
Cut a long story short, this method compiles and loads the assembly into memory. It includes some crude compilation error handling, just to demonstrate how to do it:
private static Assembly CompileScript(string script)
{
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(script);
// use "mytest.dll" if you want, random works well enough
string assemblyName = System.IO.Path.GetRandomFileName();
List<string> dlls = new List<string> {
typeof(object).Assembly.Location,
typeof(Enumerable).Assembly.Location
};
MetadataReference[] references = dlls.Distinct().Select(x => MetadataReference.CreateFromFile(x)).ToArray();
CSharpCompilation compilation = CSharpCompilation.Create(
assemblyName,
syntaxTrees: new[] { syntaxTree },
references: references,
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
// Now we actually compile the script, this includes some very crude error handling, just to show you can
using (var ms = new MemoryStream())
{
EmitResult result = compilation.Emit(ms);
if (!result.Success)
{
IEnumerable<Diagnostic> failures = result.Diagnostics.Where(diagnostic =>
diagnostic.IsWarningAsError ||
diagnostic.Severity == DiagnosticSeverity.Error);
List<string> errors = new List<string>();
foreach (Diagnostic diagnostic in failures)
{
//errors.AddDistinct(String.Format("{0} : {1}", diagnostic.Id, diagnostic.Location, diagnostic.GetMessage()));
errors.Add(diagnostic.ToString());
}
throw new ApplicationException("Compilation Errors: " + String.Join(Environment.NewLine, errors));
}
else
{
ms.Seek(0, SeekOrigin.Begin);
return Assembly.Load(ms.ToArray());
}
}
}
Execute the code
Finally, we can use reflection to instantiate an instance of the new app and then we can obtain a reference to the method and it. The name of the parameters is irrelevant, as long
we pass them through in the correct order:
for this demo the order is sort of irrelevant to, given they are all the same type ;)
private static string ExecuteScript(Assembly assembly, string nameSpace, string className, string methodName, int[] arguments)
{
var appType = assembly.GetType($"{nameSpace}.{className}");
object app = Activator.CreateInstance(appType);
MethodInfo method = appType.GetMethod(methodName);
object result = method.Invoke(app, arguments.Cast<object>().ToArray());
return result as string;
}
Output
The final output from all this for our method with 3 passed into it is:
p0=1,p1=2,p2=3
So that was super crude, you can bypass most of the indirect reflection aspects through the use of Interfaces. If your generated script inherits from types or interfaces that the calling code also has a strong reference to, then ExecuteScript in the above example might look like this:
private static string ExecuteScript(Assembly assembly, string nameSpace, string className)
{
var appType = assembly.GetType($"{nameSpace}.{className}");
object app = Activator.CreateInstance(appType);
if (app is KnownInterface known)
{
return known.GenerateMessage(1,2,3);
}
throw new NotSupportedException("Couldn't resolve known type");
}
The major benefit to using an interface or base class reference is that you can natively set properties or call other methods without having to reflect references to them all or to resort to using dynamic which would work, but becomes a bit harder to debug.
Of course the interface solution is hard to implement when we had a variable number of parameters, so that's not the best example, usually with dynamic scripts you would construct a known environment, say a known class and methods, but you might want to inject custom code into the body of the method.
It's a bit of fun in the end, but this simple example shows that C# can be used as a runtime scripting engine without too much trouble.

How to properly setup CodeContext of IronPython to directly invoke IO from C#?

I am trying to directly invoke IronPython's built-in modules from C#. It looks like I'm missing some important initialization, that I can't find anywhere in the code.
Here's what I do:
namespace py.consoleio
{
using IronPython.Runtime;
using Microsoft.Scripting.Hosting;
using Microsoft.Scripting.Hosting.Providers;
using Microsoft.Scripting.Runtime;
public static class consoleio
{
public static string name;
static void Main()
{
var setup = new ScriptRuntimeSetup();
setup.LanguageSetups.Add(
IronPython.Hosting.Python.CreateLanguageSetup(null));
var dlrRuntime = new ScriptRuntime(setup);
var scriptDomainManager = HostingHelpers.GetDomainManager(dlrRuntime);
var pythonContext = new PythonContext(scriptDomainManager, null);
var context = new CodeContext(new PythonDictionary(), new ModuleContext(new PythonDictionary(), DefaultContext.DefaultPythonContext));
name = IronPython.Modules.Builtin.input(context, "What is your name?\n");
IronPython.Modules.Builtin.print(context, "Hi, %s.", consoleio.name);
System.GC.KeepAlive(pythonContext);
}
}
}
That properly outputs "What is your name?", but then crashes trying to decode input: unknown encoding: cp437.
Now I've already found, that encodings are initialized in Src/StdLib/Lib/encodings/init.py
I can't find how it gets to loading this module in a normal IronPython run (e.g. a console host), so I can't reproduce it in C# program.
My goal here is to invoke IronPython functions without dynamic dispatch.
UPD. Now I also tried to do this:
var engine = Python.CreateEngine();
this.ScriptDomainManager = HostingHelpers.GetDomainManager(engine.Runtime);
to the same result
Figured that one out: encodings module is implemented in Python in IronPython (core modules are in C#). It always worked with IronPythonConsole project, because it implicitly adds IronPython source for standard libraries to Python path. I just had to explicitly specify path like this:
var options = new Dictionary<string, object> { ["SearchPaths"] = path };
var engine = Python.CreateEngine(options);

C#, IronPython - import(?) from a non-static class

I have a non-static C# class with some instance methods, which I need to call from IronPython scripts. Currently I'm doing it this way:
scope.SetVariable("class", instanceOfClass);
in C# code and
class.SomeMethod(args)
in script.
What I want is being able to call this class methods without adding class. each time in the script. Each script has its own instance of the class, and only one instance is used in one script.
If this class was static, the solution would be from ClassName import *, but as I know there is no similar construction for non-static classes.
How can this be done? I have some ideas (such as using reflection, or adding class. to each call in Python source programmatically), but they are overcomplicated and may be even not possible to implement.
UPD:
Problem solved by using such python code (before actual script):
def Method1(arg1): # for simple method
class.Method1(arg1)
def Method2(arg = 123): # for default values
class.Method2(arg)
def Method3(*args): # for params
class.Method3(args)
# so on
from ClassName import * is actually from namespace import type. This statement makes the type avaiable for use via the type name in Python. It makes no difference if the class is static or not. Consider this sample code - Environment being the static class.
import clr
from System import Environment
print Environment.CurrentDirectory
To solve your problem, inject a delegate to the class function into your ScriptScope, rather than the class itself.
Sample class
public class Foo {
public string GetMyString(string input) {
return input;
}
}
Usage
private static void Main(string[] args) {
ScriptEngine engine = Python.CreateEngine();
string script = "x = GetMyString('value')";
Foo foo = new Foo();
ScriptSource scriptSource = engine.CreateScriptSourceFromString(script);
ScriptScope scope = engine.CreateScope();
scope.SetVariable("GetMyString", new Func<string, string>(foo.GetMyString));
scriptSource.Execute(scope);
string output = scope.GetVariable<string>("x");
Console.WriteLine(output);
}
prints
value

Access host class from IronPython script

How do I access a C# class from IronPython script?
C#:
public class MyClass
{
}
public enum MyEnum
{
One, Two
}
var engine = Python.CreateEngine(options);
var scope = engine.CreateScope();
scope.SetVariable("t", new MyClass());
var src = engine.CreateScriptSourceFromFile(...);
src.Execute(scope);
IronPython script:
class_name = type(t).__name__ # MyClass
class_module = type(t).__module__ # __builtin__
# So this supposed to work ...
mc = MyClass() # ???
me = MyEnum.One # ???
# ... but it doesn't
UPDATE
I need to import classes defined in a hosting assembly.
You've set t to an instance of MyClass, but you're trying to use it as if it were the class itself.
You'll need to either import MyClass from within your IronPython script, or inject some sort of factory method (since classes aren't first-class objects in C#, you can't pass in MyClass directly). Alternatively, you could pass in typeof(MyClass) and use System.Activator.CreateInstance(theMyClassTypeObject) to new up an instance.
Since you also need to access MyEnum (note you're using it in your script without any reference to where it might come from), I suggest just using imports:
import clr
clr.AddReference('YourAssemblyName')
from YourAssemblyName.WhateverNamespace import MyClass, MyEnum
# Now these should work, since the objects have been properly imported
mc = MyClass()
me = MyEnum.One
You might have to play around with the script source type (I think File works best) and the script execution path to get the clr.AddReference() call to succeed.

Embedding IronPython in C#

I am just looking into using IronPython with C# and cannot seem to find any great documentation for what I need. Basically I am trying to call methods from a .py file into a C# program.
I have the following which opens the module:
var ipy = Python.CreateRuntime();
var test = ipy.UseFile("C:\\Users\\ktrg317\\Desktop\\Test.py");
But, I am unsure from here how to get access to the method inside there. The example I have seen uses the dynamic keyword, however, at work I am only on C# 3.0.
Thanks.
See embedding on the Voidspace site.
An example there, The IronPython Calculator and the Evaluator
works over a simple python expression evaluator called from a C# program.
public string calculate(string input)
{
try
{
ScriptSource source =
engine.CreateScriptSourceFromString(input,
SourceCodeKind.Expression);
object result = source.Execute(scope);
return result.ToString();
}
catch (Exception ex)
{
return "Error";
}
}
You can try use the following code,
ScriptSource script;
script = eng.CreateScriptSourceFromFile(path);
CompiledCode code = script.Compile();
ScriptScope scope = engine.CreateScope();
code.Execute(scope);
It's from this article.
Or, if you prefer to invoke a method you can use something like this,
using (IronPython.Hosting.PythonEngine engine = new IronPython.Hosting.PythonEngine())
{
engine.Execute(#"
def foo(a, b):
return a+b*2");
// (1) Retrieve the function
IronPython.Runtime.Calls.ICallable foo = (IronPython.Runtime.Calls.ICallable)engine.Evaluate("foo");
// (2) Apply function
object result = foo.Call(3, 25);
}
This example is from here.

Categories

Resources