I am planning to use IronPython along with C# for following use case.
I want to expose some functions which can invoked via python script.
Such script will be invoked by C# application.
For example there could be function prepared in C# ClearUserInput() that clears some data on WPF. Then I could expose more functions via some interface.
public interface IScriptContract
{
void ClearMainWindow();
void LoadProject(string projectName);
}
python script:
print "clearing window"
ClearMainWindow() --how to cooperating with C# code?
print "load proj"
ClearMainWindow("project.proj") --how to cooperating with C# code?
I want user can write some script that can invoke my .net function.
Is it possible to do that?
The standard way to do this is to create a 'host object' that is passed into the scripts that provides access to the host application (if you're familiar with web progamming, that's what the window instance is).
In IronPython you can use SetVariable on the ScriptScope object that is executing your code:
var contract = new ScriptContract();
var engine = Python.CreateEngine();
var mainScope = engine.CreateScope();
var scriptSource = engine.CreateScriptSourceFromFile("test.py", Encoding.Default, SourceCodeKind.File)
mainScope.SetVariable("host", contract);
scriptSource.Execute(scope);
From python it's then always available:
host.ClearMainWindow()
Related
I know it is possible to call C# code from the PowerShell script by loading an assembly. But is there any way to pass and receive a value in between both C# code and PowerShell script.
Let's say I have a $path variable in my power script. I want to pass it to my c# code. And C# code will use the $path. After doing some stuff in the c# code it will return some value to the script. Is this possible? If it is, how can I do it? I must load a third party dll in my power shell and all one or two public methods on that dll to complete some task.
My PowerShell script code:
$scriptpath = $MyInvocation.MyCommand.Path;
$cureentDir = Split-Path $scriptpath;
$isSasDir = $cureentDir -match "mydir";
$requiredFile = "Core.dll";
$myPowersehllVal = "has value for c# code";
My C# code:
$Source = #"
using System.Net;
public class ExtendedWebClient : WebClient
{
String myPowersehllVal;
public int Timeout;
protected override WebRequest GetWebRequest(System.Uri address)
{
}
}
"#;
For getting PS values into C#
https://stackoverflow.com/a/22384009/3546415
In a more general sense, System.Management.Automation (Nuget Required) looks promising, in particular, Runspace and Pipeline.
Here are some good examples of usage:
https://msdn.microsoft.com/en-us/library/ee706576(v=vs.85).aspx
Something like this one seems similar to what you want.
Or maybe just use the PowerShell Class to execute PS commands from your C# module to set PS variable values.
Alternatively, without worrying about interop, you can kind of hack this by working through the file system and/or environmental variables. Following this paradigm, you could even use a memory mapped file and share variables with a broader set of applications. Powershell side would be something like this. For objects, serialization.
I am experimenting with the Roslyn script engine. Using the following code, I set up my script engine.
var csharpEngine = new ScriptEngine();
csharpEngine.AddReference("System");
csharpEngine.AddReference(this.GetType().Assembly.Location);
scriptSession = csharpEngine.CreateSession();
Then I execute a script with the following line:
scriptSession.Execute(script);
The script contains a very simple reference to a static function on a class in my assembly.
private string script =
#"using System;
using RoslynWindow;
Hello.SayHello();";
In the output window the function merely prints to the console. So I have shown I can call into a public static member of my assembly without passing a "HostObjectModel" to the script engine. I want to prevent this from happening. I'd like to be able to register only specific members (functions, variables or properties) to be accessed by the script engine, and no others.
Any idea how to accomplish this?
Traverse the AST with Roslyn to check if the script tries to call anything you don't allow.
I'm just having a play with Roslyn but unsure on how to do the following.
To keep this simple, lets say I have a host program which has a method like so
public void DisplayMessage(string message)
{
MessageBox.Show(message);
}
Can I then have a script file called MyScript.csx and then somewhere in the script have something like
void Main()
{
Host.DisplayMessage("I am a script");
}
Then I have the host load the file and execute it.
If this sort of thing can't be done, is there a scripting system/engine based on c# that can do it?
These are the requirements
Host application can load script from a file.
Script file is written in c# and so can be written using VS2010 with syntax etc
Script file can access host public methods, properties etc
I wrote an Introduction to the Roslyn scripting API that covers most of what you're asking. The ScriptEngine type also has a ExecuteFile method that would be useful for what you're trying to do.
Disclaimer: I work for Microsoft on the Roslyn project.
Yes, you can do what you want using Roslyn.
First, create a public Host class that has a public DisplayMessage method (or use a existing class). Then create ScriptEngine, specifying the assembly that contains Host as a reference. After that, you can call ExecuteFile() on your file, with the Host object as another parameter:
var engine = new ScriptEngine(references: new[] { typeof(Host).Assembly });
engine.ExecuteFile("MyScript.csx", new Host());
The script files doesn't need any Main() method, and you call the method on the host object directly:
DisplayMessage("I am a script");
Is there any way to make this scenario work?
There is a Python script. It is built into a DLL by running this script with IronPython:
import clr
clr.CompileModules("CompiledScript.dll", "script.py")
The goal is to call this DLL's methods from C# code. .NET Reflector shows there is one class in the DLL - DLRCashedCode and the methods we are interested in are private static methods of this class.
For example, there is a function in the script:
def scriptMethod(self, text):
...
Its representation in the DLL is:
private static object scriptMethod(Closure closure1, PythonFunction $function, object self, object text)
{
...
}
Closure and PythonFunction are IronPython classes (from Microsoft.Scripting.dll and IronPython.dll).
So far so good. Is it possible this method to be called by C# code? The idea of using reflection like
Type t = typeof(DLRCachedCode);
string methodName = "scriptMethod";
MethodInfo method = t.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static);
object[] parameters = new object[] { "param1", "param2" }; // the "params problem"
method.Invoke(null, parameters);
seems harder because of setting the method's parameters. If they are (any how) initialized correctly, could we expect the method to work smoothly?
Is there a better way to call this methods from C#? For various different reasons we prefer to have the script built as a .NET assembly and not to call the script itself.
Sort of. You cannot access the Python methods directly from C# code. Unless you are playing with C# 4.0 and the dynamic keyword or you are very, very special ;). However, you can compile an IronPython class to a DLL and then use IronPython hosting in C# to access the methods (this is for IronPython 2.6 and .NET 2.0).
Create a C# program like this:
using System;
using System.IO;
using System.Reflection;
using IronPython.Hosting;
using Microsoft.Scripting.Hosting;
// we get access to Action and Func on .Net 2.0 through Microsoft.Scripting.Utils
using Microsoft.Scripting.Utils;
namespace TestCallIronPython
{
class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
ScriptEngine pyEngine = Python.CreateEngine();
Assembly myclass = Assembly.LoadFile(Path.GetFullPath("MyClass.dll"));
pyEngine.Runtime.LoadAssembly(myclass);
ScriptScope pyScope = pyEngine.Runtime.ImportModule("MyClass");
// Get the Python Class
object MyClass = pyEngine.Operations.Invoke(pyScope.GetVariable("MyClass"));
// Invoke a method of the class
pyEngine.Operations.InvokeMember(MyClass, "somemethod", new object[0]);
// create a callable function to 'somemethod'
Action SomeMethod2 = pyEngine.Operations.GetMember<Action>(MyClass, "somemethod");
SomeMethod2();
// create a callable function to 'isodd'
Func<int, bool> IsOdd = pyEngine.Operations.GetMember<Func<int, bool>>(MyClass, "isodd");
Console.WriteLine(IsOdd(1).ToString());
Console.WriteLine(IsOdd(2).ToString());
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
}
}
Make a trivial Python class like this:
class MyClass:
def __init__(self):
print "I'm in a compiled class (I hope)"
def somemethod(self):
print "in some method"
def isodd(self, n):
return 1 == n % 2
Compile it (I use SharpDevelop) but the clr.CompileModules method should also work. Then shove the compiled MyClass.dll into the directory where the compiled C# program lives and run it. You should get this as the result:
Hello World!
I'm in a compiled class (I hope)
in some method
in some method
True
False
Press any key to continue . . .
This incorporates Jeff's more direct solution that eliminates having to create and compile a small Python 'stub' and also shows how you can create C# function calls that access the methods in the Python class.
The clr.CompileModules is purely a load-time optimization - it doesn't make the scripts directly available to a static languge like C#. You'll need to host the IronPython runtime, and then you can load the DLL into the runtime and use IronPython's hosting interfaces to access it.
I've written a class in python that I want to wrap into a .net assembly via IronPython and instantiate in a C# application. I've migrated the class to IronPython, created a library assembly and referenced it. Now, how do I actually get an instance of that class?
The class looks (partially) like this:
class PokerCard:
"A card for playing poker, immutable and unique."
def __init__(self, cardName):
The test stub I wrote in C# is:
using System;
namespace pokerapp
{
class Program
{
static void Main(string[] args)
{
var card = new PokerCard(); // I also tried new PokerCard("Ah")
Console.WriteLine(card.ToString());
Console.ReadLine();
}
}
}
What do I have to do in order to instantiate this class in C#?
IronPython classes are not .NET classes. They are instances of IronPython.Runtime.Types.PythonType which is the Python metaclass. This is because Python classes are dynamic and support addition and removal of methods at runtime, things you cannot do with .NET classes.
To use Python classes in C# you will need to use the ObjectOperations class. This class allows you to operate on python types and instances in the semantics of the language itself. e.g. it uses the magic methods when appropriate, auto-promotes integers to longs etc. You can find out more about ObjectOperations by looking at the source or using reflector.
Here is an example. Calculator.py contains a simple class:
class Calculator(object):
def add(self, a, b):
return a + b
You can use it from your pre .NET 4.0 C# code like this:
ScriptEngine engine = Python.CreateEngine();
ScriptSource source = engine.CreateScriptSourceFromFile("Calculator.py");
ScriptScope scope = engine.CreateScope();
ObjectOperations op = engine.Operations;
source.Execute(scope); // class object created
object klaz = scope.GetVariable("Calculator"); // get the class object
object instance = op.Call(klaz); // create the instance
object method = op.GetMember(instance, "add"); // get a method
int result = (int)op.Call(method, 4, 5); // call method and get result (9)
You will need to reference the assemblies IronPython.dll, Microsoft.Scripting and Microsoft.Scripting.Core.
C# 4 made this much easier with the new dynamic type.
ScriptEngine engine = Python.CreateEngine();
ScriptSource source = engine.CreateScriptSourceFromFile("Calculator.py");
ScriptScope scope = engine.CreateScope();
source.Execute(scope);
dynamic Calculator = scope.GetVariable("Calculator");
dynamic calc = Calculator();
int result = calc.add(4, 5);
If you are using Visual Studio 2010 or later with NuGet support simply execute this to download and reference the appropriate libraries.
Install-Package IronPython
Now that .Net 4.0 is released and has the dynamic type, this example should be updated. Using the same python file as in m-sharp's original answer:
class Calculator(object):
def add(self, a, b):
return a + b
Here is how you would call it using .Net 4.0:
string scriptPath = "Calculator.py";
ScriptEngine engine = Python.CreateEngine();
engine.SetSearchPaths(new string[] {"Path to your lib's here. EG:", "C:\\Program Files (x86)\\IronPython 2.7.1\\Lib"});
ScriptSource source = engine.CreateScriptSourceFromFile(scriptPath);
ScriptScope scope = engine.CreateScope();
ObjectOperations op = engine.Operations;
source.Execute(scope);
dynamic Calculator = scope.GetVariable("Calculator");
dynamic calc = Calculator();
return calc.add(x,y);
Again, you need to add references to IronPython.dll and Microsoft.Scripting.
As you can see, the initial setting up and creating of the source file is the same.
But once the source is succesfully executed, working with the python functions is far easier thanks to the new "dynamic" keyword.
I am updating the above example provided by Clever Human for compiled IronPython classes (dll) instead of IronPython source code in a .py file.
# Compile IronPython calculator class to a dll
clr.CompileModules("calculator.dll", "calculator.py")
C# 4.0 code with the new dynamic type is as follows:
// IRONPYTHONPATH environment variable is not required. Core ironpython dll paths should be part of operating system path.
ScriptEngine pyEngine = Python.CreateEngine();
Assembly myclass = Assembly.LoadFile(Path.GetFullPath("calculator.dll"));
pyEngine.Runtime.LoadAssembly(myclass);
ScriptScope pyScope = pyEngine.Runtime.ImportModule("calculator");
dynamic Calculator = pyScope.GetVariable("Calculator");
dynamic calc = Calculator();
int result = calc.add(4, 5);
References:
Using Compiled Python Classes from .NET/CSharp IP 2.6
Static Compilation of IronPython scripts
I have searched high and low and I am afraid that there does not seem to be much information pertaining to this. I am pretty much certain that no one has devised a way to do this in the clean manner that you would like.
The main reason I think this is a problem is that in order to see the PokerCard type in your C# application you would have to compile your Python code to IL. I don't believe that there are any Python->IL compilers out there.