Linux上のC ++動的共有ライブラリ


167

これは、g ++による動的共有ライブラリのコンパイルのフォローアップです

LinuxのC ++で共有クラスライブラリを作成しようとしています。ライブラリをコンパイルして、ここここで見つけたチュートリアルを使用して、いくつかの(非クラス)関数を呼び出すことができます。ライブラリで定義されているクラスを使用しようとすると、問題が発生します。私がリンクした2番目のチュートリアルは、ライブラリーで定義されたクラスのオブジェクトを作成するためのシンボルをロードする方法を示していますが、これらのオブジェクトを使用して作業を完了することはできません。

別の実行可能ファイルでこれらのクラスを使用する方法も示す、共有C ++クラスライブラリを作成するためのより完全なチュートリアルを知っている人はいますか?オブジェクトの作成、使用(単純なゲッターとセッターで結構です)、そして削除が素晴らしい素晴らしいチュートリアルです。共有クラスライブラリの使用を示すいくつかのオープンソースコードへのリンクまたは参照も同様に良いでしょう。


codelogicnimrodmからの回答は機能ますが、この質問をして以来、Beginning Linux Programmingのコピーを選んだことを追加したいと思います。最初の章には、Cライブラリの例と、静的ライブラリと共有ライブラリの両方を作成および使用するための適切な説明があります。 。これらの例は、Googleブック検索を介して、その本の古い版で利用できます。


「使用する」という意味が理解できません。オブジェクトへのポインタが返されたら、他のオブジェクトへのポインタと同じように使用できます。
codelogic 2009年

リンクした記事は、dlsymを使用してオブジェクトファクトリ関数への関数ポインターを作成する方法を示しています。ライブラリからオブジェクトを作成および使用するための構文は示していません。
リザードを請求する

1
クラスを記述するヘッダーファイルが必要になります。ロード時にOSがライブラリを見つけてリンクするだけでなく、「dlsym」を使用する必要があると思うのはなぜですか?簡単な例が必要な場合はお知らせください。
nimrodm 2009年

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を基本クラスとして使用し、必要なすべての関数virtualを定義します。プラグインの作成者は、MyClassから派生し、仮想をオーバーライドして、およびを実装create_objectdestroy_objectます。メインアプリケーションを変更する必要はありません。


6
私はこれを試している最中ですが、1つだけ質問があります。void *を使用することが厳密に必要ですか、またはcreate_object関数が代わりにMyClass *を返すことができますか?これを変更するように求めているのではありません。一方を他方に使用する理由があるかどうかを知りたいだけです。
トカゲに請求する

1
おかげで、私はこれを試しましたが、コマンドラインからLinuxでそのまま機能しました(コードのコメントで提案した変更を行った後)。お時間をいただきありがとうございます。
リザードを請求する

1
これらをextern "C"で宣言する理由はありますか?これは、g ++コンパイラーを使用してコンパイルされるためです。なぜc命名規則を使いたいのですか?Cはc ++を呼び出すことができません。c ++で記述されたラッパーインターフェイスは、cからこれを呼び出す唯一の方法です。
ant2009

6
@ ant2009 はextern "C"dlsym関数がC関数であるため必要です。create_object関数を動的にロードするために、Cスタイルのリンケージを使用します。を使用しない場合、C ++コンパイラでの名前のマングリングのため、.soファイル内の関数extern "C"の名前を知る方法はありませんcreate_object
kokx 2013年

1
素晴らしい方法で、Microsoftコンパイラで誰かが行う方法とよく似ています。#if
#elseを少し加えるだけで、

52

以下は、共有クラスライブラリshared。[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;
}

libshared.soを生成し、メインを共有ライブラリにリンクするmakefile:

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 2009年

9
これはビルド時に動的にリンクされます。言い換えると、リンクするライブラリの事前知識が必要です(たとえば、dlopenの「dl」に対するリンク)。これは、事前に知識を必要としない、たとえばユーザー指定のファイル名に基づいてライブラリを動的にロードすることとは異なります。
codelogic 2009年

10
私が説明しようとしているのは(悪い)この場合、ビルド時にライブラリの名前を知る必要があるということです(-lsharedをgccに渡す必要があります)。通常、その情報が利用できない場合、つまり実行時にライブラリの名前が発見された場合(たとえば、プラグインの列挙)、dlopen()を使用します。
codelogic 2009年

3
-L. -lshared -Wl,-rpath=$$(ORIGIN)リンク時に使用し、それをドロップしLD_LIBRARY_PATH=.ます。
Maxim Egorushkin、2018年

9

基本的に、共有ライブラリでクラスを使用するコードに、クラスのヘッダーファイルを含める必要があります。次に、リンクするときに「-l」フラグ使用して、コードを共有ライブラリにリンクします。もちろん、これはOSがそれを見つけることができる場所に.soが必要です。3.5を参照してください。共有ライブラリのインストールと使用

dlsymの使用は、使用するライブラリがコンパイル時にわからない場合に使用します。それはここの場合のようには聞こえません。多分混乱は、コンパイル時または実行時(類似の方法で)リンクを実行するかどうかに関係なく、Windowsが動的にロードされたライブラリを呼び出すことですか?その場合、dlsymはLoadLibraryと同等であると考えることができます。

ライブラリを動的にロードする必要がある場合(つまり、それらはプラグインです)、このFAQが役立ちます。


1
動的共有ライブラリが必要なのは、Perlコードからも呼び出すためです。私が開発している他のC ++プログラムからも動的に呼び出す必要があるというのは、私自身の完全な誤解かもしれません。
リザードを請求する

統合されたperlとC ++を試したことはありませんが、XSを使用する必要があると思います。johnkeiser.com
Matt Lewis

5

以前の回答に加えて、ハンドラーの破壊を安全にするにRAII(Resource Acquisition Is Initialization)イディオムを使用する必要があるという事実についての認識を高めたいと思います。

以下は完全に機能する例です。

インターフェース宣言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オーバーロードを処理するケースは無視しました。

詳細を得るための2つの明確な記事:


これは優れた例です。RAIIは間違いなく進むべき道です。
David Steinhauer
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.