C++ COM 設計。合成と多重継承
-
26-09-2019 - |
質問
アプリケーション (IWebBrowser2) にブラウザー コントロールを埋め込もうとしています。これを機能させるには、IDispatch、IDocHostShowUI、IDocHostUIHandler などを実装する必要があります。私はこれを純粋な C++/Win32 API で実行しています。ATL、MFC、またはその他のフレームワークは使用していません。
TWebf というメイン クラスがあります。このクラスは、ブラウザ コントロールを配置するための Win32 ウィンドウを作成し、それを動作させるために必要なすべての OLE 呼び出しを行います。また、Refresh()、Back()、Forward() などのメソッドを使用してブラウザ コントロールを制御するためにも使用されます。
現時点では、これはコンポジションを使用して実装されています。TWebf には、すべてのさまざまなインターフェイス (IDispatch、IDocHostShowUI...) を (スタック割り当てられた) メンバーとして実装するクラスがあります。TWebf がそのコンストラクターで最初に行うことは、これらすべてのメンバーに自分自身へのポインターを与えることです (dispatch.webf = this;
等)。QueryInterface、AddRef、および Release は、すべてのインターフェイス実装の TWebf 内のメソッドへの呼び出しとして実装されます (呼び出しによって) return webf->QueryInterface(riid, ppv);
例えば)
私は、TWebf とインターフェイスを実装するクラスの間のこの循環依存関係が好きではありません。TWebf には TDispatch メンバーがあり、そのメンバーには TWebf メンバーがあり、...
そこで、代わりに多重継承を使用してこれを解決することを考えていました。これにより、QueryInterface が単純化され、常に単に返せるようになります。 this
.
私が望むものの UML 的なスケッチは次のようなものになります。(クリックすると拡大表示されます)
UML でわかるように、すべてのインターフェイスの最低限の実装を提供したいので、実際に TWebf で実質的なことを実行したいインターフェイスでこれらのメソッドをオーバーライドするだけで済みます。
「多重継承の実装」は可能でしょうか?それは良いアイデアですか?それが最善の解決策なのでしょうか?
編集:
将来の議論のために、TWebf での QueryInterface の現在の実装を次に示します。
HRESULT STDMETHODCALLTYPE TWebf::QueryInterface(REFIID riid, void **ppv)
{
*ppv = NULL;
if (riid == IID_IUnknown) {
*ppv = this;
} else if (riid == IID_IOleClientSite) {
*ppv = &clientsite;
} else if (riid == IID_IOleWindow || riid == IID_IOleInPlaceSite) {
*ppv = &site;
} else if (riid == IID_IOleInPlaceUIWindow || riid == IID_IOleInPlaceFrame) {
*ppv = &frame;
} else if (riid == IID_IDispatch) {
*ppv = &dispatch;
} else if (riid == IID_IDocHostUIHandler) {
*ppv = &uihandler;
}
if (*ppv != NULL) {
AddRef();
return S_OK;
}
return E_NOINTERFACE;
}
編集2:
いくつかのインターフェイスにこれを実装してみました。TWebf を IUnknown および TOleClientSite から継承させると正常に動作するようですが、TDispatch を継承リストに追加すると動作しなくなります。
離れて warning C4584: 'TWebf' : base-class 'IUnknown' is already a base-class of 'TDispatch'
警告 実行時エラーも発生します。実行時エラーは「位置 0x00000000 の読み取りアクセス違反」です。
ランタイム エラーは、何らかの理由で IDispatch ではなく、IOleClientSite を処理する行で発生します。なぜこれが起こっているのか、あるいはそれが本当に多重継承と関係があるのかどうかはわかりません。誰か手がかりはありますか?
編集3:
QueryInterface の不適切な実装が実行時例外の原因となっているようです。として マーク・ランサム このポインタは *ppv に割り当てられる前にキャストする必要があり、IUnknown が要求された場合には特別な注意が必要であることが正しくわかりました。読む 多重継承のあるオブジェクトに QueryInterface を実装するときに明示的なアップキャストが必要なのはなぜですか? それについての素晴らしい説明をありがとう。
なぜその特定のランタイムエラーが発生したのか、正確にはまだわかりません。
解決
多重継承はそうはい、それが可能だ、COMインターフェイスを行うには非常に一般的な方法です。
しかし、QueryInterfaceが、まだ各インターフェイスのポインタをキャストする必要があります。多重継承の興味深い特性は、ポインタが各クラスタイプのために調整されるかもしれませんということである - のIDispatchへのポインタが同じオブジェクトにも、彼らかかわらず、両方のポイント、IDocHostUIHandlerへのポインタとして同じ値を持っていません。また、IUnknownのためのQueryInterfaceが常に同じポインタを返すことを確認してください。すべてのインターフェイスがIUnknownのから派生するので、あなたはそれに直接ちょうどキャストしようとすると、あいまいなキャストを取得しますが、それはまた、あなただけの親リストの最初のものを選んで、IUnknownのよう任意のインターフェイスを使用することができることを意味します。
他のヒント
多重継承にはいくつかの制限があります
2 つのインターフェイスが同じ名前/シグネチャを持つ関数の実装を要求する場合、多重継承を使用して 2 つの異なる動作を提供することは不可能です。同じ実装が必要な場合もあれば、そうでない場合もあります。
クラスの仮想テーブルには複数の IUnknown インターフェイスが存在し、余分なメモリ使用量が増加する可能性があります。同じ実装を共有しているのは素晴らしいことです。
組成物を維持する、実質的に容易になるだろう。 MIは、仮想継承のように、多くの落とし穴があり、保守性から大幅に低下します。あなたがデータメンバーとして構成クラスにでこれを渡すために持っている場合、あなたはそれが間違ってやりました。あなたがすべきことは、彼らが他の提供方法にアクセスする必要がある場合は、メソッド呼び出しの中でこれを渡しています。あなたが構成オブジェクトに対するすべてのメソッド呼び出しを制御するので、余分なポインタを挿入する問題はないはず。これは、はるかに簡単メンテナンスおよびその他の操作のために命を行います。