C ++ Dynamic Shared Library di Linux

167

Ini adalah tindak lanjut kompilasi Dinamis Bersama Perpustakaan dengan g ++ .

Saya mencoba membuat perpustakaan kelas bersama di C ++ di Linux. Saya dapat mengkompilasi pustaka, dan saya dapat memanggil beberapa fungsi (non-kelas) menggunakan tutorial yang saya temukan di sini dan di sini . Masalah saya mulai ketika saya mencoba menggunakan kelas yang didefinisikan di perpustakaan. Tutorial kedua yang saya tautkan menunjukkan bagaimana memuat simbol untuk membuat objek dari kelas-kelas yang didefinisikan di perpustakaan, tetapi berhenti menggunakan objek-objek itu untuk menyelesaikan pekerjaan.

Adakah yang tahu tutorial yang lebih lengkap untuk membuat pustaka kelas C ++ yang dibagikan yang juga menunjukkan cara menggunakan kelas-kelas itu dalam executable terpisah? Tutorial yang sangat sederhana yang menunjukkan pembuatan objek, penggunaan (getter dan setter sederhana akan baik-baik saja), dan penghapusan akan menjadi fantastis. Tautan atau referensi ke beberapa kode sumber terbuka yang menggambarkan penggunaan perpustakaan kelas bersama akan sama baiknya.


Meskipun jawaban dari codelogic dan nimrodm melakukan pekerjaan, saya hanya ingin menambahkan bahwa saya mengambil salinan Awal Pemrograman Linux sejak menanyakan pertanyaan ini, dan bab pertama memiliki contoh kode C dan penjelasan yang baik untuk menciptakan dan menggunakan kedua statis dan shared library . Contoh-contoh ini tersedia melalui Pencarian Buku Google dalam edisi yang lebih tua dari buku itu .

Bill the Lizard
sumber
Saya tidak yakin saya mengerti apa yang Anda maksud dengan "menggunakan" itu, setelah pointer ke objek dikembalikan, Anda bisa menggunakannya seperti Anda menggunakan pointer lain ke suatu objek.
codelogic
Artikel yang saya tautkan menunjukkan cara membuat pointer fungsi ke fungsi pabrik objek menggunakan dlsym. Itu tidak menunjukkan sintaks untuk membuat dan menggunakan objek dari perpustakaan.
Bill the Lizard
1
Anda akan membutuhkan file header yang menjelaskan kelas. Mengapa Anda pikir Anda harus menggunakan "dlsym" daripada hanya membiarkan OS menemukan dan menautkan perpustakaan pada waktu buka? Beri tahu saya jika Anda membutuhkan contoh sederhana.
nimrodm
3
@nimrodm: Apa alternatif menggunakan "dlsym"? Saya (seharusnya) menulis 3 program C ++ yang semuanya akan menggunakan kelas-kelas yang didefinisikan di perpustakaan bersama. Saya juga punya 1 skrip Perl yang akan menggunakannya, tapi itu masalah lain untuk minggu depan.
Bill the Lizard

Jawaban:

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

Pada Mac OS X, kompilasi dengan:

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

Di Linux, kompilasi dengan:

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

Jika ini untuk sistem plugin, Anda akan menggunakan MyClass sebagai kelas dasar dan mendefinisikan semua fungsi yang diperlukan secara virtual. Pengaya plugin kemudian akan berasal dari MyClass, menimpa virtual dan mengimplementasikan create_objectdan destroy_object. Aplikasi utama Anda tidak perlu diubah dengan cara apa pun.

codelogic
sumber
6
Saya sedang dalam proses mencoba ini, tetapi hanya punya satu pertanyaan. Apakah benar-benar diperlukan untuk menggunakan void *, atau bisakah fungsi create_object mengembalikan MyClass *? Saya tidak meminta Anda untuk mengubah ini untuk saya, saya hanya ingin tahu apakah ada alasan untuk menggunakannya.
Bill the Lizard
1
Terima kasih, saya mencoba ini dan berfungsi seperti di Linux dari baris perintah (setelah saya membuat perubahan yang Anda sarankan dalam komentar kode). Saya menghargai waktu Anda.
Bill the Lizard
1
Apakah ada alasan Anda menyatakan ini dengan "C" dari luar? Karena ini dikompilasi menggunakan kompiler g ++. Mengapa Anda ingin menggunakan konvensi penamaan c? C tidak dapat memanggil c ++. Antarmuka wrapper yang ditulis dalam c ++ adalah satu-satunya cara untuk memanggil ini dari c.
ant2009
6
@ ant2009 Anda memerlukan extern "C"karena dlsymfungsinya adalah fungsi C. Dan untuk memuat create_objectfungsi secara dinamis , ia akan menggunakan tautan gaya-C. Jika Anda tidak akan menggunakan extern "C", tidak akan ada cara untuk mengetahui nama create_objectfungsi dalam file .so, karena nama-mangling dalam kompiler C ++.
kokx
1
Metode yang bagus, sangat mirip dengan apa yang akan dilakukan seseorang pada kompiler Microsoft. dengan sedikit #jika pekerjaan lain, Anda bisa mendapatkan sistem platform independen yang bagus
Ha11owed
52

Berikut ini menunjukkan contoh perpustakaan kelas bersama yang dibagikan. [H, cpp] dan modul main.cpp menggunakan perpustakaan. Ini adalah contoh yang sangat sederhana dan makefile bisa dibuat lebih baik. Tetapi ini berfungsi dan dapat membantu Anda:

shared.h mendefinisikan kelas:

class myclass {
   int myx;

  public:

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

shared.cpp mendefinisikan fungsi getx / setx:

#include "shared.h"

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

main.cpp menggunakan kelas,

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

dan makefile yang menghasilkan libshare.so dan tautan utama dengan pustaka bersama:

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

Untuk menjalankan 'main' yang sebenarnya dan menautkannya dengan libshare.so Anda mungkin perlu menentukan path load (atau memasukkannya ke / usr / local / lib atau yang serupa).

Berikut ini menentukan direktori saat ini sebagai jalur pencarian untuk perpustakaan dan menjalankan main (sintaks bash):

export LD_LIBRARY_PATH=.
./main

Untuk melihat bahwa program ini terhubung dengan libshare.so Anda dapat mencoba ldd:

LD_LIBRARY_PATH=. ldd main

Cetakan di mesin saya:

  ~/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
sumber
1
Tampaknya (di mata saya yang sangat tidak terlatih) untuk secara statis menghubungkan libshare.so ke executable Anda, daripada menggunakan tautan dinamis pada saat run-time. Apakah saya benar?
Bill the Lizard
10
Tidak. Ini adalah penghubung dinamis standar Unix (Linux). Pustaka dinamis memiliki ekstensi ".so" (Obyek Bersama) dan dihubungkan dengan yang dapat dieksekusi (utama dalam kasus ini) pada waktu pengambilan - setiap kali main dimuat. Menautkan statis terjadi pada waktu tautan dan menggunakan pustaka dengan ekstensi ".a" (arsip).
nimrodm
9
Ini terkait secara dinamis pada waktu pembuatan . Dengan kata lain Anda membutuhkan pengetahuan sebelumnya tentang perpustakaan yang Anda tautkan (mis. Menautkan 'dl' untuk dlopen). Ini berbeda dari memuat perpustakaan secara dinamis , berdasarkan katakanlah, nama file yang ditentukan pengguna, di mana pengetahuan sebelumnya tidak diperlukan.
codelogic
10
Apa yang saya coba jelaskan (buruk) adalah bahwa dalam hal ini, Anda perlu tahu nama perpustakaan saat membangun (Anda harus meneruskan -lshare ke gcc). Biasanya, seseorang menggunakan dlopen () ketika informasi itu tidak tersedia, yaitu nama perpustakaan ditemukan saat runtime (misalnya: enumerasi plugin).
codelogic
3
Gunakan -L. -lshared -Wl,-rpath=$$(ORIGIN)saat menghubungkan dan menjatuhkannya LD_LIBRARY_PATH=..
Maxim Egorushkin
9

Pada dasarnya, Anda harus memasukkan file header kelas dalam kode di mana Anda ingin menggunakan kelas di perpustakaan bersama. Kemudian, ketika Anda menautkan, gunakan bendera '-l' untuk menautkan kode Anda dengan perpustakaan bersama. Tentu saja, ini membutuhkan .so menjadi tempat OS dapat menemukannya. Lihat 3.5. Menginstal dan Menggunakan Perpustakaan Bersama

Menggunakan dlsym adalah untuk saat Anda tidak tahu pada waktu kompilasi perpustakaan mana yang ingin Anda gunakan. Kedengarannya bukan itu masalahnya di sini. Mungkin kebingungannya adalah bahwa Windows memanggil pustaka yang dimuat secara dinamis apakah Anda melakukan penautan saat kompilasi atau saat berjalan (dengan metode analog)? Jika demikian, maka Anda dapat menganggap dlsym sebagai padanan dari LoadLibrary.

Jika Anda benar-benar perlu memuat pustaka secara dinamis (yaitu pustaka), maka FAQ ini akan membantu.

Matt Lewis
sumber
1
Alasan saya membutuhkan pustaka bersama dinamis adalah bahwa saya juga akan memanggilnya dari kode Perl. Mungkin kesalahpahaman yang lengkap pada bagian saya sendiri bahwa saya juga perlu menyebutnya secara dinamis dari program C ++ lain yang saya kembangkan.
Bill the Lizard
Saya belum pernah mencoba perl terintegrasi dan C ++, tapi saya pikir Anda perlu menggunakan XS: johnkeiser.com/perl-xs-c++.html
Matt Lewis
5

Di atas jawaban sebelumnya, saya ingin meningkatkan kesadaran tentang fakta bahwa Anda harus menggunakan idiom RAII (Resource Acquisition Is Inisialisasi) agar aman tentang penghancuran handler.

Ini adalah contoh kerja yang lengkap:

Deklarasi antarmuka Interface.hpp::

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

using Base_creator_t = Base *(*)();

Konten perpustakaan bersama:

#include "Interface.hpp"

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

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

Penangan perpustakaan bersama dinamis 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);
        }
    }
};

Kode klien:

#include "Derived_factory.hpp"

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

catatan:

  • Saya meletakkan semuanya di file header untuk keringkasan. Dalam kehidupan nyata Anda tentu saja harus membagi kode .hppdan .cppfile Anda.
  • Untuk mempermudah, saya mengabaikan case di mana Anda ingin menangani new/ deleteoverload.

Dua artikel yang jelas untuk mendapatkan detail lebih lanjut:

Xavier Lamorlette
sumber
Ini adalah contoh yang bagus. RAII jelas merupakan jalan yang harus ditempuh.
David Steinhauer