Microsoft Visual C++    製品情報    |    検索  |    サポート  |    フィードバック   |   ホーム  
Microsoft
   Visual C++ ホームページ   |   Visual Studio   |   開発関連製品   |   MSDN online   |   各国の開発者用サイト   |
  製品のご案内
  Visual Studio.NET
  技術ドキュメント
  サポート情報
  よくある質問
  ダウンロード
  登録とオーナーエリア
  関連情報
 


banner
Member of Visual Studio 技術ドキュメント

Visual C++ 6.0 OLE DB プロバイダ テンプレートの使用


Lon Fisher
Visual C++ 開発チーム


1998 年 8 月

要約: OLE DB プロバイダ テンプレートを使って OLE DB プロバイダを作成する方法について説明します。テンプレートを使ってプロバイダを作成し、テストを行う例を示しています。また、プロバイダをテンプレート サポートの範囲を越えて拡張する方法についても解説しています。 以下のトピックを扱っています。

  • OLE DB プロバイダを作成する理由
  • プロバイダ ウィザードの使用
  • プロバイダのテスト
  • Visual C++ 5.0 Technology Preview Internet Explorer 4.0 対応版から Visual C++ 6.0 へのプロバイダの移植
はじめに

OLE DB は、Microsoft の新しいデータ アクセス方法論であり、Universal Data Access 戦略の重要な一部を占めています。このデザインは、任意のデータ ソースへの高性能のデータ アクセスを可能にします。また、任意のテーブル形式のデータを、それがデータベースに含まれているかどうかを問わず、OLE DB を通して見ることができます。この柔軟性は、開発者にきわめて大きなパワーを提供します。
OLE DB のデザインには、コンシューマとプロバイダという概念が含まれています。図 1 は OLEDB システムを示しています。コンシューマは従来のクライアントに該当します。プロバイダは、データをテーブル形式に格納し、クライアントに返します。

oledb001

図 1. OLE DBアーキテクチャ

プロバイダは、一連のインターフェイスを含んでいる COM コンポーネントのセットです。これらは標準インターフェイスなので、任意の OLE DB コンシューマが任意のプロバイダのデータにアクセスすることができます。プロバイダは COM オブジェクトなので、コンシューマは任意の言語からアクセスすることができます (C++、Basic、Java など)。多数の COM インターフェイスをインプリメントするのは面倒な作業ですが、幸いなことに、Visual C++ の OLE DB テンプレートがプロバイ ダの作成を支援してくれます。
本記事では、OLE DB プロバイダ テンプレートを使ってOLE DB プロバイダを作成する方法について説明しています。テンプレートを使ってプロバイダを作成し、テストする例を示し、テンプレート サポートの範囲を越えて拡張する方法についても解説しています。
本記事では、Visual C++ 6.0 の OLE DB プロバイダ テンプレートを使用します。Visual C++ 5.0Technology Previewと Visual C++ 6.0 では、プロバイダ テンプレートに大きな変更が加えられています。また、本記事の付録には、Visual C++ 5.0 Technology PreviewからVisual C++ 6.0 へのプロバイダの移植に関する情報があります。
本記事を有効に活用するためには、データベース機能に関するある程度の知識が必要です。また、COM とActive Template Library(ATL) に関する基本的な知識も必要となります。本ドキュメントの付録Cには、プロバイダの開発者のための他の重要な情報へのリンクが収録されています。

OLE DB プロバイダとは何か?
OLE DB プロバイダとは、持続的なソースからコンシューマに対してデータを転送する COM オブジェクトのセットです。OLE DB プロバイダは、コンシューマからの呼び出しに応えて、テーブル形式でデータを提供します。プロバイダには単純なものも複雑なものもあります。プロバイダはテーブルを返すことができ、クライアントからテーブルの形式を指定できるようにすることも、データに対して操作を行うこともできます。
各プロバイダは、クライアントからの要求を処理するために、標準的な COM オブジェクトのセットをインプリメントします。また、プロバイダはオプションの COM オブジェクトをインプリメントして、機能を追加することができます。図 2 に、プロバイダがインプリメントできる各種の COM オブジェクトを示します。

oledb001

図 2. OLE DB プロバイダに含まれる COM コンポーネント

COM コンポーネントとその用途
コンポーネント インターフェイス コメント
データ ソース [必須] IDBCreateSession
[必須] IDBInitialize
[必須] IDBProperties
[必須] IPersist
[省略可能] IDBDataSourceAdmin
[省略可能] IDBInfo
[省略可能] IPersistFile
[省略可能] ISupportErrorInfo
コンシューマからプロバイダへのコネクション。このオブジェクトは、ユーザー ID、パスワード、データ ソース名など、コネクションのプロパティを指定するために使用されます。また、このオブジェクトはデータ ソースの管理にも使用できます (作成、更新、削除、テーブルなど)。
セッション [必須] IGetDataSource
[必須] IOpenRowset
[必須] ISessionProperties
[省略可能] IDBCreateCommand
[省略可能] IDBSchemaRowset
[省略可能] IIndexDefinition
[省略可能] ISupportErrorInfo
[省略可能] ITableDefinition
[省略可能] ITransaction
[省略可能] ITransactionJoin
[省略可能] ITransactionLocal
[省略可能] ITransactionObject
セッション オブジェクトは、コンシューマとプロバイダの間の 1 つの対話を表します。これは、同時に複数のセッションがアクティブになりうるという点で、ODBC の HSTMT に似ています。セッション オブジェクトは、OLE DB の機能にアクセスするための主要なリンクです。コマンド、トランザクション、または行セット オブジェクトを取得するためには、セッションオブジェクトを使う必要があります。
コマンド [必須] IAccessor
[必須] IColumnsInfo
[必須] ICommand
[必須] ICommandProperties
[必須] ICommandText
[必須] IConvertType
[省略可能] IColumnsRowset
[省略可能] ICommandPrepare
[省略可能] ICommandWithParameters
[省略可能] ISupportErrorInfo
コマンド オブジェクトは、データに対するクエリーなどの操作を処理します。パラメータを持つステートメントと持たないステートメントを扱うことができます。 また、コマンド オブジェクトは、パラメータと出力列のバインディングを処理する責任も負っています。バインディングとは、行セットの中の列をどのように取り出すかという情報を含んでいる構造体です。これは順序番号、データ型、長さ、ステータスなどの情報を含んでいます。
行セット [必須] IAccessor
[必須] IColumnsInfo
[必須] IConvertType
[必須] IRowset
[必須] IRowsetInfo
[必須] IRowsetIdentity
[省略可能] IColumnsRowset
[省略可能] IConnectionPointContainer
[省略可能] IRowsetChange
[省略可能] IRowsetLocate
[省略可能] IRowsetResynch
[省略可能] IRowsetScroll
[省略可能] IRowsetUpdate
[省略可能] ISupportErrorInfo
行セット オブジェクトは、データ ソースのデータを表します。このオブジェクトは、そのデータのバインディングと、データに対する基本操作 (更新、取り出し、移動など) の責任を負っています。データを格納し、操作するために、行セット オブジェクトは必ず使用されます。
トランザクション [必須] IConnectionPointContainer;
[必須] ITransaction
[省略可能] ISupportErrorInfo
トランザクション オブジェクトは、データ ソースに対するアトミックな作業単位を定義し、これらの作業単位が互いにどのような関係にあるかを決定します。このオブジェクトは、OLE DB プロバイダ テンプレートでは直接にはサポートされていません (つまり独自のプロジェクトを作成する必要があります)。

各 COM コンポーネントは、一連の COM インターフェイスを含んでいます。COM インターフェイスの中には必須のものと省略可能なものがあります。必須のインターフェイスをインプリメントしているプロバイダは、すべてのクライアントが利用できる最低レベルの機能を提供することができます。また、省略可能なインターフェイスをインプリメントするプロバイダは、より多くの機能を提供し、クライアントに対してよりリッチなサービスを提供できます。クライアントは、常に QueryInterface を呼び出して、プロバイダが特定のインターフェイスをサポートしているかどうかを確認しなくてはなりません。

OLE DB プロバイダを作成する理由
OLE DB プロバイダを作成する主な理由は、Universal Data Access 戦略を活用するという点にあります。UDA 戦略に参加することには、以下のような利点があります。

  • C++、Basic、Java、Visual Basic Scripting Edition などの任意の言語を通してデータにアクセスすることができます。企業内のプログラマは、どの言語を使っている場合でも、同じデータに同じ方法でアクセスできます。
  • SQL Server、Microsoft Excel、Access などの他のデータ ソースに対してデータを公開できます。これは、データを複数のフォーマットの間で転送したい場合には、特に便利な機能です。
  • クロス データ ソース (異種) 操作に参加することができます。これはデータ ウェアハウジングのきわめて効率的な手段となります。OLE DB プロバイダを使用することで、データをネイティブ フォーマットで保持しておきながら、単純な操作でアクセスすることができます。
  • データに対して、クエリー処理などの機能を追加することができます。
  • データの操作方法を制御することで、データ アクセスのパフォーマンスを高めることができます。
  • 堅牢性を高めることができます。1人のプログラマにしか理解できない独自のデータ フォーマットを使用しているというような状況にはリスクがあります。OLE DB プロバイダを使用すれば、独自のフォーマットをすべてのプログラマに公開することができます。

OLE DB プロバイダ テンプレートとは?
OLE DB v1.1仕様には 43 個の COM インターフェイスがあります (注: 現在は、さらに多くのインターフェイスを含んでいるバージョン 2.0 の OLE DB 仕様があります。詳細については、http://www.microsoft.com/data/を参照してください)。このため、プロバイダの開発は面倒な作業になる可能性があります。幸いなことに、OLE DB プロバイダ テンプレートには、すべての必須インターフェイスのインプリメンテーションが含まれています。OLE DB プロバイダ テンプレートは ATL のエクステンションであり、プログラマの作業を大幅に節約してくれます。OLE DB プロバイダ テンプレートには以下の特長があります。

  • 6000 行のコードがすでにインプリメントされています。サポートされているインターフェイスのリストについては、付録 A を参照してください。
  • OLE DB プロバイダ ウィザード。このウィザードは ATL オブジェクト ウィザードの一部であり、実行の準備ができているデフォルトのプロバイダを生成します。いくつかのコード セクションを変更するだけで、基本的なプロバイダが動作します。
  • 小さくて高速なコード。コードは ATL スタイルで開発されます。われわれは、できる限り使いやすいものにしながら、コードの量を最小限に抑えようと試みています。
  • 柔軟性。ウィザードが生成した基本的なプロバイダをそのまま使うことができます。また、(コード例に示しているように) 追加のインターフェイスをインプリメントすることで、機能を追加することができます。つまり、テンプレートを使えば、書かなければならないコードの量を大幅に減らしながら、優れたプロバイダを開発することができます。

OLE DB プロバイダの作成

ここでは、テキスト ファイルから文字列を読み込むOLE DB プロバイダを作成します。このプロバイダのユーザーは、ファイルの内容を取り出すコマンドを実行することができます。このプロバイダでは、クライアントはデータの更新や変更は行えません (つまり、これは読み取り専用のプロバイダです)。

プロバイダ ウィザードの使用
最初に ATL COM AppWizard を実行します。ATL COM AppWizard は、[ファイル] - [新規作成] の [プロジェクト] タブにあります。次のステップを使ってプロジェクトを作成します。

  • プロジェクトに "MyProv" という名前を付けます。
  • [新規にワークスペースを作成] ボタンが設定されていることを確認します。
  • [OK] をクリックします。
  • [終了] をクリックします (デフォルトの設定を使用します)。
  • [OK] をクリックして確認ダイアログを閉じ、プロジェクトを作成します。
プロジェクトが作成されたら、ATL オブジェクト ウィザードを起動します。オブジェクト ウィザード は、[挿入] - [ATL オブジェクトの新規作成] を選択すると起動されます。

oledb003

図 3. ATL オブジェクト ウィザード

[OLE DB プロバイダ] エントリを選択し、[次へ] をクリックすると、図 4のプロパティ ページが表示されます。このタブではプロバイダの名前を設定することができます。また、プロバイダに含まれる各種の COM オブジェクトのクラス名とファイル名を設定できます。プロバイダの名前は MyProvider とします。これを [ショート ネーム] ダイアログに入力してください。オブジェクト ファイル名は後に使用するので、そのままにしておきます。
注: プロバイダ ウィザードはデータ ソース、セッション、コマンド、および行セット オブジェクトを作成します。トランザクション オブジェクトを作成したい場合には、独自に作成する必要があります。

oledb004

図 4. OLE DB プロバイダ ウィザードの名前ダイアログ

ウィザードが生成するもの
このセクションでは、生成されるコードの中から重要な箇所をいくつか取り上げます。プロバイダウィザードは、COM コンポーネントのためのファイルをいくつか生成します。


CMyProviderSource (MyProviderDS.H)
//////////////////////////////////////////////////////////////////////
///
// CMyProviderSource
class ATL_NO_VTABLE CMyProviderSource :
 public CComObjectRootEx,
 public CComCoClass,
 public IDBCreateSessionImpl,
 public IDBInitializeImpl,
 public IDBPropertiesImpl,
 public IPersistImpl,
 public IInternalConnectionImpl
図 5. CMyProviderSourceの継承チェーン

プロバイダ クラスは多重継承を使用します。図 5 はデータ ソース オブジェクトの継承チェーンを示しています。すべての COM コンポーネントは ComObjectRootExと CComCoClass から派生しています。CComObjectRootEx はIUnknown インターフェイスのすべてのインプリメンテーションを提供します。これは任意のスレッディング モデルに対応しています。CComCoClass は、必要な任意のエラー サポートを提供します。クライアントによりリッチなエラー情報を送信したい場合には、CComCoClassのいくつかのエラーAPI を利用することができます。
データ ソース オブジェクトは、いくつかの "Impl" クラスからも継承を行います。各クラスはインターフェイスのインプリメンテーションを提供します。データ ソース オブジェクトは IPersist、IDBProperties、IDBInitialize、および IDBCreateSession インターフェイスをインプリメントします。 OLE DB はこれらのインターフェイスを、データ ソース オブジェクトをインプリメントするために必要とします。開発者は、個々の "Impl" クラスを継承するかしないかを選ぶことで、特定の機能をサポートするかどうかを決めることができます。IDBDataSourceAdmin インターフェイスをサポートしたい場合には、IDBDataSourceAdminImpl クラスから継承を行って、必要な機能を取り込み ます。


BEGIN_COM_MAP(CMyProviderSource)
 COM_INTERFACE_ENTRY(IDBCreateSession)
 COM_INTERFACE_ENTRY(IDBInitialize)
 COM_INTERFACE_ENTRY(IDBProperties)
 COM_INTERFACE_ENTRY(IPersist)
 COM_INTERFACE_ENTRY(IInternalConnection)
END_COM_MAP()
図 6. データ ソース インターフェイス マップ

クライアントがデータ ソース上のインターフェイスに対して QueryInterface を呼び出すと、その呼び出しは 図 6 に示した COM マップを通して送られます。COM_INTERFACE_ENTRY マクロは ATL のもので、CComObjectRootEx の QueryInterface のインプリメンテーションに、適切なインターフェイスを返すように要求します。


BEGIN_PROPSET_MAP(CMyProviderSource)
 BEGIN_PROPERTY_SET(DBPROPSET_DATASOURCEINFO)
  PROPERTY_INFO_ENTRY(ACTIVESESSIONS)
  PROPERTY_INFO_ENTRY(DATASOURCEREADONLY)
  PROPERTY_INFO_ENTRY(BYREFACCESSORS)
  PROPERTY_INFO_ENTRY(OUTPUTPARAMETERAVAILABILITY)
  PROPERTY_INFO_ENTRY(PROVIDEROLEDBVER)
  PROPERTY_INFO_ENTRY(DSOTHREADMODEL)
  PROPERTY_INFO_ENTRY(SUPPORTEDTXNISOLEVELS)
  PROPERTY_INFO_ENTRY(USERNAME)
 END_PROPERTY_SET(DBPROPSET_DATASOURCEINFO)
 BEGIN_PROPERTY_SET(DBPROPSET_DBINIT)
  PROPERTY_INFO_ENTRY(AUTH_PASSWORD)
  PROPERTY_INFO_ENTRY(AUTH_PERSIST_SENSITIVE_AUTHINFO)
  PROPERTY_INFO_ENTRY(AUTH_USERID)
  PROPERTY_INFO_ENTRY(INIT_DATASOURCE)
  PROPERTY_INFO_ENTRY(INIT_HWND)
  PROPERTY_INFO_ENTRY(INIT_LCID)
  PROPERTY_INFO_ENTRY(INIT_LOCATION)
  PROPERTY_INFO_ENTRY(INIT_MODE)
  PROPERTY_INFO_ENTRY(INIT_PROMPT)
  PROPERTY_INFO_ENTRY(INIT_PROVIDERSTRING)
  PROPERTY_INFO_ENTRY(INIT_TIMEOUT)
 END_PROPERTY_SET(DBPROPSET_DBINIT)
 CHAIN_PROPERTY_SET(CMyProviderCommand)
END_PROPSET_MAP()
図 7. データ ソース プロパティ マップ

プロパティ マップは、プロバイダが指定したすべてのプロパティの仕様を定めています。OLEDB のプロパティはグループ化されています。データ ソース オブジェクトは、DBPROPSET_DATASOURCEINFO と DBPROPSET_DBINIT について、合わせて 2 つのプロパティ グループを持っています。DBPROPSET_DATASOURCEINFO のセットは、プロバイダとそのデータ ソースに関するプロパティに対応しています。DBPROPSET_DBINIT のセットは、初期化の際に使用されるプロパティに対応しています。OLE DB プロバイダ テンプレートは、これらのセットを PROPERTY_SET マクロを通して処理します。マクロはプロパティの配列を含んでいるブロックを作成します。クライアントが IDBProperties インターフェイスを呼び出すと、プロバイダは必ずプロパティ マップを使用します。
開発者は仕様の中のすべてのプロパティをインプリメントする必要はありません。特定のプロパティをサポートしたくない場合はマップから削除してください。特定のプロパティをサポートしたい場合は、PROPERTY_INFO_ENTRY マクロを使ってマップに追加してください。マクロは、図 8 に示すように、UPROPINFO 構造体に対応しています。


struct UPROPINFO
{
 DBPROPID dwPropId;
 ULONG  ulIDS;
 VARTYPE  VarType;
 DBPROPFLAGS dwFlags;
 union
 {
  DWORD dwVal;
  LPOLESTR szVal;
 };
 DBPROPOPTIONS dwOption;
};
図 8. UPROPINFO 構造体

この構造体の中の各要素は、プロパティを扱うための情報を表しています。これは、プロパティの GUID と ID を決定するための DBPROPID を含んでいます。また、プロパティの型と値を決定するためのエントリもあります。 プロパティのデフォルト値を変更したい場合には、PROPERTY_INFO_ENTRY_VALUE または PROPERTY_INFO_ENTRY_EX マクロを使用します。これらのマクロを使うと、対応するプロパティの値を指定することができます。プロパティとその値の詳細については、OLE DB の仕様を参照してください。PROPERTY_INFO_ENTRY_VALUE マクロは、値を変更するための簡易版のマクロです。PROPERTY_INFO_ENTRY_VALUE マクロは PROPERTY_INFO_ENTRY_EX マクロを呼び出します。このマクロでは、UPROPINFO構造体のすべての属性を追加または変更することができます。
独自のプロパティ セットを定義したい場合には、BEGIN_PROPSET_MAPとEND_PROPSET_MAP の組み合わせを新たに追加することで、新しいプロパティ セットを追加 することができます。この際には、プロパティ セットのためのGUID を定義し、独自のプロパティを定義する必要があります。プロバイダ固有のプロパティの場合は、既存のプロパティ セットではなく、新しいプロパティ セットに追加するようにしてください。これにより、次のバージョンの OLE DB が出荷されたときに、問題が生じるのを防ぐことができます。

CMyProviderSession(MyProviderSess.H)
MyProviderSess.H ファイルには、OLE DB セッション オブジェクトの宣言とインプリメンテーションが含まれています。データ ソース オブジェクトはセッション オブジェクトを作成します。これはコンシューマとプロバイダの間の 1 つの対話を表します。1 つのデータ ソースに対して、同時に複数のセッションをオープンすることができます。図 9 に継承リストを示します。


//////////////////////////////////////////////////////////////////////
///
// CMyProviderSession
class ATL_NO_VTABLE CMyProviderSession :
 public CComObjectRootEx,
 public IGetDataSourceImpl,
 public IOpenRowsetImpl,
 public ISessionPropertiesImpl,
 public IObjectWithSiteSessionImpl,
 public IDBSchemaRowsetImpl,
 public IDBCreateCommandImpl
図 9. CMyProviderSession の継承チェーン

セッション オブジェクトは、IGetDataSource、IOpenRowset、ISessionProperties、および IDBCreateCommand から継承を行います。IGetDataSourceインターフェイスにより、セッションは自分を作成したデータ ソースを取得することができます。これは、作成したデータ ソースのプロパティを取得したいときや、データ ソースが提供できるその他の情報を取得したいときに便利です。ISessionPropertiesインターフェイスは、そのセッションのすべてのプロパティを処理します。IOpenRowset および IDBCreateCommand インターフェイスは、データベース操作を実行するために使用されます。プロバイダがコマンドをサポートする場合には、IDBCreateCommand インターフェイスをインプリメントします。これは、コマンドを実行できるコマンド オブジェクトの作成に使用されます。プロバイダは必ずIOpenRowsetオブジェクトをインプリメントします。これは、プロバイダの単純な行セットを生成するために使用されます。これはプロバイダからのデフォルトの行セット (select * from fooなどで取得されるもの) となります。
さらに、ウィザードは、CMyProviderSessionColSchema、CMyProviderSessionPTSchema、およびCMyProviderSessionTRSchemaの 3 つのセッション クラスも生成します。これらのセッションはスキーマ行セットに使用されます。プロバイダはスキーマ行セットにより、コンシューマがクエリーやデータ フェッチを実行しなくても、コンシューマに対してメタデータを返すことができます。メタデータのフェッチは、プロバイダの能力を調べるよりもはるかに高速に行えます。
OLE DB 仕様では、IDBSchemaRowset インターフェイスをインプリメントするプロバイダは、DBSCHEMA_COLUMNS、DBSCHEMA_PROVIDER_TYPES、および DBSCHEMA_TABLES の 3 つのスキーマ行セット タイプをサポートしなければならないことになっています。ウィザードはこれらのスキーマ行セットのインプリメンテーションを生成します。ウィザードが生成する各クラスにはExecute 関数が含まれています。この Execute 関数の中では、どのテーブル、列、およびデータ型をサポートするのかということに関するデータをプロバイダに返すことができます。このデータは通常はコンパイル時にわかっています。

CMyProviderCommand(MyProviderRS.H)
CMyProviderCommand クラスはプロバイダ コマンド オブジェクトのインプリメンテーションです。 これはIAccessor、ICommandText、および ICommandProperties インターフェイスのインプリメンテーションを提供します。IAccessor インターフェイスは、行セットのインターフェイスとまったく同じです。コマンド オブジェクトは、アクセサを使って、パラメータのバインディングを指定しま す。行セット オブジェクトは、これを使って出力列のバインディングを指定します。ICommandText インターフェイスは、コマンドをテキストとして指定するのに使用します。本記事では、後に独自のコードを追加するときに ICommandText インターフェイスを使用します。また、ICommand::Execute() メソッドをオーバーライドします。ICommandProperties インターフェイスは、コマンドおよび行セット オブジェクトのすべてのプロパティを処理します。


// CMyProviderCommand
class ATL_NO_VTABLE CMyProviderCommand :
class ATL_NO_VTABLE CMyProviderCommand :
 public CComObjectRootEx,
 public IAccessorImpl,
 public ICommandTextImpl,
 public ICommandPropertiesImpl,
 public IObjectWithSiteImpl,
 public IConvertTypeImpl,
 public IColumnsInfoImpl
図 10. CMyProviderCommand の継承チェーン

IAccessor インターフェイスは、コマンドと行セットに使われているすべてのバインディングを管理しています。コンシューマは、DBBINDING 構造体の配列を指定して、IAccessor::CreateAccessor() を呼び出します。個々の DBBINDING 構造体は、列のバインディングをどのように処理すべきかを示す情報を含んでいます (型、長さなど)。プロバイダはこの構造体を取得した上で、データをどのように転送するか、またデータの変換が必要かどうかを決定します。IAccessor インターフェイスは、コマンド オブジェクトの中で、コマンド内の任意のパラメータを処理するために使用されます。
コマンド オブジェクトは IColumnsInfo のインプリメンテーションも提供します。OLE DB では IColumnsInfo インターフェイスは必須です。このインターフェイスにより、コンシューマはコマンドからパラメータに関する情報を取得することができます。行セット オブジェクトは IColumnsInfo インターフェイスを使って、出力列に関する情報をプロバイダに返します。
プロバイダは IObjectWithSite という名前のインターフェイスも含んでいます。IObjectWithSite インターフェイスは ATL 2.0 でインプリメントされたもので、インプリメンタが自分自身に関する情報を子に渡すために使用されます。コマンド オブジェクトは IObjectWithSite の情報を使って、生成された行セット オブジェクトに対して、誰が作成者なのかを知らせます。

CMyProviderRowset(MyProviderRS.H)
ウィザードは行セット オブジェクトのエントリを生成します。われわれのケースでは、これは CMyProviderRowset という名前になります。CMyProviderRowset クラスは、CRowsetImpL という名前の OLE DB プロバイダ クラスから継承を行います。CRowsetImpl クラスは、行セット オブジェクトのすべての必須インターフェイスをインプリメントしています。図 11 に、CRowsetImpl クラスの継承チェーンを示します。


template ,
 class RowClass = CSimpleRow,
 class RowsetInterface = IRowsetImpl < T, IRowset, RowClass> >
class CRowsetImpl :
 public CComObjectRootEx,
 public IAccessorImpl,
 public IRowsetIdentityImpl,
 public IRowsetCreatorImpl,
 public IRowsetInfoImpl,
 public IColumnsInfoImpl,
 public IConvertTypeImpl,
 public RowsetInterface
図 11. CRowsetImpl クラス

CRowsetImplはIAccessor および IColumnsInfo インターフェイスも使用します。これらのインターフェイスは、テーブル内の出力フィールドに使用されます。また、このクラスは IRowsetIdentity のインプリメンテーションも提供します。IRowsetIdentity により、コンシューマは 2 つの行が同一であるかどうかを判定することができます。IRowsetInfo インターフェイスは行セット オブジェクトのプロパティをインプリメントしています。IConvertType インターフェイスにより、プロバイダはコンシューマから要求されたデータ型と、プロバイダが使用しているデータ型の間の違いを解消することができます。
実際に行の取り出しを処理するのは IRowset インターフェイスです。コンシューマは、まず GetNextRows という名前のメソッドを呼び出して、行に対するハンドル (HROW) を受け取ります。その後、コンシューマはこの HROW を使ってIRowset::GetData を呼び出して、要求したデータを取得することができます。
また、CRowsetImpl はいくつかのテンプレート パラメータを取ります。これらのパラメータにより、開発者は CRowsetImpl クラスがデータをどのように処理するかを調べることができます。ArrayType 引数により、開発者は行データの格納にどのようなストレージ メカニズムが使われているのかを調べることができます。RowClass パラメータは、どのクラスが HROW を含んでいるのかを指定します。
RowsetInterface パラメータにより、開発者は IRowsetLocate または IRowsetScroll インターフェイスも使えるようになります。IRowsetLocate および IRowsetScroll インターフェイスは、どちらも IRowset から継承を行っています。このため、OLE DB プロバイダ テンプレートは、これらのインターフェイスに対する特殊な処理を提供しなければなりません。どちらかのインターフェイスを使いたい場合には(このペーパーでは後に IRowsetLocate を使用します)、このパラメータを使用する必要があります。

CMyProviderWindowsFile
ウィザードは 1 つのデータ行を含んでいるクラスを作成します。われわれのプロバイダでは、これは CMyProviderWindowsFile という名前になります。図 12 にCMyProviderWindowsFile のコードを示します。ウィザードが生成するコードは、WIN32_FIND_DATA 構造体を使って、ディレクトリ内のすべてのファイルをリストします。CMyProviderWindowsFile は WIN32_FIND_DATA 構造体から継承を行っています。


/////////////////////////////////////////////////////////////////////
// MyProviderRS.H

class CMyProviderWindowsFile:
 public WIN32_FIND_DATA
{
public:
BEGIN_PROVIDER_COLUMN_MAP(CMyProviderWindowsFile)
 PROVIDER_COLUMN_ENTRY("FileAttributes", 1, dwFileAttributes)
 PROVIDER_COLUMN_ENTRY("FileSizeHigh", 2, nFileSizeHigh)
 PROVIDER_COLUMN_ENTRY("FileSizeLow", 3, nFileSizeLow)
 PROVIDER_COLUMN_ENTRY("FileName", 4, cFileName)
 PROVIDER_COLUMN_ENTRY("AltFileName", 5, cAlternateFileName)
END_PROVIDER_COLUMN_MAP()
};
図 12. CMyProviderWindowsFile

また、CMyProviderWindowsFile にはプロバイダの行セットに含まれる列を記述するマップも含まれています。プロバイダの列マップには、ROVIDER_COLUMN_ENTRY マクロを使って、行セットの中の各フィールドについて1 つのエントリが格納されています。これらのマクロは列名、順序番号、および構造体エントリへのオフセットを指定します。図 12 のエントリは、WIN32_FIND_DATA 構造体へのオフセットを含んでいます。コンシューマが IRowset::GetData を呼び出すと、データは 1 つの連続したバッファに入れて転送されます。このマップにより、開発者はポインタ演算を行わなくても、データ メンバを指定することができます。
また、CMyProviderRowset クラスには Execute 関数が含まれています。Execute 関数は実際にネイティブ ソースからデータを読み込む関数です。図 13 にウィザードが生成した Execute 関数を示します。この関数は、Win32 のFindFirstFile() および FindNextFile() API を使って、ディレクトリ内のファイルに関する情報を取り出し、これらを CMyProviderWindowsFile クラスのインスタンスに格納します。
サーチするディレクトリは m_strCommandText 変数によって表現されます。 m_strCommandText 変数は、コマンド オブジェクトの ICommandText インターフェイスによって表現されるテキストを含んでいます。ディレクトリが指定されていなければ、カレント ディレクトリが使用されます。メソッドは各ファイルについて1 つのエントリを作成し (これは 1 つの行に対応します)、これを m_rgRowData データ メンバに格納します。m_rgRowData データ メンバは CRowsetImpl クラスによって定義されています。この配列の中のデータはテーブル全体を表現しており、テンプレートの中で頻繁に使用されます。


/////////////////////////////////////////////////////////////////////
// MyProviderRS.H

HRESULT Execute(DBPARAMS * pParams, LONG* pcRowsAffected)
{
 USES_CONVERSION;
 BOOL bFound = FALSE;
 HANDLE hFile;
 LPTSTR szDir = (m_strCommandText == _T("")) ? _T("*.*") :
  OLE2T(m_strCommandText);
 CMyProviderWindowsFile wf;
 hFile = FindFirstFile(szDir, &wf);
 if (hFile == INVALID_HANDLE_VALUE)
  return DB_E_ERRORSINCOMMAND;
 LONG cFiles = 1;
 BOOL bMoreFiles = TRUE;
 while (bMoreFiles)
 {
  if (!m_rgRowData.Add(wf))
   return E_OUTOFMEMORY;
  bMoreFiles = FindNextFile(hFile, &wf);
  cFiles++;
 }
 FindClose(hFile);
 if (pcRowsAffected != NULL)
  *pcRowsAffected = cFiles;
 return S_OK;
}
図 13. CMyProviderWindowsFileのExecute メソッド

単純な読み取り専用プロバイダの生成
最初のステップとして、プロバイダがコンシューマに対してどのようなデータを、どのような条件下で送るのかということを検討してください。コマンド、トランザクション、およびその他のオプションのオブジェクトをサポートする必要があるかどうかを決めることがきわめて重要です。事前に適切なデザインを行っておくことで、インプリメンテーションとテストに要する期間が短縮されます。われわれのプロバイダでは、ウィザードがデフォルトで生成するコードを使い、いくつかのセクションを変更します。われわれのプロバイダはテキスト ファイルからデータを読み取ります。まず CMyProviderWindowsFile クラスを変更することから始めます。図 14 に、変更後のクラスの内容を示します。


//////////////////////////////////////////////////////////////////////
//
// MyProviderRS.h

class CTextData
{
public:
 DWORD dwBookmark;
 char szField1[16];
 char szField2[16];

BEGIN_PROVIDER_COLUMN_MAP(CMyProviderWindowsFile)
 PROVIDER_COLUMN_ENTRY_STR("Field1", 1, szField1)
 PROVIDER_COLUMN_ENTRY_STR("Field2", 2, szField2)
END_PROVIDER_COLUMN_MAP()
};
図 14. CTextData クラス

szCommand および szText メンバは、ファイルから読み込む 2 つの文字列を表しています。 dwBookmark メンバは次のセクションで使用されます。マップ エントリは 2 つのフィールドに対応しています。PROVIDER_COLUMN_ENTRY_STR マクロは、コンシューマに対して、可変長のフィールドが含まれていることを通知します。コンシューマの中には、この情報に依存しているものがあります。
CMyProviderWindowsFile クラスのすべてのインスタンスを CTextData に変更する必要があります。これは検索と置換の機能を使って行えます。また、Execute メソッドを変更して、テキストファイルからデータを取り出すようにします。


//////////////////////////////////////////////////////////////////////
///
// MyProviderRS.H
// Class: CMyProviderRowset
HRESULT CMyProviderRowset::Execute(DBPARAMS * pParams, LONG *
pcRowsAffected)
{
USES_CONVERSION;
 FILE* pFile;
 TCHAR szString[256];
 TCHAR szFile[MAX_PATH];
 size_t nLength;
 ObjectLock lock(this);


 // From a filename, passed in as a command text, scan
 // the file placing data in the data array.
 if (m_strCommandText == (BSTR)NULL)
 {
  ATLTRACE("No filename specified");
  return E_FAIL;
 }

 // Open the file
 _tcscpy(szFile, OLE2T(m_strCommandText));
 if (szFile[0] == _T('\0') || ((pFile = fopen(&szFile[0], "r")) == NULL))
 {
  ATLTRACE("Could not open file");
  return DB_E_NOTABLE;
 }

 // scan and parse the file. The file should contain
 // two strings per record
 LONG cFiles = 0;
 while (fgets(szString, 256, pFile) != NULL)
 {
  nLength = strlen(szString);
  szString[nLength-1] = '\0'; // Strip off trailing CR/LF
  CTextData am;
  _tcscpy(am.szField1, szString);

  if (fgets(szString, 256, pFile) != NULL)
  {
   nLength = strlen(szString);
   szString[nLength-1] = '\0'; // Strip off trailing CR/LF
   _tcscpy(am.szField2, szString);
  }

  if (!m_rgRowData.Add(am))
  {
   ATLTRACE("Couldn't add data to array");
   fclose(pFile);
   return E_FAIL;
  }
 }

 if (pcRowsAffected != NULL)
  *pcRowsAffected = cFiles;
 return S_OK;
}
図 15. CMyProviderRowset の Execute メソッド

関数はファイルを開き、fgets を使って 2 つの文字列を取得します。文字列の各セットについて、CTextFile クラスのインスタンスを作成し、これを配列に格納します。 次にスキーマ行セット クラスのExecute メソッドを変更します。ここでは、テーブル スキーマを変更して、1 つのファイル名を返すようにします。テキスト ファイルとしては何でも使えるのですが、ドライブ上のすべてのテキスト ファイルを探すのは大変なので、ここでは 1 つのファイル名を使用し、これをユーザーが変更できるようにしておきます。次に、この部分のコード セグメントを示します。


/////////////////////////////////////////////////////////////////////
// MyProviderSess.H
// Class: CMyProviderSessionTRSchemaRowset

HRESULT Execute(LONG* pcRowsAffected, ULONG, const VARIANT*)
{
 USES_CONVERSION;
 CTextFile tf;
 CTABLESRow trData;
 lstrcpyW(trData.m_szType, OLESTR("TABLE"));
 lstrcpyW(trData.m_szDesc, OLESTR("The Text File Table"));

 TCHAR szFile[255];
 tcscpy(szFile, _T("c:\\code\\myprov\\sample.txt"));
 lstrcpynW(trData.m_szTable, T2OLE(szFile),
  SIZEOF_MEMBER(CTABLESRow, m_szTable);

 if (!m_rgRowData.Add(trData))
  return E_OUTOFMEMORY;
 *pcRowsAffected = 1;
 return S_OK;
}
図 16. CMyProviderSessionTRSchemaRowset のExecute メソッド

このコードは CTABLESRow という名前のクラスを使用しています。CTABLESRow は OLE DB プロバイダ テンプレートによって定義されており、DBSCHEMA_TABLES 行セットのすべてのデータ メンバを含んでいます。ここではテーブル名、説明、およびタイプを示すデータ フィールドに入力を行います。その後、この構造体のインスタンスを m_rgRowData 配列に追加します。列スキーマ行セットはテーブルと同じように動作します。CCOLUMNSRow という名前の構造体に入力を行います。構造体のインスタンスは個々のテキスト列ごとに、つまり合わせて2 つ作成します。次に列スキーマ行セットのコードを示します。


/////////////////////////////////////////////////////////////////////
// MyProviderSess.H
// Class: CMyProviderSessionColSchemaRowset

HRESULT Execute(LONG* pcRowsAffected, ULONG, const VARIANT*)
{
 USES_CONVERSION;
 CCOLUMNSRow trData[2];

 // 'NULL' out everything before we begin
 memset(trData, 0, sizeof(CCOLUMNSRow)*2);

 // Fill out common fields between columns
 for (long l=0; l<2; l++)
 {
  lstrcpyW(trData[l].m_szTableName,
    OLESTR("c:\\code\\myprov\\sample.txt"));
  trData[l].m_ulOrdinalPosition = l+1;
  trData[l].m_bIsNullable = VARIANT_FALSE;
  trData[l].m_bColumnHasDefault = VARIANT_FALSE;
  trData[l].m_ulCharMaxLength = 16;
  trData[l].m_ulColumnFlags = 0;
  trData[l].m_nDataType = DBTYPE_STR;

  // Add to the row data
  m_rgRowData.Add(trData[l]);
 }

 // Setup column names
 lstrcpyW(trData[0].m_szColumnName, OLESTR("Field1"));
 lstrcpyW(trData[1].m_szColumnName, OLESTR("Field2"));

 // Set up description columns
 lstrcpyW(trData[0].m_szDescription, OLESTR("Field1"));
 lstrcpyW(trData[1].m_szDescription, OLESTR("Field2"));

 *pcRowsAffected = 2;  // for the four columns
 return S_OK;
}
図 17. CMyProviderSessionColSchemaRowset のExecute メソッド

最後のコードは、プロバイダ タイプに関するものです。ここでは 1 つのタイプの文字列しか返しません。プロバイダ テンプレートの CPROVIDER_TYPERow クラスを使用することにします。この構造体に入力を行い、これを m_rgRowData メンバに追加します。次にプロバイダ タイプ スキーマ行セットのコードを示します。スキーマ行セットの詳細については、OLE DB の仕様を参照してください。


/////////////////////////////////////////////////////////////////////
// MyProviderSess.H
// Class: CMyProviderSessionPTSchemaRowset

HRESULT Execute(LONG* pcRowsAffected, ULONG, const VARIANT*)
{
 USES_CONVERSION;
 CPROVIDER_TYPERow trData;

 memset(&trData, 0, sizeof(CPROVIDER_TYPERow));

 lstrcpyW(trData.m_szName, OLESTR("String"));
 trData.m_nType = DBTYPE_STR;
 trData.m_ulSize = 16;
 trData.m_bIsLong = VARIANT_FALSE;
 trData.m_bIsNullable = VARIANT_FALSE;
 trData.m_bCaseSensitive = VARIANT_FALSE;
 trData.m_bSearchable = DB_UNSEARCHABLE;

 m_rgRowData.Add(trData);
 *pcRowsAffected = 1;
 return S_OK;
}
図 18. CMyProviderSessionPTSchemaRowset の Execute メソッド

プロバイダのテスト
プロバイダをテストするためにはコンシューマが必要となります。このときには、コンシューマが最初からプロバイダに合わせて作られていると便利です。OLE DB コンシューマ テンプレートは、OLE DB を包む薄いラッパで、プロバイダの COM オブジェクトに対応して作られています。コンシューマ テンプレートにはソースが付属しているので、これを使ってプロバイダをデバッグするのも簡単です。また、コンシューマ テンプレートはきわめて小さく、コンシューマ アプリケーションを短期間で開発することができます。
ここでは、テスト コンシューマとしてデフォルトの MFC AppWizardアプリケーションを作成します。テスト アプリケーションは、OLE DB コンシューマ テンプレートのコードが追加された単純なダイアログです。アプリケーションを作成するには、次のステップを実行します。

  • MFC AppWizard を起動します ([ファイル] - [新規作成] - [プロジェクト] を選択し、MFCAppWizard Exeの項目を選択します)。
  • プロジェクト名として"TestProv"と入力し、[OK] をクリックしてプロジェクトを作成します。
  • [ダイアログ ベース] を選択して[次へ] をクリックします。
  • [オートメーション] を選択して[終了] をクリックします。
注: CTestProvApp::InitInstanceにCoInitializeを追加すれば、オートメーションのステップは省略することができます。

oledb019

図 19. テスト ダイアログの例

リソース ファイルを読み込んでダイアログを見ると (IDD_TESTPROV_DIALOG)、図 19 のダイアログが表示されます。ここでは、このダイアログに、行セットに含まれる各文字列について1 つずつ、合わせて2 つのリスト ボックスを配置します。どちらのリスト ボックスについてもソートのプロパティはオフにします (リスト ボックスが選択されている状態でENTERキーを押し、[スタイル] タブを選択して、[ソート] チェックボックスをオフにします)。また、実際にファイルを取り出すための [実行] ボタンをダイアログに追加します。完成したダイアログは図 20 のようになります。

oledb020

図 20. TestProv の完成したダイアログ

ダイアログ クラスのヘッダー ファイルを開きます (この例では TestProvDlg.H)。ヘッダー ファイルに次のコードを追加します (すべてのクラス宣言の外)。


//////////////////////////////////////////////////////////////////////
//
// TestProvDlg.h

class CProvider
{
// Attributes
public:
 char szField1[16];
 char szField2[16];

 // Binding Maps
BEGIN_COLUMN_MAP(CProvider)
 COLUMN_ENTRY(1, szField1)
 COLUMN_ENTRY(2, szField2)
END_COLUMN_MAP()
};
図 21. TestProvDlg.h のユーザー レコード

このコードは、行セットに含まれる列を定義する「ユーザー レコード」を表しています。クライアントは、IAccessor::CreateAccessorを呼び出すときに、これらのエントリを使ってどの列をバインドするのかを指定します。OLE DB コンシューマ テンプレートでは、列を動的にバインドすることもできます。COLUMN_ENTRY マクロはPROVIDER_COLUMN_ENTRY マクロのクライアント サイドのバージョンです。2 つの COLUMN_ENTRY マクロは、2 つの文字列の順序番号、型、 長さ、およびデータ メンバを指定しています。
[実行] ボタンのためのハンドラ関数を追加します ([実行] ボタンをダブルクリックし、メンバ関数名をOnRunにします)。関数に次のコードを追加します。


//////////////////////////////////////////////////////////////////////
/
// TestProvDlg.cpp

void CtestProvDlg::OnRun()
{
 CCommand > table;
 CDataSource source;
 CSession session;

 if (source.Open(CLSID_MyProvider, NULL) != S_OK)
  return;

 if (session.Open(source) != S_OK)
  return;

 if (table.Open(session, _T("c:\\code\\myprov\\sample.txt")) != S_OK)
  return;

 while (table.MoveNext() == S_OK)
 {
  m_ctlString1.AddString(table.szField1);
  m_ctlString2.AddString(table.szField2);
 }
}
図 22. TestProvDlg.cpp の OnRun ハンドラ

CCommand、CDataSource、および CSession クラスは、いずれも OLE DB コンシューマ テンプレートに属しています。各クラスはプロバイダの中の COM オブジェクトに似ています。 CCommandオブジェクトは、ヘッダー ファイルの中でテンプレート パラメータとして宣言した CProvider クラスを取ります。CProvider パラメータは、プロバイダのデータにアクセスするために使用するバインディングを表しています。


if (source.Open("MyProvider.MyProvider.1", NULL) != S_OK)
  return;

 if (session.Open(source) != S_OK)
  return;

 if (table.Open(session, _T("c:\\code\\myprov\\sample.txt")) != S_OK)
  return;
図 23. データ ソース、セッション、およびコマンドをオープンするコード

個々のクラスをオープンする行は、プロバイダの中にそれぞれの COM オブジェクトを作成します。プロバイダを探すのには、プロバイダの ProgID を使用しています。ProgID はシステム レジストリで、または MyProvider.rgsファイルで調べることができます (プロバイダのディレクトリの中で、ProgID キーを検索します)。
foo.txtファイルは、本記事のサンプル コード ディレクトリに入っています。独自のファイルを作成するには、エディタを使って、各文字列の間で RETURN キーを押しながら、偶数個の文字列を入力します。ファイルを移動したときには、パス名を変更するのを忘れないようにしてください。 table.Open の行に "c:\\public\\testprov\foo.txt" という文字列を渡しています。Open の呼び出しにステップ インすると、この文字列がプロバイダの SetCommandText メソッドに渡さていることがわかります。覚えているかもしれませんが、これは ICommandText::Execute() 関数で使用した文字列です。


while (table.MoveNext() == S_OK)
 {
  m_ctlString1.AddString(table.szField1);
  m_ctlString2.AddString(table.szField2);
 }
図 24. データのフェッチと移動のコード

データをフェッチするには、単にテーブルに対して MoveNext() を呼び出します。MoveNext() API はIRowset::GetNextRows()GetRowCount()、および GetData() 関数を呼び出します。行がそれ以上存在しなければ (つまり、行セットの中の現在位置が GetRowCount() よりも大きければ)、ループは終了します。
注: 行がそれ以上存在しないと、プロバイダはDB_S_ENDOFROWSETを返します。DB_S_ENDOFROWSET の値はエラーではありません。プログラマは必ず S_OK をチェックして、SUCCEEDED マクロを使わずに、データ フェッチ ループを取り消すようにしなければなりません。
これでプログラムをビルドし、テストする準備ができました。

プロバイダに対する拡張

ブックマークのサポートの追加
プロバイダには、これ以外にもさまざまな機能を追加することができます。更新、トランザクションの処理、より堅牢な行フェッチ アルゴリズムの実行などを行わせることができます。本記事ではこれらの機能について詳しくは扱いませんが、追加の方法を知っておくと便利です。このセクションでは、CMyProviderRowset クラスにIRowsetLocate インターフェイスを追加します。ほとんどの場合は、既存の COM オブジェクトにインターフェイスを追加することから始めることになります。その後、コンシューマ テンプレートからの呼び出しを追加して、テストを行います。 われわれの例は以下の方法を示しています。

  • プロバイダにインターフェイスを追加する方法。
  • コンシューマに返す列を動的に決定する方法。
  • ブックマーク サポートを追加する方法。
IRowsetLocate インターフェイスはIRowsetインターフェイスから継承を行います。IRowset については、OLE DB プロバイダ テンプレートのインプリメンテーションを使うことにします。 IRowsetLocate インターフェイスを追加するために、CMyProviderRowset にIRowsetLocateImpl クラスから継承を行わせます。その後、IRowsetLocateImpl クラスのインプリメンテーションを用意します (図 27 を参照)。
IRowsetLocate インターフェイスの追加は、大部分のインターフェイスとは若干異なる方法で行います。VTABLE を列挙するために、OLE DB プロバイダ テンプレートは、派生インターフェイスを処理するためのテンプレート パラメータを持っています。図 25 に新しい継承チェーンを示します。4、5、および 6 番目のパラメータが新しく追加されます。われわれは 4 番目と 5 番目のパラメータについてはデフォルト値を使用しますが、6 番目のパラメータとしてはRowsetLocateImplを指定します。IRowsetLocateImpl クラスは独自のクラスですが、2 つのテンプレート パラメータを取ります。テンプレート パラメータは、IRowsetLocateインターフェイスをCMyProviderRowset クラスにフックアップする役割を果たしています。大部分のインターフェイスを追加するときには、このステップをスキップして、次に進むことができます。
この方法で扱わなければならないインターフェイスは IRowsetLocateIRowsetScroll だけです。


//////////////////////////////////////////////////////////////////////
// MyProviderRS.h

// CMyProviderRowset
class CMyProviderRowset : public CRowsetImpl< CMyProviderRowset,
  CTextData, CMyProviderCommand, CSimpleArray,
  CSimpleRow,
   IRowsetLocateImpl >
図 25. CMyProviderRowset の新しい継承リスト

次に、CMyProviderRowset に対して、QueryInterface を使ってIRowsetLocate インターフェイスを要求するように指示する必要があります。そこで、マップに COM_INTERFACE_ENTRY(IRowsetLocate) という行を追加します。マップは図 26 のようになります。また、われわれのマップを CRowsetImpl クラスにフックする必要があります。そこで、COM_INTERFACE_ENTRY_CHAIN マクロに CRowsetImpl マップをフックするコードを追加します。また、継承情報から構成される _RowsetBaseClass という名前の typedef を作成します。 このtypedef は無視してかまいません。


//////////////////////////////////////////////////////////////////////
//
// MyProviderRS.h

typedef CRowsetImpl< CMyProviderRowset, CTextData, CMyProviderCommand,
CSimpleArray, CSimpleRow,
IRowsetLocateImpl > _RowsetBaseClass;

BEGIN_COM_MAP(CMyProviderRowset)
 COM_INTERFACE_ENTRY(IRowsetLocate)
 COM_INTERFACE_ENTRY_CHAIN(_RowsetBaseClass)
END_COM_MAP()
図 26. CMyProviderRowset の新しい継承マップ

次に、IRowsetLocateImpl インターフェイスをインプリメントします。インターフェイス定義は OLE DB SDK にあります (付録Cを参照)。OLE DB SDK で OLEDB.H ファイルを見つけ (includes ディレクトリ)、定義を探してください。新しいインターフェイスを作成するには次の操作を行います。

  • OLEDB.H ファイルからインターフェイスのメソッドをクリップボードにコピーします。
  • クラス定義を作成します (私は RowLoc.h という名前の新しいファイルを作成し、プロジェクトに追加しました)。クラスは IRowsetLocateImpL という名前を持ち、IRowsetImpl から派生していなくてはなりません。これはテンプレート化されたクラスであることに注意してください (図 27 を参照)。
  • クリップボードの内容をクラス定義にコピーし、HRESULT の戻りコードを STDMETHOD に変更し、メソッド名を括弧で囲みます。図 27 を参照してください。


///////////////////////////////////////////////////////////////////
/////
// RowLoc.h


///////////////////////////////////////////////////////////////////
/////
// class IRowsetLocateImpl

template ,
 class BookmarkKeyType = LONG, class BookmarkType = LONG,
 class BookmarkMapClass = CSimpleMap < BookmarkKeyType, BookmarkType
> >
class ATL_NO_VTABLE IRowsetLocateImpl : public IRowsetImpl {
public:
 STDMETHOD (Compare)(HCHAPTER hReserved, ULONG cbBookmark1,
  const BYTE * pBookmark1, ULONG cbBookmark2, const BYTE *
   pBookmark2, DBCOMPARE * pComparison)
 {
  return S_OK;
 }

 STDMETHOD (GetRowsAt)(HWATCHREGION hReserved1, HCHAPTER hReserved2,
  ULONG cbBookmark, const BYTE * pBookmark, LONG lRowsOffset,
  LONG cRows, ULONG * pcRowsObtained, HROW ** prghRows)
 {
  return S_OK;
 }

 STDMETHOD (GetRowsByBookmark)(HCHAPTER hReserved, ULONG cRows,
  const ULONG rgcbBookmarks[], const BYTE * rgpBookmarks[],
  HROW rghRows[], DBROWSTATUS rgRowStatus[])
 {
  return S_OK;
 }

 STDMETHOD (Hash)(HCHAPTER hReserved, ULONG cBookmarks,
  const ULONG rgcbBookmarks[], const BYTE * rgpBookmarks[],
  DWORD rgHashedValues[], DBROWSTATUS rgBookmarkStatus[])
 {
  ATLTRACENOTIMPL("IRowsetLocateImpl::GetRowsByBookmark");
 }
};
図 27. IRowsetLocateImpl クラスの定義

  • 図 28のようにインプリメンテーションを追加します。
  • 
    ///////////////////////////////////////////////////////////////////
    /////
    // RowLoc.h
    
    
    ///////////////////////////////////////////////////////////////////
    /////
    // class IRowsetLocateImpl
    
    template ,
     class BookmarkKeyType = LONG, class BookmarkType = LONG,
     class BookmarkMapClass = CSimpleMap < BookmarkKeyType, BookmarkType
    > >
    class ATL_NO_VTABLE IRowsetLocateImpl : public IRowsetImpl {
    public:
     STDMETHOD (Compare)(HCHAPTER hReserved, ULONG cbBookmark1,
      const BYTE * pBookmark1, ULONG cbBookmark2, const BYTE *
       pBookmark2, DBCOMPARE * pComparison)
     {
      ATLTRACE("IRowsetLocateImpl::Compare");
    
      HRESULT hr = ValidateBookmark(cbBookmark1, pBookmark1);
      if (hr != S_OK)
       return hr;
    
      hr = ValidateBookmark(cbBookmark2, pBookmark2);
      if (hr != S_OK)
       return hr;
    
      if (pComparison == NULL)
       return E_INVALIDARG;
    
      // Return the value based on the bookmark values
      if (*pBookmark1 == *pBookmark2)
       *pComparison = DBCOMPARE_EQ;
    
      if (*pBookmark1 < *pBookmark2)
       *pComparison = DBCOMPARE_LT;
    
      if (*pBookmark1 > *pBookmark2)
       *pComparison = DBCOMPARE_GT;
    
      return S_OK;
     }
    
     STDMETHOD (GetRowsAt)(HWATCHREGION hReserved1, HCHAPTER hReserved2,
      ULONG cbBookmark, const BYTE * pBookmark, LONG lRowsOffset,
      LONG cRows, ULONG * pcRowsObtained, HROW ** prghRows)
     {
      ATLTRACE("IRowsetLocateImpl::GetRowsAt");
    
      // Check bookmark
      HRESULT hr = ValidateBookmark(cbBookmark, pBookmark);
      if (hr != S_OK)
       return hr;
    
      // Check the other pointers
      if (pcRowsObtained == NULL || prghRows == NULL)
       return E_INVALIDARG;
    
      // Set the current row position to the bookmark. Handle any
      // normal values
      m_iRowset = *pBookmark;
      if (*pBookmark == DBBMK_FIRST)
       m_iRowset = 1;
    
      if (*pBookmark == DBBMK_LAST)
       m_iRowset = m_pCommandHelper->GetRowCount();
    
      // Call IRowsetImpl::GetNextRows to actually get the rows.
      return GetNextRows(hReserved2, lRowsOffset,
       cRows, pcRowsObtained, prghRows);
     }
    
     STDMETHOD (GetRowsByBookmark)(HCHAPTER hReserved, ULONG cRows,
      const ULONG rgcbBookmarks[], const BYTE * rgpBookmarks[],
      HROW rghRows[], DBROWSTATUS rgRowStatus[])
     {
      HRESULT hr = S_OK;
      ATLTRACE("IRowsetLocateImpl::GetRowsByBookmark");
    
      if (rgcbBookmarks == NULL || rgpBookmarks == NULL ||
         rghRows == NULL)
       return E_INVALIDARG;
    
      if (cRows == 0)
       return S_OK; // No rows fetched in this case.
    
      bool bErrors = false;
      for (ULONG l=0; lGetRowCount();
      if ((*pBookmark <= 0 || *pBookmark > nRows)
       && *pBookmark != DBBMK_FIRST && *pBookmark !=
         DBBMK_LAST)
      {
       ATLTRACE("Bookmark has invalid range");
       return DB_E_BADBOOKMARK;
      }
    
      return S_OK;
     }
    };
    
    図 28. IrowsetLocateImpL の完成したインプリメンテーション

    IRowsetLocate インターフェイスは、ブックマークのインプリメンテーションを必要とします。ブックマークは、データへの高速なアクセスを可能にする、行セットのインデックスです。プロバイダはどのブックマークが行を一意に識別できるのかを判断します。われわれのケースでは、m_rgRowData 配列のインデックスを使用します。この配列のインデックスを使うことで、指定された行を高速にフェッチすることができます。
    ブックマークは任意のフォーマットで (ブックマークは任意の長さを持つことができる)、また任意のインデックス値で受け取る可能性があるので、その値が有用であることを確認するための ValidateBookmark() という関数を用意しておきます。この関数は、これがまず有効なブックマーク ポインタであるかどうかをチェックします。その後、ブックマークのサイズが DWORD であるかどうかをチェックします。図 13 に示したように、dwBookmark エントリは DWORD と定めています。 最後に、インデックスが配列内の有効な位置をポイントしていることをチェックします。
    注: DBMRK_FIRST と DBMRK_LAST という、有効であることがわかっている具体的なブックマーク値が 2 つあります。
    Compare()メソッドは、2 つのブックマークを取り、一方がもう一方よりも小さい、大きい、または等しいのいずれであるかを決定します。われわれの関数は、両方のブックマークの有効性をチェックした後に、配列インデックスを互いに比較します。
    GetRowsAt() 関数は、ブックマークからレコードをフェッチするという点を除けば、IRowset::GetNextRows メソッドと同じように動作します。われわれはブックマークの有効性をチェックした後に、実際に IRowsetImpl::GetNextRows を呼び出します。DBMRK_FIRST また は DBMRK_LAST の値ならば、配列内の最初または最後の行に設定します。
    GetRowsByBookmark() 関数は、いくつかの位置から行をフェッチします。これは行セットから 1 行目、23 行目、72 行目などの行をセットとして取り出すときに便利です。われわれは個々のブックマークの有効性をチェックした後に、IRowsetImpl の関数 CreateRow() を呼び出します。CreateRow()は必要であれば行を作成し、これをユーザーによって指定された配列に格納します。エラーが発生した場合、ユーザーがステータス配列を指定していれば、そこに適切なエラーコードを格納します。
    Hash() メソッドは、ブックマーク値を実際の行ハンドルに変換するために使用されます。われわれは、ブックマークの値は固定しているので、このメソッドは実際には必要ありません。このインプリメンテーションは単なる参考として示しています。 最後に、IColumnsInfo::GetColumnsInfo() 呼び出しを処理します。通常、この目的には PROVIDER_COLUMN_ENTRY マクロを使用します。しかし、コンシューマはブックマークを使う場合も使わない場合もあります。われわれは、コンシューマがブックマークを要求したかどうかに応じて、プロバイダが返す列を変更できなくてはなりません。
    IColumnsInfo::GetColumnsInfo() 呼び出しを処理するには、CTextData クラスの中の PROVIDER_COLUMN マップを削除します。PROVIDER_COLUMN_MAP マクロは GetColumnInfo() という名前の関数を定義しています。われわれは独自の GetColumnInfo() 関数を定義しなくてはなりません。この関数の関数プロトタイプを次に示します。

    
    //////////////////////////////////////////////////////////////////////
    //
    // MyProviderRS.H
    
    class CTextData
    {
      …
      // NOTE: Be sure you removed the PROVIDER_COLUMN_MAP!
     static ATLCOLUMNINFO* GetColumnInfo(CMyProviderRowset* pThis,
      ULONG* pcCols);
     static ATLCOLUMNINFO* GetColumnInfo(CMyProviderCommand* pThis,
      ULONG* pcCols);
      …
    };
    
    図 29. CTextData の中の GetColumnInfo の宣言

    次に、MyProviderRS.cpp ファイルの中に GetColumnInfo() 関数をインプリメントします。ファイルに図 30 のコードを追加してください。

    
    ////////////////////////////////////////////////////////////////////
    // MyProviderRS.cpp
    
    template 
    ATLCOLUMNINFO* CommonGetColInfo(IUnknown* pPropsUnk, ULONG* pcCols)
    {
     static ATLCOLUMNINFO _rgColumns[5];
     ULONG ulCols = 0;
    
     CComQIPtr spProps = pPropsUnk;
    
     CDBPropIDSet set(DBPROPSET_ROWSET);
     set.AddPropertyID(DBPROP_BOOKMARKS);
     DBPROPSET* pPropSet = NULL;
     ULONG ulPropSet = 0;
     HRESULT hr;
    
     if (spProps)
      hr = spProps->GetProperties(1, &set, &ulPropSet, &pPropSet);
    
     // Check the property flag for bookmarks, if it is set, set the
    // zero ordinal entry in the column map with the bookmark
    // information.
    
     if (pPropSet)
     {
      CComVariant var = pPropSet->rgProperties[0].vValue;
      CoTaskMemFree(pPropSet->rgProperties);
      CoTaskMemFree(pPropSet);
    
      if ((SUCCEEDED(hr) && (var.boolVal == VARIANT_TRUE)))
      {
       ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Bookmark"), 0,
          sizeof(DWORD), DBTYPE_BYTES,
       0, 0, GUID_NULL, CAgentMan, dwBookmark,
          DBCOLUMNFLAGS_ISBOOKMARK)
       ulCols++;
      }
    
     }
    
    
     // Next set the other columns up.
     ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Field1"), 1, 16, DBTYPE_STR,
       0xFF, 0xFF, GUID_NULL, CTextData, szField1)
     ulCols++;
     ADD_COLUMN_ENTRY_EX(ulCols, OLESTR("Field2"), 2, 16, DBTYPE_STR,
      0xFF, 0xFF, GUID_NULL, CTextData, szField2)
     ulCols++;
    
     if (pcCols != NULL)
      *pcCols = ulCols;
    
     return _rgColumns;
    }
    
    
    ATLCOLUMNINFO* CTextData::GetColumnInfo(CMyProviderCommand* pThis,
      ULONG* pcCols)
    {
     return CommonGetColInfo(pThis->GetUnknown(),
      pcCols);
    }
    
    
    ATLCOLUMNINFO* CAgentMan::GetColumnInfo(RUpdateRowset* pThis, ULONG*
    pcCols)
    {
     return CommonGetColInfo(pThis->GetUnknown(), pcCols);
    }
    
    図 30. MyProviderRS.cpp の中の _GetColumnInfo のインプリメンテーション

    われわれの GetColumnInfo() 関数は、まず DBPROP_IRowsetLocate という名前のプロパティが設定されているかどうかをチェックします。OLE DB は、行セット オブジェクトの個々のオプション インターフェイスについてプロパティを持っています。コンシューマは、いずれかのオプションインターフェイスを使いたいと考えたときには、対応するプロパティを true に設定します。プロバイダはこのプロパティをチェックし、これに基づいて特殊なアクションを実行することができます。 われわれのインプリメンテーションでは、コマンド オブジェクトへのポインタを使ってプロパティを取得しています。pThis ポインタは行セットまたはコマンド クラスを表しています。ここではテンプレートを使用しているので、これは void ポインタとして渡さないと、コードを正常にコンパイルできません。
    列情報を格納するための静的な配列を指定しています。コンシューマがブックマーク列を使わない場合には、配列の中のエントリが 1 つ無駄になります。必要ならばこの配列を動的に割り当てることもできますが、その際には配列が正しく破棄されるように注意する必要があります。われわれは独自のマクロ (ADD_COLUMN_ENTRY と ADD_COLUMN_ENTRY_EX) を使って、この情報を配列に挿入しています。これらのマクロを図 31 に示します。マクロは MyProviderRS.H ファイルに追加することができます。

    
    //////////////////////////////////////////////////////////////////////
    //
    // MyProviderRS.h
    
    #define ADD_COLUMN_ENTRY(ulCols, name, ordinal, colSize, type, precision,
    scale, guid, dataClass, member) \
     _rgColumns[ulCols].pwszName = (LPOLESTR)name; \
     _rgColumns[ulCols].pTypeInfo = (ITypeInfo*)NULL; \
     _rgColumns[ulCols].iOrdinal = (ULONG)ordinal; \
     _rgColumns[ulCols].dwFlags = 0; \
     _rgColumns[ulCols].ulColumnSize = (ULONG)colSize; \
     _rgColumns[ulCols].wType = (DBTYPE)type; \
     _rgColumns[ulCols].bPrecision = (BYTE)precision; \
     _rgColumns[ulCols].bScale = (BYTE)scale; \
     _rgColumns[ulCols].cbOffset = offsetof(dataClass, member);
    
    #define ADD_COLUMN_ENTRY_EX(ulCols, name, ordinal, colSize, type,
    precision, scale, guid, dataClass, member, flags) \
     _rgColumns[ulCols].pwszName = (LPOLESTR)name; \
     _rgColumns[ulCols].pTypeInfo = (ITypeInfo*)NULL; \
     _rgColumns[ulCols].iOrdinal = (ULONG)ordinal; \
     _rgColumns[ulCols].dwFlags = flags; \
     _rgColumns[ulCols].ulColumnSize = (ULONG)colSize; \
     _rgColumns[ulCols].wType = (DBTYPE)type; \
     _rgColumns[ulCols].bPrecision = (BYTE)precision; \
     _rgColumns[ulCols].bScale = (BYTE)scale; \
     _rgColumns[ulCols].cbOffset = offsetof(dataClass, member); \
     memset(&(_rgColumns[ulCols].columnid), 0, sizeof(DBID)); \
     _rgColumns[ulCols].columnid.uName.pwszName = (LPOLESTR)name;
    
    図 31. MyProviderRS.Hの中のADD_COLUMN_ENTRY マクロ

    コンシューマの中のコードをテストするためには、OnRun ハンドラにいくつかの変更を加える必要があります。コードは図 32 のようになるはずです。関数に加える最初の変更は、プロパティ セットにプロパティを追加するためのコードを追加するということです。このコードは DBPROP_IRowsetLocate プロパティを true に設定します。これはプロバイダに対して、ブックマーク列を使用することを通知します。

    
    //////////////////////////////////////////////////////////////////////
    // TestProv Consumer Application in TestProvDlg.cpp
    
    void CTestProvDlg::OnRun()
    {
     CCommand > table;
     CDataSource source;
     CSession session;
    
     if (source.Open("MyProvider.MyProvider.1", NULL, NULL, NULL,
       NULL) != S_OK)
      return;
    
     if (session.Open(source) != S_OK)
      return;
    
     CDBPropSet propset(DBPROPSET_ROWSET);
     propset.AddProperty(DBPROP_IRowsetLocate, true);
     if (table.Open(session, _T("c:\\public\\testprf2\\foo.txt"),
       &propset) != S_OK)
      return;
    
     CBookmark<4> tempBookmark;
     ULONG ulCount=0;
     while (table.MoveNext() == S_OK)
     {
    
      DBCOMPARE compare;
      if (ulCount == 2)
       tempBookmark = table.bookmark;
      HRESULT hr = table.Compare(table.dwBookmark, table.dwBookmark,
         &compare);
      if (FAILED(hr))
       ATLTRACE(_T("Compare failed: 0x%X\n"), hr);
      else
       _ASSERTE(compare == DBCOMPARE_EQ);
    
      m_ctlString1.AddString(table.szField1);
      m_ctlString2.AddString(table.szField2);
      ulCount++;
     }
    
     table.MoveToBookmark(tempBookmark);
     m_ctlString1.AddString(table.szField1);
     m_ctlString2.AddString(table.szField2);
    }
    
    図 32. TestProvの中の新しい OnRun ハンドラ

    while ループはIRowsetLocate インターフェイスの中の Compare() メソッドを呼び出すためのコードを含んでいます。われわれは同じブックマークを比較しているので、ここの部分はつねに正になるはずです。また、1 つのブックマークを一時的な変数に格納しておいて、while ループが終了した後に、コンシューマ テンプレートの中の MoveToBookmark() 関数を呼び出すときにこのブックマークを使用します。MoveToBookmark() 関数は RowsetLocate の中の GetRowsAt() メソッドを呼び出します。
    さらに、コンシューマの中のユーザー レコードを更新する必要があります。クラスにブックマークを処理するためのエントリを追加し、COLUMN_MAP にエントリを追加しなければなりません。この部分を図 33 に示します。

    
    //////////////////////////////////////////////////////////////////////
    /
    // TestProvDlg.cpp
    
    class CProvider
    {
    // Attributes
    public:
     CBookmark<4> bookmark; // Add this line
     char szCommand[16];
     char szText[16];
    
     // Binding Maps
    BEGIN_ACCESSOR_MAP(CProvider, 1)
     BEGIN_ACCESSOR(0, true) // auto accessor
      BOOKMARK_ENTRY(4, bookmark) // Add this line
      COLUMN_ENTRY(1, DBTYPE_STR, 16, szField1)
      COLUMN_ENTRY(2, DBTYPE_STR, 16, szField2)
     END_ACCESSOR()
    END_ACCESSOR_MAP()
    };
    
    図 33. TestProv の中の更新されたユーザー レコード

    コードを更新したら、ビルドを行い、IRowsetLocate インターフェイスを使ってプロバイダを実行できるようになります。

    OLE DB 適合性テストに合格する方法
    プロバイダの一貫性を高めるために、Data Access Group は OLE DB 適合性テストのセットを提供しています。これらのテストはプロバイダのあらゆる側面をチェックし、プロバイダが期待どおりに動作することを、ある程度のレベルで保証してくれます。OLE DB 適合性テストは MicrosoftData Access SDK(MSDASDK) に含まれています。この SDK は Visual Studio 6.0 と VisualC++ 6.0 に付属しています。このセクションでは、適合性テストに合格するために行わなければならない事柄に焦点を当てています。OLE DB 適合性テストの実行方法については、SDK を参照してください。
    Visual C++ 6.0 プロバイダ テンプレートには、値とプロパティをチェックするためのフッキング関数がいくつか追加されています。ほとんどの関数は、適合性テストの結果を反映させて、VisualC++ 6.0 で新しく追加されたものです。
    注: OLE DB 適合性テストに合格するためには、プロバイダにいくつかの妥当性確認関数を追加する必要があります。
    われわれのプロバイダは 2 つの妥当性確認ルーチンを必要とします。第 1 のルーチンは ValidateCommandID という名前です。ValidateCommandID メソッドは行セット クラスの一部です。これは、行セットの作成の際に、プロバイダ テンプレートから呼び出されます。サンプルでは、このルーチンを使って、インデックスをサポートしていないことをコンシューマに通知しています。最初の呼び出しは CRowsetImpl::ValidateCommandID クラスに対するものです (図 26 で追 加した _RowsetBaseClass typedef を使用しているので、長いテンプレート引数を入力しなくても済んでいることに注意してください)。次に、インデックス パラメータが NULL でなければ、DB_E_NOINDEX を返します (これはコンシューマがインデックスを使用したいと知らせていることを意味します)。コマンド ID の詳細については、OLE DB 仕様の IOpenRowset::OpenRowset の項を参照してください。

    
    /////////////////////////////////////////////////////////////////////
    // MyProviderRS.H
    // Class: CMyProviderRowset
    
    HRESULT ValidateCommandID(DBID* pTableID, DBID* pIndexID)
    
    {
     HRESULT hr = _RowsetBaseClass::ValidateCommandID(pTableID, pIndexID);
     if (hr != S_OK)
      return hr;
    
     if (pIndexID != NULL)
      return DB_E_NOINDEX; // We don't support indexes
    
     return S_OK;
    }
    
    図 34. OLE DB 適合性テストの妥当性確認ルーチン

    第 2 のルーチンは、ブックマーク サポートのために必要となります。OLE DB の一部のプロパティは「チェーン」されています。1 つのプロパティが変化すると、他のプロパティも同時に変化します。ブックマーク サポートは、チェーンされているいくつかのプロパティを含んでいます。われわれは行セット クラスに、これらのプロパティを扱うための OnPropertyChanged() という名前のオーバーライドを追加します。

    
    /////////////////////////////////////////////////////////////////////
    // MyProviderRS.H
    // Class: CMyProviderRowset
    
    HRESULT OnPropertyChanged(ULONG iCurSet, DBPROP* pDBProp)
    {
     ATLASSERT(pDBProp != NULL);
    
     DWORD dwPropertyID = pDBProp->dwPropertyID;
    
     if (dwPropertyID == DBPROP_IRowsetLocate ||
      dwPropertyID == DBPROP_LITERALBOOKMARKS ||
      dwPropertyID == DBPROP_ORDEREDBOOKMARKS)
     {
      CComVariant var = pDBProp->vValue;
    
      if (var.boolVal == VARIANT_TRUE)
      {
       // Set the bookmarks property as these are chained
       CComVariant bookVar(true);
       CDBPropSet set(DBPROPSET_ROWSET);
       set.AddProperty(DBPROP_BOOKMARKS, bookVar);
    
       return SetProperties(1, &set);
      }
     }
    
     return S_OK;
    }
    
    図 35. OnPropertyChanged メソッド

    プロバイダ テンプレートは、誰かが DBPROPSET_ROWSET グループのプロパティを変更すると、このメソッドを呼び出します。他のグループのプロパティを処理したい場合には、適切なオブジェクトに追加してください(たとえば、DBPROPSET_SESSIOn のチェックは、CMyProviderSession クラスに入れるべきです)。
    このコードは、まずプロパティが別のプロパティにリンクされているかどうかをチェックします。プロパティがチェーンされている場合には、DBPROP_BOOKMARKS プロパティを true に設定します。プロパティに関する情報は、OLE DB Reference の付録 C にあります。また、この情報を使えば、プロパティが互いにチェーンされているかどうかを調べることもできます。また、コードに IsValidValue ルーチンを追加することもできます。テンプレートは、プロパティの設定を試みる際に、IsValidValue を呼び出します。プロパティ値を設定するときの処理を追加したい場合には、このメソッドをオーバーライドしてください。これらのメソッドは、各プロパティ セットごとに 1 つ持つことができます。

    要約

    OLE DB プロバイダは、データをUniversal Data Access 戦略に組み込むための手段となります。 OLE DB プロバイダは、コンシューマからのインターフェイス呼び出しに反応する一連の COM コンポーネントです。各コンポーネントは複数のインターフェイスを含んでいます。すべてのインターフェイスをインプリメントする必要はありません。プロバイダは、最小限の機能をサポートすることもできますし、さらに多くのインターフェイスをインプリメントすることで、高機能なプロダクションクオリティのプロバイダとなることもできます。
    Visual C++ 6.0 に付属する OLE DB プロバイダ テンプレートは、プロバイダを作成するのに便利なツールです。ウィザードによるサポートがあり、また 20 個の一般的な必須インターフェイスのインプリメンテーションが提供されます。実際、単にウィザードを実行するだけで、完全に機能するプロバイダを作成することができるのです。
    ウィザードが生成したコードに機能を追加するときには、一般に COM インターフェイスのサポートを追加していきます。OLE DB プロバイダ テンプレートに組み込まれているサポート クラスにより、機能の追加は簡単に行えます。

    付録 A: サポートされているインターフェイス

    インターフェイス完全にサポートされているかどうか
    IAccessorYes
    IColumnsInfoYes
    ICommandYes
    ICommandPropertiesYes
    ICommandTextYes
    IConvertTypeYes
    IDBCreateCommandYes
    IDBCreateSessionYes
    IDBDataSourceAdminNo
    IDBInfoNo
    IDBInitializeYes
    IDBPropertiesYes
    IGetDataSourceYes
    IOpenRowsetYes
    IPersistYes
    IRowsetYes
    IRowsetChangeNo
    IRowsetInfoYes
    ISessionPropertiesYes
    ITransactionLocalNo
    IDBSchemaRowsetYes

    付録 B: 有用な情報

    このセクションでは、有用な Web サイトをいくつか紹介します。
    [英語サイト]
    http://www.microsoft.com/data/: Universal Data Access のメインの Web サイトです。サンプルソフトウェアやホワイト ペーパーなどがあります。
    http://www.microsoft.com/data/oledb/ : OLE DB SDK の Web サイトです。このサイトからOLEDB 仕様をダウンロードすることができます。
    http://msdn.microsoft.com/visualc/ : Microsoftが提供する、Visual C++ に関する情報の決定的なリソース。このサイトでは、テクニカル ホワイト ペーパーを含めて、Visual C++ 6.0 に関する情報が提供されています。
    [日本語サイト]
    http://www.microsoft.com/japan/msdn/data/: Universal Data Access の日本語 Web サイトです。
    http://www.microsoft.com/japan/msdn/visualc/: Visual C++ の日本語 Web サイトです。

    付録 C: Tech PreviewからVisual C++ 6.0 への変更点

    Visual C++ 5.0 Technology Preview Internet Explorer 4.0 対応版と Visual C++ 6.0 では、OLE DB プロバイダ テンプレートにいくつかの変更が加えられています。変更点の大部分は行セットとプロパティの処理に関連しています。大部分の変更は、OLE DB 適合性テストに基づいて決められたものです。

    コマンド ヘルパ クラスの削除
    Technology Preview と Visual C++ 6.0 のテンプレートの大きな違いの 1 つは、コマンド ヘルパが削除されたことです。コマンド ヘルパ クラスは、RowsetImpL とデータ クラスの形に進化しました。この変更が加えられた理由は、プロバイダ上で複数の行セットをサポートしやすくすることです。複数の行セットにより、スキーマ行セットや複数の静的行セットなどをサポートすることができます。
    コマンド ヘルパ戦略を採ったとすると、ユーザーは、プロバイダの新しい行セット タイプごとに、コマンド ヘルパ、行セット、およびコマンド クラスをコピーしなければならなかったでしょう。これはプロバイダのコード サイズを大幅に増やす結果になります。新しい戦略では、サポートされる各行セットについて、新しい行セットとデータ クラスを追加するだけで済みます。

    スキーマ行セット サポートの追加
    OLE DB と ADO のウィザードの多くは、行セットをオープンしなくても、行セットに関して何らかの情報が得られることを必要としています。これは通常はメタデータによってサポートされます。OLE DB はメタデータをスキーマ行セットを通して処理します。OLE DB プロバイダ テンプレートは、IDBSchemaRowset インターフェイスのインプリメンテーションを提供しています。

    プロパティ サポートの変更
    プロパティ コードに変更を加え、プロパティをより簡単に追加できるようにしています。Technology Preview では、プロパティは次の構文を使って指定していました。
    PROPERTY_INFO_ENTRY(IAccessor, VT_BOOL, DBPROPFLAGS_ROWSET |
    DBPROPFLAGS_READ, VARIANT_TRUE, 0)
    Visual C++ 6.0 のテンプレートでは、各プロパティのデフォルト値が提供されるので、プロパティの指定が簡単になっています。新しい構文を次に示します。
    PROPERTY_INFO_ENTRY(IAccessor)
    PROPERTY_INFO_ENTRY マクロはプロパティのデフォルト値を指定します。独自の値を設定したい場合には、PROPERTY_INFO_ENTRY_VALUE マクロを使って、適切なプロパティ データ型を指定します。

    付録 D: Tech Preview プロバイダの Visual C++ 6.0 への移 植

    Visual C++ 5.0 Technology Preview Internet Explorer 4.0 対応版で開発されていたプロバイダを Visual C++ 6.0 に移植するために必要な作業のチェックリストを示します。

    1. 移植作業の最初のステップとして、ウィザードを実行します。スキーマ行セットがデフォルトでサポートされます (一部のウィザードは、コンシューマを作成する際にこのサポートを利用します)。
    2. MYPROV および PROVIDER サンプルの Tech Preview と 6.0 のバージョンを比較します。
    3. 行セットのプロパティを調べるときには、コマンド ヘルパではなく IRowsetInfo インターフェイスを使用するようにします。
    4. IRowsetLocate または IRowsetScroll をインプリメントするときには、CRowsetImpL の RowsetInterface テンプレート パラメータを使用するようにします (これは ATLDB.H に定義されています)。
    5. Execute および GetColumnInfo 関数をコマンド ヘルパから行セット クラスに移動します。
    6. CleanupData 関数を削除します。
    7. 行セット データ キャッシュは、コマンド ヘルパから CRowsetImpL に移されています (現在は m_rgRowData という名前になっています)。
    8. プロパティ マクロが変更されています。プロパティを直接に移植したい場合は、コピーする各プロパティの末尾に _EX を追加します (例: PROPERTY_INFO_ENTRY_EX)。Visual C++ 6.0 では必要な構文が短縮されています。
    9. IsValidValue、OnPropertyChanged、ValidateCommandID、および GetDBStatus の関数をインプリメントします。適合性テストに合格するためには、最初の 3 つが必要です。最後の関数は NULL データを返すときに便利です (つまり DBSTATUS_S_ISNULL を返す)。

    (C) 1999 Microsoft Corporation. All rights reserved. Terms of Use.