Динамическая общая библиотека C ++ в Linux - PullRequest
153 голосов
/ 30 января 2009

Это продолжение динамической компиляции совместно используемой библиотеки с g ++ .

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

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


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

Ответы [ 4 ]

143 голосов
/ 30 января 2009

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. Ваше основное приложение не нужно менять каким-либо образом.

51 голосов
/ 30 января 2009

Ниже приведен пример совместно используемой библиотеки классов. [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)
8 голосов
/ 30 января 2009

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

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

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

1 голос
/ 08 февраля 2019

В дополнение к предыдущим ответам я хотел бы повысить осведомленность о том факте, что вам следует использовать идиому 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.

Две понятные статьи, чтобы получить более подробную информацию:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...