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

在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 00:48:10