web-dev-qa-db-ja.com

C ++:dllからクラスを動的にロードする

私の現在のプロジェクトでは、dllからいくつかのクラスをロードできるようにしたいです(これは常に同じであるとは限らず、アプリのコンパイル時にも存在しない可能性があります)。特定のクラスにいくつかの代替dllが存在する場合もあります(たとえば、Direct3D9の実装とOpenGLの実装)が、一度にロード/使用されるのは1つのdllのみです。

インターフェイスを定義する基本クラスのセットに加えて、ロードするクラスのいくつかの基本的なメソッド/メンバー(つまり、参照カウント用のもの)があります。これは、dllプロジェクトがクラスを作成するときに派生します。

//in namespace base
class Sprite : public RefCounted//void AddRef(), void Release() and unsigned refCnt
{
public:
    virtual base::Texture *GetTexture()=0;
    virtual unsigned GetWidth()=0;
    virtual unsigned GetHeight()=0;
    virtual float GetCentreX()=0;
    virtual float GetCentreY()=0;
    virtual void SetCentre(float x, float y)=0;

    virtual void Draw(float x, float y)=0;
    virtual void Draw(float x, float y, float angle)=0;
    virtual void Draw(float x, float y, float scaleX, flota scaleY)=0;
    virtual void Draw(float x, float y, float scaleX, flota scaleY, float angle)=0;
};

問題は、実行可能ファイルや他のdllがこれらのクラスをロードして使用できるようにする方法がわからないことです。これは、dllが1つしかないdllしか使用しておらず、VisualStudioリンカーですべてを分類できるためです。 dllをコンパイルするときに取得する.libファイルを使用します。

クラスをインスタンス化するためにファクトリメソッドを使用してもかまいません。それらの多くはすでに設計によって作成されています(つまり、SpriteクラスはメインのGraphicsクラスによって作成されます(例:Graphics-> CreateSpriteFromTexture(base :: Texture *))。

編集:pythonで使用するためにいくつかのc ++ dllを作成する必要があるとき、pyCxxというライブラリを使用しました。結果のdllは基本的に、「Module」クラスのインスタンスを作成する1つのメソッドのみをエクスポートしました。次に、他のクラスなどを作成するためのファクトリメソッドを含めることができます。

結果のdllは、「import [dllname]」を使用してpythonにインポートできます。

//dll compiled as cpputill.pyd
extern "C" void initcpputill()//only exported method
{
    static CppUtill* cpputill = new CppUtill;
}

class CppUtill : public Py::ExtensionModule<CppUtill>
{
public:
    CppUtill()
    : Py::ExtensionModule<CppUtill>("cpputill")
    {
        ExampleClass::init_type();

        add_varargs_method("ExampleClass",&CppUtill::ExampleClassFactory, "ExampleClass(), create instance of ExampleClass");
        add_varargs_method("HelloWorld",  &CppUtill::HelloWorld,  "HelloWorld(), print Hello World to console");

        initialize("C Plus Plus module");
    }
...
class ExampleClass
...
    static void init_type()
    {
        behaviors().name("ExampleClass");
        behaviors().doc ("example class");
        behaviors().supportGetattr();
        add_varargs_method("Random", &ExampleClass::Random, "Random(), get float in range 0<=x<1");
    }

それはどの程度正確に機能しますか?純粋にc ++環境で使用して、ここでの問題を解決できますか?

24
Fire Lancer

これを行う最も簡単な方法であるIMHOは、他の場所で説明されているインターフェイスへのポインタを返す単純なC関数を使用することです。そうすれば、アプリは、使用しているクラスを実際に知らなくても、そのインターフェースのすべての関数を呼び出すことができます。

編集:これは簡単な例です。

メインのアプリコードで、インターフェースのヘッダーを作成します。

_class IModule
{
public:
    virtual ~IModule(); // <= important!
    virtual void doStuff() = 0;
};
_

メインアプリは、インターフェースの実際の実装に関する詳細なしで、上記のインターフェースを使用するようにコーディングされています。

_class ActualModule: public IModule
{
/* implementation */
};
_

これで、モジュール(DLLにはそのインターフェイスの実際の実装があり、それらのクラスをエクスポートする必要さえありません)__declspec (dllexport)は必要ありません。モジュールの唯一の要件は、インターフェイスの実装を作成して返す単一の関数をエクスポートすることです。

___declspec (dllexport) IModule* CreateModule()
{
    // call the constructor of the actual implementation
    IModule * module = new ActualModule();
    // return the created function
    return module;
}
_

注:エラーチェックは省略されています-newが正しいポインタを返したかどうかを通常はチェックする必要があり、ActualModuleクラスのコンストラクタでスローされる可能性のある例外から身を守る必要があります。

次に、メインアプリで必要なのは、モジュール(LoadLibrary関数)をロードし、関数CreateModuleGetProcAddr関数)を見つけることだけです。次に、インターフェースを介してクラスを使用します。

編集2:RefCount(インターフェースの基本クラス)は、メインアプリに実装(およびエクスポート)できます。次に、すべてのモジュールをメインアプリのlibファイルにリンクする必要があります(はい!EXEファイルはDLLファイル!)のようにLIBファイルを持つことができます)そしてそれで十分です。

21
Paulius

あなたはCOMを再発明しています。 RefCountedクラスはIUnknownです。抽象クラスはインターフェースです。 DLLのCOMサーバーには、DllGetClassObject()という名前のエントリポイントがあります。これはクラスファクトリです。MicrosoftからCOMに関するドキュメントがたくさんありますので、少し調べてみてください。 。

16
Hans Passant

[編集:私がこれらすべてを作曲している間、PauliusMaruškaは彼のコメントを編集して基本的に同じことを言った。重複して申し訳ありません。私はあなたが今予備のために1つを持っていると思いますが:)]

頭のてっぺんから、Visual C++を想定しています...

LoadLibraryを使用してDLLを動的にロードし、GetProcAddressを使用して、DLLコードが実装する実際の派生クラスを作成する関数のアドレスを取得する必要があります。これを正確に行う方法はあなた次第です(DLLが見つける必要がある、その機能を公開する方法を指定する必要があるなど)ので、今のところ、プラグインは新しいSprite実装のみを提供すると仮定しましょう。

これを行うには、メインプログラムがこれらの新しいスプライトの1つを作成するために呼び出すDLL内の関数のシグネチャを決定します。これは適切に見えます:

typedef Sprite *(*CreateSpriteFn)();

次に、メインプログラムから、DLLをロードして(このDLLを見つける方法はあなた次第です)、そこからスプライト作成関数を取得する必要があります。スプライト作成関数を「CreateSprite」と呼ぶことにしました。

HMODULE hDLL=LoadLibrary(pDLLName);
CreateSpriteFn pfnCreateSprite=CreateSpriteFn(GetProcAddress(hDLL,"CreateSprite"));

次に、これらの1つを実際に作成するには、関数を呼び出すだけです。

Sprite *pSprite=(*pfnCreateSprite)();

DLLを使い終わって、それによって作成されたオブジェクトが残っていない場合は、FreeLibraryを使用してそれを取り除きます。

FreeLibrary(hDLL);

このインターフェイスを備えたDLLを作成するには、派生クラスのコードなどを記述し、DLLコードのどこかに、適切な署名を使用して、呼び出し側プログラムが必要とするCreateSprite関数を提供します。

__declspec(dllexport)
extern "C"
Sprite *CreateSprite()
{
    return new MyNewSprite;
}

Dllexportは、GetProcAddressを使用してこの関数を名前で取得できることを意味し、extern "C"は、名前がマングルされておらず、 "CreateSprite @ 4"などになっていないことを保証します。

他の2つのメモ:

  1. GetProcAddressは、関数が見つからなかった場合は0を返すため、これを使用して、インターフェイスをサポートするDLLを探しているDLLのリスト(FindFirstFileや友人から返されたものなど)をスキャンしたり、複数のエントリポイントを見つけたりすることができます。プラグインごとに複数のタイプをサポートします。
  2. メインプログラムが削除演算子を使用してスプライトを削除する場合、DLLとメインプログラムの両方が同じランタイムライブラリを使用してビルドされる必要があるため、メインプログラムの削除とDLLの新しいものは両方とも同じを使用しますヒープ。 DLLにDeleteSprite関数を提供させることで、これをある程度回避できます。この関数は、メインプログラムではなくDLLのランタイムライブラリを使用してスプライトを削除しますが、それでもプログラムの残りの部分に注意する必要があります。したがって、DLLライターにプログラムと同じランタイムライブラリを使用させることができます。 (これを行うことであなたが良い仲間になるとは言えませんが、それは珍しいことではありません。たとえば、3D Studio MAXではこれが必要です。)
4

おそらくあなたはDLL Delay-Loading( http://www.codeproject.com/KB/DLL/Delay_Loading_Dll.aspx )-これはあなたに与えるでしょう一生懸命働かなくても欲しいもの

2
Ana Betts