Динамічна спільна бібліотека C ++ у Linux


167

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

Я намагаюся створити спільну бібліотеку класів на C ++ в Linux. Я можу змусити бібліотеку складати, і я можу викликати деякі (некласові) функції, використовуючи підручники, які я знайшов тут і тут . Мої проблеми починаються, коли я намагаюся використовувати класи, визначені в бібліотеці. Другий підручник, з яким я пов’язаний, показує, як завантажувати символи для створення об'єктів класів, визначених у бібліотеці, але перестає використовувати ці об'єкти для виконання будь-якої роботи.

Хтось знає про більш повний підручник для створення спільних бібліотек класів C ++, який також показує, як використовувати ці класи в окремому виконуваному файлі? Дуже простий підручник, який показує створення об’єктів, використання (прості геттери та сетери будуть непогані), а видалення було б фантастичним. Посилання або посилання на якийсь код з відкритим вихідним кодом, який ілюструє використання бібліотеки загального класу, було б однаково добре.


Хоча відповіді коделогічного та nimrodm справді працюють, я просто хотів додати, що я взяв копію початкового програмування Linux після того, як поставив це питання, і його перша глава містить приклад C коду та хороші пояснення для створення та використання як статичних, так і загальних бібліотек. . Ці приклади доступні через пошук книг Google у старішому виданні цієї книги .


Я не впевнений, що я розумію, що ви маєте на увазі під його "використанням", як тільки повертається вказівник на об'єкт, ви можете використовувати його, як і будь-який інший вказівник на об'єкт.
codelogic

У статті, до якої я посилався, показано, як створити покажчик функції на фабричну функцію за допомогою об'єкта dlsym. Він не показує синтаксис для створення та використання об'єктів з бібліотеки.
Білл Ящірка

1
Вам знадобиться файл заголовка, що описує клас. Чому ви вважаєте, що вам доведеться використовувати "dlsym", а не просто дозволяти ОС знаходити та зв’язувати бібліотеку під час завантаження? Повідомте мене, якщо вам потрібен простий приклад.
nimrodm

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


6
Я зараз пробую це, але у мене просто одне питання. Чи категорично потрібно використовувати void *, чи може функція create_object повернути MyClass * замість цього? Я не прошу вас змінити це для мене, я просто хотів би знати, чи є причина використовувати одне за іншим.
Білл Ящірка

1
Дякую, я спробував це, і він працював так, як це є в Linux із командного рядка (одного разу я вніс зміни, які ви запропонували в коментарях до коду). Я ціную ваш час.
Білл Ящірка

1
Чи є якась причина, щоб ви заявили про це із зовнішнім "C"? Оскільки це складено за допомогою компілятора g ++. Чому ви хочете використовувати конвенцію c іменування? C не може викликати c ++. Інтерфейс обгортки, написаний на c ++, - це єдиний спосіб зателефонувати до цього із c.
ant2009

6
@ ant2009 вам потрібна функція, extern "C"оскільки це dlsymфункція C. А для динамічного завантаження create_objectфункції вона використовуватиме зв'язок у стилі С. Якщо ви не використовуєте extern "C", не було б способу дізнатися ім'я create_objectфункції у файлі .so через керування іменами у компіляторі C ++.
kokx

1
Хороший метод, він дуже схожий на те, що хтось робив би на компіляторі Microsoft. трішки роботи #if #else, ви можете отримати хорошу незалежну платформу
Ha11owed

52

Далі показаний приклад спільної бібліотеки класів, що ділиться. [H, cpp] та модуля main.cpp, що використовує бібліотеку. Це дуже простий приклад, і makefile можна зробити набагато краще. Але це працює і може допомогти вам:

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;
}

і makefile, який генерує libshared.so і пов'язує головне зі спільною бібліотекою:

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 або подібне).

Далі вказується поточний каталог як шлях пошуку бібліотек та працює головний (синтаксис 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)

1
Це здається (на мій дуже нетренований погляд) статичним зв'язком libshared.so з вашим виконуваним файлом, а не з використанням динамічного посилання під час виконання. Я прав?
Білл Ящірка

10
Ні. Це стандартне динамічне з'єднання Unix (Linux). Динамічна бібліотека має розширення ".so" (Спільний об'єкт) і пов'язана з виконуваним файлом (головним у цьому випадку) під час завантаження - кожного разу, коли завантажується основний. Статичне посилання відбувається в час посилання і використовує бібліотеки з розширенням ".a" (архів).
nimrodm

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 (придбання ресурсів - ініціалізація), щоб бути безпечною щодо знищення обробника.

Ось повний робочий приклад:

Декларація інтерфейсу 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, безумовно, шлях.
Девід Штайнгауер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.