none
CComObject::CreateInstanceで生成するオブジェクトに引数を渡したい RRS feed

  • 質問

  • CComObject::CreateInstance( やCoCreateInstance )を利用して CComObjectオブジェクト を生成できますが、
    このオブジェクトのコンストラクタに任意の引数を与えるなどして、コンストラクタ以降の処理を切り分けたいのですが、可能でしょうか?
    # 厳密には、生成するオブジェクトのFinalConstruct()での処理を切り分けたいと思っています。

    生成側では、
      CComObject<XXX>* pPtr = NULL;
      hr = CComObject<XXX>::CreateInstance( &pPtr );
      ...
    としてXXXオブジェクト pPtr を生成しています。
    生成前の条件によって XXX 生成時に専用の情報を与え、XXX::FinalConstruct()での処理を変えたいと思っているのですが。。。

    CreateInstanceから戻る頃には XXX のコンストラクタおよびFinalConstruct()の処理は終わっているため、実現できません。
    CreateInstance以前に何とかすれば良いとは思っているのですが。。。

    開発環境は、VS2008(MFC9)を使用しています。アンマネージコードです。
    本件とについてご存知の方、あるいは他の方法を使えばできる!などの情報をご存知の方、
    どうかよろしくお願いいたします。

    2009年12月1日 10:52

回答

  • ClassFactory は、自前で持っているという認識でいいでしょうか?<ここ重要

    であれば、現在

    CComObject<XXX>* pPtr = NULL;
    hr = CComObject<XXX>::CreateInstance( &pPtr );
    としている個所で、CreateInstance をそのまま呼び出す代わりに自分で新たに作った(中身は基本的に丸ごとコピーでよい)新たな CreateInstance を用意しておき、その中で、独自処理を挟み込めばいいと思います。

    CComObject::CreateInstance() は static メソッドなので、オーバーライドというよりは、似て非なるものを用意するというほうが正しいかな。

    実装詳細をちゃんとチェックしていないのでわからんですが、場合によっては、CComObject の派生クラスを作ってそれを pPtr の型にする必要があるかもしれません(protectedなメソッド呼び出しなどがある可能性があるため)。
    その場合は、CComObject の派生クラスを作ってそれにメソッドを用意すればいいと思います(派生じゃなくて似て非なるものでもいいですがw)。

    挙動がよくわからないという場合は、現在のオブジェクト構築の流れ(CreateInstanceLicの中)をデバッガでトレース実行してみるなどをしてみるとよいでしょう。
    その際、潜れる範囲はATLのソースもすべて潜って具体的にどういう処理をどのタイミングで行っているのかを把握しておくことをお勧めします。

    このあたり、一度がっちりと把握しておけば、かなりシビアな案件でも考えられないくらい簡単なカスタマイズで実現できるようになりますよ。

    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク runta 2009年12月4日 10:00
    2009年12月3日 1:43

  • ふつうの ::CoCreateInstance や
    CComPtr の CoCreateInstance や
    _com_ptr_t (や そのtypedef) の CreateInstance
    では,COM仕様でいうところのインタフェースポインタが返って来ます。

    一方,CComObject::CreateInstance は,
    COM仕様のクラスのインスタンス自体への普通のポインタが返って来ます。
    ふつうの生C++ポインタなので,
    これは,COM仕様として外部に公開していない
    publicなメンバにアクセスするのに使えます。

    その後,外部に渡すには,
    それに対してQueryInterface してやって
    インタフェースポインタを受け取って
    インタフェースポインタを渡してやる必要があります。

    現在は,(以下確認処理略)

    CComObject<XXX>* pPtr = NULL;
    hr = CComObject<XXX>::CreateInstance( &pPtr ); // ここを中身べた書きに変えるだけ

    // pPtr は,生C++ポインタなので,
    pPtr->QueryInterface( riid, ppvObject );  // ppvObject にCOMインタフェースポインタ取り出し

    な感じになっているんですよね?

    その場合,
    FinalConstruct() をまるっと抜かすだけでいい のなら
    とっちゃんさんが書かれているように
    上のCreateInstanceのところを変えるだけですよね。


    ------

    どうやって割り込ませているのかはわかりませんが
    それは置いておいて,以下は一般的な?話に戻してみます。


    > オーバーライドするイメージでしょうか?

    ATL_NO_VTABLE の指定があるように
    仕組み的にはいわゆるオーバーライドではなく
    たんなる上書き
    (simple override)になります。

    ですが,
    クラスに既に存在している
    ふたつオーバーロードの CreateInstance は別物で,
    それは上書きしません。

    実は,他に,
    CreateInstance は,ふたつあります。

    継承元の CComCoClass で定義されている

    DECLARE_CLASSFACTORY()
    DECLARE_AGGREGATABLE(T)

    がそれの元になっています。

    定義しているクラスが,
    クラスファクトリ(クラスオブジェクト) と
    実際のCOMクラス(CoClass) の両方を兼ねて いるので
    ふたつあります。

    で,
    その継承元の CComCoClass で定義されている

    DECLARE_CLASSFACTORY()
    DECLARE_AGGREGATABLE(T)

    は,必要に応じて自作のクラスで上書き していますよね。

    そのマクロですが,見るとわかると思いますが,
    最終的に,それぞれ typedef された

    _ClassFactoryCreatorClass や
    _CreatorClass

    に CreateInstance がそれぞれある仕組みになってます。
    そこにふたつあるわけです。

    なので,そこを入れ替えればいいわけです。

    _ClassFactoryCreatorClass::CreateInstance は,
    クラスファクトリ自身のインスタンスの作成です。

    _CreatorClass::CreateInstance は,
    オブジェクト自身(CoClass)のインスタンスを作成するものです。

    それぞれ
    DECLARE_CLASSFACTORY()
    DECLARE_AGGREGATABLE(T)
    の typedef の中で
    CComCreator の CreateInstance がそれに相当 する箇所になります。

    なので,仕掛けを入れるとしたら,
    CComCreator の自作版

    template <class T1>
    class CComCreatorSkipFinalConstruct(){...};

    のようなものを作って,
    ひとつだけあるメソッド
    static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv) //シグナチャ注意
    の中身を,
    CComCreator概存の
    マルチ・フェーズ・コンストラクション と言われている手順をまねて書きます。

    特に,
    p->InternalFinalConstructAddRef(); と
    p->InternalFinalConstructRelease();
    は一見無駄なように見えるけれど,
    おまじない なので入れておきます。


    クラスファクトリにしか使わないのなら
    CComObjectCashed< CHoge > のようにべた書きしてもいいけれど,
    元のを見るとわかるように,
    CComObject や CComObjectCashed 等を
    テンプレート引数( T1 )で受け取る形になってるので,
    単にスキップするコードを入れるだけならば,
    そこは T1 を使って同じように書くようにしておけばいいです。

    DECLARE_CLASSFACTORY() や DECLARE_CLASSFACTORY2(lic) は,
    CComCreator のテンプレート引数に CComObjectCashed を使ってますよね。
    DECLARE_AGGREGATABLE(T) や DECLARE_NOT_AGGREGATABLE(T) も
    それぞれ,CComCreator のテンプレート引数に
    CComAggObject や CComObject を使ってます。

    逆に
    テンプレートにしないでべた書きするならば,
    どこで使うか限定したような名前にした方が無難です。



    で,

    (クラスファクトリは)
    DECLARE_CLASSFACTORY() / DECLARE_CLASSFACTORY2(lic)

    (オブジェクトは)
    DECLARE_AGGREGATABLE(T) / DECLARE_NOT_AGGREGATABLE(T) 等

    で定義された

    _ClassFactoryCreatorClass や
    _CreaterClass

    の該当する方を typedef  し直します。
    そうすれば,
    該当する方の CreateInstance を差し替えたことになりますよね。



    追記:
    CreateInstanceメソッドだけ挿げ替える方法もありますが,
    やり方が悪いのかもしれませんが,
    確認したところ,なぜか二度呼ばれてしまうので
    CComCreator ごと挿げ替えた方が無難です。


    稍丼 / yayadon
    • 編集済み yayadon 2009年12月3日 12:46 追記
    • 回答としてマーク runta 2009年12月4日 9:59
    2009年12月3日 12:32

すべての返信

  • 基本的に、COM のインスタンスの生成時に引数を与えることはできないと思います。
    引数をどうしても与えたい場合は、Initialize メソッドを別途用意して、何よりも先にそれを呼ぶように設計してはいかがでしょうか?


    なお、FinalConstruct でなければならない理由が今ひとつ見えません。
    それを説明していない現状では、「別メソッドに分ければ良いのでは」という、楽であり、良さそうなパターンでの提案になります。
    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2009年12月1日 14:07
    モデレータ
  • Azuleanさん、回答ありがとうございます。

    > 基本的に、COM のインスタンスの生成時に引数を与えることはできないと思います。
    やはりそうなのですか。。

    今回は、既存(公開済み)のCOMオブジェクトに対して変更を施したく思っています。
    Azuleanさんが仰る、「別メソッドに分ければ良いのでは」というのは、現在FinalConstructで行っている処理を別途新たなイニシャライズ関数で実装すれば、ということですよね。しかし、公開済みのオブジェクトのため、変更影響範囲が広くなかなか採用しにくい状態です。

    生成時に引数を与えられないとなると、例えば、既存のCOMオブジェクトを継承する形(FinalConstructとイニシャライズ関数以外は全て既存のCOMオブジェクトの関数を利用)で新たなCOMオブジェクトクラスを定義すれば良いかとも思いますが、COMクラスを親にしたCOMクラスの実装ってできるのでしょうか?
    # 継承などせず、ソースコードをすべてベタ書きで複製すればできるのは当然ですが...

    できるだけインプリメント量を減らして実現したいと思っているので、何かアドバイスや手がかりがあればお教え願いたいと思っています。
    宜しくお願いいたします。

    2009年12月2日 5:56
  • COMは実装詳細を利用者側に見せないことで自由に拡張できるつくりになっています。

    CComObject を云々ということは、サーバー側は自由にいじれるということですよね?なら、コンストラクションしているメソッドを好きに書き変えてしまえばそれで済むと思いますが?

    CComClassFactory のデフォルト実装や、通常のサンプルでは CComObject<Base>::CreateInstance を呼び出しますが、それでは都合が悪いのなら、そのメソッドを呼び出している部分をそっくり丸ごと、別のメソッドとして定義してそれを呼び出せば事足りるという話ではないでしょうか?

    今回のようなカスタマイズなら、コンストラクタでは渡せないものの、SetVoid() を呼び出している部分(詳細はatlcom.hを参照)に、追加処理を入れてやれば、いくらでも書き換えできるものと思います。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    2009年12月2日 6:54
  • とっちゃんさん、回答ありがとうございます。

    が、こちらの知識不足で、よく内容が分かりませんでした。
    もう少し詳細をご教授願えないでしょうか?

    現在サーバ側のFinalConstructで行っている処理を新たな関数にそのまま移し、
    クライアント側ではCreateInstanceした後にそれを呼び出すようにすればよい、ということでしょうか?
    或いは、COMのインスタンス生成をCComObject::CreateInstance以外の方法(新たに作る関数を使用)で行うということでしょうか?
    また、SetVoidのくだりはどういう意味か殆ど分かりませんでした。
    #すいません。。

    こちらが行いたいことをもう一度詳細に説明したいと思います。

    ・既存のCOMオブジェクト(=A)は、CreateInstanceされると自動的に呼び出されるFinalConstruct内である処理(=B)を行っている。
    ・処理Bは、現時点でオブジェクトAを生成するためには必須の(省略してはいけない)処理になっている。
    今回、既存のコードならば全て処理Bを通るという仕様はキープしつつ、処理Bを通らずにオブジェクトAを生成できるルートを特別に作りたい。
    という課題にぶち当たっています。
    AをCreateする時、条件を渡せれば良さそうと思いましたが、方法が分からず当初の質問となりました。
    また、処理Bを新しいメソッドに移動してしまうと、既存のコードが(変更しない限り)処理Bを通らなくなってしまいNGとなります。

    ...と言う事なのですが、教えて下さっている方法は上記の状況でも適用できるのでしょうか?
    こちらの知識不足で申し訳ありませんが、なにとぞ宜しくお願いいたします。

    2009年12月2日 9:13
  • 制御を分けたいのは、クライアント(COMサーバーの外側)からの情報をもとになのでしょうか?

    とすると、呼び出すのは、CoCreateInstance ですよね?
    これだと、パラメータで変化させられるのは、CLSID または、IID のどちらかだけなので
    それで判断するようにするか、IClassFactory の代わりに独自の構築インターフェースを用意するか?となると思います。

    そうでななく、サーバー内部でということであれば
    CComObject::CreateInstance を呼ぶ代わりに独自の構築処理を書いてしまえばよいのでは?

    ということです。

    最初に出ていたのが CComObject だったので、サーバーサイドだけで完結する内部的問題と思っていたのですがそうではないということでしょうか?
    もうちょっと詳しい状況が分かれば、何かしら助言できるかもしれませんが、COM のアーキテクチャの基本構造をわかってないと
    この先のカスタマイズはかなり厳しい気がします。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    2009年12月2日 10:06
  • とっちゃんさん、回答ありがとうございます。

    以前の回答と矛盾してしまう内容になるかもしれませんが、こちらの状況を再度まとめてみます。

    現在、サーバ側のXxxClassFactory::CreateInstanceLic で、COMオブジェクト(=A)をCComObject::CreateInstance しています。
    Aは、FinalConstruct内で、必ず処理Bを行うことになっています。

    今回、Aを処理BなしでCreateInstanceするか、これまで通りCreateInstanceするか分けたいと思っています。
    判定材料は、クライアント側からの情報を元にしますが、CreateInstanceLicの第4引数の文字列情報でカバーしようと思っています。

    つまり、CreateInstanceLicの第4引数が YYY なら 処理Bを行わずにクリエイト(新たなルート)、
    YYY 以外ならこれまで通りクリエイトしたいのです。

    CreateInstance する時に何かしら引数が渡せるなら、A::FinalConstruct内での処理を分けることができそうと思ったのですが、
    単純な引数では渡せないみたいですね。

    Azuleanさん、とっちゃんさんからは処理BをFinalConstructから他に移せば?というアドバイスも頂きましたが、
    (自分もここまで書いてようやく意味が分かってきました)
    これはこちらの特別な事情なのですが処理Bの場所はできるだけ変えたくないのです。。

    となると、とっちゃんさんが仰る
    > CComObject::CreateInstance を呼ぶ代わりに独自の構築処理を書いてしまえばよいのでは?
    というのができれば実現できそうな気がしていているのですが、これは具体的にはどういうことでしょうか?
    CreateInstance をオーバーライドするイメージでしょうか?

    こちらの知識不足で分かりにくい内容になってしまい申し訳ありませんが、状況はうまく伝わったでしょうか?
    是非宜しくお願いいたします。
    2009年12月2日 11:30
  • ClassFactory は、自前で持っているという認識でいいでしょうか?<ここ重要

    であれば、現在

    CComObject<XXX>* pPtr = NULL;
    hr = CComObject<XXX>::CreateInstance( &pPtr );
    としている個所で、CreateInstance をそのまま呼び出す代わりに自分で新たに作った(中身は基本的に丸ごとコピーでよい)新たな CreateInstance を用意しておき、その中で、独自処理を挟み込めばいいと思います。

    CComObject::CreateInstance() は static メソッドなので、オーバーライドというよりは、似て非なるものを用意するというほうが正しいかな。

    実装詳細をちゃんとチェックしていないのでわからんですが、場合によっては、CComObject の派生クラスを作ってそれを pPtr の型にする必要があるかもしれません(protectedなメソッド呼び出しなどがある可能性があるため)。
    その場合は、CComObject の派生クラスを作ってそれにメソッドを用意すればいいと思います(派生じゃなくて似て非なるものでもいいですがw)。

    挙動がよくわからないという場合は、現在のオブジェクト構築の流れ(CreateInstanceLicの中)をデバッガでトレース実行してみるなどをしてみるとよいでしょう。
    その際、潜れる範囲はATLのソースもすべて潜って具体的にどういう処理をどのタイミングで行っているのかを把握しておくことをお勧めします。

    このあたり、一度がっちりと把握しておけば、かなりシビアな案件でも考えられないくらい簡単なカスタマイズで実現できるようになりますよ。

    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/
    • 回答としてマーク runta 2009年12月4日 10:00
    2009年12月3日 1:43
  • # レスを付けたけど,確認してからにします。

    稍丼 / yayadon
    2009年12月3日 5:08

  • ふつうの ::CoCreateInstance や
    CComPtr の CoCreateInstance や
    _com_ptr_t (や そのtypedef) の CreateInstance
    では,COM仕様でいうところのインタフェースポインタが返って来ます。

    一方,CComObject::CreateInstance は,
    COM仕様のクラスのインスタンス自体への普通のポインタが返って来ます。
    ふつうの生C++ポインタなので,
    これは,COM仕様として外部に公開していない
    publicなメンバにアクセスするのに使えます。

    その後,外部に渡すには,
    それに対してQueryInterface してやって
    インタフェースポインタを受け取って
    インタフェースポインタを渡してやる必要があります。

    現在は,(以下確認処理略)

    CComObject<XXX>* pPtr = NULL;
    hr = CComObject<XXX>::CreateInstance( &pPtr ); // ここを中身べた書きに変えるだけ

    // pPtr は,生C++ポインタなので,
    pPtr->QueryInterface( riid, ppvObject );  // ppvObject にCOMインタフェースポインタ取り出し

    な感じになっているんですよね?

    その場合,
    FinalConstruct() をまるっと抜かすだけでいい のなら
    とっちゃんさんが書かれているように
    上のCreateInstanceのところを変えるだけですよね。


    ------

    どうやって割り込ませているのかはわかりませんが
    それは置いておいて,以下は一般的な?話に戻してみます。


    > オーバーライドするイメージでしょうか?

    ATL_NO_VTABLE の指定があるように
    仕組み的にはいわゆるオーバーライドではなく
    たんなる上書き
    (simple override)になります。

    ですが,
    クラスに既に存在している
    ふたつオーバーロードの CreateInstance は別物で,
    それは上書きしません。

    実は,他に,
    CreateInstance は,ふたつあります。

    継承元の CComCoClass で定義されている

    DECLARE_CLASSFACTORY()
    DECLARE_AGGREGATABLE(T)

    がそれの元になっています。

    定義しているクラスが,
    クラスファクトリ(クラスオブジェクト) と
    実際のCOMクラス(CoClass) の両方を兼ねて いるので
    ふたつあります。

    で,
    その継承元の CComCoClass で定義されている

    DECLARE_CLASSFACTORY()
    DECLARE_AGGREGATABLE(T)

    は,必要に応じて自作のクラスで上書き していますよね。

    そのマクロですが,見るとわかると思いますが,
    最終的に,それぞれ typedef された

    _ClassFactoryCreatorClass や
    _CreatorClass

    に CreateInstance がそれぞれある仕組みになってます。
    そこにふたつあるわけです。

    なので,そこを入れ替えればいいわけです。

    _ClassFactoryCreatorClass::CreateInstance は,
    クラスファクトリ自身のインスタンスの作成です。

    _CreatorClass::CreateInstance は,
    オブジェクト自身(CoClass)のインスタンスを作成するものです。

    それぞれ
    DECLARE_CLASSFACTORY()
    DECLARE_AGGREGATABLE(T)
    の typedef の中で
    CComCreator の CreateInstance がそれに相当 する箇所になります。

    なので,仕掛けを入れるとしたら,
    CComCreator の自作版

    template <class T1>
    class CComCreatorSkipFinalConstruct(){...};

    のようなものを作って,
    ひとつだけあるメソッド
    static HRESULT WINAPI CreateInstance(void* pv, REFIID riid, LPVOID* ppv) //シグナチャ注意
    の中身を,
    CComCreator概存の
    マルチ・フェーズ・コンストラクション と言われている手順をまねて書きます。

    特に,
    p->InternalFinalConstructAddRef(); と
    p->InternalFinalConstructRelease();
    は一見無駄なように見えるけれど,
    おまじない なので入れておきます。


    クラスファクトリにしか使わないのなら
    CComObjectCashed< CHoge > のようにべた書きしてもいいけれど,
    元のを見るとわかるように,
    CComObject や CComObjectCashed 等を
    テンプレート引数( T1 )で受け取る形になってるので,
    単にスキップするコードを入れるだけならば,
    そこは T1 を使って同じように書くようにしておけばいいです。

    DECLARE_CLASSFACTORY() や DECLARE_CLASSFACTORY2(lic) は,
    CComCreator のテンプレート引数に CComObjectCashed を使ってますよね。
    DECLARE_AGGREGATABLE(T) や DECLARE_NOT_AGGREGATABLE(T) も
    それぞれ,CComCreator のテンプレート引数に
    CComAggObject や CComObject を使ってます。

    逆に
    テンプレートにしないでべた書きするならば,
    どこで使うか限定したような名前にした方が無難です。



    で,

    (クラスファクトリは)
    DECLARE_CLASSFACTORY() / DECLARE_CLASSFACTORY2(lic)

    (オブジェクトは)
    DECLARE_AGGREGATABLE(T) / DECLARE_NOT_AGGREGATABLE(T) 等

    で定義された

    _ClassFactoryCreatorClass や
    _CreaterClass

    の該当する方を typedef  し直します。
    そうすれば,
    該当する方の CreateInstance を差し替えたことになりますよね。



    追記:
    CreateInstanceメソッドだけ挿げ替える方法もありますが,
    やり方が悪いのかもしれませんが,
    確認したところ,なぜか二度呼ばれてしまうので
    CComCreator ごと挿げ替えた方が無難です。


    稍丼 / yayadon
    • 編集済み yayadon 2009年12月3日 12:46 追記
    • 回答としてマーク runta 2009年12月4日 9:59
    2009年12月3日 12:32
  • とっちゃんさん

    返信ありがとうございます。

    クラスファクトリは自前で持っています。
    どうやら、CreateInstanceを自前でやるのがいい感じと分かりました。

    コピーついでに、ATLの中も追ってみたいと思います。

    ありがとうございました。
    2009年12月4日 9:54
  • yayadonさん、ありがとうございます。

    > ~な感じになっているんですよね?
    そうです、まさにその通りです。

    上でも書きましたが、atlcom.hからCreateInstanceをコピペした関数を新しいCreateInstanceとして作ることにします。
    # オブジェクトの new 直後にやりたい処理を追加します

    また、一般的なCOMの話ありがとうございます。

    ちょっとすぐには全て理解できませんでしたが、折を見て調べたいと思います。大変参考になります。

    ありがとうございました。
    2009年12月4日 9:59