テクニカル ノート 41: MFC/OLE1 から MFC/OLE 2 への移植
更新 : 2007 年 11 月
メモ : |
---|
次のテクニカル ノートは、最初にオンライン ドキュメントの一部とされてから更新されていません。結果として、一部のプロシージャおよびトピックが最新でないか、不正になります。最新の情報について、オンライン ドキュメントのキーワードで関係のあるトピックを検索することをお勧めします。 |
移植に関する一般的な問題点
MFC 2.5 以降に付属する OLE 2 のクラス デザインの指針の 1 つは、MFC 2.0 付属の OLE 1.0 がサポートしていた機能をほぼ同じ形で維持するということです。この結果、MFC 2.0 と同じ OLE クラス (COleDocument、COleServerDoc、COleClientItem、COleServerItem) が現在のバージョンの MFC にも含まれます。これらのクラスには、API もほとんど同じ形で残されています。OLE 2 は OLE 1.0 とは大幅に異なるため、MFC も細かい部分では変更点がありますが、MFC 2.0 の OLE 1 に慣れているプログラマは、OLE 2 でも従来の知識を役立てることができます。
既存の MFC/OLE1 アプリケーションに OLE 2 機能を付加する場合は、このテクニカル ノートの内容が参考になります。このテクニカル ノートでは、OLE1 の機能を MFC/OLE 2 に移植するときに発生する一般的な問題点について説明します。また、MFC 2.0 付属の 2 つのアプリケーション (MFC OLE のサンプル OCLIENT と HIERSVR) を例にとってさらに補足して説明します。
MFC のドキュメント ビュー アーキテクチャ
移植するアプリケーションが MFC のドキュメント ビュー機構を使用していない場合は、移植するときにドキュメント ビュー機構を組み込んでおくことをお勧めします。MFC OLE 2 クラスの利点は、これらの機能と組み合わせることによって最大限に活用できます。
MFC の機能を使用せずにオブジェクト アプリケーションやコンテナを作成することも可能ですが、あまりお勧めしません。
MFC の実装の利用
CToolBar、CStatusBar、CScrollView などの MFC 組み込み実装を持つクラスは、OLE 2 をサポートするために特殊な条件分岐コードを組み込んでいます。このため、これらのクラスを使用して作成したアプリケーションは OLE を認識できます。ここでも、同じような機構を持つ "自動判別" クラスを独自に作成することは可能ですが、このような方法はお推めしません。同様の機能を独自に作成するときは、OLE の機能の細部を利用している MFC のソース コードが参考になります。このときは、特に埋め込み先編集の有効化に関する処理に注意してください。
MFC サンプル コードの内容
以下に、OLE 機能を持つ MFC サンプル アプリケーションを示します。これらのアプリケーションはそれぞれ異なる角度から OLE 機能を利用しています。
HIERSVR このアプリケーションは、サーバー アプリケーション用に設計されています。このアプリケーションは MFC 2.0 では MFC/OLE1 アプリケーションとして用意されましたが、現在では MFC/OLE2 に移植され、OLE 2 対応の各機能を取り入れて拡張されています。
OCLIENT スタンドアロン コンテナ アプリケーションです。このアプリケーションは、コンテナとしてさまざまな OLE 機能を利用しています。このアプリケーションも MFC 2.0 から移植されたもので、カスタム クリップボード形式や埋め込みアイテムへのリンクなど、新しい OLE 機能を利用して拡張されています。
DRAWCLI このアプリケーションは OCLIENT と同じように OLE コンテナをサポートしています。ただし、アプリケーションは既存のオブジェクト指向描画プログラムのフレームワークを使用して作成されています。このアプリケーションは既存のプログラムに OLE コンテナのサポート機能を付加した例です。
SUPERPAD このアプリケーションは完全なスタンドアロン アプリケーションであると同時に、OLE サーバーとしても使用できます。ただし、サーバーとしての機能は最低限のものだけをサポートしています。このサンプル アプリケーションで重要なのは、OLE のクリップボード サービスを利用してデータをクリップボードにコピーしている点と、それに対してクリップボード貼り付けの機能は Windows の "エディット" コントロールを利用している点です。このサンプル アプリケーションは、従来の Windows API と OLE の API を組み合わせて利用した例です。
各サンプル アプリケーションの詳細については、『MFC サンプル』のヘルプを参照してください。
移植例 : MFC 2.0 OCLIENT の移植
前のセクションで説明したように、OCLIENT は MFC 2.0 に付属しており、MFC/OLE1 の機能を利用しています。ここでは、このアプリケーションで MFC/OLE 2 をクラスを使用できるように移植するための基本的な手順を説明します。この移植によって MFC/OLE クラスの特徴を利用した数多くの機能を付け加えることができます。ここでは個別の機能については説明しません。拡張された機能については、サンプル アプリケーションを調べてみてください。
メモ : |
---|
コンパイラ エラーおよび詳細なプロセスは、Visual C++ 2.0 で作成されたものです。エラー メッセージとエラーの発生位置については一部 Visual C++ 4.0 で変更されているものもありますが、基本的な意味は変わりません。 |
移植の方針
OCLIENT サンプルを MFC/OLE 対応に移植するには、まずアプリケーションをビルドし、発生するコンパイラ エラーを 1 つずつ修正していきます。実際に MFC 2.0 の OCLIENT を新しいバージョンの MFC を使用してコンパイルすると、修正する必要のあるエラーはそれほど多くないことがわかります。次に、これらのエラーを発生順に示します。
コンパイルとエラーの修正
\oclient\mainview.cpp(104) : error C2660: 'Draw' : function does not take 4 parameters
最初に発生するエラーは COleClientItem::Draw に関連しています。MFC/OLE1 のこの関数は、MFC/OLE よりも多くのパラメータを使用します。MFC/OLE 版にないパラメータは多くの場合使用されず、普通はこの例のように NULL になっています。新しい MFC では、描画する CDC がメタファイル DC のときは、lpWBounds 値は自動的に決定されます。また、新しい MFC ではパラメータ pFormatDC は不要です。この情報は、渡された pDC の "属性 DC" からフレームワークが自動的に作成します。このエラーを修正するには、Draw 呼び出しに含まれる余分な (NULL 値が渡されている) 2 つのパラメータを除去します。
\oclient\mainview.cpp(273) : error C2065: 'OLE_MAXNAMESIZE' : undeclared identifier
\oclient\mainview.cpp(273) : error C2057: expected constant expression
\oclient\mainview.cpp(280) : error C2664: 'CreateLinkFromClipboard' : cannot convert parameter 1 from 'char [1]' to 'enum ::tagOLERENDER '
\oclient\mainview.cpp(286) : error C2664: 'CreateFromClipboard' : cannot convert parameter 1 from 'char [1]' to 'enum ::tagOLERENDER '
\oclient\mainview.cpp(288) : error C2664: 'CreateStaticFromClipboard' : cannot convert parameter 1 from 'char [1]' to 'enum ::tagOLERENDER '
これらのエラーは、MFC/OLE1 関数 COleClientItem::CreateXXXX の呼び出し時に、一意なアイテム名を指定しなかったために発生します。これはこの関数の基底となる OLE API による制限です。OLE2 は通信に DDE を使用しないため、MFC/OLE2 ではこの制限はありません (ここで指定される名前は DDE 処理で必要だったものです)。この点を修正するには、関数 CreateNewName とこの関数を参照している部分を削除します。これらの関数に置き換えられる MFC/OLE 関数がどのような処理を行うかを調べるには、関数呼び出しが記述されている位置にカーソルを移動して F1 キーを押します。
もう 1 つの大きな変更点は、OLE2 におけるクリップボード処理です。OLE1 では Windows のクリップボード API を使用してクリップボードとデータをやり取りしていました。OLE2 ではこれとは異なる機構が使用されます。MFC/OLE1 の API では COleClientItem オブジェクトをクリップボードにコピーする前に、クリップボードが開いていることが前提となっていました。この前提は現在は不要で、クリップボードが閉じていると MFC/OLE のすべてのクリップボード操作が失敗します。CreateNewName に関係するコードを除去すると共に、Windows クリップボードを開く部分と閉じる部分のコードを除去する必要があります。
\oclient\mainview.cpp(332) : error C2065: 'AfxOleInsertDialog' : undeclared identifier
\oclient\mainview.cpp(332) : error C2064: term does not evaluate to a function
\oclient\mainview.cpp(344) : error C2057: expected constant expression
\oclient\mainview.cpp(347) : error C2039: 'CreateNewObject' : is not a member of 'CRectItem'
これらのエラーは CMainView::OnInsertObject ハンドラに起因します。[挿入] メニューの [新規オブジェクト] コマンド ハンドラも新しい OLE で変更された部分の 1 つです。このエラーを修正する最も簡単な方法は、元のコードと AppWizard が OLE コンテナ アプリケーション用に生成した新しいコードをつなぎ合わせることです。この手法はほかのアプリケーションを移植するときにも使用できます。MFC/OLE1 では、AfxOleInsertDialog を呼び出し [オブジェクトの挿入] ダイアログ ボックスを表示するようになっていました。新しい OLE では COleInsertObject ダイアログ オブジェクトを作成し、DoModal を呼び出します。また、クラス名文字列ではなく CLSID を持つ新しい OLE アイテムが作成されます。これらの変更の結果、コードは次のようになります。
COleInsertDialog dlg;
if (dlg.DoModal() != IDOK)
return;
BeginWaitCursor();
CRectItem* pItem = NULL;
TRY
{
// First create the C++ object
pItem = GetDocument()->CreateItem();
ASSERT_VALID(pItem);
// Initialize the item from the dialog data.
if (!dlg.CreateItem(pItem))
AfxThrowMemoryException();
// any exception will do
ASSERT_VALID(pItem);
// run the object if appropriate
if (dlg.GetSelectionType() ==
COleInsertDialog::createNewItem)
pItem->DoVerb(OLEIVERB_SHOW, this);
// update right away
pItem->UpdateLink();
pItem->UpdateItemRectFromServer();
// set selection to newly inserted item
SetSelection(pItem);
pItem->Invalidate();
}
CATCH (CException, e)
{
// clean up item
if (pItem != NULL)
GetDocument()->DeleteItem(pItem);
AfxMessageBox(IDP_FAILED_TO_CREATE);
}
END_CATCH
EndWaitCursor();
メモ : |
---|
新規オブジェクトの挿入は、アプリケーションによって異なる可能性があります。 |
<afxodlgs.h> もインクルードする必要があります。このファイルは COleInsertObject ダイアログ クラスなどの MFC 標準ダイアログを宣言します。
\oclient\mainview.cpp(367) : error C2065: 'OLEVERB_PRIMARY' : undeclared identifier
\oclient\mainview.cpp(367) : error C2660: 'DoVerb' : function does not take 1 parameters
このエラーは、OLE1 のいくつかの定数値が OLE2 では変更されているために起こります。ただしこれらの定数の意味は変更されていません。例では、OLEVERB_PRIMARY が OLEIVERB_PRIMARY に変更されたことが原因です。OLE1 と OLE2 では、ユーザーがアイテムをダブルクリックすると、主動詞がコンテナによって実行されます。
また、新しい OLE では DoVerb のパラメータが 1 つ追加されています。追加されたパラメータはビューへのポインタ (CView*) です。このパラメータはビジュアル編集 (埋め込み先編集の有効化) を行うときだけ使用されます。この例ではビジュアル編集機能は使用しないため、パラメータは NULL にします。
フレームワークが埋め込み先編集の有効化処理を起動しないことを保証するために、COleClientItem::CanActivate を次のようにオーバーライドします。
BOOL CRectItem::CanActivate()
{
return FALSE;
}
\oclient\rectitem.cpp(53) : error C2065: 'GetBounds' : undeclared identifier
\oclient\rectitem.cpp(53) : error C2064: term does not evaluate to a function
\oclient\rectitem.cpp(84) : error C2065: 'SetBounds' : undeclared identifier
\oclient\rectitem.cpp(84) : error C2064: term does not evaluate to a function
MFC/OLE1 では COleClientItem::GetBounds および SetBounds を使用してアイテムのサイズを操作していました (メンバ left および top は常に 0)。MFC/OLE2 では、この機能は COleClientItem::GetExtent および SetExtent によってサポートされます。これらの関数は SIZE と CSize を直接操作します。
移植後の SetItemRectToServer と UpdateItemRectFromServer の呼び出しは次のようになります。
BOOL CRectItem::UpdateItemRectFromServer()
{
ASSERT(m_bTrackServerSize);
CSize size;
if (!GetExtent(&size))
return FALSE; // blank
// map from HIMETRIC to screen coordinates
{
CClientDC screenDC(NULL);
screenDC.SetMapMode(MM_HIMETRIC);
screenDC.LPtoDP(&size);
}
// just set the item size
if (m_rect.Size() != size)
{
// invalidate the old size/position
Invalidate();
m_rect.right = m_rect.left + size.cx;
m_rect.bottom = m_rect.top + size.cy;
// as well as the new size/position
Invalidate();
}
return TRUE;
}
BOOL CRectItem::SetItemRectToServer()
{
// set the official bounds for the embedded item
CSize size = m_rect.Size();
{
CClientDC screenDC(NULL);
screenDC.SetMapMode(MM_HIMETRIC);
screenDC.DPtoLP(&size);
}
TRY
{
SetExtent(size); // may do a wait
}
CATCH(CException, e)
{
return FALSE; // links will not allow SetBounds
}
END_CATCH
return TRUE;
}
\oclient\frame.cpp(50) : error C2039: 'InWaitForRelease' : is not a member of 'COleClientItem'
\oclient\frame.cpp(50) : error C2065: 'InWaitForRelease' : undeclared identifier
\oclient\frame.cpp(50) : error C2064: term does not evaluate to a function
MFC/OLE1 は基本的に非同期な動作をするため、コンテナからサーバーへの同期的な API 呼び出しはシミュレートされていました。このためユーザーからのコマンドを実行する前に、非同期呼び出しがあるかをチェックする必要がありました。これを行っていたのは MFC/OLE1 関数 COleClientItem::InWaitForRelease です。MFC/OLE2 ではこの処理は不要なので、CMainFrame の OnCommand オーバーライドは削除できます。
以上の修正により、OCLIENT のコンパイルとリンクは正常に終了します。
そのほかの必要な変更点
OCLIENT の実行を妨げる問題点は、上記以外にほとんどありません。ただし、次の点を早期に修正しておくことをお勧めします。
まず、OLE ライブラリの初期化が必要です。これは InitInstance から AfxOleInit を呼び出すことで実行します。
if (!AfxOleInit())
{
AfxMessageBox("Failed to initialize OLE libraries");
return FALSE;
}
また、仮想関数のパラメータリストが変更されていないかを調べることをお勧めします。このような関数としては COleClientItem::OnChange があります。この関数はあらゆる MFC/OLE コンテナ アプリケーションでオーバーライドされます。オンライン ドキュメントを調べると、新しく 'DWORD dwParam' が追加されていることがわかります。修正後の CRectItem::OnChange は次のようになります。
void
CRectItem::OnChange(OLE_NOTIFICATION wNotification, DWORD dwParam)
{
if (m_bTrackServerSize &&
!UpdateItemRectFromServer())
{
// Blank object
if (wNotification == OLE_CLOSED)
{
// no data received for the object - destroy it
ASSERT(!IsVisible());
GetDocument()->DeleteItem(this);
return; // no update (item is gone now)
}
}
if (wNotification != OLE_CLOSED)
Dirty();
Invalidate(); // any change will cause a redraw
}
MFC/OLE1 のコンテナ アプリケーションでは、ドキュメント クラスは COleClientDoc から派生されます。MFC/OLE2 ではこのクラスは削除され、COleDocument に置き換えられています (新しいクラス編成では、コンテナ/サーバー アプリケーションのビルドが容易になるように設計されています)。OCLIENT では、MFC/OLE1 から MFC/OLE2 に移植するために、COleClientDoc を COleDocument に変換する #define 行が追加されています。COleClientDoc にあって COleDocument でサポートされない機能の 1 つは、標準コマンド メッセージ マップ エントリです。この機能が廃止されたのは、サーバー アプリケーションで間接的に COleDocument が使用されるため、コンテナ/サーバー アプリケーションを作成しないときは、これらのコマンド ハンドラによるオーバーヘッドを回避するためです。このため CMainDoc メッセージ マップに次のエントリを追加する必要があります。
ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdatePasteMenu)
ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE_LINK, OnUpdatePasteLinkMenu)
ON_UPDATE_COMMAND_UI(ID_OLE_EDIT_LINKS, OnUpdateEditLinksMenu)
ON_COMMAND(ID_OLE_EDIT_LINKS, COleDocument::OnEditLinks)
ON_UPDATE_COMMAND_UI(ID_OLE_VERB_FIRST, OnUpdateObjectVerbMenu)
ON_UPDATE_COMMAND_UI(ID_OLE_EDIT_CONVERT, OnUpdateObjectVerbMenu)
ON_COMMAND(ID_OLE_EDIT_CONVERT, OnEditConvert)
これらのコマンドの実装はすべて COleDocument に含まれています。クラスはすべてのドキュメント クラスの基本クラスです。
以上の修正によって、OCLIENT は OLE コンテナ アプリケーションの機能を持ちます。任意のタイプ (OLE1/OLE2) のアイテムを挿入することが可能です。埋め込み先編集の有効化を行うために必要なコードは実装されていないので、OLE1 と同じようにアイテムは独立したウィンドウの中で編集されます。埋め込み先編集 (ビジュアル編集ともいう) を実現するために必要な修正点については、次のセクションで説明します。
"ビジュアル編集" 機能の追加
OLE の最も有用な機能の 1 つは埋め込み先編集の有効化 (ビジュアル編集) です。この機能によって、コンテナ アプリケーションのユーザー インターフェイスの一部を介し、サーバー アプリケーションを使用できます。これによってユーザーは "継ぎ目のない" 編集操作が可能になります。OCLIENT で埋め込み先編集の有効化を可能にするには、特別なリソースとコードを追加する必要があります。通常、追加するリソースとコードは AppWizard によって生成されます。実際、ここで使用するコードの大半は AppWizard を使用して作成したコンテナ アプリケーションから流用しています。
まず、アイテムに対して埋め込み先編集の有効化を行うときに表示するメニュー リソースを追加します。このメニュー リソースは Visual C++ の中で IDR_OCLITYPE リソースをコピーし、そこから [ファイル] と [ウィンドウ] 以外のポップアップ メニューを削除して作成します。[ファイル] と [ウィンドウ] の間には 2 つの区切り記号を挿入してメニュー グループを分割します。"ファイル | | ウィンドウ" のようになります。区切り記号の意味や、オブジェクト アプリケーションとコンテナのメニューの合成については、『Visual C++ の概念 : 機能の追加』の「メニューとリソース : メニューの結合」を参照してください。
次に、作成したメニューをフレームワークに教えます。これはドキュメント テンプレートに対して CDocTemplate::SetContainerInfo を呼び出すことによって行います。この関数呼び出しは InitInstance 中でメニューをドキュメント テンプレート リストに追加する前に行います。ドキュメント テンプレートを登録するコードは次のようになります。
CDocTemplate* pTemplate = new CMultiDocTemplate(
IDR_OLECLITYPE,
RUNTIME_CLASS(CMainDoc),
RUNTIME_CLASS(CMDIChildWnd), // standard MDI child frame
RUNTIME_CLASS(CMainView));
pTemplate->SetContainerInfo(IDR_OLECLITYPE_INPLACE);
AddDocTemplate(pTemplate);
リソース IDR_OLECLITYPE_INPLACE は、Visual C++ を使用して埋め込み先編集用に作成したリソースです。
埋め込み先編集の有効化を行うには、CView 派生クラス (CMainView) と COleClientItem 派生クラス (CRectItem) にも変更を追加する必要があります。これらのオーバーライドは AppWizard によって行われます。ここで使用する機能の大半は AppWizard が既定で生成するアプリケーションから直接流用できます。
移植の最初の段階では、COleClientItem::CanActivate のオーバーライドによって埋め込み先編集の有効化全体が無効になっています。埋め込み先編集の有効化を行うには、このオーバーライドを除去します。次に、DoVerb 呼び出しはすべて (2 か所あります) NULL を渡します。ここで本来渡すべきビューは、埋め込み先編集の有効化のためだけに必要なものです。埋め込み先編集の有効化を完全にサポートするには、これらの DoVerb 呼び出しに適切なビューを渡すようにします。DoVerb 呼び出しの 1 つは CMainView::OnInsertObject にあります。これは次のようになります。
pItem->DoVerb(OLEIVERB_SHOW, this);
もう 1 つの DoVerb 呼び出しは CMainView::OnLButtonDblClk にあります。
m_pSelection->DoVerb(OLEIVERB_PRIMARY, this);
COleClientItem::OnGetItemPosition のオーバーライドも必要です。この関数は、アイテムに対して埋め込み先編集の有効化を行うとき、コンテナ ウィンドウを基準とする相対的な表示位置をサーバー アプリケーションに伝えます。OCLIENT では次のように記述します。
void CRectItem::OnGetItemPosition(CRect& rPosition)
{
rPosition = m_rect;
}
ほとんどのサーバーは "埋め込み先サイズ変更" と呼ばれる機能を持っています。これは、埋め込み先編集中にサーバー ウィンドウの位置とサイズを変更できる機能です。ウィンドウの位置とサイズを変更するとコンテナ ドキュメント自体にも反映されるので、コンテナもこの処理の一部を担当する必要があります。OCLIENT では m_rect を使用して変更後の位置とサイズを調整します。
BOOL CRectItem::OnChangeItemPosition(const CRect& rectPos)
{
ASSERT_VALID(this);
if (!COleClientItem::OnChangeItemPosition(rectPos))
return FALSE;
Invalidate();
m_rect = rectPos;
Invalidate();
GetDocument()->SetModifiedFlag();
return TRUE;
}
以上の修正によって、埋め込み先編集の有効化とアイテムのサイズと位置の変更がサポートされるようになりますが、埋め込み先編集の有効化を終了するための処理が実装されていません。サーバー アプリケーションによっては Esc キー入力を検出して終了を処理できるものもありますが、アイテムの編集を終了する 2 つの方法をコンテナ側で用意することをお勧めします。この 2 つの方法とは (1) アイテム以外の部分をクリックする方法、(2) Esc キーを押す方法です。
Esc キーの処理は、Visual C++ を使用してアクセラレータ テーブルに VK_ESCAPE キーと ID_CANCEL_EDIT コマンドの対応を登録してサポートされます。このコマンドのハンドラは次のようになります。
// The following command handler provides the standard
// keyboard user interface to cancel an in-place
// editing session.void CMainView::OnCancelEdit()
{
// Close any in-place active item on this view.
COleClientItem* pActiveItem =
GetDocument()->GetInPlaceActiveItem(this);
if (pActiveItem != NULL)
pActiveItem->Close();
ASSERT(GetDocument()->GetInPlaceActiveItem(this) == NULL);
}
ユーザーがアイテム以外の場所をクリックしたときの処理は、CMainView::SetSelection の先頭に次のコードを追加することによって実現します。
if (pNewSel != m_pSelection || pNewSel == NULL)
{
COleClientItem* pActiveItem =
GetDocument()->GetInPlaceActiveItem(this);
if (pActiveItem != NULL && pActiveItem != pNewSel)
pActiveItem->Close();
}
埋め込み先編集の有効化が行われるアイテムにフォーカスを与える必要があります。次のように OnSetFocus を使用し、ビューがフォーカスを受け取ると、アクティブなアイテムにフォーカスが与えられるようにします。
// Special handling of OnSetFocus and OnSize are required
// when an object is being edited in-place.
void CMainView::OnSetFocus(CWnd* pOldWnd)
{
COleClientItem* pActiveItem =
GetDocument()->GetInPlaceActiveItem(this);
if (pActiveItem != NULL &&
pActiveItem->GetItemState() == COleClientItem::activeUIState)
{
// need to set focus to this item if it is same view
CWnd* pWnd = pActiveItem->GetInPlaceWindow();
if (pWnd != NULL)
{
pWnd->SetFocus(); // don't call the base class
return;
}
}
CView::OnSetFocus(pOldWnd);
}
ビューのサイズが変更されたときは、アクティブなアイテムにクリッピング領域の変更を通知する必要があります。このために OnSize ハンドラを作成します。
void CMainView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
COleClientItem* pActiveItem =
GetDocument()->GetInPlaceActiveItem(this);
if (pActiveItem != NULL)
pActiveItem->SetItemRects();
}
移植例 : MFC 2.0 HIERSVR の移植
もう 1 つの MFC 2.0 付属サンプル アプリケーション HIERSVR も、MFC/OLE1 を使用して OLE を実装しています。ここではこのアプリケーションを MFC/OLE2 対応に変換する基本的な方法を簡単に説明します。この移植によって MFC/OLE2 クラスの特徴を利用した数多くの機能が付加されます。ここでは個別の機能については説明しません。拡張された機能については、サンプル アプリケーションを調べてみてください。
メモ : |
---|
コンパイラ エラーおよび詳細なプロセスは、Visual C++ 2.0 で作成されたものです。エラー メッセージとエラーの発生位置については一部 Visual C++ 4.0 で変更されているものもありますが、基本的な意味は変わりません。 |
移植の方針
HIERSVR を MFC/OLE 対応に移植するには、まずアプリケーションをビルドし、発生するコンパイラ エラーを 1 つずつ修正していきます。実際に MFC 2.0 の HIERSVR を新しいバージョンの MFC を使用してコンパイルすると、修正する必要のあるエラーは多くないことがわかります (ただし OCLIENT の場合よりは多くなります)。次に、これらのエラーを発生順に示します。
コンパイルとエラーの修正
\hiersvr\hiersvr.cpp(83) : error C2039: 'RunEmbedded' : is not a member of 'COleTemplateServer'
最初に発生するエラーはサーバーの InitInstance 関数に関連しています。OLE サーバーの初期化処理は、MFC/OLE1 アプリケーションを移植する場合に大幅な変更が必要になる処理です。これには、AppWizard が生成した OLE サーバーのコードを流用して、それに必要な変更を追加する方法が最も簡単です。この方法を使用するときの注意点を次に挙げます。
AfxOleInit を呼び出して、OLE ライブラリを初期化する必要があります。
ドキュメント テンプレート オブジェクトに対して SetServerInfo を呼び出し、CDocTemplate コンストラクタで設定できないサーバー リソース ハンドルやランタイム クラス情報を設定します。
コマンド ラインで /Embedding を指定したときは、アプリケーションのメイン ウィンドウを表示しないようにします。
ドキュメントにグローバル一意識別子 (GUID: Globally Unique Identifier) を与える必要があります。GUID はドキュメント タイプに割り当てる 128 ビットの識別子です。AppWizard は GUID を 1 つ生成します。AppWizard のコードを流用するときは、この GUID を借用します。これ以外の場合は、フォルダ \BIN にあるユーティリティ GUIDGEN.EXE を使用して GUID を生成します。
COleTemplateServer::ConnectTemplate を呼び出して、COleTemplateServer オブジェクトをドキュメント テンプレートに "接続" する必要があります。
アプリケーションをスタンドアロンで実行するときはシステム レジストリを更新します。ユーザーがアプリケーションの .EXE ファイルを移動すると、移動後のアプリケーションを起動することにより、Windows のシステム登録データベースは新しい位置を指すように更新されます。
AppWizard が生成した InitInstance を基にしてこれらの変更を追加すると、HIERSVR の InitInstance (とそれに関連する GUID) は次のようになります。
// this is the GUID for HIERSVR documents
static const GUID BASED_CODE clsid =
{ 0xA0A16360L, 0xC19B, 0x101A, { 0x8C, 0xE5, 0x00, 0xDD, 0x01, 0x11, 0x3F, 0x12 } };
/////////////////////////////////////////////////////////////////////////////
// COLEServerApp initialization
BOOL COLEServerApp::InitInstance()
{
// OLE 2 initialization
if (!AfxOleInit())
{
AfxMessageBox("Initialization of the OLE failed!");
return FALSE;
}
// Standard initialization
LoadStdProfileSettings(); // Load standard INI file options
// Register document templates
CDocTemplate* pDocTemplate;
pDocTemplate = new CMultiDocTemplate(IDR_HIERSVRTYPE,
RUNTIME_CLASS(CServerDoc),
RUNTIME_CLASS(CMDIChildWnd),
RUNTIME_CLASS(CServerView));
pDocTemplate->SetServerInfo(IDR_HIERSVRTYPE_SRVR_EMB);
AddDocTemplate(pDocTemplate);
// create main MDI Frame window
CMainFrame* pMainFrame = new CMainFrame;
if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
return FALSE;
m_pMainWnd = pMainFrame;
SetDialogBkColor(); // gray look
// enable file manager drag/drop and DDE Execute open
m_pMainWnd->DragAcceptFiles();
EnableShellOpen();
m_server.ConnectTemplate(clsid, pDocTemplate, FALSE);
COleTemplateServer::RegisterAll();
// try to launch as an OLE server
if (RunEmbedded())
{
// "short-circuit" initialization -- run as server!
return TRUE;
}
m_server.UpdateRegistry();
RegisterShellFileTypes();
// not run as OLE server, so show the main window
if (m_lpCmdLine[0] == '\0')
{
// create a new (empty) document
OnFileNew();
}
else
{
// open an existing document
OpenDocumentFile(m_lpCmdLine);
}
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();
return TRUE;
}
上のコードでは新しいリソース ID である IDR_HIERSVRTYPE_SRVR_EMB が使用されています。このメニュー リソースは別のコンテナに埋め込まれているドキュメントを編集するときに使用します。MFC/OLE1 では埋め込みアイテム編集時に使用するメニュー アイテムを実行時に動的に変更していました。埋め込みアイテム編集時と通常のファイル ドキュメント編集時で別のメニュー構造を用意すると、2 つのモードで個別のユーザー インターフェイスを使用できるようになります。後で述べるように、埋め込みオブジェクトの編集時には、まったく別のメニュー リソースが使用されます。
このリソースを作成するには、Visual C++ にリソース スクリプトを読み込み、既存の IDR_HIERSVRTYPE メニュー リソースにコピーします。追加したリソースの名前を IDR_HIERSVRTYPE_SRVR_EMB に変更します (この名前は AppWizard の名前付け規則に準拠しています)。次に [ファイル] メニューの [上書き保存] を [ファイル] メニューの [更新] に変更します。このコマンドにコマンド ID として ID_FILE_UPDATE を割り当てます。同じように、[ファイル] メニューの [名前を付けて保存] を [ファイル] メニューの [複製保存] に変更し、コマンドに ID_FILE_SAVE_COPY_AS を割り当てます。これらのコマンドのハンドラはフレームワークによって与えられます。
\hiersvr\svritem.h(60) : error C2433: 'OLESTATUS' : 'virtual' not permitted on data declarations
\hiersvr\svritem.h(60) : error C2501: 'OLESTATUS' : missing decl-specifiers
\hiersvr\svritem.h(60) : error C2146: syntax error : missing ';' before identifier 'OnSetData'
\hiersvr\svritem.h(60) : error C2061: syntax error : identifier 'OLECLIPFORMAT'
\hiersvr\svritem.h(60) : error C2501: 'OnSetData' : missing decl-specifiers
OnSetData のオーバーライドによって多数のエラーが発生します。この原因は、オーバーライドの中で OLESTATUS 型を使用しているためです。OLESTATUS は OLE1 が返すエラー型です。OLE2 では HRESULT に変更されていますが、通常、MFC は HRESULT をエラーを含む COleException に変換します。このアプリケーションの場合は OnSetData のオーバーライドは不要になるので、これを削除します。
\hiersvr\svritem.cpp(30) : error C2660: 'COleServerItem::COleServerItem' : function does not take 1 parameters
COleServerItem コンストラクタには BOOL 型パラメータが 1 つ追加されています。このフラグは COleServerItem オブジェクトのメモリ管理方法を指定します。フラグを TRUE にすると、このオブジェクトに対するメモリ管理がフレームワークによって行われます。つまり、使用されなくなったオブジェクトはフレームワークによって破棄されます。HIERSVR はネイティブ データ形式の一部として CServerItem (COleServerItem 派生クラス) オブジェクトを使用するので、このフラグは FALSE に設定します。個別のサーバー アイテムをいつ破棄するかは HIERSVR の中で決定します。
\hiersvr\svritem.cpp(44) : error C2259: 'CServerItem' : illegal attempt to instantiate abstract class
\hiersvr\svritem.cpp(44) : error C2259: 'CServerItem' : illegal attempt to instantiate abstract class
これらのエラーが示すように、CServerItem にはオーバーライドされていない純粋仮想関数が含まれています。この原因として考えられるのは、OnDraw のパラメータリストが変更されていることです。このエラーを修正するには、CServerItem::OnDraw を次のように変更します。同時に svritem.h の宣言も変更します。
BOOL CServerItem::OnDraw(CDC* pDC, CSize& rSize)
{
// request from OLE to draw node
pDC->SetMapMode(MM_TEXT); // always in pixels
return DoDraw(pDC, CPoint(0,0), FALSE);
}
新しく追加するパラメータは 'rSize' です。これは、描画するサイズを必要に応じて直接指定するために使用します。サイズは HIMETRIC である必要があります。このアプリケーションではこのパラメータを直接指定せず、フレームワークに OnGetExtent を呼び出してサイズを調べます。したがって、OnGetExtent を次のように記述します。
BOOL CServerItem::OnGetExtent(DVASPECT dwDrawAspect, CSize& rSize)
{
if (dwDrawAspect != DVASPECT_CONTENT)
return COleServerItem::OnGetExtent(dwDrawAspect, rSize);
rSize = CalcNodeSize();
return TRUE;
}
\hiersvr\svritem.cpp(104) : error C2065: 'm_rectBounds' : undeclared identifier
\hiersvr\svritem.cpp(104) : error C2228: left of '.SetRect' must have class/struct/union type
\hiersvr\svritem.cpp(106) : error C2664: 'void __pascal __far DPtoLP(struct ::tagPOINT __far *,int )__far const ' : cannot convert parameter 1 from 'int __far *' to 'struct ::tagPOINT __far *'
関数 CServerItem::CalcNodeSize によってアイテムのサイズは HIMETRIC に変換され、m_rectBounds に保存されていました。この COleServerItem の非公開のメンバ m_rectBounds は現在では存在しません (その機能の一部は m_sizeExtent に置き換えられています。しかし OLE2 のこのメンバ変数の使用法は OLE1 の m_rectBounds とは多少異なります)。古いメンバ変数に HIMETRIC 型サイズを指定する代わりに、サイズを戻り値として返します。この戻り値は、前に説明したように、OnGetExtent の中で使用されます。
CSize CServerItem::CalcNodeSize()
{
CClientDC dcScreen(NULL);
m_sizeNode = dcScreen.GetTextExtent(m_strDescription,
m_strDescription.GetLength());
m_sizeNode += CSize(CX_INSET * 2, CY_INSET * 2);
// set suggested HIMETRIC size
CSize size(m_sizeNode.cx, m_sizeNode.cy);
dcScreen.SetMapMode(MM_HIMETRIC);
dcScreen.DPtoLP(&size);
return size;
}
CServerItem では COleServerItem::OnGetTextData もオーバーライドしています。この関数は MFC/OLE2 では新しい機構に置き換えられています。MFC 3.0 に付属する MFC OLE のサンプル HIERSVR では、COleServerItem::OnRenderFileData をオーバーライドして同等の機能を実現しています。ここで行う基本的な移植作業にこの機能は重要ではないため、OnGetTextData のオーバーライドは削除します。
svritem.cpp には、これまで説明しなかったエラーも多数発生します。実質的にはこれらはエラーではなく、これまでに示したエラーによって副次的に引き起こされているものです。
\hiersvr\svrview.cpp(325) : error C2660: 'CopyToClipboard' : function does not take 2 parameters
現在の COleServerItem::CopyToClipboard は bIncludeNative フラグをサポートしていません。常にアプリケーションのネイティブ データ (サーバー アイテムの Serialize 関数が書き出すデータ) がコピーされるようになり、第 1 パラメータで指定されるフラグは削除します。また、CopyToClipboard でエラーが発生すると、FALSE が返される代わりに、例外がスローされるようになりました。変更後の CServerView::OnEditCopy は次のようになります。
void CServerView::OnEditCopy()
{
if (m_pSelectedNode == NULL)
AfxThrowNotSupportedException();
TRY
{
m_pSelectedNode->CopyToClipboard(TRUE);
}
CATCH_ALL(e)
{
AfxMessageBox("Copy to clipboard failed");
}
END_CATCH_ALL
}
MFC 2.0 付属の HIERSVR をコンパイルすると OCLIENT よりも多くのエラーが発生しますが、変更が必要な部分はむしろ少なくなっています。
以上の修正により、HIERSVR のコンパイルとリンクは正常に終了し、OLE オブジェクト アプリケーションとしての機能を備えたアプリケーションが生成されます。ただし埋め込み先編集機能はまだサポートされていません。これについては次のセクションで説明します。
"ビジュアル編集" 機能の追加
このオブジェクト アプリケーションに "ビジュアル編集" 機能 (埋め込み先編集の有効化機能) を追加するには、次の点を考慮する必要があります。
埋め込み先編集の有効化を行うときに使用する専用のメニュー リソースが必要です。
このアプリケーションはツール バーを持っています。サーバーとして起動するとき、サーバーのメニュー コマンドで使用するコマンドに対応する機能 (上で説明したメニュー リソースに対応する機能) だけを持つツール バーを用意する必要があります。
埋め込み先編集のユーザー インターフェイスのために COleIPFrameWnd 派生クラスを作成する必要があります。これは MDI ユーザー インターフェイスのために CMDIFrameWnd 派生クラス CMainFrame を作成する作業に似ています。
新しく作成したこれらのリソースやクラスをフレームワークに教える必要があります。
メニュー リソースの作成は簡単です。Visual C++ を実行してメニュー リソース IDR_HIERSVRTYPE をコピーし、IDR_HIERSVRTYPE_SRVR_IP という名前にします。このメニューから [編集] と [ヘルプ] 以外のポップアップ メニューを削除します。次に [編集] と [ヘルプ] の間に 2 つの区切り記号を追加します。"編集 | | ヘルプ" のようになります。区切り記号の意味や、サーバーとコンテナのメニューのマージ方法については、『Visual C++ の概念 : 機能の追加』の「メニューとリソース : メニューの結合」を参照してください。
サブセット ツール バーのビットマップは簡単に作成できます。AppWizard の"Server" オプションをオンにし、生成されたアプリケーションからコピーするだけです。このビットマップを Visual C++ に読み込み、ビットマップの ID として IDR_HIERSVRTYPE_SRVR_IP を与えます。
COleIPFrameWnd の派生クラスも、AppWizard が生成したオブジェクト アプリケーションからコピーできます。ファイル IPFRAME.CPP と IPFRAME.H をコピーしてプロジェクトに加えます。LoadBitmap 呼び出しの中であらかじめ作成したビットマップ IDR_HIERSVRTYPE_SRVR_IP を参照します。
必要なリソースとクラスが作成できたので、フレームワークにこれらを教える (また、このアプリケーションが埋め込み先編集をサポートすることを教える) ためのコードを記述します。つまり、関数 InitInstance 中の SetServerInfo 呼び出しに対するパラメータを追加します。
pDocTemplate->SetServerInfo(IDR_HIERSVRTYPE_SRVR_EMB,
IDR_HIERSVRTYPE_SRVR_IP, RUNTIME_CLASS(CInPlaceFrame));
以上の手順によって、任意のコンテナからの埋め込み先編集の有効化が可能になります。ただし、小さなバグが 1 つ残されています。HIERSVR はマウスの右ボタンを押したときに表示されるコンテキスト メニューを持っています。このメニューは HIERSVR が独立したアプリケーションとして起動したときは正常に動作しますが、埋め込み先編集時には正しく動作しません。この問題の原因は、CServerView::OnRButtonDown 中にある次の 1 行です。
pMenu->TrackPopupMenu(TPM_CENTERALIGN | TPM_RIGHTBUTTON,
point.x, point.y, AfxGetApp()->m_pMainWnd);
ここで AfxGetApp()->m_pMainWnd を参照している点に注意してください。サーバーを埋め込み先編集の有効化のために起動すると、メイン ウィンドウと m_pMainWnd が作成されていますが、通常ウィンドウは表示されません。この AfxGetApp()->m_pMainWnd はアプリケーションのメイン ウィンドウ、つまり MDI フレーム ウィンドウを指しています。この MDI フレーム ウィンドウは、サーバーが独立して表示または起動されるときも、埋め込み先編集の有効化のために起動されるときも存在します。したがって、AfxGetApp()->m_pMainWnd は埋め込み先編集の有効化を行うときのアクティブなフレーム ウィンドウ、つまり COleIPFrameWnd の派生フレーム ウィンドウは指していません。埋め込み先編集時にも正しいアクティブ フレーム ウィンドウを得るために、新しい MFC には関数 AfxGetMainWnd が用意されています。通常、AfxGetApp()->m_pMainWnd の代わりにこの AfxGetMainWnd を使用することをお勧めします。修正後のコードは次のようになります。
pMenu->TrackPopupMenu(TPM_CENTERALIGN | TPM_RIGHTBUTTON,
point.x, point.y, AfxGetMainWnd());
以上の手順によって、必要最小限の埋め込み先編集の有効化機能を備えた OLE オブジェクト アプリケーションが作成されました。しかし MFC/OLE2 には MFC/OLE1 にはない、さまざまな機能が用意されています。これらの機能については、サンプル アプリケーション HIERSVR を参照してください。HIERSVR には次のような機能があります。
ズーム機能。コンテナの内容を WYSISYG で出力します。
ドラッグ アンド ドロップとカスタム クリップボード形式。
選択項目の変更に合わせたコンテナ ウィンドウのスクロール。
MFC 3.0 のサンプル アプリケーション HIERSVR ではサーバー アイテムのデザインが多少異なります。これは、メモリ消費を節約し、リンクをより柔軟にします。MFC 2.0 の HIERSVR では、ツリー内の各ノードが COleServerItem として実現されます。COleServerItem は、各ノードが実際に必要とする機能以外に多少のオーバーヘッドを含んでいますが、これはアクティブなリンクを作成するために必要です。しかしほとんどの場合、どの時点においてもアクティブなリンクが多く使用されることはありません。この効率を改善するために、MFC 3.0 の HIERSVR ではノードと COleServerItem を区別して扱っています。つまり、CServerNode クラスと CServerItem クラスが用意され、CServerItem (COleServerItem の派生クラス) は必要なときにだけ生成されます。コンテナが特定のノードとのリンクを使用しなくなると、CServerNode に結び付けられている CServerItem が破棄されます。このデザインはより効率的で柔軟な処理を可能にします。この柔軟性は複数選択リンクがあるとき効果的です。新旧どちらのバージョンの HIERSVR も複数選択はサポートしませんが、MFC 3.0 の HIERSVR では COleServerItem がネイティブ データから独立しているので、複数選択 (およびそれに対応したリンク) を簡単にサポートできます。