Вкратце: я пытаюсь скомпилировать файл .cpp, который ссылается на другой скомпилированный API (файлы .so + .h), и при попытке загрузить его я получаю отсутствующий символ. Ниже приведен простой пример того, как я создавал модули, и все шаги, которые я делал на этом пути. Помощь будет очень признательна.
ПРИМЕЧАНИЕ. Прежде чем вы решите пропустить этот вопрос, потому что вы можете ничего не знать о Cython, это скорее вопрос компоновщика/g++, чем вопрос о cython, поэтому, пожалуйста, дочитайте до конца.
Я пытаюсь создать модуль Cython, обертывающий предварительно скомпилированную библиотеку C++ API, и получаю странные ошибки при импорте в python. Поэтому я сделал простой пример, во время которого:
Сначала у меня есть следующие два файла:
// circle.h
#ifndef CIRCLE_H
#define CIRCLE_H
class Circle {
public:
Circle(float radius);
float getDiameter() const;
private:
float m_radius;
};
#endif // CIRCLE_H
//circle.cpp
#include "circle.h"
Circle::Circle(float radius) : m_radius(radius) {}
float Circle::getDiameter() const {
return m_radius * 2.0f;
}
Я компилирую их в build/Circle.so
, используя следующие команды:
mkdir -p build
g++ -O2 -fPIC -c circle.cpp -o build/Circle.o
g++ -shared build/Circle.o -o build/Circle.so
cp circle.h build/Circle.h
Теперь я перешел к созданию своего модуля Cython, создав следующий файл с именем example.pyx
:
# distutils: language = c++
cdef extern from "build/Circle.h":
cdef cppclass Circle:
Circle(float radius)
float getDiameter()
cdef class PyCircle:
cdef Circle* c_circle
def __cinit__(self, float radius):
self.c_circle = new Circle(radius)
def __dealloc__(self):
del self.c_circle
def getDiameter(self):
return self.c_circle.getDiameter()
Затем я запустил cythonize -i example.pyx
и получил два файла в корневом каталоге: example.cpp
(исходный файл, сгенерированный Cython) и example.cpython-39-x86_64-linux-gnu.so
. Я переименовал последний в example.so
и попытался импортировать его из питона (import example
), и я получаю следующую ошибку:
ImportError: example.cpython-39-x86_64-linux-gnu.so: неопределенный символ: _ZNK6Circle11getDiameterEv
Итак, я заглянул в предварительно скомпилированный (это почти слово) модуль Circle.so
с помощью команды nm -D build/Circle.so | grep Diameter
и действительно увидел, что недостающий символ находится внутри этого модуля:
0000000000001110 T _ZNK6Circle11getDiameterEv
Поэтому я подумал: «Должна быть какая-то проблема с компоновщиком. Я знаю! Давайте скомпилируем этот автоматически сгенерированный исходный код .cpp и свяжем его с этой библиотекой!». Поэтому я сделал следующее:
g++ -O2 -fPIC -c example.cpp -I /usr/include/python3.9 -I build/Circle.h
build/Circle.so
в libCircle.so
g++ -shared example.o -Lbuild -lCircle -o example.so
import example
в консоли Python и получил ту же ошибку.Что я делаю не так? Почему этот символ не загружается?
Когда я пытаюсь выполнить второй набор шагов локально (после очистки всех объектов и общих библиотек), я получаю ImportError: libCircle.so: cannot open shared object file: No such file or directory
, а не undefined symbol: _ZNK6Circle11getDiameterEv
Основная проблема заключается в том, что Cython не знает о circle.cpp
и определениях внутри. Есть несколько способов исправить это, в зависимости от конечного варианта использования. Первые два требуют circle.cpp
для сборки модуля Cython, а третьему просто нужна общая библиотека и заголовки.
circle.cpp
(документы)Вы можете сообщить Cython о файле circle.cpp
с помощью блока cdef extern from
, который он будет #include
из сгенерированного файла example.cpp
:
cdef extern from "circle.cpp":
pass
circle.cpp
(документы)Добавьте директиву вверху example.pyx
, которая говорит cythonize собрать circle.cpp
и статически связать его с example.so
:
# distutils: sources = circle.cpp
libCircle.so
(документы)Добавьте несколько директив в example.pyx
, которые, по сути, добавляют -Lbuild -lCircle
к вызову компоновщика:
# distutils: library_dirs = build
# distutils: libraries = Circle
Вам нужно убедиться, что libCircle.so
находится где-то, о чем знает динамический компоновщик: либо установите его, добавьте каталог сборки в $LD_LIBRARY_PATH
, либо добавьте запись rpath, чтобы разрешить ее относительно example.so
.
Также см. этот SO ответ для очень подробного описания того, как работает динамическая загрузка библиотек в Cython.
Отличный ответ. Последний вариант помог. Кроме того, я добавил # distutils: extra_link_args = -Wl,-rpath=build
, чтобы это работало без переменной среды. Спасибо!
Используя demangler.com, я могу преобразовать ваш неопределенный символ в
Circle::getDiameter() const
. Это помогает?