尝试使用 Cython 扩展时未定义的符号

Posted

技术标签:

【中文标题】尝试使用 Cython 扩展时未定义的符号【英文标题】:Undefined symbols when trying to use Cython Extensions 【发布时间】:2017-08-12 23:37:41 【问题描述】:

我确信我缺少一些简单的东西,但我已经盯着它看了很长时间,以至于我看不到它。我已经阅读了here、here、here、here 和here 的问题和答案,但没有找到适合我用例的解决方案。我正在尝试组合一个简单的测试结构来使用智能指针和 cython(尽管我什至无法进入智能指针部分)。代码如下:

test_unique_ptr.h

struct TestStruct 
    int a;
    int b;
;

class TestClass

public:
    TestClass(void);
    ~TestClass(void);

    TestStruct myts;

    int getA()  return myts.a; 
    int getB()  return myts.b; 
;

test_unique_ptr.cpp

#include "test_unique_ptr.h"

TestClass::TestClass(void) 
    myts.a = 4;
    myts.b = 7;

这成功编译成libTestUPtr.so

test_u_ptr.pyx

from libcpp.memory cimport unique_ptr
from cython.operator cimport dereference as deref

cdef extern from "test_unique_ptr.h":
    ctypedef struct TestStruct:
        int a
        int b

    cdef cppclass TestClass:
        TestClass()
        TestStruct foo
        int getA()
        int getB()

cdef class TestClass1:
    cdef:
        TestClass tc

    def __cinit__(self):
        self.tc = TestClass()

    def getValue(self):
        print(self.tc.getA())

foo = TestClass1()
foo.getValue()

setup.py

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
import numpy

setup(
    cmdclass = 'build_ext': build_ext,
    ext_modules = [
        Extension("test_u_ptr",
            language="c++",
            sources=["test_u_ptr.pyx"],
            include_dirs=["/home/share/data/code/python/test/include", numpy.get_include()],
            libraries = ["TestUPtr"],
            library_dirs=["/home/share/data/code/python/test/lib"],
            extra_compile_args=['-lstdc++','-std=c++11', '-v'],
            extra_link_args=['-lstdc++', '-v'],
            )
        ]
    )

这也成功编译成 test_u_ptr.cpython-35m-x86_64-linux-gnu.so。现在是有趣的部分。

$ python3
>>> import test_u_ptr
ImportError: /home/share/data/code/python/test/test_u_ptr.cpython-35m-x86_64-linux-gnu.so: undefined symbol: _ZN9TestClassD1Ev

符号似乎在 libTestUPtr.so 中正确定义

$ nm -C libTestUPtr.so

0000000000201030 B __bss_start

0000000000201030 b completed.6973
                 w __cxa_finalize
0000000000000580 t deregister_tm_clones
00000000000005f0 t __do_global_dtors_aux
0000000000200e38 t __do_global_dtors_aux_fini_array_entry
0000000000201028 d __dso_handle
0000000000200e48 d _DYNAMIC
0000000000201030 D _edata
0000000000201038 B _end
0000000000000680 T _fini
0000000000000630 t frame_dummy
0000000000200e30 t __frame_dummy_init_array_entry
0000000000000700 r __FRAME_END__
0000000000201000 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
0000000000000530 T _init
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
0000000000200e40 d __JCR_END__
0000000000200e40 d __JCR_LIST__
                 w _Jv_RegisterClasses
00000000000005b0 t register_tm_clones
0000000000201030 d __TMC_END__
0000000000000670 T TestClass::TestClass()
0000000000000670 T TestClass::TestClass()

但是,cython 库中几乎所有内容都未定义。

$ nm -C test_u_ptr.cpython-35m-x86_64-linux-gnu.so

0000000000204530 B __bss_start
0000000000204540 b completed.6973
                 w __cxa_finalize@@GLIBC_2.2.5
0000000000001be0 t deregister_tm_clones
0000000000001c50 t __do_global_dtors_aux
0000000000203d40 t __do_global_dtors_aux_fini_array_entry
0000000000204180 d __dso_handle
0000000000203d50 d _DYNAMIC
0000000000204530 D _edata
00000000002046f8 B _end
0000000000003154 T _fini
0000000000001c90 t frame_dummy
0000000000203d38 t __frame_dummy_init_array_entry
00000000000035b8 r __FRAME_END__
0000000000204000 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
00000000000015e8 T _init
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
0000000000203d48 d __JCR_END__
0000000000203d48 d __JCR_LIST__
                 w _Jv_RegisterClasses
                 U PyBaseObject_Type
                 U PyBytes_FromStringAndSize
                 U PyCapsule_New
                 U PyCFunction_Type
                 U _Py_CheckRecursionLimit
                 U _Py_CheckRecursiveCall
                 U PyCode_New
                 U PyDict_GetItem
                 U PyDict_GetItemString
                 U PyDict_New
                 U PyDict_SetItem
                 U PyDict_SetItemString
                 U PyErr_Format
                 U PyErr_Occurred
                 U PyErr_SetString
                 U PyErr_WarnEx
                 U PyEval_EvalCodeEx
                 U PyEval_EvalFrameEx
                 U PyExc_ImportError
                 U PyExc_NameError
                 U PyExc_SystemError
                 U PyExc_TypeError
                 U PyFrame_New
                 U PyFunction_Type
                 U Py_GetVersion
                 U PyImport_AddModule
                 U PyImport_GetModuleDict
00000000000023d0 T PyInit_test_u_ptr
                 U PyLong_FromLong
                 U PyMem_Malloc
                 U PyMem_Realloc
                 U PyMethod_Type
                 U PyModule_Create2
                 U PyModule_GetDict
                 U _Py_NoneStruct
                 U PyObject_Call
                 U PyObject_CallFinalizerFromDealloc
                 U PyObject_GetAttr
                 U PyObject_SetAttrString
                 U PyOS_snprintf
                 U PyThreadState_Get
                 U PyTraceBack_Here
                 U PyTuple_New
                 U PyTuple_Pack
                 U PyType_Ready
                 U PyUnicode_Decode
                 U PyUnicode_FromFormat
                 U PyUnicode_FromString
                 U PyUnicode_FromStringAndSize
                 U PyUnicode_InternFromString
0000000000204560 B __pyx_module_is_main_test_u_ptr
0000000000001c10 t register_tm_clones
                 U __stack_chk_fail@@GLIBC_2.4
0000000000204530 d __TMC_END__
000000000000333b r __pyx_k_end
0000000000003337 r __pyx_k_foo
0000000000204650 b __pyx_print
0000000000003332 r __pyx_k_file
0000000000003329 r __pyx_k_main
0000000000003320 r __pyx_k_test
00000000002046a0 b __pyx_lineno
0000000000204690 b __pyx_clineno
000000000000331a r __pyx_k_print
0000000000204580 b __pyx_methods
0000000000204630 b __pyx_n_s_end
0000000000204620 b __pyx_n_s_foo
0000000000204680 b __pyx_filename
0000000000204628 b __pyx_n_s_file
0000000000204600 b __pyx_n_s_main
00000000002045d0 b __pyx_n_s_test
0000000000204320 d __pyx_moduledef
00000000002045f0 b __pyx_n_s_print
0000000000204660 b __pyx_code_cache
0000000000003311 r __pyx_k_getValue
00000000002041a0 d __pyx_string_tab
00000000002046b0 b __pyx_empty_bytes
00000000002046c0 b __pyx_empty_tuple
0000000000001e10 t __Pyx_AddTraceback(char const*, int, int, char const*)
0000000000003302 r __pyx_k_pyx_vtable
0000000000204610 b __pyx_n_s_getValue
00000000002046a8 b __pyx_empty_unicode
00000000000018e0 t __Pyx_PyObject_Call(_object*, _object*, _object*)     
00000000002045e0 b __pyx_n_s_pyx_vtable
00000000000019c1 t __Pyx_PyFunction_FastCallDict(_object*, _object**, int,     _object*) [clone .constprop.3]
00000000002043a0 d __pyx_type_10test_u_ptr_TestClass1
0000000000204640 b __pyx_ptype_10test_u_ptr_TestClass1
0000000000001d30 t __pyx_tp_new_10test_u_ptr_TestClass1(_typeobject*, _object*, _object*)
00000000002045c0 b __pyx_vtable_10test_u_ptr_TestClass1
00000000002045a0 b __pyx_methods_10test_u_ptr_TestClass1
0000000000204670 b __pyx_vtabptr_10test_u_ptr_TestClass1
0000000000001ce0 t __pyx_tp_dealloc_10test_u_ptr_TestClass1(_object*)
0000000000002230 t __pyx_f_10test_u_ptr_10TestClass1_getValue(__pyx_obj_10test_u_ptr_TestClass1*)
0000000000001cd0 t __pyx_f_10test_u_ptr_10TestClass1_printValue(__pyx_obj_10test_u_ptr_TestClass1*)
00000000002046d0 b __pyx_b
00000000002046e0 b __pyx_d
00000000002046f0 b __pyx_m
                 U TestClass::TestClass()
                 U TestClass::~TestClass()

两个库都是 64 位的。

$ file libTestUPtr.so 
libTestUPtr.so: ELF 64-bit LSB  shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=4c6eb8560a244ebd485d483f1c27004145ad1882, not stripped

$ file test_u_ptr.cpython-35m-x86_64-linux-gnu.so 
test_u_ptr.cpython-35m-x86_64-linux-gnu.so: ELF 64-bit LSB  shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=5a2daa515d4a22dd46484968e817cd3a3bcebe5a, not stripped

libTestUPtr.so 位于 LD_LIBRARY_PATH 中且位置正确

$ echo $LD_LIBRARY_PATH 
/home/share/data/code/python/test/lib

$ ls /home/share/data/code/python/test/lib/
libTestUPtr.so

我不知道发生了什么。任何见解将不胜感激。

gcc 是 4.8.4 版本。 Python 是 3.5.3 版。 Cython 是 0.25.2 版。

【问题讨论】:

添加链接器选项“-Wl,--no-undefined”似乎已经解决了这个问题。我完全不知道为什么。我将暂时保持开放状态,以防有人能弄清楚到底发生了什么。 我认为你的 pyx 文件中应该是 def getValue() 而不是 cdef getValue() 你是对的。我已经对其进行了编辑以反映这一点。 【参考方案1】:

您的问题没有什么神秘之处,我怀疑添加 --no-undefined 已解决此问题。

丢失的符号是类TestClass 的析构函数。您的共享库为TestClass 提供了两个符号:但是这两个符号都是构造函数(我不完全理解为什么有两个符号以及为什么两个符号具有相同的地址)并且它们都不是所需的析构函数。

这个问题的根源是你声明了你的析构函数,所以它不再被编译器默认实现,但是你没有在 cpp 文件中提供实现。

所以你的选择是:

    从h文件中删除析构函数的声明,让编译器处理。

    在 cpp 文件中实现你的析构函数。

哪个更好取决于上下文。


gcc 为构造函数发出两个符号,为析构函数发出两个符号,这似乎是一个已知的错过优化,here is a great explanation。

地址是相同的,因为编译器给两个相同的方法起别名:

_ZN9TestClassC2Ev: //Constructor of the BaseObject-constructor
.LFB3:
    ...
    ret
.LFE3:
    ..
    .globl  _ZN9TestClassC1Ev
    .set    _ZN9TestClassC1Ev,_ZN9TestClassC2Ev //HERE WE GO, Complete Object constructor which we use normally

【讨论】:

你是对的。问题是缺少析构函数,我认为 --no-undefined 实际上与修复它没有任何关系。

以上是关于尝试使用 Cython 扩展时未定义的符号的主要内容,如果未能解决你的问题,请参考以下文章

为 C 库生成 Python SWIG 绑定时未定义的符号

ImportError:在 python 中导入 swigged c++-class 时未定义的符号

Cython:共享对象中的未定义符号

链接到 MacOS 上预编译的 QuantLib 二进制文件时未定义的 Boost 符号

WebAssembly 编译器错误:使用外部库编译时未定义符号

在 C++ 中使用 boost:regex_error 时未定义符号?