184

これは、 g++ を使用した動的共有ライブラリのコンパイルのフォローアップです。

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

別の実行可能ファイルでそれらのクラスを使用する方法も示している、共有 C++ クラス ライブラリを作成するためのより完全なチュートリアルを知っている人はいますか? オブジェクトの作成、使用 (単純な getter と setter で十分です)、および削除を示す非常に単純なチュートリアルは素晴らしいものです。共有クラス ライブラリの使用法を説明するオープン ソース コードへのリンクまたは参照も同様に有効です。


codelogicnimrodmからの回答は機能しますが、この質問をした後、 Beginning Linux Programmingのコピーを手に取ったことを付け加えたいと思います。その最初の章には、C コードの例と、静的ライブラリと共有ライブラリの両方を作成および使用するための適切な説明があります。 . これらの例は、その書籍の旧版でGoogle ブック検索から入手できます。

4

4 に答える 4

176

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 から派生させ、バーチャルをオーバーライドして and を実装create_objectdestroy_objectます。メイン アプリケーションを変更する必要はまったくありません。

于 2009-01-30T20:37:27.293 に答える
56

以下は、共有クラス ライブラリ 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 を生成し、main を共有ライブラリにリンクする 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 などに配置する) 必要があります。

以下は、現在のディレクトリをライブラリの検索パスとして指定し、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)
于 2009-01-30T20:16:05.727 に答える
9

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

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

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

于 2009-01-30T20:20:45.313 に答える
9

以前の回答に加えて、ハンドラーの破棄について安全を確保するために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オーバーロードを処理するケースは無視しました。

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

于 2019-02-07T21:33:33.083 に答える