带std::string成员的导出C++类的Cython包装无法返回对象
Cython封装带std::string参数的C++ DLL类时,创建对象静默崩溃的问题
项目环境与结构
项目根目录为C:\USERS\<MyName>\PROJECTS\PBTOY2,目录结构如下:
├───Examples └───MyLib ├───include │ └───MyLib ├───python │ └───src └───src └───MyLib
C++库代码(可正常编译运行)
头文件 MyLib/include/MyLib/mylib.h
#pragma once #include <iostream> #include <string> #include <MyLib/exports.h> EXPIMP_TEMPLATE template class MYLIB_API std::basic_string<char,std::char_traits<char>,std::allocator<char>>; class MYLIB_API Person { public: Person(std::string name, int id, std::string email); Person(int id); ~Person(); const std::string getName() const; const std::string getEmail() const; const int getID() const; const bool isValid() const; private: std::string name{}; int id{}; std::string email{}; };
导出定义 MyLib/include/MyLib/exports.h
#pragma once #ifdef MYLIB_EXPORTS # define MYLIB_API __declspec(dllexport) # define EXPIMP_TEMPLATE #else # define MYLIB_API __declspec(dllimport) # define EXPIMP_TEMPLATE extern #endif
库实现 MyLib/src/MyLib/mylib.cpp
#include <MyLib/mylib.h> Person::Person(std::string name, int id, std::string email) : name{ name }, id{ id }, email{ email } { } Person::Person(int id) : id{ id } { } Person::~Person() {} const std::string Person::getName() const { return name; } const std::string Person::getEmail() const { return email; } const int Person::getID() const { return id; } const bool Person::isValid() const { if (name.empty()) { return false; } return true; }
C++测试代码 Examples/example1.cpp(运行正常)
#include <iostream> #include <MyLib/mylib.h> int main (int argc, char *argv[]) { std::string name{ "Homer" }; int id {1}; std::string email{ "me@there.com"}; Person p = Person(name, 1, email); std::cout << "Hello, " << p.getName() << "! You are number " << p.getID() << "! Can I contact you at " << p.getEmail() << "?" << std::endl; return 0; }
编译后生成mylib.dll和toy-example.exe,均可正常运行。
Cython封装代码
MyLib/python/src/MyLib.pxd
# distutils: language = c++ from libcpp.string cimport string cdef extern from "mylib.h": cdef cppclass Person: Person(string, int, string) except + string getName() string getEmail() int getID()
MyLib/python/src/MyLib.pyx
# cython: c_string_type=unicode, c_string_encoding=utf8 from MyLib cimport Person cdef class PyPerson: cdef Person* c_person_ptr def __cinit__(self, str name, int id, str email): print("creating the pointer for person with name ", name) self.c_person_ptr = new Person(name, id, email) print("created the pointer") def get_name(self): return self.c_person_ptr.getName() def get_id(self): return self.c_person_ptr.getID() def get_email(self): return self.c_person_ptr.getEmail() def __dealloc__(self): print("deallocating pointer") del self.c_person_ptr
MyLib/python/setup.py
from setuptools import setup, Extension from Cython.Build import cythonize import os ext = [Extension(name = "*", sources = [os.path.join(os.path.dirname(__file__), "src", "*.pyx")], language = "c++", library_dirs = ["../../build/MyLib/Debug"], libraries = ['MyLib',]) ] setup( name = "MyLib", ext_modules = cythonize(ext, language_level = "3"), include_dirs = ['../include/MyLib', '../include'], )
Python测试脚本 useMylib.py
import os dllPath = r'C:\Users\<MyHomeDirectory>\projects\PBToy2\build\install\bin' os.add_dll_directory(dllPath) if os.path.exists(dllPath): import MyLib print("I'm really here") myPerson = MyLib.PyPerson("Homer", 0, "hsimpson@somewhere.com") print( "my ID is ", myPerson.get_id()) else: print("sorry, couldn't find the module")
问题现象
- 构建Cython扩展时,出现警告:
std::_Compressed_pair<std::allocator<char>,std::_String_val<std::_Simple_types<_Elem>>,true>需要DLL接口,尝试添加对应导出指令但VS无法识别该类型。 - 扩展模块构建成功,但运行Python脚本时,执行到
__cinit__中的print("creating the pointer for person with name ", name)后程序静默终止,无法完成Person对象的创建,后续代码无法执行。
解决方案
1. 修复std::string的DLL导出问题
当前仅导出了std::basic_string<char>,但C++标准库的string内部依赖的辅助类型(比如_Compressed_pair)未被导出,导致跨DLL边界使用时内存管理混乱。
修改mylib.h,添加以下导出声明(针对MSVC编译器):
// 在现有EXPIMP_TEMPLATE行下方添加 EXPIMP_TEMPLATE template class MYLIB_API std::_String_val<std::_Simple_types<char>>; EXPIMP_TEMPLATE template class MYLIB_API std::_Compressed_pair<std::allocator<char>, std::_String_val<std::_Simple_types<char>>, true>;
注意:这些类型是MSVC特定的,不同编译器可能有差异。如果后续仍有类似警告,可以通过VS的编译器输出找到对应的未导出类型,逐一添加。
2. 确保Cython和C库使用相同的C标准与运行时
- 编译
mylib.dll时,确保使用多线程DLL运行时(/MD或/MDd),Cython扩展也需要使用相同的运行时,避免内存分配/释放跨不同运行时导致崩溃。 - 两者需使用相同的C标准版本(比如C17)。
3. 调整Cython中string的传递方式
在__cinit__中,显式转换Python字符串为C++ string,避免隐式转换可能的问题:
def __cinit__(self, str name, int id, str email): print("creating the pointer for person with name ", name) cdef string c_name = name.encode('utf-8') cdef string c_email = email.encode('utf-8') self.c_person_ptr = new Person(c_name, id, c_email) print("created the pointer")
同时,返回string时显式转换为Python字符串:
def get_name(self): return self.c_person_ptr.getName().decode('utf-8') def get_email(self): return self.c_person_ptr.getEmail().decode('utf-8')
4. 启用异常捕获与调试
修改Python脚本,添加异常捕获以查看隐藏错误:
try: myPerson = MyLib.PyPerson("Homer", 0, "hsimpson@somewhere.com") print( "my ID is ", myPerson.get_id()) except Exception as e: print(f"Error occurred: {e}")
另外,可以使用VS调试Python进程,查看崩溃时的调用栈,定位具体问题。
内容的提问来源于stack exchange,提问作者tjd
相关产品推荐
相关产品推荐

