◇ MFCを用いたサーバの作成方法
○ サーバDLLの作成
Visual C++でMFCを用いる場合、オートメーションサーバとなるDLLは、以下の手順で作成できます。○ オブジェクトの作成
なお、ここでの説明はVC4.0をベースにしていますが、それ以降のバージョンでも同様の操作で作成が可能です。(1) App WizardでDLLを生成
・MFCスタティックDLLの形式で作成する。
・OLE Automationをサポートするよう指定する。(2) Class Wizardを使ってOLE Automationのベースクラスを生成
・ClassWizardの「OLE オートメーション」タグで新しいクラスを作成する。
・クラス名は内部で使用するため、任意に指定して良い。
・CCmdTargetをベースとする。
・OLEオートメーションオプションは「タイプIDを指定する」を選択する
・指定したタイプIDがサーバの名称になる。
・上記の指定により以下の記述がソースに自動的に追加される。
// {D76A31EC-88BA-11D0-9C78-00000E1CC678}
IMPLEMENT_OLECREATE(XXXXAPP, "XXXX.Application",
0xd76a31ec, 0x88ba, 0x11d0, 0x9c, 0x78, 0x0, 0x0,
0xe, 0x1c, 0xc6, 0x78)
(3) DLLのInitInstanceに下記のOLEの初期化処理を追加する。
// OLE ライブラリの初期化
if (!AfxOleInit()) {
return FALSE;
}
COleObjectFactory::RegisterAll();
COleObjectFactory::UpdateRegistryAll();
オートメーションサーバの母体となるオブジェクトは前述のDLL作成処理で既に作成できています。○ コレクション/疑似コレクションの実現方法
さらに別のオブジェクトを追加作成する場合は以下の手順で作成できます。(1) Class Wizardを使ってオブジェクトのベースクラスを生成
・ClassWizardの「OLE オートメーション」タグで新しいクラスを作成する。
・クラス名は内部で使用するため、任意に指定して良い。
・CCmdTargetをベースとする。
・OLEオートメーションオプションは「オートメーション」を選択する
オートメーションサーバでは、複数の同様なオブジェクトが存在する場合、「コレクション」という
手法でこれを表現するのが一般的です。具体的には以下のようなケースです。
・アプリケーションに複数の文書が開くようなケースこのようなコレクションオブジェクトの作成はMFC標準では残念ながら作成できません。
Application.Documents(1).FileName
コレクションオブジェクト
作成にはいくつかのコレクション処理用のプロパティ/メソッドや処理を追加しなければなりません。コレクション実現に必要なプロパティ/メソッドや処理には以下のものがあります。
(1) _NewEnumプロパティ <必須>
・パラメータ:なし
・戻り値: VT_UNKNOWN (IEnumVariantインターフェースへのポインタ)
・TypeLib上でのIDを”-4”とすること。(2) Countプロパティ <必須>
#define DISPID_NEWENUM -4 propaerties:
[id(DISPID_NEWENUM)] Iunknown* _NewEnum;
・上書き禁止プロパティ
・戻り値: VT_I4 (コレクション内のアイテム数を返す)
(3) Itemメソッド <必須>
・パラメータ: 任意
(オブジェクトで必要に応じて定義,ODL上はVARIANT)
・戻り値: VT_DISPATCH
(指定されたアイテムを示すオブジェクトへのポインタ)
・デフォルトメンバ
(4) IEnumVariantの実装
・Microsoftより情報提供されています。参考文献等をご参照ください。ただ、上記のような対応は非常に難解で、MFCを使うメリットが希薄になってしまいます。
こうしたオブジェクト構造が非常に重要であるケースを除いては、疑似コレクション対応を行うことでも十分な効果が上げられます。疑似コレクション対応とは自オブジェクトのベースとなるクラスのポインタを親や子に相当するオブジェクトのメンバや管理テーブルに保管し、この値を戻すことでオブジェクトのつながりを表現する方法です。
この手法を用いればコレクションにより提供される、ほとんどの機能を網羅でき、作成もMFCベースで効率的に行うことができます。疑似コレクション実現に必要な処理は以下の通りです。
(1) 親オブジェクトの実現クラスへのポインタをメンバに保管・自オブジェクトのコンストラクタで処理するのが望ましい。(2) Parentプロパティを実装する。・保管しておいた親オブジェクトのポインタからIDISPATCHの値を導出。(3) 子オブジェクトの疑似コレクションをサポートするプロパティを実装する。
lpDisp = m_ParentClassPtr->GetIDispatch( FALSE ); ・プロパティ名は任意(子オブジェクトの複数形の名称が望ましい)(4) 子オブジェクトの個数の取得をサポートするプロパティを実装する。
・子オブジェクトのインデックスを示す引数が必要
数値の場合、1から始まるのが通常。
名称あるいは数値との併用も可能。
・保管しておいた子オブジェクトのテーブル上のポインタからIDISPATCHの値を導出。
lpDisp = m_ChildClassPtrTbl[i]->GetIDispatch( FALSE );
・プロパティ名は通常「Count」とするケースが多い。
・子オブジェクトの個数を返す「取得のみ」のプロパティとする。
○ メソッド/プロパティの作成
上記の手順で作成したオブジェクトのベースクラスにメンバを追加することでメソッドやプロパティを追加することができます。メソッドの追加手順は以下のとおりです。
(1) Class Wizardを使ってメソッドの処理メンバ関数を追加
・ClassWizardの「OLE オートメーション」タグで「メソッドの追加」を選択する。
・外部名・内部名は任意に指定して良い。
・戻り値や引数のタイプはVARIANTを指定するのが望ましい。
・ 変数名は任意に指定して良い。プロパティの追加手順は以下のとおりです。
(1) Class Wizardを使ってプロパティの処理メンバ関数を追加
・ClassWizardの「OLE オートメーション」タグで「プロパティの追加」を選択する。
・ 外部名・取得関数・設定関数は任意に指定して良い。
・ 「インプリメント」は「メソッドの取得/設定」を選択するのが望ましい。
・タイプ・引数のタイプはVARIANTを指定するのが望ましい。
・ 変数名は任意に指定して良い。
○ 引数の処理方法
メソッドやプロパティの引数、戻り値などは基本的に全て「VARIANT」で処理します。
VARIANTには前述の通り、様々な型の値を入れられる特別な変数で、数値や文字列、オブジェクトなど様々な値を同じ変数で取り扱うことができます。
また、引数の指定を省略したようなケースについても取り扱うことができます。VARIANTへの値の設定方法は前述の通りですが、メソッドやプロパティの引数として指定された値が仕様と一致した型や範囲になっているか、範囲・省略の有無などをチェックする必要があります。
以下のような手順で事前処理やチェックを行う必要があるでしょう。
(1) VARIANT時の型変換
・VARIANTの内部の型がさらにVARIANTになっている場合があるため。
・仕様上求められる型に変換し、変換の可/不可を判定します。
・COleVariant::ChangeType()を用いると簡単に行えます。・整数を扱うようなケースでは一旦longに変換するのが望ましいでしょう。
[型変換] COleVariant cvRet; cvRet.ChangeType( VT_I4, NULL );
(2) 引数省略の判定
・仕様上の省略可/不可と実際の指定が一致しているかを判定します。(3) 型チェック
[引数省略チェック] vt = RubberType.vt & TYPE_MASK;
//引数省略チェック
if ( vt == VT_ERROR ) {
if ( RubberType.scode != DISP_E_PARAMNOTFOUND ) {
//引数が省略されている
}
}
・vtを用いて型を確認します。(4) 配列チェック
[型チェック] vt = vaVal.vt & TYPE_MASK;
if ( vt != VT_I4 ) {
//型不一致
}
・配列の有無が仕様と一致しているかを確認します。
・確認には以下の関数を用いると良いでしょう。・配列を利用する場合には安全配列(SAFEARRAY)を理解する必要があります。
[配列チェック] int dCheckVariantArray( const VARIANT FAR& vaCheck )
{
VARTYPE vt;vt = vaCheck.vt & VT_ARRAY;
if ( vt == VT_ARRAY )
return VT_IS_ARRAY;
else
return VT_NOT_ARRAY;
}(5) 引数の取得
・事前に取得したい型に適宜変換しておく必要がある。
・BYREFとBYVALの2つの格納方法が存在し、両者に対応する必要がある。・long値の取得の実例を以下に示します。
BYREF 参照渡し [ポインタ] BYVAL 値渡し [実値] ・配列を利用する場合には安全配列(SAFEARRAY)を理解する必要があります。
[引数の取得] if ( (vaVal.vt & VT_BYREF) == VT_BYREF ) {
if ( vaVal.plVal != NULL ) {
lVal = *(vaVal.plVal);
}
}
else {
lVal = vaVal.lVal;
}
○ エラーの処理方法
引数の指定ミスなどオートメーションサーバ内のメソッド処理でエラーが発生した場合、利用しているクライアントに対してエラーを通達する必要があります。こうした場合、OLE DISPATCH例外をスローすることで通達でき、通常は以下の関数が用いられます。
AfxThrowOleDispatchException( wCode, nDescriptionID, nHelpID ); ただし、上記関数はVisualBasicの一部のバージョンなどに正しい戻り値を返せないケースがあり、以下の関数を利用することを推奨します。
void ThrowOLEAError( WORD wCode, UINT nDescriptionID, UINT nHelpID )
{
COleDispatchException* pCErr;
CString CMsg;CMsg.LoadString( nDescriptionID );
pCErr = new COleDispatchException( CMsg, nHelpID, 0 );
pCErr->m_scError = (SCODE)wCode;
pCErr->m_wCode = wCode;
pCErr->m_dwHelpContext = nHelpID;THROW(pCErr);
}○ タイプライブラリの作成
作成したサーバ中のオブジェクトやメソッド/プロパティは「タイプライブラリ」というファイルを通じて、クライアント側のVisualBasicやVBAのエディタから「オブジェクトブラウザ」を使って参照することができます。○ ヘルプ対応方法タイプライブラリはClass Wisardにより自動生成されるODLファイルから作成されますが、必要に応じて情報の修正・追加や属性の設定などを行うことができます。
ここでは、ODLファイル中の記述について良く使われるものをいくつか説明しておきます。
helpstring("xxxxxxxxxxxxxx") ・タイプライブラリの名称を指定します。
・ODL定義の先頭に記述します。
helpfile("xxxxxxx.hlp") ・タイプライブラリに対応するヘルプのファイル名を指定します。
・ODL定義の先頭に記述します。
・ヘルプファイルはタイプライブラリと同一フォルダに存在する必要があります。
[hidden] ・メソッド/プロパティを非表示にします。
・メソッド/プロパティ定義行の先頭に付加します。
・オブジェクトブラウザには表示されませんが、実際の使用はできます。
[optional] ・指定した引数が省略可能であることを示します。
・メソッド/プロパティの各引数の定義の先頭に付加します。
・指定より右に記載された全引数に指定されている必要があります。
・引数省略の判定はメソッド/プロパティの処理で別途行う必要があります。
[helpcontext(HELPID)] ・メソッド/プロパティに対応するヘルプのIDを指定します。
・helpfile()にて、ヘルプファイル名を別途指定しておく必要があります。
組み込み定数 ・引数にモードなどを数値指定する場合に便利な定数を定義することができます。
typedef enum {
XXXX_NORMAL = 0,
XXXX_MINIMIZED = 1,
XXXX_MAXIMIZED = 2
} XXXX_WINSTATE;
タイプライブラリからの指定やメソッド/プロパティのエラー時で対応するヘルプを表示することができます。○ Visual C++のバージョン非互換
ヘルプファイル自体は通常のヘルプと作成方法に何ら差違はありません。ヘルプファイル名はODLファイル中のhelpfile()で定義します。この時、ヘルプファイルはタイプライブラリと同一フォルダに存在しなければなりません。
helpfile("xxxxxxx.hlp") タイプライブラリからの指定時はODLファイル中の[helpcontext(HELPID)]で対応するヘルプのコンテキストIDを指定することで対応できます。
[id(20), helpcontext(35)] void Activate(); メソッド/プロパティのエラー時はプログラム中からOLEDISPATCH例外をスローする際に対応するヘルプのコンテキストIDを指定することで対応できます。
void ThrowOLEAError( WORD wCode, UINT nDescriptionID, UINT nHelpID )
ここまでの記述はVisual C++ Version 4.x をベースにしていますが、それ以降のバージョンでも同様に適用することができます。
ただし、以下の点でバージョンによる非互換箇所がありますので、注意してください。
VARIANT構造体のメンバ名称が変更になっている。(VC5.0以降) bool => boolVal
pbool => pboolVal