プロバイダのブックマーク サポート
更新 : 2007 年 11 月
このトピックの例では、CMyProviderRowset クラスに IRowsetLocate インターフェイスを追加します。ほとんどの場合、既存の COM オブジェクトにインターフェイスを追加することから始めます。その後、コンシューマ テンプレートからの呼び出しを追加して、インターフェイスをテストできます。この例では、次の方法を示します。
プロバイダにインターフェイスを追加する方法
コンシューマに返す列を動的に決める方法
ブックマーク サポートを追加する方法
IRowsetLocate インターフェイスは IRowset インターフェイスを継承します。IRowsetLocate インターフェイスを追加するには、CMyProviderRowset を IRowsetLocateImpl から派生させます。
IRowsetLocate インターフェイスの追加方法は、ほかのほとんどのインターフェイスの場合と少し異なります。VTABLE を準備するために、OLE DB プロバイダ テンプレートには、派生インターフェイスを処理するテンプレート パラメータがあります。新しい継承のリストを次のコードに示します。
////////////////////////////////////////////////////////////////////////
// MyProviderRS.h
// CMyProviderRowset
class CMyProviderRowset : public CRowsetImpl< CMyProviderRowset,
CTextData, CMyProviderCommand, CAtlArray<CTextData>,
CSimpleRow,
IRowsetLocateImpl<CMyProviderRowset, IRowsetLocate> >
4 番目、5 番目、および 6 番目のパラメータがすべて追加されます。この例では、4 番目と 5 番目のパラメータには既定値を使用しますが、6 番目のパラメータとして IRowsetLocateImpl を指定します。IRowsetLocateImpl は、2 つのテンプレート パラメータを取得する OLE DB テンプレート クラスです。これらのパラメータは、IRowsetLocate インターフェイスを CMyProviderRowset クラスにフックします。ほとんどのインターフェイスの追加では、この手順をスキップして次の手順に進みます。この方法で処理する必要があるのは、IRowsetLocate インターフェイスと IRowsetScroll インターフェイスだけです。
次に、CMyProviderRowset に IRowsetLocate インターフェイスの QueryInterface を呼び出させる必要があります。COM_INTERFACE_ENTRY(IRowsetLocate) 行をマップに追加します。CMyProviderRowset のインターフェイス マップは、次のコードのようになります。
////////////////////////////////////////////////////////////////////////
// MyProviderRS.h
typedef CRowsetImpl< CMyProviderRowset, CTextData, CMyProviderCommand, CAtlArray<CTextData>, CSimpleRow, IRowsetLocateImpl<CMyProviderRowset, IRowsetLocate> > _RowsetBaseClass;
BEGIN_COM_MAP(CMyProviderRowset)
COM_INTERFACE_ENTRY(IRowsetLocate)
COM_INTERFACE_ENTRY_CHAIN(_RowsetBaseClass)
END_COM_MAP()
マップを CRowsetImpl クラスにフックする必要もあります。フックする COM_INTERFACE_ENTRY_CHAIN マクロを CRowsetImpl マップに追加します。また、継承情報から構成される RowsetBaseClass という typedef を作成します。この typedef は任意であり、無視できます。
最後に、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);
...
};
その後、次のように GetColumnInfo 関数を MyProviderRS.cpp ファイルに実装します。
////////////////////////////////////////////////////////////////////
// MyProviderRS.cpp
template <class TInterface>
ATLCOLUMNINFO* CommonGetColInfo(IUnknown* pPropsUnk, ULONG* pcCols)
{
static ATLCOLUMNINFO _rgColumns[5];
ULONG ulCols = 0;
CComQIPtr<TInterface> 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<ICommandProperties>(pThis->GetUnknown(),
pcCols);
}
ATLCOLUMNINFO* CAgentMan::GetColumnInfo(RUpdateRowset* pThis, ULONG* pcCols)
{
return CommonGetColInfo<IRowsetInfo>(pThis->GetUnknown(), pcCols);
}
GetColumnInfo は、最初に、DBPROP_IRowsetLocate というプロパティが設定されているかどうかを調べます。OLE DB では、行セット オブジェクトとは独立して、省略可能なインターフェイスごとにプロパティが用意されています。省略可能なインターフェイスを使用するコンシューマは、そのインターフェイスのプロパティを true に設定します。プロバイダはこのプロパティを調べ、これに基づいて独自の処置をとることができるようになります。
実装では、コマンド オブジェクトへのポインタを使用してプロパティを取得します。pThis ポインタは行セットまたはコマンド クラスを表します。ここではテンプレートを使用するため、このポインタを void 型のポインタとして渡す必要があります。そうしないとコードがコンパイルされません。
次に、列情報を格納するための静的配列を指定します。コンシューマがブックマーク列を要求しない場合は、配列のエントリを 1 つ余分に使用することになります。この配列を動的に割り当てることができますが、割り当てた配列が適切に破棄されるようにする必要があります。ここでは ADD_COLUMN_ENTRY マクロと ADD_COLUMN_ENTRY_EX マクロを定義および使用して、情報を配列に挿入します。このマクロを以下のように 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;
コンシューマ内のコードをテストするには、OnRun ハンドラに多少の変更を加える必要があります。この関数に加える最初の変更は、プロパティ セットにプロパティを追加するコードを追加することです。このコードは DBPROP_IRowsetLocate プロパティを true に設定します。これでブックマーク列を使用することをプロバイダに通知できます。OnRun ハンドラのコードは次のようになります。
//////////////////////////////////////////////////////////////////////
// TestProv Consumer Application in TestProvDlg.cpp
void CTestProvDlg::OnRun()
{
CCommand<CAccessor<CProvider> > 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\\myData.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);
}
while ループには、IRowsetLocate インターフェイスの Compare メソッドを呼び出すためのコードが含まれています。まったく同じブックマークを比較することになるため、このコードは常に成功します。同時に一方のブックマークをテンポラリ変数に格納します。これは、while ループの終了後にこのブックマークを使用して、コンシューマ テンプレートの MoveToBookmark 関数を呼び出すためです。MoveToBookmark 関数は、IRowsetLocate の GetRowsAt メソッドを呼び出します。
コンシューマのユーザー レコードも更新する必要があります。ブックマークを処理するクラスと COLUMN_MAP にそれぞれエントリを 1 つ追加します。
///////////////////////////////////////////////////////////////////////
// TestProvDlg.cpp
class CProvider
{
// Attributes
public:
CBookmark<4> bookmark; // Add this line
char szCommand[16];
char szText[256];
// Binding Maps
BEGIN_ACCESSOR_MAP(CProvider, 1)
BEGIN_ACCESSOR(0, true) // auto accessor
BOOKMARK_ENTRY(bookmark) // Add this line
COLUMN_ENTRY(1, szField1)
COLUMN_ENTRY(2, szField2)
END_ACCESSOR()
END_ACCESSOR_MAP()
};
コードを更新すると、IRowsetLocate インターフェイスを使用してプロバイダをビルドし、実行できるようになります。