我正在尝试学习如何在 Cython 中包装 C++ 库类(其构造函数以 std::string 作为参数)来为我的库构建 python 接口。但是生成的模块无法返回我尝试包装的类的对象,即使它没有返回任何错误或异常。我在下面提供了一个最小的(但不幸的是仍然很长)示例:
我的玩具项目的目录结构如下(下面提到的所有路径都是相对于所示的PBTOY2项目根目录):
C:\USERS\<MyName>\PROJECTS\PBTOY2
├───Examples
└───MyLib
├───include
│ └───MyLib
├───python
│ └───src
└───src
└───MyLib
我的玩具库在
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{};
};
其中
exports.h
位于 MyLib/include
并包含:
#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;
}
我使用从
example1.cpp
中的源代码构建的可执行文件来测试该库(在Examples
中找到,其中包含以下内容:
#include <iostream>
#include <MyLib/mylib.h>
int main (int argc, char *argv[]) {
std::string name{ "Homer" };
int id {1};
std::string email{ "[email protected]"};
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;
}
所有这些都可以正常编译和运行(在 Windows 10 环境中使用 CMake)。库编译为
mylib.dll
,可执行文件编译为 toy-example.exe
。
同时,在
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.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().decode()
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
我在
setup.py
中有一个 MyLib/python
文件,其中包含:
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 脚本
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, "[email protected]")
print( "my ID is ", myPerson.get_id())
else:
print("sorry, couldn't find the module")
(我在上面编辑了我的主目录的实际名称)
我正在使用命令
MyLib
在 MyLib/python
中构建 python setup.py build_ext --inplace
扩展。
我确实收到一个关于该类需要 dll 接口的警告
std::_Compressed_pair<std::allocator<char>,std::_String_val<std::_Simple_types<_Elem>>,true>
但是当我尝试在 mylib.h
中包含为此的指令时,Visual Studio 无法识别该类型。 (细心的读者会注意到,我确实在 MyLib.h
中有一个 Visual Studio 能够识别的另一种类型的指令。我把它放在那里是为了解释我收到的另一个警告)。
python 模块构建成功,并且
useMylib.py
确实运行,但永远不会从 __cinit__
返回并带有指向 Person
的指针,并且对 myPerson.get_id()
的调用永远没有机会执行。在到达MyLib.pyx
中的打印语句后,它只是默默地停止,其中我打印了“为具有姓名的人创建指针...”,这表明指针永远不会从该打印语句下方的调用中返回。
我已经能够使用在库外部定义的类来执行此类操作,但我需要能够在编译为 dll 的库内执行此操作,如此处所示的情况。
我真的很困惑,我在这里或其他任何地方都没有看到任何可以称之为“重复”的问题。如果我忽略了一些事情,我很抱歉。提前感谢您仔细研究这个问题!
好吧,我解决了我自己的问题(某种程度上)。通常会发生这种情况,但这次不是在发布我的问题之前。我正在使用调试配置构建我的 C++ 项目。我一时兴起决定尝试发布配置,然后voila。问题就消失了。我希望这对像我这样尝试做类似事情的人有帮助。