DLLの中のクラス

[戻る]
hirocco 2011/01/20(木) 11:25:34
いまさらで、馬鹿げた質問かもしれません...
MFCのようにDLLの中のクラスを外部でnewさせる仕組みがよくわからないです
いつもはDLLの中に static とか friend なメソッドを作っておいて、そこから生成させ
てるんですけど、やっぱりnewしたいかもです
やっぱり、インターフェイスクラスとか関係ありますか?

Blue 2011/01/20(木) 11:51:02
なんだかよくわからないのですが、exportを指定してもダメってこと?

__declspec(dllexport) を使った DLL からのエクスポート
http://msdn.microsoft.com/ja-jp/library/a90k134d%28v=vs.80%29.aspx

MFCのDLLならAFX_EXT_CLASSを使うと便利
http://msdn.microsoft.com/ja-jp/library/9xyb5w93%28v=vs.80%29.aspx

仲澤@失業者 2011/01/20(木) 12:22:51
自分で作ったDLL内のクラスをnewできないのは大変な損失です。
DLL内のクラスをexportするには、

#define EXPORTS_CLASS __declspec(dllexport)
class EXPORTS_CLASS Class_In_DLL
{
};

とするだけです。案ずるよりも、ってことですね(^^)/。

hirocco 2011/01/20(木) 13:46:59
えっと、__declspec(dllexport)をつけるだけで、そのDLLを利用する例えばアプリケー
ションで new で生成させても大丈夫なんですか?
知らなかったです...
メモリの都合上だめなのかと思ってました...
ひょっとして newのオーバーライドが必要かと思ってましたけど...
sizeofでのサイズが公開されているヘッダと実体が一致していないとどうなんでしょう
か?
DLLとexe間の仮想テーブルとか気にしなくて関係ないんですか?

Blue 2011/01/20(木) 13:56:34
よくいいたいことがわからないですが、

DLLのクラスを変更したとき、*.lib、*.h とも変更後のものを
あるEXEで使わないとダメなケースが多いです。
(メンバ変数の型が変わったり、メソッドの引数の型が変わったりした時点でアウッ・・・)


ちなみに、MFCを使っている場合、
DLLのMFCのバージョンとEXE側のMFCのバージョンが違うとまずかったような。

hirocco 2011/01/20(木) 14:40:45
基本として以下のようにしてます

"DECLSPEC_API" は
DLL側では_declspec(dllexport)
利用側では_declspec(dllimport)と読み替えてください

宣言部
DECLSPEC_API void* DllClass1NewDllClass();
DECLSPEC_API void DllClass1DeleteDllClass(void* obj);
class DllClass1
{
protected:
    DllClass1();
    virtual ~DllClass1();

public:
    friend DECLSPEC_API void* DllClass1NewDllClass();
    friend DECLSPEC_API void DllClass1DeleteDllClass(void* obj);
};

定義
void* DllClass1NewDllClass()
{
    return DllClass1();
}
void DllClass1DeleteDllClass(void* obj)
{
    delete (DllClass1*)obj;
}
DllClass1::DllClass1()  {}
DllClass1::~DllClass1()  {}

で、そのDLLを利用するDLLをつくるとします
宣言部
#include "DllClass1.h"
DECLSPEC_API void* DllClass2NewDllClass();
DECLSPEC_API void DllClass2DeleteDllClass(void* obj);
class DECLSPEC_API DllClass2
{
protected:
    DllClass2();
    virtual ~DllClass2(){}
private:
    DllClass1* m_dllClass;

public:
    friend DECLSPEC_API void* DllClass2NewDllClass();
    friend DECLSPEC_API void DllClass2DeleteDllClass(void* obj);
};

定義
void* DllClass2NewDllClass()
{
    DllClass2* obj    =new DllClass2();
    obj->m_dllClass    =(DllClass1*)DllClass1::DllClass1NewDllClass():
    return obj;
}
void DllClass2DeleteDllClass(void* obj)
{
    DllClass1::DllClass1DeleteDllClass( obj->m_dllClass ):
    delete (DllClass2*)obj;
}
DllClass2::DllClass2()  {}
DllClass2::~DllClass2() {}


で、そのDLLを利用するexeをつくるとします
でも使用するのはDllClass2だけでDllClass1は直接使いわないとしたら、DllClass1のヘ
ッダを持つのは邪魔くさいので、exeで使用する為の公開ヘッダを以下のようにします

#include "DllClass1.h"        // 消してみます
DECLSPEC_API void* DllClass2NewDllClass();
DECLSPEC_API void DllClass2DeleteDllClass(void* obj);
class DECLSPEC_API DllClass2
{
protected:
    DllClass2();
    virtual ~DllClass2();

private:
//  DllClass1* m_dllClass;    // 消してみます

public:
    friend DECLSPEC_API void* DllClass2NewDllClass();
    friend DECLSPEC_API void DllClass2DeleteDllClass(void* obj);
};

これだとDllClass2の内部的にm_dllClass(DllClass1)も生成できていて、かつexe側は
DllClass1の存在さえ知らずに利用できるかなと思ってました

これで lib からも LoadLibrary からも利用できるようになるんですけど,
公開ヘッダにいたずらしてるので new (もちろんコンストラクタをpublicに変更します)
だとサイズが異なっていて、宜しくないです

公開ヘッダにいたずらが、非常識なもので、内部的な参照のヘッダも必ずつけておくの
が常識なら new はできそうですけど、私の場合、常識がよくわかっていないみたいです

あ、ながながとごめんなさい

hirocco 2011/01/21(金) 11:18:55
[[解決]]
結局, 暗黙なリンクだったらnewで生成しちゃってもよかったって事なんですね
明示的にLoadLibraryの場合と混同していたみたいです
まぁ, たぶん解決です

で、newする時は呼び出し側が本当のサイズを知っていれば, ::operator newで呼び出さ
れるアロケートも成功すると仮定すると, 公開ヘッダにいたずらして, 変数を隠蔽する
なら隠蔽するべき変数と同じサイズのprivate変数をダミーで入れてあげたらOKという理
解でどうなんでしょうね
もしくは, sizeofをオーバーライドできたらよかったんですけど...

PATIO 2011/01/21(金) 19:36:40
公開ヘッダを変更して云々と言うのは私自身としてはやりませんね。
常識的なのかどうかと言う問いには答えられませんけれど、
実際に内部で開発に使っているヘッダファイルと公開用のヘッダファイルを
直接エディタで書き換えて変えてしまうような方法だと変更ミスでわけの分からない
バグが出そうで怖いです。

基本的には指定した使い方以外の使い方をしておかしな事になっても
間違った使い方をした方の問題と言う気もしますし、
仕事で提供するような場合にはクラスリファレンスをつけて
使い方に関してはきちんと指定する事になるのでわざわざヘッダーファイルを
改変までして内部実装を隠蔽しようとした事は有りません。

本当に公開したくない部分はヘッダファイルよりもcppファイルの方に
あると思っているのでライブラリとして提供する場合は気にしていません。
あるとすれば、公開したくない実装はヘッダファイル内二書かない位でしょう。

仲澤@失業者 2011/01/24(月) 09:37:32
自分の場合は、公開用のヘッダーとコンパイル用のヘッダーは
まったく別物にしてますです。
いろいろと工夫が必要で、またやや危険でもありますが、
本来「DLLのインターフェース定義」とはそういうものです。
従って、1つのDLLに対して「公開用のヘッダー」が数種類存在し
クライアントによって、使い分けています。これは、
そのDLLを使うときの言語(C、C++、VB、C#等)が違ったり、
公開してほしい機能が微妙に異なったりするせいです。
もっとも。これはこれで結構大変なんですけどね。

hirocco 2011/01/24(月) 14:14:44
いろいろ, ご意見ありがとうございます

えっと, DLLを解析されたらどうしようもないんでしょうけど, 解析は論外として
NDAとか絡んじゃうとなるべく隠蔽しておきたくなっちゃうんですよね...
NDAってめんどくさくないですか?

あと, メンバ変数を隠蔽すると, newでのサイズ取得時に具合がよくないですけど,
メソッドの場合は, なにか問題ってあるのでしょうか?
ブイテーブルとかポインタとかズレちゃったりってありえるんでしょうか?

Ban 2011/01/25(火) 08:52:34
ヘッダの細工はISO/IEC24882 3.5 One Definition Rule (ODR)に則さないと
未定義(undefined)になるので基本的に私はやりません。


この手の隠蔽手段の常道は多分「Pimpl」にして、Dll2でのDllClass1.hのincludeを
ソースでする(ヘッダではしない)ことじゃないでしょうか。


ちなみにこの細工、newというか常にヒープを利用しないと破綻するのでは?
DllClass2 a;
とかした時点でm_dllClassの領域ないですし、
DllClass2* p = DllClass2NewDllClass();
DllClass2 a = *p;
とかされてもお困りでは。
まぁそもそもDllClass2をコピーされるとm_dllClassのコピー処理が必要ですが、
仮にそれを入れたとしてもスタック上に生成されるとスライシングと同じ問題が
発生しそうです。


> 結局, 暗黙なリンクだったらnewで生成しちゃってもよかったって事なんですね
> 明示的にLoadLibraryの場合と混同していたみたいです

暗黙でも明示でも「newとdeleteが常に対応づいている」ことが重要で、
とりあえずこれが守られてるなら問題ないかと。
これが守られないと、例えばVCのDebug版実装でnewで生成されたクラスを、
VCのRelease版実装するdeleteで削除したら、(Debug版ライブラリは色々記録を
取ってたりしますので)各標準ライブラリ実装内のヒープ管理が破綻します。


> 公開ヘッダにいたずらして, 変数を隠蔽するなら隠蔽するべき変数と
> 同じサイズのprivate変数をダミーで入れてあげたらOKという理解で
> どうなんでしょうね

サイズだけの問題でもないと思うので一般論でいえばODRが守れない記述
(=未定義)は駄目駄目だと思いますが、特定の事例ということであれば、
(コンパイラに頼らず)プログラマの責任において、バイナリをきちんと
検証すればやむなしかと。

> メソッドの場合は, なにか問題ってあるのでしょうか?

仮想関数を弄ったりすれば当然vtblも影響を受けますね。
それ以外にも、ODRにわざわざrequirementで列記してあることに違反していれば
問題になりうるのでは(何か問題になりうるからこその今の規定でしょうし)。

PATIO 2011/01/25(火) 10:01:00
どうしても隠蔽したいというのであれば、
それも込みのクラス設計をと言う事になるんじゃないかと。
公開部分のクラスと内部実装部分のクラスを切り離した形で
実装しておいて、公開部分では必要なクラスや構造体だけで
構成されたヘッダーファイルを提供できるようにするとか
そんな話なんじゃないかなと思っています。
(状況によってはそうはいかない部分があるかもしれませんが)

作るときは普通に作ってヘッダファイルを細工して隠蔽と言う
方法論もあるとは思いますけれど、私的には管理が煩雑になる事や
そこに起因する不具合の可能性を考えるとお勧めできる方法では
ないように感じています。

まあ、最終的には、
提供側がきちんと責任を持って提供できるならありなんでしょうけれど。

hirocco 2011/01/25(火) 15:41:13
> この手の隠蔽手段の常道は多分「Pimpl」にして
初めて知りました...
これは参考になりそうです

maru 2011/01/25(火) 17:46:55
ある意味COMの実装がそうですよね。
外部インターフェースはIxxxxで内部の実装はCxxxx。
COMコンポーネントを作る場合、必然的にそのパターンになります。

古いスレッドには追加発言できません。