C#教程

关注公众号 jb51net

关闭
首页 > 软件编程 > C#教程 > C#调用Python编译模块

C#通过Python.NET调用Python pyd扩展模块的实践指南

作者:加号3

在工业软件与算法融合的场景中,经常需要将 Python 生态的高性能算法库集成到 C# 桌面或后端应用中,下面我们就来看看在C#应用中调用Python编译模块(pyd)有哪些技术方案吧

一、背景与核心挑战

在工业软件与算法融合的场景中,经常需要将 Python 生态的高性能算法库(如 NumPy、OpenCV、PyTorch)集成到 C# 桌面或后端应用中。Python.NET(pythonnet)是实现这一目标的经典桥梁,但当目标 Python 代码被编译为 pyd 文件(Python C 扩展模块)时,调用方式与纯 .py 脚本存在显著差异。
核心挑战在于:pyd 模块本质上是动态链接库,其内部类结构、方法签名和内存布局由 C/Cython 编译决定,C# 侧需要准确理解 Python 侧的命名空间、类型系统和 GIL(全局解释器锁)机制,才能实现多类实例化、方法调用和复杂参数传递。

二、Python.NET 的工作原理

Python.NET 并非简单的进程间通信或 REST 封装,而是在 .NET 运行时内嵌 Python 解释器。这意味着:

当调用 pyd 文件时,Python.NET 的加载逻辑与导入普通 .py 模块一致——通过 import 机制将 pyd 映射为 Python 模块对象,但其内部类可能由 Cython 生成,元信息相对隐蔽。

三、pyd 模块的特殊性分析

pyd 文件是 Python 的 C 扩展格式(Windows 下为 .pyd,Linux 下为 .so)。与纯 Python 模块相比,它具备以下特征:

3.1 编译后的类结构

3.2 类型系统的刚性

3.3 依赖环境敏感

四、多类调用与参数传递的设计策略

4.1 模块初始化与类发现

在 C# 中加载 pyd 模块后,首要任务是定位内部类。由于 pyd 缺乏便捷的反射机制,建议:

4.2 参数传递的映射规则

复杂参数传递策略:

4.3 多类协作的调用模式

当 pyd 模块包含多个需要交互的类时(如 Processor 类处理 DataLoader 类输出的数据),推荐两种架构:

模式 A:Python 侧封装门面(Facade)

在 Python 层编写一个纯 Python 的协调类,封装 pyd 内部多个类的交互逻辑。C# 仅调用这个门面类的单一入口方法,降低跨语言调用的复杂度。

优势:C# 侧代码简洁,Python 侧逻辑易于调试;pyd 内部类的生命周期由 Python 管理,避免跨语言内存泄漏风险。

模式 B:C# 侧显式管理对象

C# 分别实例化 pyd 的多个类,手动传递对象引用。此时需注意:

五、代码实现

5.1 Python实现

Add.py类实现加法计算

def add(x,y):
    return x+y

Test.py类实现调用Add.py加法计算

import Add
def ShowNum(x,y):
    print('和为:%d' % Add.add(x,y))
    return Add.add(x,y)
if __name__ == "__main__":
    ShowNum(2,3)

setup.py类实现pyd生成

from distutils.core import setup
from Cython.Build import cythonize
setup(ext_modules = cythonize("Test.py"))
setup(ext_modules = cythonize("Add.py"))

5.2 生成pyd文件

在终端输入 python setup.py build_ext --inplace,然后按回车,如图所示

5.3 C#调用python的pyd文件

先在nuget下载对应的pythonnet版本(根据python版本选择)

C#代码实现

 private void TestPython()
 {
     try
     {
         //python环境路径
         string pathToVirtualEnv = @"H:\ProgramData\anaconda3\envs\python39";
         Environment.SetEnvironmentVariable("PATH", pathToVirtualEnv, EnvironmentVariableTarget.Process);
         Environment.SetEnvironmentVariable("PYTHONHOME", pathToVirtualEnv, EnvironmentVariableTarget.Process);
         Environment.SetEnvironmentVariable("PYTHONPATH", pathToVirtualEnv + "\\Lib\\site-packages;" + pathToVirtualEnv + "\\Lib", EnvironmentVariableTarget.Process);
         PythonEngine.PythonHome = pathToVirtualEnv;
         PythonEngine.PythonPath = PythonEngine.PythonPath + ";" + Environment.GetEnvironmentVariable("PYTHONPATH", EnvironmentVariableTarget.Process);
         PythonEngine.Initialize();
         PythonEngine.BeginAllowThreads();
         using (Py.GIL()) // 使用这个来包裹你调用python方法的代码
         {
             // 先引入python模块,也就是我们上面生成的pyd文件,如Test.cp39-win_amd64.pyd
             dynamic my_module = Py.Import("Test");
             // Call your python functions.
             int value = my_module.ShowNum(5,21);
             Debug.Write("[Debug]:" + value +"\t\n");
         }
     }
     catch (Exception ex)
     {
         Debug.WriteLine("[ERROR]:" + ex.Message + "\t\n");
     }
 }

六、关键工程实践

6.1 GIL 的精细化管理

Python.NET 的所有 Python 操作默认在 GIL 下执行,但长时间持有 GIL 会阻塞其他线程。建议:

6.2 异常处理的双向捕获

pyd 中 C 层抛出的异常可能无法被 Python 标准异常机制捕获,表现为进程崩溃。防御策略:

6.3 调试与诊断

七、总结

C# 通过 Python.NET 调用 pyd 文件,本质是在统一进程内实现 .NET 与 Python C-API 的深度互操作。成功的关键在于:

这种混合编程模式虽然增加了架构复杂度,但能够充分利用 Python 在算法领域的生态优势与 C# 在工程化方面的成熟框架,是实现高性能跨语言系统的有效路径。

到此这篇关于C#通过Python.NET调用Python pyd扩展模块的实践指南的文章就介绍到这了,更多相关C#调用Python编译模块内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文