如何在pybind11中销毁Python解释器以实现C++循环调用Python函数
解决pybind11封装DLL被C#循环调用时的解释器重复初始化异常
问题根源
原C++代码中,py::finalize_interpreter();位于return result;之后,函数执行到return会直接退出,导致销毁解释器的代码永远不会执行。第一次调用后Python解释器持续存活,第二次调用py::initialize_interpreter()时,由于Python解释器在一个进程中只能初始化一次,因此抛出pybind11::error_already_set异常。
另外,C#代码中对参数a的MarshalAs(UnmanagedType.LPArray)是错误的,a是单个double值,并非数组,会导致参数传递异常。
最优解决方案:全局初始化一次解释器
由于Python解释器初始化和销毁的开销极大,且重复操作容易引发问题,最佳实践是在首次调用DLL函数时初始化一次解释器,后续调用直接复用,程序结束时再销毁。
修改后的C++代码(add.dll)
#include <pybind11/pybind11.h> #include <iostream> #include <stdexcept> namespace py = pybind11; // 全局保存Python加法函数,避免重复导入 static py::object g_addition_func; static bool is_interpreter_initialized = false; extern "C" { __declspec(dllexport) double add(double a, double b) { // 首次调用时初始化Python解释器并导入目标函数 if (!is_interpreter_initialized) { try { py::initialize_interpreter(); py::module pyfile = py::module::import("pyfile"); g_addition_func = pyfile.attr("addition"); is_interpreter_initialized = true; } catch (const py::error_already_set& e) { std::cerr << "Python初始化失败:" << e.what() << std::endl; if (Py_IsInitialized()) { py::finalize_interpreter(); } return -1.0; // 返回错误标记值 } } // 调用Python函数计算结果 try { double result = g_addition_func(a, b).cast<double>(); std::cout << "C++侧计算结果:" << result << std::endl; return result; } catch (const py::error_already_set& e) { std::cerr << "Python函数调用失败:" << e.what() << std::endl; return -1.0; } } // 供C#调用,用于程序结束时销毁Python解释器 __declspec(dllexport) void cleanup_python_env() { if (is_interpreter_initialized && Py_IsInitialized()) { py::finalize_interpreter(); g_addition_func = py::object(); is_interpreter_initialized = false; } } }
修改后的C#代码
using System; using System.Runtime.InteropServices; class Program { // 修正参数MarshalAs错误,直接传递double值 [DllImport("add.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] public static extern double add(double a, double b); // 导入销毁Python环境的函数 [DllImport("add.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] public static extern void cleanup_python_env(); static void Main(string[] args) { double a = 5.5; double b = 3.5; for(int i = 0; i < 5; i++) { double result = add(a, b); Console.WriteLine($"C#侧输出结果:{result}"); } // 程序结束前销毁Python解释器 cleanup_python_env(); } }
备选方案:每次调用都初始化/销毁解释器(不推荐)
如果因特殊需求必须每次调用都重新初始化解释器,需要确保py::finalize_interpreter()无论是否发生异常都能执行:
#include <pybind11/pybind11.h> #include <iostream> namespace py = pybind11; extern "C" { __declspec(dllexport) double add(double a, double b) { double result = -1.0; bool interpreter_initialized = false; try { py::initialize_interpreter(); interpreter_initialized = true; result = py::module::import("pyfile").attr("addition")(a, b).cast<double>(); std::cout << "C++侧计算结果:" << result << std::endl; } catch (const py::error_already_set& e) { std::cerr << "错误:" << e.what() << std::endl; } finally { // 确保解释器被销毁 if (interpreter_initialized && Py_IsInitialized()) { py::finalize_interpreter(); } } return result; } }
注意:此方案每次调用都会初始化和销毁Python解释器,性能开销极大,且部分Python模块可能无法完全卸载,存在内存泄漏风险,仅在特殊场景下使用。
内容的提问来源于stack exchange,提问作者Chanukya N
相关产品推荐
相关产品推荐

