
在C#中嵌入pythonnet,使用python脚本互操作.Net对象
手头的一个老的项目,之前一直用的lua作为窗口语言,用户可以自行编写脚本来调用系统的各种资源,包括一些自定义的函数或算法,以及系统之前定义好的插件和硬件设备,因为项目面向的是一些高校和研究所,所以就陆续有人建议把python也加入进来,正好最近手头的活不是很紧急了,就稍稍研究了一下。以上这段代码摘自网络,已验证通过,没有任何问题,可以放到program文件中的Main方法中,进程启动加载一次就可以
手头的一个老的项目,之前一直用的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();即可调用该方法,需要注意的是这样的调用没有返回值。
更多推荐
所有评论(0)