Calling C# methods from IronPython

10,262

Solution 1

Under normal circumstances, IronPython only has access to public classes and members on .Net types. You should be able to just set your object as a variable in the scope and access any of the public members on that object from your scripts. However the methods you have in your example are private so therefore you can't really access them. If you wish to be able to access them, you need to expose the non-public members somehow so you can manipulate them or use reflection.

A pattern you could try would be to add a method that would create a proxy for your object that would have all the methods you would want to expose. Then add that proxy object to your scope then use that proxy to call your methods.

public partial class MyForm : Form
{
    private readonly ScriptEngine m_engine;
    private readonly ScriptScope m_scope;

    public MyForm()
    {
        InitializeComponent();
        m_engine = Python.CreateEngine();

        dynamic scope = m_scope = m_engine.CreateScope();
        // add this form to the scope
        scope.form = this;
        // add the proxy to the scope
        scope.proxy = CreateProxy();
    }

    // public so accessible from a IronPython script
    public void ShowMessage(string message)
    {
        MessageBox.Show(message);
    }

    // private so not accessible from a IronPython script
    private int MyPrivateFunction()
    {
        MessageBox.Show("Called MyForm.MyPrivateFunction");
        return 42;
    }

    private object CreateProxy()
    {
        // let's expose all methods we want to access from a script
        dynamic proxy = new ExpandoObject();
        proxy.ShowMessage = new Action<string>(ShowMessage);
        proxy.MyPrivateFunction = new Func<int>(MyPrivateFunction);
        return proxy;
    }
}

With this, you can execute your scripts accessing your form through the form variable or the proxy through the proxy variable. For good measure, here's how you could easily access variables and other objects from the scope.

private void DoTests()
{
    // try to call the methods on the form or proxy objects
    var script = @"
form.ShowMessage('called form.ShowMessage')
# formFuncResult = form.MyPrivateFunction() # fail, MyPrivateFunction is not accessible
proxy.ShowMessage('called proxy.ShowMessage')
proxyFuncResult = proxy.MyPrivateFunction() # success, MyPrivateFunction on the proxy is accessible
";
    m_engine.Execute(script, m_scope);

    // access the scope through a dynamic variable
    dynamic scope = m_scope;

    // get the proxyFuncResult variable
    int proxyFuncResult = scope.proxyFuncResult;
    MessageBox.Show("proxyFuncResult variable: " + proxyFuncResult);

    // call the the function on the proxy directly
    int directResult = scope.proxy.MyPrivateFunction();
    MessageBox.Show("result of MyPrivateFunction: " + directResult);
}

Solution 2

I'm unsure of what you're trying to achieve, but how about this?

In C#:

m_scope.SetVariable("myAssembly", System.Reflection.Assembly.GetExecutingAssembly());

[...]

var result = (string) m_scope.GetVariable("theOutVar");

Then in the IronPython script:

import clr
clr.AddReference(myAssembly)
import MyNamespace
theOutVar = MyNamespace.MyClass.MyStaticMethod("hi")

Or perhaps like this?

In C#:

m_scope.SetVariable("theTestObject", myTestObj);

And in IronPython:

result = myTestObj.DoSomething("hi there", 10, false)
Share:
10,262
Gene Myers
Author by

Gene Myers

Updated on June 13, 2022

Comments

  • Gene Myers
    Gene Myers about 2 years

    I'm building a C# WebKit Web browser that can be automated with IronPython to aid with Quality Assurance testing. I'll be creating test plans with IronPython that will run a number of the browser methods, providing the arguments, and evaluating the results.

    Most of the documentation for IronPython illustrates how to call IronPython methods with C#, yet I've figured out how to set arguments to a method, and how to retrieve a methods return values, but not from the same method. You'll note in the example below, I'm passing arguments to a method, which in turn is setting a class member variable, and then retrieving that value with another method.

    Can anyone suggest a more elegant pattern?

    using System;
    using System.Windows.Forms;
    using IronPython.Hosting;
    using Microsoft.Scripting;
    using Microsoft.Scripting.Hosting;
    
    namespace PythonScripting.TestApp
    {
    
     public partial class Form1 : Form
     {
       private ScriptEngine m_engine = Python.CreateEngine();
       private ScriptScope m_scope = null;
    
       //used to call funcs by Python that dont need to return vals
       delegate void VoidFunc(string val);
    
       public Form1()
       {
          InitializeComponent();
       }
    
       private void doSomething()
       {
           MessageBox.Show("Something Done", "TestApp Result");
       }
    
       private string _rslt = "";
    
       private string getSomething()
       {
          return _rslt;
       }
    
       private void setSomething(string val)
       {
           _rslt = val;
       }
    
       private void Form1_Load(object sender, EventArgs e)
       {
           m_scope = m_engine.CreateScope();
    
           Func<string> PyGetFunction = new Func<string>(getSomething);
           VoidFunc PySetFunction = new VoidFunc(setSomething);
    
           m_scope.SetVariable("txt", txtBoxTarget);
           m_scope.SetVariable("get_something", PyGetFunction);
           m_scope.SetVariable("set_something", PySetFunction);           
       }
    
       private void button1_Click(object sender, EventArgs e)
       {
           string code = comboBox1.Text.Trim();
           ScriptSource source = m_engine.CreateScriptSourceFromString(code, SourceCodeKind.SingleStatement);
    
           try
           {
              source.Execute(m_scope);
              Func<string> result = m_scope.GetVariable<Func<string>>("get_something");
    
              MessageBox.Show("Result: " + result(), "TestApp Result");
           }
           catch (Exception ue)
           {
               MessageBox.Show("Unrecognized Python Error\n\n" + ue.GetBaseException(), "Python Script Error");
           }
       }  
     }
    }