You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

带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")

问题现象

  1. 构建Cython扩展时,出现警告:std::_Compressed_pair<std::allocator<char>,std::_String_val<std::_Simple_types<_Elem>>,true>需要DLL接口,尝试添加对应导出指令但VS无法识别该类型。
  2. 扩展模块构建成功,但运行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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 09:44:59