VS2005でAFX_EXT_CLASSを使ってMFC拡張DLLを作成しています。
このエクスポートクラスの中にDLL内部のみで使う別クラスのメンバを
privateで持たせたいのですが、拡張DLLのクラス宣言の中にこのクラスを入れると、
そのクラスまでエクスポートしないといけなくなってしまいますし、
連鎖的にその他の内部クラスにまで広がってしまいます。
あくまでDLL内部だけで使うクラスであって、
拡張DLLを使う側には全く関係の無いprivateメンバなのですが、
これはどのようにして保持すればよいものなのでしょうか。
一応、クラス宣言ではvoidポインタをメンバとして持たせておき、
実体はコンストラクタでnew、デストラクタでdeleteし、
DLL内部で使うたびにキャストするという方法を考えたのですが、
もっとスマートな方法もあるのではと感じています。
拡張DLLを作られているかたで、同じような疑問を持たれたかたは
いらっしゃいませんでしょうか。
どのように解決されたのでしょうか。
どうしてもという場合。クラスごとエクスポートしないで、
必要なメンバだけちまちまとエクスポートしてます。
もっとも、DLLで共用するぐらいの優れたものなら
たいてい全てエクスポートしちゃいます。
クラスをエクスポートしたからといって
privateなものには触れるわけではないですし。
あえてその存在を隠蔽する場合は、コンパイル用の
ヘッダと、公開用のヘッダを別にして、
公開用のヘッダには隠蔽したメンバを記述しない
という方法があります。ただDumpbinなどツールを使用すれば
見えてしまうので、こーゆー所に労力と知恵を使わないで
素直に書いとくのが得策でしょう。
追補
上流のやつらには「sizeof()なんかつかうんぢゃねぇ。」
と脅しとくこと(笑)。
voidポインタでなく、実体ポインタでもクラス宣言無しでいけます。
なぜなら、どんなクラスでも、ポインタのサイズは固定だから。
(例) CExportSampleクラスをエクスポートし、CTestクラスをメンバに持つ場合
class CTest; // この宣言でポインタや参照ならinclude不要になる
class AFX_EXT_CLASS CExportSample
{
CTest* m_lpTest;
:
};
仲澤@失業者さん、bunさん、解説ありがとうございます。
> どうしてもという場合。クラスごとエクスポートしないで、
> 必要なメンバだけちまちまとエクスポートしてます。
これって、メンバの宣言自体はクラスにあるわけですよね。
となるとやはり、エクスポートするクラスのヘッダには
メンバのクラスのヘッダ(bunさんの例だとTest.h)を
インクルードする必要が出てきてしまいませんでしょうか?
使用者側にはこのエクスポートクラスのヘッダのみを渡すようにしたいのですが。
もしそのあたりも解決できるものであれば、解説いただけると嬉しいです。
> voidポインタでなく、実体ポインタでもクラス宣言無しでいけます。
> なぜなら、どんなクラスでも、ポインタのサイズは固定だから。
なるほど、使うたびにいちいちキャストする必要は無くなりますね。
やはりメンバとして持たせているクラス自体を外から隠したい場合には、
エクスポートするヘッダには実体ではなくポインタを持たせて、
ソースファイル内でnew/deleteするしか無いのでしょうかね。
外に見せたりさわらせたりしたくないものだから
privateにしているのに、しっかり外に見せちゃってますよね。
構造上仕方無いのかなとは思いますが、なんか腑に落ちないところです…。
> これって、メンバの宣言自体はクラスにあるわけですよね。
> となるとやはり、エクスポートするクラスのヘッダには
> メンバのクラスのヘッダ(bunさんの例だとTest.h)を
> インクルードする必要が出てきてしまいませんでしょうか?
やってみましたか?
> class CTest; // この宣言でポインタや参照ならinclude不要になる
この時点では、識別子 CTest はクラスである、とコンパイラに教えているだけです。
ヘッダ内で CExportSample::m_lpTest に対してアクセスしない限り、
CTest クラスのヘッダは不要です。
# 似たようなもんとして、STL にも前方宣言用に #include <iosfwd>
# なんて手法もあります。
> > これって、メンバの宣言自体はクラスにあるわけですよね。
> > となるとやはり、エクスポートするクラスのヘッダには
> > メンバのクラスのヘッダ(bunさんの例だとTest.h)を
> > インクルードする必要が出てきてしまいませんでしょうか?
> やってみましたか?
はい。CExportSampleクラス宣言の中に
CTest m_test;
と実体(ポインタではなく)を置き、
それ以外のメンバにAFX_EXT_CLASSを付けてみましたが、
「未定義の class 'CTest' で使用しています」といったエラーが出たため、
必要なメンバだけエクスポートすれば本当にインクルードを入れなくて済むのかな
と思って確認させていただきました。
> この時点では、識別子 CTest はクラスである、とコンパイラに教えているだけです。
> ヘッダ内で CExportSample::m_lpTest に対してアクセスしない限り、
> CTest クラスのヘッダは不要です。
こちらのほうはよく理解できています。
やはりヘッダには実体型ではprivateメンバを持てないのでしょうか?
> どうしてもという場合。クラスごとエクスポートしないで、
> 必要なメンバだけちまちまとエクスポートしてます。
これはコンストラクタ、デストラクタも関数としてクラス自体を隠す方法で
しょう。
こんな感じ。
--- Interface.h (公開ヘッダ)---
void* construct(void);
int member(void*);
void destruct(void*);
--- Client.cpp (利用者側)---
#include Interface.h
hoge()
{ void* pExportedDll = construct();
int n = member(pExportedDll);
destruct(pExportedDll);
}
--- Implement.h (非公開ヘッダ)---
class CImplement
{
public:
CImplement(){}
~CImplement(){}
int member_func(void);
};
--- Implement.cpp (実装)---
#include Interface.h
#include Implement.h
void* construct(void)
{ return new CImplement;
}
int member(void* pInterface)
{ CImplement* p = static_cast<CImplement*>(pInterface);
return p->member_func();
}
void destruct(void* pInterface)
{ CImplement* p = static_cast<CImplement*>(pInterface);
delete p;
}
C++なんざ知らん! Cでインターフェースしろ! という人向けによくや
ります。
# 実際にCでインターフェースするには 宣言にextern C等が必要ですが。
DLLのインターフェースだけ公開して、実装を見せないようにするにはイン
ターフェース部分を抽象クラスにします。
--- Interface.h (公開ヘッダ)---
class AFX_EXT_CLASS CMyClass
{
public:
virtual ~CMyClass();
static CMyClass* get(void);
virtual int member(void) = 0;
protected:
CMyClass(); // 抽象クラスなので元々直接コンストラクトできない。
};
--- Client.cpp (利用者側)---
#include Interface.h
hoge()
{ CMyClass* pMyClass = CMyClass::get();
pMyClass->member();
delete pMyClass;
}
--- Implement.h (非公開ヘッダ)---
#include internal.h // CInternalの宣言/定義、内容は省略
class CImplement : public CMyClass
{
public:
CImplement(){}
virtual ~CImplement(){}
virtual int member(void);
private:
CInternal m_internal: // 内部で必要なクラス
};
--- Implement.cpp (実装)---
#include Interface.h
#include Implement.h
CMyClass::~CMyClass() {}
CMyClass* CMyClass::get(void)
{ return new CImplement();
}
int CMyClass::member(void)
{ // 実装
}
利用者側からはCMyClassを直接コンストラクトすることができないのが難点
ですが、実装は隠すことが出来ます。
デザインパターンの本に種々書いてありますね。
あれ!? 最後の
> int CMyClass::member(void)
は
int CImplement::member(void)
の間違いです。