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

167

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

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

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


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

Билл Ящерица
источник
Я не уверен, что понимаю, что вы подразумеваете под «использованием» этого, как только указатель на объект возвращается, вы можете использовать его так же, как и любой другой указатель на объект.
codelogic
В статье, на которую я ссылался, показано, как создать указатель на функцию фабрики объектов с помощью dlsym. Он не показывает синтаксис для создания и использования объектов из библиотеки.
Билл Ящерица
1
Вам понадобится заголовочный файл с описанием класса. Как вы думаете, почему вы должны использовать «dlsym» вместо того, чтобы просто позволить ОС найти и связать библиотеку во время загрузки? Дайте мне знать, если вам нужен простой пример.
Нимродм
3
@nimrodm: Какая альтернатива использованию "dlsym"? Я (должен быть) пишу 3 программы на C ++, которые будут использовать классы, определенные в разделяемой библиотеке. У меня также есть 1 скрипт на Perl, который будет его использовать, но это совсем другая проблема на следующей неделе.
Билл Ящерица

Ответы:

154

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

codelogic
источник
6
Я пытаюсь это сделать, но у меня только один вопрос. Строго необходимо использовать void *, или вместо этого функция create_object может вернуть MyClass *? Я не прошу вас изменить это для меня, я просто хотел бы знать, есть ли причина использовать один над другим.
Билл Ящерица
1
Спасибо, я попробовал это, и это работало как на Linux из командной строки (как только я сделал изменения, которые вы предложили в комментариях к коду). Я ценю ваше время.
Билл Ящерица
1
Есть ли какая-то причина, по которой вы бы объявили это с помощью внешней буквы "C"? Поскольку это скомпилировано с использованием компилятора g ++. Почему вы хотите использовать соглашение об именовании? C не может вызвать c ++. Интерфейс-обертка, написанный на c ++, является единственным способом вызвать это из c.
ant2009
6
@ ant2009 вам нужно, extern "C"потому что dlsymфункция является функцией C. И для динамической загрузки create_objectфункции она будет использовать связь в стиле C. Если бы вы не использовали extern "C", не было бы способа узнать имя create_objectфункции в файле .so из-за искажения имени в компиляторе C ++.
kokx
1
Хороший метод, он очень похож на то, что кто-то делает на компиляторе Microsoft. немного #if # работы, вы можете получить хорошую платформо-независимую систему
Ha11owed
52

Ниже приведен пример совместно используемой библиотеки классов. [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)
nimrodm
источник
1
По-моему (на мой взгляд неопытный человек) статически связывает libshared.so с вашим исполняемым файлом, а не использует динамическое связывание во время выполнения. Я прав?
Билл Ящерица
10
Нет. Это стандартное динамическое связывание Unix (Linux). Динамическая библиотека имеет расширение «.so» (Shared Object) и связана с исполняемым файлом (в данном случае main) во время загрузки - каждый раз, когда загружается main. Статическое связывание происходит во время соединения и использует библиотеки с расширением «.a» (архив).
Нимродм
9
Это динамически связано во время сборки . Другими словами, вам необходимо предварительное знание библиотеки, с которой вы ссылаетесь (например, ссылка на dl для dlopen). Это отличается от динамической загрузки библиотеки, например, на основе указанного пользователем имени файла, где предварительные знания не требуются.
codelogic
10
Что я пытался объяснить (плохо), так это то, что в этом случае вам нужно знать имя библиотеки во время сборки (вам нужно передать -lshared в gcc). Обычно используется dlopen (), когда эта информация недоступна, то есть имя библиотеки обнаруживается во время выполнения (например, перечисление плагинов).
codelogic
3
Используйте -L. -lshared -Wl,-rpath=$$(ORIGIN)при связывании и отбросьте это LD_LIBRARY_PATH=..
Максим Егорушкин
9

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

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

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

Мэтт Льюис
источник
1
Причина, по которой мне нужна динамическая разделяемая библиотека, заключается в том, что я также буду вызывать ее из кода Perl. С моей стороны, это может быть полным заблуждением, что мне также нужно динамически вызывать его из других программ на C ++, которые я разрабатываю.
Билл Ящерица
Я никогда не пробовал интегрировать Perl и C ++, но я думаю, что вам нужно использовать XS: johnkeiser.com/perl-xs-c++.html
Мэтт Льюис,
5

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

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

Ксавье Ламорлетт
источник
Это отличный пример. RAII, безусловно, путь.
Дэвид Штайнхауэр