在IronPython中调用C#方法

3
我正在构建一个C# WebKit Web浏览器,可以使用IronPython进行自动化,以帮助质量保证测试。我将使用IronPython创建测试计划,运行一些浏览器方法并提供参数,并评估结果。
大部分IronPython的文档都是说明如何使用C#调用IronPython方法,但我已经找出如何设置方法参数并检索方法返回值,但不能在同一个方法中完成。您可以在下面的示例中看到,我正在将参数传递给一个方法,该方法又会设置一个类成员变量,然后使用另一个方法检索该值。
有没有人能建议更优雅的模式?
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");
       }
   }  
 }
} 

你的框架版本是哪个?在这里使用“dynamic”将使所有这些变得微不足道。 - Jeff Mercado
谢谢Jeff,我显然是一个IronPython新手-我会研究一下这个... - Gene Myers
理解这段代码对我非常有帮助,如果有人感兴趣可以参考这个链接:http://thinkingeek.com/2009/03/11/using-ironpython-to-extend-your-net-applications/ - Gene Myers
2个回答

7
在正常情况下,IronPython只能访问.Net类型的公共类和成员。您应该能够将对象设置为范围内的变量,并从脚本中访问该对象上的任何公共成员。但是,您示例中的方法是private,因此您实际上无法访问它们。如果您希望能够访问它们,则需要以某种方式公开非公共成员,以便您可以操作它们或使用反射。
您可以尝试的一种模式是添加一个方法来创建一个代理,该代理将具有您想要公开的所有方法。然后,将该代理对象添加到您的范围中,然后使用该代理调用您的方法。
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;
    }
}

通过这个,您可以通过form变量或proxy变量访问您的表单来执行您的脚本。为了保险起见,以下是如何轻松访问范围内的变量和其他对象的方法。
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);
}

谢谢Jeff,实际上我在发布这篇文章和你现在回复之间已经将它们改为public了。这太棒了。代理看起来就是我要找的,但我不知道表单变量如何使用,所以我会再仔细研究一下。再次感谢。-G - Gene Myers
如果您查看我的示例中的构造函数,您会发现我将一个名为form的变量添加到作用域中,它是对this表单对象的引用(据我所知,这就是您想要交互的对象)。您还会看到proxy变量被设置为创建的代理对象。它们仅被添加以演示脚本如何访问这些对象。 - Jeff Mercado
Jeff,谢谢你的分享 - 我仍在研究你的示例 - 对于任何希望了解动态关键字和ExpandoObject的其他人,这篇MSDN文章为http://msdn.microsoft.com/en-us/magazine/gg598922.aspx奠定了良好的基础。 - Gene Myers

3

我不确定你想要实现什么,但是这样如何?

使用 C#:

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

[...]

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

然后在IronPython脚本中:

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

也许应该像这样吗?
在C#中:
m_scope.SetVariable("theTestObject", myTestObj);

在IronPython中:

result = myTestObj.DoSomething("hi there", 10, false)

我想要做的事情,确切地说,是能够从一个IronPython脚本中调用C#方法(设置方法参数),并且让结果在C#应用程序中轻松可用。我的困惑在于使用GetVariable方法的正确语法。我所需要做的就是像你上面演示的那样将方法返回到IronPython变量中,result = GetSomething("my val"),然后调用scope.GetVariable("result"). - Gene Myers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接