新手如何有效避免Pybind11使用的各种坑【undefine symbol xxx, dynamic module does not define xxx等】

从 Python 调用 C++ 基本上有两种方法:一是使用 PyBind11 C++ 库生成 Python 模块,二使用 cytpes Python 包访问已编译的共享库。 使用cytpes还需要额外对c++代码进行处理,而使用 Pybind11能够直接对c++代码进行导出,并且支持c++的许多数据类型如vector的自动转换,十分方便。

安装的教程很多,在此不再赘述。然而,对一个新手来说, 难免在使用的时候会遇到很多坑,而有些坑比较玄学,本教程也只是记录遇到过的一些坑的解决办法,便于自己回顾类似的问题,如果能顺便帮助到其他人,不上荣幸。

注意:本文只适用于ubuntu系统

Example:

将c++代码导出为python接口至少需要两个文件,源文件和cmakelists.txt文件,假设都在example文件下:

1. 源文件,需要包含c++代码和pybind11的接口绑定:

pybind_test.cpp

#include <pybind11/pybind11.h>
#include <string>
#include <iostream>

using namespace std;
namespace py = pybind11;
 
 
class Pybindtest
{
public:
    virtual ~Pybindtest();
    Pybindtest(std::string &inputs)
    {
        info = inputs;
    }
    void printInfo()
    {
        std::cout << "Your input is " << info << std::endl;
        
    }
private:
    std::string info;
};
 
PYBIND11_MODULE(libmy_module, m) 
{
    m.doc() = "pybind11 Hierarchical Localization cpp backend";
 
    py::class_<Pybindtest>(m, "Pybindtest")
    .def(py::init<std::string &>())
    .def("printInfo", &Pybindtest::printInfo);
}

2.CMakeLists.txt

cmake_minimum_required(VERSION  3.18.2)
project(HFnet_map)
add_compile_options(-std=c++11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g")
set(CMAKE_BUILD_TYPE "Release")
set(PYBIND11_CPP_STANDARD -std=c++11)
 
# python
find_package(Python 3.8 REQUIRED COMPONENTS Interpreter Development)
find_package(pybind11 REQUIRED)
 

INCLUDE_DIRECTORIES(
  ${pybind11_INCLUDE_DIRS})

# 创建库和添加宏定义
add_library(my_module MODULE ${cpp_srcs})
target_compile_definitions(my_module PRIVATE PYBIND11_MODULE_NAME=my_module)

target_link_libraries(my_module PRIVATE
    pybind11::module)

# 设置共享库生成的路径
set_target_properties(my_module PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)

# 禁用共享库的安装
set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}/install CACHE PATH "Installation Directory" FORCE)

坑1:ModuleNotFoundError: No module named XXXXXX或者是ImportError: dynamic module does not define module export function (PyInit_libxxxx)

这个问题通常是因为绑定的名称不匹配,需要注意三个地方:

// pybind_test.cpp
PYBIND11_MODULE(libmy_module, m) 
//CMakeLists.txt
add_library(my_module MODULE ${cpp_srcs})
target_link_libraries(my_module PRIVATE
    pybind11::module)

这三个地方的名称一定要一致,如果使用的是add_library(my_module xxx),因为c++编译器会给.so文件添加lib,所以PYBIND11_MODULE(libmy_module, m) 这里面需要加上lib,my_module可以替换成其他任意的名称,这是自己设置的。最后的target_link_libraries中,也一定要添加PRIVATE和pybind11::module,否则编译的时候就会报错

另外,需要注意CMakeLists.txt如果需要指定python版本,要放在pybind11之前,否则,有时候还是会直接连接到系统自带的python版本。

# python
find_package(Python 3.8 REQUIRED COMPONENTS Interpreter Development)
message(STATUS "Python Version: ${Python_Version}")
# pybind11
find_package(pybind11 REQUIRED)

坑2:undefined symbol: _ZTV6Logger   (./libyolov8_inference.so)

这个问题是最折磨人的问题之一,代码编写了,编译也正常了,也导出了自己定义的.so文件。当觉得万事具备,只欠python的import时,然后突然就冒出了这个问题,瞬间感觉被泼一把冷水!然后到处查也查不出个所以然。

那么为啥会出现这个问题?

这个问题一般出现在编写的项目比较大,源文件比较多,所需的各种库也比较多的时候,原因就是遗漏了某些库或者源文件。可以使用使用ldd -r ./yolov8_inference.so(自己导出的so文件)进行查看:

        linux-vdso.so.1 =>  (0x00007ffc61818000)
        /lib64/libstdc++.so (0x00007f49ba1aa000)
        libcudart.so.10.2 => /usr/local/cuda-10.2/lib64/libcudart.so.10.2 (0x00007f49b9f2c000)
        libnvinfer.so.8 => /home/hx/TensorRT-8.4.2.4/lib/libnvinfer.so.8 (0x00007f49abdf2000)
        libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f49aba0e000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f49ab7f6000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f49ab42c000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f49ab228000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f49ba5c3000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f49ab00b000)
        librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f49aae03000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f49aaafa000)
undefined symbol: _ZTV6Logger   (./libyolov8_inference.so)
undefined symbol: _Z9compareABfff       (./libyolov8_inference.so)
undefined symbol: _Z3NMSRKSt6vectorI10detectionsSaIS0_EERS2_Rf  (./libyolov8_inference.so)
undefined symbol: _ZN2cv6String10deallocateEv   (./libyolov8_inference.so)

会发现果然出现了undefined symbol问题,再使用c++filt +报错符号查看:

// 解码命令
c++filt _Z3NMSRKSt6vectorI10detectionsSaIS0_EERS2_Rf
// 输出
NMS(std::vector<detections, std::allocator<detections> > const&, std::vector<detections, std::allocator<detections> >&, float&)

很显然,这些都是自己定义的一些函数。

这一类错误编译器不会报出来,但是一使用python进行import的时候就会出错,解决这个问题的方法也很简单粗暴:缺啥补啥!例如我报错的这些函数都是在编写CMakeList.txt时,忘了把这些函数的源文件添加进去,在add_library()中添加进去遗漏的源文件后就正常了。有时候还会出现下面这种情况(只截取了一部分):

undefined symbol: PyInstanceMethod_Type (./libyolov8_inference.so)
undefined symbol: PyExc_ValueError      (./libyolov8_inference.so)
undefined symbol: _Py_TrueStruct        (./libyolov8_inference.so)
undefined symbol: PyExc_IndexError      (./libyolov8_inference.so)
undefined symbol: PyCapsule_Type        (./libyolov8_inference.so)
undefined symbol: PyModule_Type (./libyolov8_inference.so)
undefined symbol: _Py_NoneStruct        (./libyolov8_inference.so)
undefined symbol: PyExc_MemoryError     (./libyolov8_inference.so)

这是缺少某些库导致的,例如上面这个就是缺少了python的相关库,只需要把这部分补充上就行了,在pybind11::module后面加上Python::Python,如果出现的是其他库,继续添加到后面即可。

target_link_libraries(yolov8_inference PRIVATE
    pybind11::module
    Python::Python)

坑3:还是undefined symbol

这个类型的错误就很玄学了,出现的情景就是:在a.h中定义了类A,而A中的某个方法使用到了pybind11的数据结构例如py::list作为参数,假设这个函数为void test(py::list),然后在a.cpp中去实现了这个方法,那么在另一个文件b.cpp中去绑定这个类的时候,会出现undefined symbol: test这个函数。也就是说,类A中的方法test总是被认为未定义,导致python进行import的时候报错!

解决的办法就是:A的所有方法都不要包含pybind11的数据结构,然后在另一个源文件例如b.cpp中另外去写一个函数或者类去对这个数据结构进行处理,然后再传给类A的函数进行处理,这样就不会报错。

这个情况比较特殊,感觉挺玄学的,但是有可能是本人水平有限的原因,仅供参考。

坑4:段错误

运行代码最可怕的就是段错误了,短短的三个字,也没有指定位置,让人像大海捞针一样去查代码,如果代码量惊人,那简直要命。

如果c++运行没有问题,但python出现了这个问题。那大概率是使用pybind11绑定了类,然后类中刚好有一些指针成员,然后析构函数也没有好好释放。。。。。如果出现了这个问题,建议把c++中自己把握不住的一些指针改成智能指针,让c++去自己进行管理。因为python调用c++代码,中间涉及了啥秘密交易我们也理不清楚,不如交给系统自己处理。

整理记录于2024.01.25!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱哭的熊孩子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值