プログラミングの視点で言うとこれはどういうことなのでしょうか。このときはその埋め込まれたオブジェクトのアドレスが返ることになります。コンテナはこれを使用することでオブジェクトを操作できるようになります。オブジェクトを埋め込むとは、IOleObjectを実装したオブジェクトのアドレスを取得することです。コンテナもCOMオブジェクトを内部で作成することになっています。このオブジェクトは一般にサイトと呼ばれ、IOleClientSiteを実装することになっています。次に、コンテナとオブジェクトの関係を図示します。B.exeやC.exeがオブジェクトを実装するサーバーであるとします。サイトを表すオブジェクトは埋め込んだオブジェクトの数だけ作成します。コンテナは1つ以上のサイトのポインタを維持することになります。コンテナやサイトのオブジェクトを作成するためには、そのオブジェクトの型となるクラスが必要になります。今回はコンテナのためにCContainerというクラスを定義し、サイトのためにCSiteというクラスを定義しています。メニューから「オブジェクトの挿入」という項目が選択された場合は、InsertObjectというメソッドを呼び出すことになっています。このメソッドでは、オブジェクトを埋め込むためのダイアログを表示します。OleUIInsertObjectの呼び出しに必要なOLEUIINSERTOBJECT構造体に注目します。一部のメンバには適切な値を指定する必要があります。cbStructは構造体のサイズを指定します。「アイコンの表示」というチェックボックスを非表示にできます。IOF_CREATENEWOBJECTによって「新規作成」で選択されたオブジェクトが実際に作成されます。「ファイルから作成」でもオブジェクトを作成できるようにするためには、IOF_CREATEFILEOBJECTも指定します。IOF_DISABLELINKを指定しているのは、ファイルから作成する際に「リンク」を選択できないようにするためです。hWndOwnerは、ダイアログの親ウインドウにするハンドルを指定します。lpszFileにはファイルパスを受け取る変数を指定し、cchFileにはその変数のサイズを指定します。iidは、オブジェクトに要求するインターフェースのIIDを指定します。今回はオブジェクトをIOleObjectで識別したいため、IID_IOleObjectを指定しています。oleRenderは、オブジェクトのレンダリングオプションを指定します。0を指定した場合はオブジェクトが描画されなくなるため、OLERENDER_DRAWを指定するようにします。lpIStorageは、オブジェクトのために使用する構造化ストレージを指定します。事前にm_pRootStorage->CreateStorageを呼び出しているのはこのためです。ppvObjは、オブジェクトを受け取る変数のアドレスを指定します。オブジェクトを作成したら、オブジェクトが1つ増えたということでm_nObjectCountをカウントします。これは複数個存在する可能性があるため、リスト構造として管理するようにします。そのオブジェクトのm_pNextにCSiteを格納します。Initializeを呼び出せば、CSiteにオブジェクトの情報を渡すことができます。*lprc;elseUpdateRect();}引数として与えられたIOleObjectとIStorageは後で必要になるため、メンバ変数に保存しておきます。IOleObject::SetClientSiteを呼び出すことで、オブジェクトに対してIOleClientSiteを実装したオブジェクト(CSite)を渡すことができます。これにより、オブジェクトはIOleClientSiteを通じてCSiteと通信できることになります。IOleObject::SetHostNamesを呼び出せば、作成されたオブジェクトの名前を第2引数から渡すことができます。この名前は、オブジェクトのウインドウのメニューで表示されることがあります。第1引数はコンテナの名前を指定しますが、これはコンテナのウインドウの名前などを指定すればよいと思われます。m_rcにはオブジェクトの位置を格納しますが、lprcが与えられていない場合はUpdateRectという自作メソッドで初期化します。sizel.cy;}オブジェクトのサイズは、IOleObject::GetExtentを呼び出すことで取得できます。これをデバイス単位に変換するためにHIMETRICtoDPという自作メソッドを呼び出しています。変換が終了したら、位置とサイズを足すことでrightとbottomを初期化します。その通知がどのような意味を持つのかを理解しておく必要があります。各メソッドの処理を順に見ていきます。S_OK;}SaveObjectは、オブジェクトのウインドウを閉じた場合や、メニューからオブジェクトを更新した場合に呼ばれます。Saveという自作関数でそれを行っています。この関数の内部については後で取り上げます。自作関数のUpdateRectを呼び出しているのは、オブジェクトのサイズを更新するためです。ここでそれを確認するようにしています。InvalidateRectで再描画を促します。E_NOTIMPL;}GetMonikerは、コンテナ内のオブジェクトの識別するモニカを取得する場合に呼ばれます。今回は実装しないということで、E_NOTIMPLを返しています。E_NOINTERFACE;}GetContainerは、IOleObject::SetClientSiteの内部で呼ばれることが多いようです。今回のコンテナはIOleContainerを実装していません。よって、ppContainerにNULLを格納してE_NOINTERFACEを返しています。コンテナに埋め込まれたオブジェクトの列挙をサポートできます。S_OK;}ShowObjectは、オブジェクトのウインドウを表示する段階になると呼ばれます。基本的には、IOleObject::DoVerbの内部で呼ばれていると考えてよいでしょう。行うことがない場合はS_OKを返すだけで構いません。S_OK;}OnShowWindowは、ウインドウの表示または非表示が完了した場合に呼ばれます。それが完了した場合にOnShowWindowが呼ばれることになります。InvalidateRectで再描画を促しているのは、オブジェクトのイメージにハッチパターンを描画するためです。現在の表示状態をメンバ変数に保存している理由については、次節で説明します。E_NOTIMPL;}RequestNewObjectLayoutは、呼ばれることがないようなのでE_NOTIMPLを返しています。多くの通知を必要とする場合はIAdviseSinkを実装することもできます。以下、IAdviseSinkのメソッドを順に示します。*pStgmed){}OnDataChangeは、オブジェクトの内部データ(Excelにおけるセルの値など)が変更された場合に呼ばれます。これを検出する場合は、事前にIDataObject::DAdviseを呼び出しておく必要があります。IDataObjectは、m_pOleObject->QueryInterfaceから取得することができます。TRUE);}OnViewChangeは、オブジェクトの外観が変更された場合に呼ばれます。InvalidateRectを呼び出すようにしています。UpdateRectでサイズも更新しておきます。OnViewChangeを検出する場合は、事前にIViewObject::SetAdviseを呼び出しておく必要があります。CSite::OnClose(){}OnRenameは、リンクオブジェクトが以前と異なる場所に保存された際に呼ばれます。このメソッドが呼ばれることはないはずです。OnSaveは、オブジェクトが保存された際に呼ばれます。これは、IOleClientSite::SaveObjectが制御を返した後に相当します。OnCloseは、オブジェクトのウインドウが閉じられた際に呼ばれます。これらのメソッドの呼び出しを検出する場合は、事前にIOleObject::Adviseを呼び出す必要があります。示したIOleClientSite::SaveObjectでは、Saveという自作関数を呼び出していました。この関数では、オブジェクトに対してデータを保存する機会を与えています。FALSE);pPersistStorage->SaveCompleted(NULL);pPersistStorage->Release();}オブジェクトにデータを保存させるためには、IPersistStorage::Saveを呼び出す必要があります。m_pOleObjectに対してQueryInterfaceを取得すれば問題なく取得することができます。IPersist::GetClassIDとWriteClassStgを呼び出してくれるからです。データの保存とオブジェクトのCLSIDの書き込みを同時に行えます。第1引数はIPersistStorageインターフェースであり、第2引数はデータの保存先とする構造化ストレージを指定します。第3引数は、データを一から全て保存することを示すFALSEでよいでしょう。OleSaveの呼び出しが終われば、保存が完了したことを伝えるためにSaveCompletedを呼び出します。コンテナのウインドウが閉じられる場合は、オブジェクトのプロセスも終了するべきといえます。このような場合は、Closeという自作メソッドが呼ばれます。{m_pOleObject->Close(OLECLOSE_PROMPTSAVE);m_pOleObject->Unadvise(m_dwConnection);}}OleIsRunningは、内部でIRunnableObject::IsRunningを呼び出すことによって、オブジェクトが実行中であるかを確認します。そのような場合は終了処理を行うわけにはいきません。実行中である場合はIOleObject::Closeを呼び出すことで、プロセスを終了させるようにします。OLECLOSE_SAVEIFDIRTYはオブジェクトがダーティ状態である場合にデータを保存することを意味します。OLECLOSE_PROMPTSAVEは保存するかを確認するためのUIを促します。上記ではOLECLOSE_NOSAVEを指定していますが、多くの場合はこれが妥当であると思われます。そのような場合にオブジェクトのデータが構造化ストレージに保存されても意味を持たないからです。これが行えないことには構造化ストレージを更新しても意味がありません。これをCloseの前に呼び出した場合はIAdviseSink::OnCloseが送られないことになります。そのオブジェクトのイメージやウインドウを自作のアプリケーションで表示することを意味します。埋め込み元となるオブジェクトが常に存在しなければならないように思えますが、実際にはそうとは限りません。その際にはオブジェクトを作成したアプリケーションは既に終了しています。これはデフォルトハンドラが作成したオブジェクトです。実を言うと、アプリケーションが識別しているのは常にデフォルトハンドラのオブジェクトです。デフォルトハンドラの正体はole32.dllであり、オブジェクトはOleCreateDefaultHandlerで作成されています。このオブジェクトが埋め込み元のオブジェクトのIOleObjectに処理を通知するのです。デフォルトハンドラという仲介が存在する理由は、オブジェクトが常に埋め込まれているように見せかけるためです。オブジェクトを再び実行することもてきなくなります。このオブジェクトを通じて、埋め込み元のオブジェクトを再び実行することができます。IRunnableObjectとIViewObject2を実装するようになっています。IViewObject2はオブジェクトのイメージの描画に使用されます。アプリケーションが維持しているIOleObjectが、埋め込み元のオブジェクトを直接識別していないことを物語っています。埋め込み元オブジェクトが終了しているのにも関わらず、これはどのように実現すればよいのでしょうか。実は、ここではCreateDataCacheで作成されるデータキャッシュと呼ばれるオブジェクトが使用されています。データキャッシュのIViewObjectを使用してイメージを描画できるようになります。オブジェクトを埋め込めるといっても、常にデフォルトハンドラのオブジェクトが使用されるとは限りません。代わりに独自のハンドラを指定することも許されています。このような独自のハンドラを作成すれば、イメージのキャッシュなどを拡張することができるようになるでしょう。オブジェクトをインプロセスサーバーとして作成する場合は、ハンドラという概念自体がなくなります。これによってオブジェクトの実体がコンテナ上で作成されるからです。ActiveXコントロールを作成する場合は、オブジェクトをインプロセスサーバーとして実装することになります。