Programming with Microsoft Visual C++.NET 6ed [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Programming with Microsoft Visual C++.NET 6ed [Electronic resources] - نسخه متنی

George Shepherd, David Kruglinski

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
توضیحات
افزودن یادداشت جدید








Creating an OLE DB Provider


It's pretty obvious how OLE DB consumers are useful. You just ask a wizard to create a wrapper for you, and you get a fairly easy way to access the data in a database. However, it might be a bit less obvious why you'd want to create an OLE DB provider.

Writing an OLE DB provider allows you to insert a layer between a client of some data and the data itself. Here are just a few reasons you might want to write a provider:



  • Writing an OLE DB provider means that clients don't necessarily touch the data directly. Therefore, you can add additional capabilities to your data, such as query processing.



  • In some cases, writing an OLE DB provider allows you to increase data access performance by controlling how the data is manipulated.



  • Adding an OLE DB provider layer increases the potential audience of your data. For example, if you have a proprietary data format that can be accessed by only one programming language, you have a single point of failure. OLE DB providers give you a way to open that proprietary format to a wider variety of programmers, regardless of the programming language they use.



Working with the OLE DB providers is similar to working with the OLE DB consumers. The wizards do a lot of the work for you. You just need to know how to work with the generated classes. The steps for creating an OLE DB provider are as follows:



  1. Decide what you want the provider to do. Remember the philosophy behind OLE DB: It's all about providing a singular way to access multiple data sources. For example, you might want to write a provider that recursively enumerates the contents of a structured storage file. Or you might want a provider that sifts through e-mail folders and allows clients database-style access to your e-mail system. The possibilities are nearly endless.



  2. Use the ATL OLE DB Provider Wizard to create a provider. (Choose Add Class from the Project menu, and then select ATL OLE DB Provider from the class templates.) The wizard will ask you to provide a name for your object and will allow you to modify the default names for the files it will create.



  3. After you click Finish, the ATL OLE DB Provider Wizard will create the code for a provider, including a data source, a rowset, and a session. A provider also supports one or more properties, which are defined in property maps within the files created by the wizard. When the wizard creates the files, it inserts maps for the properties belonging to the OLE DB property group that was defined for the object or objects included in those files. For example, the header file containing the data source object also contains the property map for the data source properties. The session header file contains the property map for the session properties. Finally, the rowset and command objects reside in a single header file, which includes properties for the command object.



For example, let's look at what the ATL OLE DB Provider Wizard produces for an OLE DB provider named AProvider. First, the wizard creates a data source object, which lives in a file named AProviderDS.h:

class ATL_NO_VTABLE CAProviderSource : 
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CAProviderSource, &CLSID_AProvider>,
public IDBCreateSessionImpl<CAProviderSource, CAProviderSession>,
public IDBInitializeImpl<CAProviderSource>,
public IDBPropertiesImpl<CAProviderSource>,
public IPersistImpl<CAProviderSource>,
public IInternalConnectionImpl<CAProviderSource>
{
public:
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct()
{
return FInit();
}
void FinalRelease()
{
}
DECLARE_REGISTRY_RESOURCEID(IDR_APROVIDER)
BEGIN_COM_MAP(CAProviderSource)
COM_INTERFACE_ENTRY(IDBCreateSession)
COM_INTERFACE_ENTRY(IDBInitialize)
COM_INTERFACE_ENTRY(IDBProperties)
COM_INTERFACE_ENTRY(IPersist)
COM_INTERFACE_ENTRY(IInternalConnection)
END_COM_MAP()
BEGIN_PROPSET_MAP(CAProviderSource)
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(CAProviderSession)
CHAIN_PROPERTY_SET(CAProviderCommand)
END_PROPSET_MAP()
public:
};

In addition to the data object, the ATL OLE DB Provider Wizard produces a command object and a rowset that both live in AProviderRS.h:

class ATL_NO_VTABLE CAProviderCommand : 
public CComObjectRootEx<CComSingleThreadModel>,
public IAccessorImpl<CAProviderCommand>,
public ICommandTextImpl<CAProviderCommand>,
public ICommandPropertiesImpl<CAProviderCommand>,
public IObjectWithSiteImpl<CAProviderCommand>,
public IConvertTypeImpl<CAProviderCommand>,
public IColumnsInfoImpl<CAProviderCommand>,
public IInternalCommandConnectionImpl<CAProviderCommand>
{
public:
BEGIN_COM_MAP(CAProviderCommand)
COM_INTERFACE_ENTRY(ICommand)
COM_INTERFACE_ENTRY(IObjectWithSite)
COM_INTERFACE_ENTRY(IAccessor)
COM_INTERFACE_ENTRY(ICommandProperties)
COM_INTERFACE_ENTRY2(ICommandText, ICommand)
COM_INTERFACE_ENTRY(IColumnsInfo)
COM_INTERFACE_ENTRY(IConvertType)
COM_INTERFACE_ENTRY(IInternalConnection)
END_COM_MAP()
// ICommand
public:
HRESULT FinalConstruct()
{
HRESULT hr = CConvertHelper::FinalConstruct();
if (FAILED (hr))
return hr;
hr = IAccessorImpl<CAProviderCommand>::FinalConstruct();
if (FAILED(hr))
return hr;
return CUtlProps<CAProviderCommand>::FInit();
}
void FinalRelease()
{
IAccessorImpl<CAProviderCommand>::FinalRelease();
}
HRESULT WINAPI Execute(IUnknown * pUnkOuter,
REFIID riid, DBPARAMS * pParams,
LONG * pcRowsAffected, IUnknown ** ppRowset);
static ATLCOLUMNINFO* GetColumnInfo(CAProviderCommand* pv,
ULONG* pcInfo)
{
return CAProviderWindowsFile::GetColumnInfo(pv, pcInfo);
}
BEGIN_PROPSET_MAP(CAProviderCommand)
BEGIN_PROPERTY_SET(DBPROPSET_ROWSET)
PROPERTY_INFO_ENTRY(IAccessor)
PROPERTY_INFO_ENTRY(IColumnsInfo)
PROPERTY_INFO_ENTRY(IConvertType)
PROPERTY_INFO_ENTRY(IRowset)
PROPERTY_INFO_ENTRY(IRowsetIdentity)
PROPERTY_INFO_ENTRY(IRowsetInfo)
PROPERTY_INFO_ENTRY(IRowsetLocate)
PROPERTY_INFO_ENTRY(BOOKMARKS)
PROPERTY_INFO_ENTRY(BOOKMARKSKIPPED)
PROPERTY_INFO_ENTRY(BOOKMARKTYPE)
PROPERTY_INFO_ENTRY(CANFETCHBACKWARDS)
PROPERTY_INFO_ENTRY(CANHOLDROWS)
PROPERTY_INFO_ENTRY(CANSCROLLBACKWARDS)
PROPERTY_INFO_ENTRY(LITERALBOOKMARKS)
PROPERTY_INFO_ENTRY(ORDEREDBOOKMARKS)
END_PROPERTY_SET(DBPROPSET_ROWSET)
END_PROPSET_MAP()
};
class CAProviderRowset :
public CRowsetImpl< CAProviderRowset,
CAProviderWindowsFile, CAProviderCommand>
{
public:
HRESULT Execute(DBPARAMS * pParams, LONG* pcRowsAffected)
{
USES_CONVERSION;
BOOL bFound = FALSE;
HANDLE hFile;
LPTSTR szDir =
(m_strCommandText == _T(")) ? _T("*.*") :
OLE2T(m_strCommandText);
CAProviderWindowsFile wf;
hFile = FindFirstFile(szDir, &wf);
if (hFile == INVALID_HANDLE_VALUE)
return DB_E_ERRORSINCOMMAND;
LONG cFiles = 1;
BOOL bMoreFiles = TRUE;
while (bMoreFiles)
{
_ATLTRY
{
m_rgRowData.Add(wf);
}
_ATLCATCH( e )
{
_ATLDELETEEXCEPTION( e )
return E_OUTOFMEMORY;
}
bMoreFiles = FindNextFile(hFile, &wf);
cFiles++;
}
FindClose(hFile);
if (pcRowsAffected != NULL)
*pcRowsAffected = cFiles;
return S_OK;
}
};

The wizard produces a session object in a file named AProviderSess.h, as shown in this code:

class ATL_NO_VTABLE CAProviderSession : 
public CComObjectRootEx<CComSingleThreadModel>,
public IGetDataSourceImpl<CAProviderSession>,
public IOpenRowsetImpl<CAProviderSession>,
public ISessionPropertiesImpl<CAProviderSession>,
public IObjectWithSiteSessionImpl<CAProviderSession>,
public IDBSchemaRowsetImpl<CAProviderSession>,
public IDBCreateCommandImpl<CAProviderSession, CAProviderCommand>
{
public:
CAProviderSession()
{
}
DECLARE_PROTECT_FINAL_CONSTRUCT()
HRESULT FinalConstruct()
{
return FInit();
}
void FinalRelease()
{
}
STDMETHOD(OpenRowset)(IUnknown *pUnk, DBID *pTID,
DBID *pInID, REFIID riid,
ULONG cSets, DBPROPSET rgSets[],
IUnknown **ppRowset)
{
CAProviderRowset* pRowset;
return CreateRowset(pUnk, pTID, pInID, riid, cSets,
rgSets, ppRowset, pRowset);
}
void SetRestrictions(ULONG cRestrictions,
GUID* rguidSchema, ULONG* rgRestrictions)
{
for (ULONG l=0; l<cRestrictions; l++)
{
// We support restrictions on the table name but nothing else
if (InlineIsEqualGUID(rguidSchema[l], DBSCHEMA_TABLES))
rgRestrictions[l] = 0x04;
else if (InlineIsEqualGUID(rguidSchema[l], DBSCHEMA_COLUMNS))
rgRestrictions[l] = 0x04;
else if (InlineIsEqualGUID(rguidSchema[l],
DBSCHEMA_PROVIDER_TYPES))
rgRestrictions[l] = 0x00;
}
}
BEGIN_PROPSET_MAP(CAProviderSession)
BEGIN_PROPERTY_SET(DBPROPSET_SESSION)
PROPERTY_INFO_ENTRY(SESS_AUTOCOMMITISOLEVELS)
END_PROPERTY_SET(DBPROPSET_SESSION)
END_PROPSET_MAP()
BEGIN_COM_MAP(CAProviderSession)
COM_INTERFACE_ENTRY(IGetDataSource)
COM_INTERFACE_ENTRY(IOpenRowset)
COM_INTERFACE_ENTRY(ISessionProperties)
COM_INTERFACE_ENTRY(IObjectWithSite)
COM_INTERFACE_ENTRY(IDBCreateCommand)
COM_INTERFACE_ENTRY(IDBSchemaRowset)
END_COM_MAP()
BEGIN_SCHEMA_MAP(CAProviderSession)
SCHEMA_ENTRY(DBSCHEMA_TABLES, CAProviderSessionTRSchemaRowset)
SCHEMA_ENTRY(DBSCHEMA_COLUMNS, CAProviderSessionColSchemaRowset)
SCHEMA_ENTRY(DBSCHEMA_PROVIDER_TYPES,
CAProviderSessionPTSchemaRowset)
END_SCHEMA_MAP()
};




Modifying the Provider Code


As with most wizard-generated code, the OLE DB provider code generated by the ATL OLE DB Provider Wizard is just boilerplate code—it doesn't do very much. You must take several steps to turn this boilerplate code into a real OLE DB provider. The two critical things you must do are to add the user record and code to manage a dataset and to set up the data as rows and columns.

The ATL OLE DB Provider Wizard provides a default user record named CAProviderWindowsFile. You'll probably want to scrap this user record and replace it with something useful in your domain. As a simple example, imagine that you want to write an OLE DB provider that enumerates a compound file. Your user record might look like this:

struct CStgInfo {
BEGIN_PROVIDER_COLUMN_MAP(CStgInfo)
PROVIDER_COLUMN_ENTRY("StgName", 1, szName)
PROVIDER_COLUMN_ENTRY("Size", 2, cbSizeLow)
PROVIDER_COLUMN_ENTRY("Size", 2, cbSizeHigh)
END_PROVIDER_COLUMN_MAP()
OLECHAR szName[256];
long cbSizeLow;
long cbSizeHigh;
};

This structure contains the data fields for the name and size of the substorage. The provider column map macros map the data into columns. You can actually derive the structure from a STATSTG structure (which is used to enumerate structured storages)—you just add entries to the provider column map to handle the members.

The other important addition to the provider is the code for opening the data set. This happens in the rowset's Execute function. Many kinds of functionality can go in here. For example, if you want to enumerate the top-level substorages in a compound file, you can open the storage and then enumerate the contents as shown in the following code snippet:

class RStgInfoProviderRowset : 
public CRowsetImpl<RStgInfoProviderRowset,
CStgInfo,
CStgInfoProviderCommand>
{
public:
HRESULT Execute(DBPARAMS * pParams, LONG* pcRowsAffected)
{
USES_CONVERSION;
LPTSTR szFile =
m_strCommandText == _T(")) ? _T(") :
OLE2T(m_strCommandText);
IStorage* pStg = NULL;
HRESULT hr = StgOpenStorage(szFile, NULL,
STGM_READ|STGM_SHARE_EXCLUSIVE,
NULL, NULL, &pStg);
if(FAILED(hr))
return DB_E_ERRORSINCOMMAND;
LONG cStgs = 0;
IEnumSTATSTG* pEnumSTATSTG;
hr = pStg->EnumElements(0, 0, 0, &pEnumSTATSTG);
if(pEnumSTATSTG) {
STATSTG rgSTATSTG[100];
ULONG nFetched;
hr = pEnumSTATSTG->Next(100, rgSTATSTG, &nFetched);
for(ULONG i = 0; i < nFetched; i++) {
CStgInfo stgInfo;
stgInfo.cbSizeLow = rgSTATSTG[i].cbSize.LowPart;
stgInfo.cbSizeHigh = rgSTATSTG[i].cbSize.HighPart;
wcsncpy(stgInfo.szName,
rgSTATSTG[i].pwcsName,
255);
CoTaskMemFree(rgSTATSTG[i].pwcsName);
if (!m_rgRowData.Add(stgInfo))
return E_OUTOFMEMORY;
cStgs++;
}
pEnumSTATSTG->Release();
}
if(pStg)
pStg->Release();
if (pcRowsAffected != NULL)
*pcRowsAffected = cStgs;
return S_OK;
}
}

When some client code tries to open the OLE DB data provider, the call will end up inside this function. This function simply opens the structured storage file that was passed in as the command text and uses the standard structured storage enumerator to find the top-level substorages. The Execute function then stores the name of the substorage and the size of the substorage in an array. The OLE DB provider uses this array to fulfill requests for the column data.



Enhancing the Provider


Of course, you can do a lot to beef up this OLE DB provider. We've barely scratched the surface of what you can do with a provider. When the ATL OLE DB Provider Wizard pumps out the default provider, it's a read-only provider— that is, users cannot change the contents of the data. The OLE DB templates provide support for locating rowsets and setting bookmarks. In most cases, enhancing the provider is a matter of tacking on implementations of COM interfaces provided by the OLE DB templates.



/ 319