例えば、以下のようなテンプレートクラスがあるとします。
template<class T> struct X { static int n__; static void x(); }; template<class T> int X::n__ = 0; template<class T> void X::x() { }
このクラスをintで実体化してX.EXEとY.DLLで使うと、X<int>::n__やX<int>::x()の実体はXとYの両方に作成されます。これを無理やりY.DLL側からエクスポートするにはこうします。
template class __declspec(dllexport) X<int>;
インポートする側では以下のようにします。
extern template class __declspec(dllimport) X<int>;
externを付けておかないと、dllimportのくせに変数や関数の定義があるといってエラーになってしまいます。逆にエクスポートする側にexternをつけておくと、DLLを作成するときにシンボル未解決でリンクエラーになります。ちなみに、ヘッダにexternをつけたバージョンを書いておいて、ソースにexternを外したバージョンを書いておくと良いかと思ったのですが、extern付きとexternなしの両方を書くと、それはそれでコンパイルエラーになるのでした。
ついでですが、DLL側が複数のファイルで構成されるときにはシンボルが重複してエラーになってしまうのではないかと思ったのですが、リンカが勝手にまとめてくれるようです。
というわけで、こういう風にしておけばよいと言う事でしょう。
#ifdef EXPORT # define E # define D __declspec(dllexport) #else # define E extern # define D __declspec(dllimport) #endif E template class D X<int>;
このようにすると、X<int>::n__やX<int>::x()はY.DLL側からエクスポートされるようになり、X.EXE側からも共有されるようになります。
他にも、普通のクラスのエクスポートと同じように、クラス宣言自体や関数宣言、変数宣言に__declspec(dllexport)をつけて回ったりすることも出来ますが、結局のところDLL側で、明示的なインスタンス化をしておかないとエクスポートされず、インポート側でリンクエラーになってしまうので注意ですね。また、__declspec(dllimport)をつけた関数や変数の定義が存在するとエラーになってしまうので、これも#ifdefを使ってインポート時には見えなくする必要があるあたりも結構面倒です。
なんで上のような事を実験したのかというと、STLportの__node_allocを複数のDLLで共有したかったからなのです。__node_allocはアロケートしたメモリチャンクのリストをクラスのstaticメンバに保持しているので、そのままだとDLLごとにリストを持つことになります。
このため、A.DLLで確保して、B.DLLで開放するということを繰り返すと、B.DLLのリストに再利用可能メモリのリストがどんどん増えるものの、A.DLLのリストは空なので、どんどんメモリを消費するようなことになってしまいます。
なので、DLL間でSTLのオブジェクトを受け渡しするには注意が必要だったのですが(というよりは不可能に近い)、やはりそれだと辛いので、__node_alloc自体をエクスポートして共有するようにしてみました。
やったことは簡単で、以下のような行をSTLportに追加します。
#ifdef _STLP_EXPORT_NODE_ALLOC template class __declspec(dllexport) __node_alloc<true, 0>; #elif defined _STLP_IMPORT_NODE_ALLOC extern template class __declspec(dllimport) __node_alloc<true, 0>; #endif
そしてエクスポートしたいDLLで_STLP_EXPORT_NODE_ALLOCを定義してコンパイルし、インポートしたいDLL/EXEでは_STLP_IMPORT_NODE_ALLOCを定義してコンパイルします。そうすると、メモリチャンクのリストが共有されるようになるため、DLL間でSTLのオブジェクトを渡しても大丈夫になります。
DLLのベースアドレスを指定するのを忘れていたので、指定するようにしました。今までは全てのDLLがデフォルトの0x1000000にロードされるようになっていたのでローダによって再配置されていましたが、ばらばらなアドレスにロードされるようにしたので多少はロードが速くなったかもしれません。