2016年2月26日金曜日

C++の共有ライブラリをdlsymで呼び出す


C++で共有ライブラリを作って、dlsym()でクラスメソッドを動的に呼び出す場合、どうすればよいのか調べてみました。

ネットでよく出回っている方法は、MicrosoftがComponentObjectModelで昔よく使っていたクラスメソッドにすべてvirtualをつけて宣言し、クラスを作ったり破棄したりするC言語形式のファクトリー関数を用意する方法です。


------------------------------------
#ifdef _WIN32
#ifdef CPPDLL_EXPORTS
#define CPPDLL_API __declspec(dllexport)
#else
#define CPPDLL_API __declspec(dllimport)
#endif
#else
#define CPPDLL_API
#endif

class Ccppdll {
public:
Ccppdll();
virtual ~Ccppdll();

virtual void test1(void);
};

#ifdef __cplusplus
extern "C"{
#endif
//factory


CPPDLL_API Ccppdll* create_cppdll();
CPPDLL_API void destroy_cppdll(Ccppdll*);

#ifdef __cplusplus
}
#endif



Ccppdll::Ccppdll()
{
printf("Ccppdll::Ccppdll()\n");
}

Ccppdll::~Ccppdll()
{
printf("Ccppdll::~Ccppdll()\n");
}

void Ccppdll::test1()
{
printf("Ccppdll::test1\n");
}

Ccppdll* create_cppdll()
{
return new Ccppdll();
}
void destroy_cppdll(Ccppdll* a)
{
delete a;
}

------------------------------------

上記の例で説明すると
dlsym()でcreate_cppdll()のアドレスを取得して関数を呼び出せば、あとはCcppdllのポインタ経由でメンバー変数と仮想関数の呼び出しができます。

virtualで宣言したメンバ関数はコンストラクタで作られる仮想関数テーブル経由で関数が呼び出されるので、共有ライブラリ内でのシンボル名が解決していなくてもよいんです。

でもC++のクラスって、オペレーター演算子の関数が定義されてたり、テンプレートが使われていたりします。長年の疑問だったのですが、そういう場合、動的ロードできるのか調べてみました。


まず、オペレータ演算子のメソッド関数。
この関数そもそもvirtualにできるんですかね?
調べてみた結果、なんとできます。
すごいねぇ、オペレーターメソッド関数って仮想関数にできるのね。

次にテンプレート関数。
この関数はvirtualにできません。
そりゃーそうでしょう。だってテンプレートだから、C++のクラスの仮想関数テーブルの関数の順番が特定できないからね。
でも、テンプレート関数はstaticな無名外部関数に展開されるはずなので、テンプレート関数内部で使っている関数がすべて仮想関数ならばコンパイルが通り動的にロードできます。


実験してみました。
------------------------------------
#ifdef _WIN32
#ifdef CPPDLL_EXPORTS
#define CPPDLL_API __declspec(dllexport)
#else
#define CPPDLL_API __declspec(dllimport)
#endif
#else
#define CPPDLL_API
#endif

class Ccppdll {
public:

//ファクトリーで作る
Ccppdll();
virtual ~Ccppdll();

//呼び出せる
virtual void test1(void);
virtual int operator+(Ccppdll &a);

//呼び出せない
void test2(void);
int operator-(Ccppdll &a);

//呼び出せない
static void test3(void);

//コンパイルエラー
//template<typename t> virtual void test4(t);
//template<typename t> virtual int operator*(t);

//呼び出せる
template<typename t> void test5(t);

//呼び出せない
template<typename t> void test6(t);
};

template<typename a> void Ccppdll::test5(a){
test1();
}

template<typename a> void Ccppdll::test6(a){
test2();
}

#ifdef __cplusplus
extern "C"{
#endif

//factory
CPPDLL_API Ccppdll* create_cppdll();
CPPDLL_API void destroy_cppdll(Ccppdll*);

#ifdef __cplusplus
}
#endif


Ccppdll::Ccppdll()
{
printf("Ccppdll::Ccppdll()\n");
}

Ccppdll::~Ccppdll()
{
printf("Ccppdll::~Ccppdll()\n");
}

void Ccppdll::test1()
{
printf("Ccppdll::test1\n");
}
void Ccppdll::test2()
{
printf("Ccppdll::test2\n");
}

void Ccppdll::test3()
{
printf("Ccppdll::test2\n");
}


int Ccppdll::operator+(Ccppdll& a)
{
return 123;
}

int Ccppdll::operator-(Ccppdll& a)
{
return 456;
}

Ccppdll* create_cppdll()
{
return new Ccppdll();
}
void destroy_cppdll(Ccppdll* a)
{
delete a;
}

------------------------------------




0 件のコメント:

コメントを投稿