手头的一个老的项目,之前一直用的lua作为窗口语言,用户可以自行编写脚本来调用系统的各种资源,包括一些自定义的函数或算法,以及系统之前定义好的插件和硬件设备,因为项目面向的是一些高校和研究所,所以就陆续有人建议把python也加入进来,正好最近手头的活不是很紧急了,就稍稍研究了一下。最初选型的时候定的是ironpython,结果发现这个插件已经不再维护了,且扩展性存在问题,后来就果断弃用,最终确定使用python.net。

        先说下开发环境,懂得都懂啊,开发环境如果有问题,累死都出不来效果 -_-!

        操作系统:Windows 11 专业版 x64

        vs版本:Microsoft Visual Studio Community 2022 (64 位) 

        python版本:Python38

        pythonnet版本:3.0.4

        项目类型:Winform

        .Net版本:.Net Framework 4.8.1

        窗口编辑器:jacobslusser.ScintillaNET 3.6.3

以上为本次开发所需要的所有环境布置,python因为pythonnet的要求,必须是3.8以上的版本,如果版本太新或太低都有可能导致脚本初始化报错。下面直接进入主题,安装好python之后,在nuget中安装pythonnet,之后就可以开始编码了(注意替换正确的python安装路径)。

 string dllPath = @".\Python38\python38.dll";
 string pythonHomePath = @".\Python38";

 // 对应python内的重要路径
 string[] py_paths = {"DLLs", "lib", "lib\\site-packages", "lib\\site-packages\\win32"
     , "lib\\site-packages\\win32/lib", "lib\\site-packages\\Pythonwin" };
 string pySearchPath = $"{pythonHomePath};";
 foreach (string p in py_paths)
 {
     pySearchPath += $"{pythonHomePath}\\{p};";
 }

 // 此处解决BadPythonDllException报错
 Runtime.PythonDLL = dllPath;
 Environment.SetEnvironmentVariable("PYTHONNET_PYDLL", dllPath);
 // 配置python环境搜索路径解决PythonEngine.Initialize() 崩溃
 PythonEngine.PythonHome = pythonHomePath;
 PythonEngine.PythonPath = pySearchPath;

以上这段代码摘自网络,已验证通过,没有任何问题,可以放到program文件中的Main方法中,进程启动加载一次就可以了,如果不配置PythonDLL的路径,会导致下面的初始化失败。

在需要执行python脚本的地方,先初始化:

PythonEngine.Initialize();

如果需要获取执行的结果或者需要附加一些net的对象、变量,建议创建一个Scope来操作更方便。

var _scope = Py.CreateScope();

如果有一个C#的字典对象需要附加到_scope中,我们需要调用_scope的Set方法附加即可:

foreach (var deviceObject in globalDeviceObjects)
{
    _scope.Set(deviceObject.DeviceKey,deviceObject.IDevice);
}

这样就把一个net的对象附件到了python的Scope中,在接下来的操作中可直接使用。

假设要执行的python脚本代码在变量chunk中:

 using (Py.GIL())
 {
     _scope.Exec(chunk);
 }

这样,整个调用操作就完成了,为了便于在系统中调用,我做了一些简单的封装,定义了一个名为IScriptLanguage的接口类,用来实现不同的方法调用(lua和python),具体如下:


    /// <summary>
    /// 脚本语言的基类
    /// </summary>
    public interface IScriptLanguage
    {
        /// <summary>
        /// 执行脚本
        /// </summary>
        /// <param name="str"></param>
        /// <param name="log"></param>
        /// <returns></returns>
        object[] DoString(string str);

        /// <summary>
        /// 获取方法
        /// </summary>
        /// <param name="funName"></param>
        /// <returns></returns>
        dynamic GetFunction(string funName);

        /// <summary>
        /// 脚本类型
        /// </summary>
        ScriptLanguageType LanguageType { get; }
    }
public class BasePython: IScriptLanguage
{
    protected PyModule _scope;
    private ILog m_Log;

    /// <summary>
    /// 脚本类型
    /// </summary>
    public ScriptLanguageType LanguageType
    { 
        get { return ScriptLanguageType.Python; }
    }

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="log"></param>
    public BasePython(ILog log)
    {
        PythonEngine.Initialize();
        _scope = Py.CreateScope();
        m_Log = log;
    }

    /// <summary>
    /// 查找自定义函数
    /// </summary>
    /// <param name="name"></param>
    /// <returns></returns>
    public dynamic GetFunction(string name)
    {
        return _scope.Eval(name);
    }

    private void AddLog(string strLog)
    {
        if (m_Log != null)
        {
            m_Log.AddLog(strLog);
        }
    }
    public object[] DoString(string chunk)
    {
        try
        {
            using (Py.GIL())
            {
                _scope.Exec(chunk);
            }
                
            return null;
        }
        catch (OperationCanceledException ex)
        {
            if (m_Log != null)
            {
                m_Log.AddLog("用户中止");
            }
            return new object[] { };
        }
        catch (NotSupportedException ex)
        {
            if (m_Log != null)
            {
                m_Log.AddLog(string.Format("Execute Lua error({0})", ex.Message));
            }
            return new object[] { };
        }
        catch (NotImplementedException ex)
        {
            if (m_Log != null)
            {
                AddLog(ex.Message);
            }
            return new object[] { };
        }
        catch (Exception ex)
        {
            if (m_Log != null)
            {
                AddLog(ex.Message);
            }
            return new object[] { };
        }
    }
    private bool RunPythonCode(string strLuaCode)
    {
        try
        {
            using (Py.GIL())
            {
                _scope.Exec(strLuaCode);
            }
            return true;
        }
        catch (OperationCanceledException ex)
        {
            AddLog("用户中止");
            return false;
        }
        catch (NotSupportedException ex)
        {
            AddLog(string.Format("Execute Lua error({0})", ex.Message));
            return false;
        }
        catch (Exception ex)
        {
            AddLog(ex.Message);
            return false;
        }
    }
}
public class CiAlignerXPython: BasePython
{
    private const string LUA_GLOBAL_VAR = "AX";
    private CPluginOjbectCollection m_PluginObjects;
    CiAlignerXPlatform m_Platform;

    public CiAlignerXPython(CiAlignerXPlatform platform, IDevice_Paras_Collection globalDeviceObjects, CPluginOjbectCollection pluginObjects, CancellationToken stopToken, ILog log = null)
        : base(log)
    {
        m_Platform = platform;
        m_PluginObjects = pluginObjects;

        platform.SetStopToken(stopToken);
        //_pydic[LUA_GLOBAL_VAR] = (new LuaPlatform(platform, log)).ToPython();
        _scope.Set(LUA_GLOBAL_VAR, new LuaPlatform(platform, log));

        foreach (var deviceObject in globalDeviceObjects)
        {
            _scope.Set(deviceObject.DeviceKey,deviceObject.IDevice);
        }
        foreach (var key in platform.PubPlugins.Keys)
        {
            platform.PubPlugins[key].SetToken(stopToken);
            _scope.Set(key, platform.PubPlugins);
        }

        foreach (var pluginObject in pluginObjects)
        {
            pluginObject.PluginInfo.Plugin.SetToken(stopToken);
            _scope.Set(pluginObject.ObjectName, pluginObject.PluginInfo.Plugin);
        }

        foreach (var item in platform.PubFunctions)
        {
            base.DoString(item.ToCompletePythonCode());
        }

    }
          
}

以上为pythonnet调用的所有代码,CiAlignerXPython类主要是为了演示如何附加net对象。对外调用的方法主要还是DoString,以及GetFunction方法,找到自定义函数后,直接fun.Invoke();即可调用该方法,需要注意的是这样的调用没有返回值。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐