在Cython中调用跨文件C++函数时遭遇未定义符号错误的求助
问题:Cython调用C++ trim函数时出现未定义符号错误
错误信息
在main.cpp中调用trim函数后,Python导入Cython模块时触发以下运行时错误:
Traceback (most recent call last): File "/home/jeff/projects/c/bfg/trim_test/trim_test.py", line 1, in <module> import trim_binder ImportError: ./libmain.so: undefined symbol: _Z4trimRNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
代码获取
可通过以下命令克隆完整代码:
git clone https://psionman@bitbucket.org/psionman/test_trim.git
相关代码文件
main.h
#ifndef TEST_TRIM_H #define TEST_TRIM_H #include <iostream> std::string testTrim(std::string); #endif
main.cpp
#include <iostream> #include "trim.h" std::string testTrim(std::string line) { std::string trimmed {}; trimmed = line; trimmed = trim(line); return trimmed; }
trim.h
#ifndef TRIM_STRINGS_H #define TRIM_STRINGS_H std::string& trim(std::string& s); #endif
trim.cpp
#include <string> #include "trim.h" // trim from left inline std::string& ltrim(std::string& s, const char* t = " \t\n\r\f\v") { s.erase(0, s.find_first_not_of(t)); return s; } // trim from right inline std::string& rtrim(std::string& s, const char* t = " \t\n\r\f\v") { s.erase(s.find_last_not_of(t) + 1); return s; } // trim from left & right inline std::string& trim(std::string& s, const char* t = " \t\n\r\f\v") { return ltrim(rtrim(s, t), t); }
trim_test.py
import trim_binder line = ' AAA BB CC E ' trimmed = trim_binder.get_trimmed_text(line) print(f'In trim_test: {trimmed.replace(" ", ".")}')
trim_wrapper.pyx
from libcpp.string cimport string cdef extern from 'main.h': string testTrim(char* input_text) def get_trimmed_text(path): input_text = str.encode(str(path)) py_bytes_object = input_text cdef string s = py_bytes_object raw_trimmed = testTrim(py_bytes_object) trimmed = raw_trimmed.decode() return trimmed
tasks.py
import invoke WRAPPER_NAME = 'trim_wrapper.cpp' BINDER = 'trim_binder' # .pyx file name C_FILE = 'main' compile_files = ['main', 'trim'] @invoke.task() def build_c_function(c): """Build the shared library for the sample C++ code""" files = [file + '.cpp' for file in compile_files] files_str = ' '.join(files) invoke.run( f'g++ -O3 -Wall -Werror -shared -std=c++11 -fPIC {files_str} ' '-I . ' f'-o lib{C_FILE}.so ' ) print('c function build complete') def compile_python_module(cpp_name, extension_name): invoke.run( 'g++ -O3 -Wall -Werror -shared -std=c++11 -fPIC ' '`python3 -m pybind11 --includes` ' '-I . ' '{0} ' '-o {1}`python3-config --extension-suffix` ' f'-L. -l{C_FILE} -Wl,-rpath,.'.format(cpp_name, extension_name) ) @invoke.task(build_c_function) def build_cython(c): """Build the cython extension module""" invoke.run(f'cython --cplus -3 {BINDER}.pyx -o {WRAPPER_NAME}') compile_python_module(WRAPPER_NAME, BINDER) print('cython build complete')
符号列表
libmain.so的符号列表:
w __cxa_finalize@GLIBC_2.2.5 w __gmon_start__ U __gxx_personality_v0@CXXABI_1.3 w _ITM_deregisterTMCloneTable w _ITM_registerTMCloneTable U _Unwind_Resume@GCC_3.0 0000000000001140 T _Z8testTrimNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE U _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE10_M_disposeEv@GLIBCXX_3.4.21 U _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE9_M_assignERKS4_@GLIBCXX_3.4.21 U _ZSt21ios_base_library_initv@GLIBCXX_3.4.32
解决方案
问题根源
trim、ltrim、rtrim均为inline函数,C++标准规定:inline函数的定义必须在每个调用它的编译单元中可见,否则编译器不会生成该函数的符号。当main.cpp调用trim时,仅包含了trim.h的声明,未获取到函数定义;而trim.cpp中的inline函数默认不会被导出为共享库符号,导致libmain.so缺少trim的符号,最终触发导入错误。
修复方案
方案1:将inline函数定义移到头文件
把trim.cpp中的函数定义直接放入trim.h,让每个调用trim的编译单元(如main.cpp)都能获取到定义,编译器会直接展开inline函数,无需依赖共享库符号:
修改后的trim.h:
#ifndef TRIM_STRINGS_H #define TRIM_STRINGS_H #include <string> // trim from left inline std::string& ltrim(std::string& s, const char* t = " \t\n\r\f\v") { s.erase(0, s.find_first_not_of(t)); return s; } // trim from right inline std::string& rtrim(std::string& s, const char* t = " \t\n\r\f\v") { s.erase(s.find_last_not_of(t) + 1); return s; } // trim from left & right inline std::string& trim(std::string& s, const char* t = " \t\n\r\f\v") { return ltrim(rtrim(s, t), t); } #endif
删除trim.cpp,并修改tasks.py中的compile_files为['main'](无需再编译trim.cpp)。
方案2:取消inline关键字,编译进共享库
若不想将定义放在头文件,可去掉trim.cpp中三个函数的inline关键字,让编译器生成函数符号并编译到libmain.so中:
修改后的trim.cpp:
#include <string> #include "trim.h" // trim from left std::string& ltrim(std::string& s, const char* t = " \t\n\r\f\v") { s.erase(0, s.find_first_not_of(t)); return s; } // trim from right std::string& rtrim(std::string& s, const char* t = " \t\n\r\f\v") { s.erase(s.find_last_not_of(t) + 1); return s; } // trim from left & right std::string& trim(std::string& s, const char* t = " \t\n\r\f\v") { return ltrim(rtrim(s, t), t); }
确保trim.h的声明与定义一致,重新编译即可。
额外修复:Cython接口类型不匹配
当前trim_wrapper.pyx中声明testTrim接受char*,但实际testTrim参数为std::string,会导致类型错误。修改trim_wrapper.pyx:
from libcpp.string cimport string cdef extern from 'main.h': string testTrim(string input_text) # 改为std::string类型 def get_trimmed_text(path): cdef string s = str(path) # 直接转换为std::string,无需手动encode raw_trimmed = testTrim(s) return str(raw_trimmed) # 直接转换为Python字符串
内容的提问来源于stack exchange,提问作者Psionman
相关产品推荐
相关产品推荐

