Динамическая общая библиотека C++ в Linux

Это продолжение компиляции Dynamic Shared Library с g ++.

Я пытаюсь создать разделяемую библиотеку классов в C++ в Linux. Я могу получить библиотеку для компиляции, и я могу вызывать некоторые (не классовые) функции, используя учебники, которые я нашел здесь и здесь. Мои проблемы начинаются, когда я пытаюсь использовать классы, которые определены в библиотеке. Второй учебник, на который я ссылаюсь, показывает, как загрузить символы для создания объектов классов, определенных в библиотеке, но не использует эти объекты для выполнения какой-либо работы.

Кто-нибудь знает более полный учебник по созданию общих библиотек классов C++, который также показывает, как использовать эти классы в отдельном исполняемом файле? Очень простое руководство, которое показывает создание объекта, его использование (было бы неплохо использовать простые методы получения и установки) и удаление. Ссылка или ссылка на некоторый открытый исходный код, который иллюстрирует использование разделяемой библиотеки классов, была бы одинаково хороша.


Хотя ответы от codelogic и nimrodm работают, я просто хотел добавить, что я поднял копию Beginning Linux Programming после того, как задал этот вопрос, и в его первой главе приведен пример кода C и хорошие объяснения для создания и использования статических и разделяемых библиотек., Эти примеры доступны в Поиске книг Google в более старом издании этой книги.

5 ответов

Решение

myclass.h

#ifndef __MYCLASS_H__
#define __MYCLASS_H__

class MyClass
{
public:
  MyClass();

  /* use virtual otherwise linker will try to perform static linkage */
  virtual void DoSomething();

private:
  int x;
};

#endif

myclass.cc

#include "myclass.h"
#include <iostream>

using namespace std;

extern "C" MyClass* create_object()
{
  return new MyClass;
}

extern "C" void destroy_object( MyClass* object )
{
  delete object;
}

MyClass::MyClass()
{
  x = 20;
}

void MyClass::DoSomething()
{
  cout<<x<<endl;
}

class_user.cc

#include <dlfcn.h>
#include <iostream>
#include "myclass.h"

using namespace std;

int main(int argc, char **argv)
{
  /* on Linux, use "./myclass.so" */
  void* handle = dlopen("myclass.so", RTLD_LAZY);

  MyClass* (*create)();
  void (*destroy)(MyClass*);

  create = (MyClass* (*)())dlsym(handle, "create_object");
  destroy = (void (*)(MyClass*))dlsym(handle, "destroy_object");

  MyClass* myClass = (MyClass*)create();
  myClass->DoSomething();
  destroy( myClass );
}

В Mac OS X скомпилируйте с:

g++ -dynamiclib -flat_namespace myclass.cc -o myclass.so
g++ class_user.cc -o class_user

В Linux скомпилируйте с:

g++ -fPIC -shared myclass.cc -o myclass.so
g++ class_user.cc -ldl -o class_user

Если бы это было для системы плагинов, вы бы использовали MyClass в качестве базового класса и определяли бы все необходимые виртуальные функции. Затем автор плагина наследует MyClass, переопределяет виртуальные и реализует create_object а также destroy_object, Ваше основное приложение не должно быть изменено каким-либо образом.

Ниже приведен пример разделяемой библиотеки классов shared.[H,cpp] и модуля main.cpp с использованием библиотеки. Это очень простой пример, и make-файл может быть сделан намного лучше. Но это работает и может помочь вам:

shared.h определяет класс:

class myclass {
   int myx;

  public:

    myclass() { myx=0; }
    void setx(int newx);
    int  getx();
};

shared.cpp определяет функции getx/setx:

#include "shared.h"

void myclass::setx(int newx) { myx = newx; }
int  myclass::getx() { return myx; }

main.cpp использует класс,

#include <iostream>
#include "shared.h"

using namespace std;

int main(int argc, char *argv[])
{
  myclass m;

  cout << m.getx() << endl;
  m.setx(10);
  cout << m.getx() << endl;
}

и make-файл, который генерирует libshared.so и связывает main с разделяемой библиотекой:

main: libshared.so main.o
    $(CXX) -o main  main.o -L. -lshared

libshared.so: shared.cpp
    $(CXX) -fPIC -c shared.cpp -o shared.o
    $(CXX) -shared  -Wl,-soname,libshared.so -o libshared.so shared.o

clean:
    $rm *.o *.so

Для фактического запуска 'main' и связи с libshared.so вам, вероятно, потребуется указать путь загрузки (или поместить его в /usr/local/lib или аналогичный).

Далее указывается текущий каталог в качестве пути поиска для библиотек и запускается main (синтаксис bash):

export LD_LIBRARY_PATH=.
./main

Чтобы увидеть, что программа связана с libshared.so, вы можете попробовать ldd:

LD_LIBRARY_PATH=. ldd main

Печать на моей машине:

  ~/prj/test/shared$ LD_LIBRARY_PATH=. ldd main
    linux-gate.so.1 =>  (0xb7f88000)
    libshared.so => ./libshared.so (0xb7f85000)
    libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0xb7e74000)
    libm.so.6 => /lib/libm.so.6 (0xb7e4e000)
    libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0xb7e41000)
    libc.so.6 => /lib/libc.so.6 (0xb7cfa000)
    /lib/ld-linux.so.2 (0xb7f89000)

Вдобавок к предыдущим ответам я хотел бы повысить осведомленность о том, что вы должны использовать идиому RAII (Resource Acquisition Is Initialisation), чтобы быть уверенным в уничтожении обработчика.

Вот полный рабочий пример:

Объявление интерфейса: Interface.hpp:

class Base {
public:
    virtual ~Base() {}
    virtual void foo() const = 0;
};

using Base_creator_t = Base *(*)();

Содержимое общей библиотеки:

#include "Interface.hpp"

class Derived: public Base {
public:
    void foo() const override {}
};

extern "C" {
Base * create() {
    return new Derived;
}
}

Динамический обработчик разделяемой библиотеки: Derived_factory.hpp:

#include "Interface.hpp"
#include <dlfcn.h>

class Derived_factory {
public:
    Derived_factory() {
        handler = dlopen("libderived.so", RTLD_NOW);
        if (! handler) {
            throw std::runtime_error(dlerror());
        }
        Reset_dlerror();
        creator = reinterpret_cast<Base_creator_t>(dlsym(handler, "create"));
        Check_dlerror();
    }

    std::unique_ptr<Base> create() const {
        return std::unique_ptr<Base>(creator());
    }

    ~Derived_factory() {
        if (handler) {
            dlclose(handler);
        }
    }

private:
    void * handler = nullptr;
    Base_creator_t creator = nullptr;

    static void Reset_dlerror() {
        dlerror();
    }

    static void Check_dlerror() {
        const char * dlsym_error = dlerror();
        if (dlsym_error) {
            throw std::runtime_error(dlsym_error);
        }
    }
};

Код клиента:

#include "Derived_factory.hpp"

{
    Derived_factory factory;
    std::unique_ptr<Base> base = factory.create();
    base->foo();
}

Замечания:

  • Я помещаю все в заголовочные файлы для краткости. В реальной жизни вы должны, конечно, разделить ваш код между .hpp а также .cpp файлы.
  • Чтобы упростить, я проигнорировал случай, когда вы хотите обработать new/delete перегрузки.

Две понятные статьи, чтобы получить больше деталей:

По сути, вы должны включить заголовочный файл класса в код, где вы хотите использовать класс в общей библиотеке. Затем, когда вы ссылаетесь, используйте флаг '-l', чтобы связать ваш код с разделяемой библиотекой. Конечно, для этого требуется, чтобы.so находился там, где его может найти ОС. Смотрите 3.5. Установка и использование общей библиотеки

Использование dlsym предназначено для случаев, когда вы не знаете во время компиляции, какую библиотеку вы хотите использовать. Это не похоже на то, что дело здесь. Может быть, путаница заключается в том, что Windows вызывает динамически загружаемые библиотеки, независимо от того, выполняете ли вы компоновку во время компиляции или во время выполнения (аналогичными методами)? Если так, то вы можете думать о dlsym как о эквиваленте LoadLibrary.

Если вам действительно нужно динамически загружать библиотеки (то есть, они являются подключаемыми модулями), тогда этот FAQ должен помочь.

В ответ на ответ @Xavier LamorLette я создал репозиторий git с рабочим Makefile.

https://github.com/greergan/cpp-dynamic-so-loader Это точный метод кодирования проблемы, которую я искал.

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