Ошибка отсутствия символа после компиляции библиотеки, которая ссылается на другую предварительно скомпилированную библиотеку

Вкратце: я пытаюсь скомпилировать файл .cpp, который ссылается на другой скомпилированный API (файлы .so + .h), и при попытке загрузить его я получаю отсутствующий символ. Ниже приведен простой пример того, как я создавал модули, и все шаги, которые я делал на этом пути. Помощь будет очень признательна.

ПРИМЕЧАНИЕ. Прежде чем вы решите пропустить этот вопрос, потому что вы можете ничего не знать о Cython, это скорее вопрос компоновщика/g++, чем вопрос о cython, поэтому, пожалуйста, дочитайте до конца.

Я пытаюсь создать модуль Cython, обертывающий предварительно скомпилированную библиотеку C++ API, и получаю странные ошибки при импорте в python. Поэтому я сделал простой пример, во время которого:

  1. Скомпилируйте простую библиотеку C++ (файлы .so + .h)
  2. Скомпилируйте простой модуль Cython, который ссылается на него.
  3. Загрузите этот модуль в Python, чтобы получить доступ к базовой библиотеке C++.

Сначала у меня есть следующие два файла:

// 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 и свяжем его с этой библиотекой!». Поэтому я сделал следующее:

  1. Побежал g++ -O2 -fPIC -c example.cpp -I /usr/include/python3.9 -I build/Circle.h
  2. Переименован build/Circle.so в libCircle.so
  3. Побежал g++ -shared example.o -Lbuild -lCircle -o example.so
  4. Запустил import example в консоли Python и получил ту же ошибку.

Что я делаю не так? Почему этот символ не загружается?

Используя demangler.com, я могу преобразовать ваш неопределенный символ в Circle::getDiameter() const. Это помогает?

Paul Sanders 19.04.2023 00:21

Когда я пытаюсь выполнить второй набор шагов локально (после очистки всех объектов и общих библиотек), я получаю ImportError: libCircle.so: cannot open shared object file: No such file or directory, а не undefined symbol: _ZNK6Circle11getDiameterEv

yut23 19.04.2023 02:53
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
2
68
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Основная проблема заключается в том, что 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, чтобы это работало без переменной среды. Спасибо!

EZLearner 19.04.2023 10:29

Другие вопросы по теме