MDAC 2.5 SDK - OLE DB Programmer's Reference
Appendix F: Sample OLE DB Consumer Application
//---------------------------------------------------------------------------
// Microsoft® OLE DB Programmer's Reference Sample.
// Copyright (C) 1998 By Microsoft® Corporation.
//
// @doc
//
// @module PRSAMPLE.H
//
//---------------------------------------------------------------------------
/////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////
#ifndef __PRSAMPLE_H__
#define __PRSAMPLE_H__
#include "oledb.h" // OLE DB Header
#include "oledberr.h" // OLE DB Errors
#include "msdasc.h" // OLE DB Service Component header
#include "msdaguid.h" // OLE DB Root Enumerator
#include "msdasql.h" // MSDASQL - Default provider
#include <stdio.h> // input and output functions
#include <conio.h> // getch, putch
#include <locale.h> // setlocale
/////////////////////////////////////////////////////////////////
// Globals
//
/////////////////////////////////////////////////////////////////
extern DWORD g_dwFlags;
/////////////////////////////////////////////////////////////////
// Defines
//
/////////////////////////////////////////////////////////////////
#define __LONGSTRING(string) L##string
#define LONGSTRING(string) __LONGSTRING(string)
// Goes to CLEANUP on Failure_
#define CHECK_HR(hr) \
if(FAILED(hr)) \
goto CLEANUP
// Goes to CLEANUP on Failure, and displays any ErrorInfo
#define XCHECK_HR(hr) \
{ \
if( g_dwFlags & DISPLAY_METHODCALLS ) \
fwprintf(stderr, LONGSTRING(#hr) L"\n"); \
if(FAILED(myHandleResult(hr, LONGSTRING(__FILE__), __LINE__))) \
goto CLEANUP; \
}
#define CHECK_MEMORY(hr, pv) \
{ \
if(!pv) \
{ \
hr = E_OUTOFMEMORY; \
CHECK_HR(hr); \
} \
}
#define MAX_COL_SIZE 5000
#define MAX_NAME_LEN 256
#define MAX_ROWS 10
#define MAX_DISPLAY_SIZE 20
#define MIN_DISPLAY_SIZE 3
// ROUNDUP on all platforms pointers must be aligned properly
#define ROUNDUP_AMOUNT 8
#define ROUNDUP_(size,amount) (((ULONG)(size)+((amount)-1))&~((amount)-1))
#define ROUNDUP(size) ROUNDUP_(size, ROUNDUP_AMOUNT)
enum
{
// Connecting
USE_PROMPTDATASOURCE = 0x0001,
USE_ENUMERATOR = 0x0002,
// Rowset
USE_COMMAND = 0x0010,
// Storage objects
USE_ISEQSTREAM = 0x0100,
// Display options
DISPLAY_METHODCALLS = 0x1000,
DISPLAY_INSTRUCTIONS = 0x2000,
};
/////////////////////////////////////////////////////////////////////////////
// Function prototypes
//
/////////////////////////////////////////////////////////////////////////////
// Main
BOOL myParseCommandLine();
void myDisplayInstructions();
BOOL myGetInputFromUser(LPWSTR pwszInput, LPCWSTR pwszFmt, ...);
CHAR myGetChar();
// Enumerator
HRESULT myCreateEnumerator(REFCLSID clsidEnumerator, CLSID* pCLSID);
// Data source
HRESULT myCreateDataSource(IUnknown** ppUnkDataSource);
HRESULT myDoInitialization(IUnknown* pIUnknown);
HRESULT myGetProperty(IUnknown* pIUnknown, REFIID riid, DBPROPID
dwPropertyID, REFGUID guidPropertySet, BOOL*
pbValue);
void myAddProperty(DBPROP* pProp, DBPROPID dwPropertyID, VARTYPE vtType =
VT_BOOL, LONG lValue = VARIANT_TRUE, DBPROPOPTIONS
dwOptions = DBPROPOPTIONS_OPTIONAL);
// Session
HRESULT myCreateSession(IUnknown* pUnkDataSource, IUnknown**
ppUnkSession);
HRESULT myCreateSchemaRowset(GUID guidSchema, IUnknown* pUnkSession,
ULONG cchBuffer, LPWSTR pwszBuffer);
// Command
HRESULT myCreateCommand(IUnknown* pUnkSession, IUnknown** ppUnkCommand);
HRESULT myExecuteCommand(IUnknown* pUnkCommand, WCHAR* pwszCommandText,
ULONG cPropSets, DBPROPSET* rgPropSets,
IUnknown** ppUnkRowset);
// Rowset
HRESULT myCreateRowset(IUnknown* pUnkSession, IUnknown** ppUnkRowset);
HRESULT mySetupBindings(IUnknown* pUnkRowset, ULONG* pcBindings,
DBBINDING** prgBindings, ULONG* pcbRowSize);
HRESULT myCreateAccessor(IUnknown* pUnkRowset, HACCESSOR* phAccessor,
ULONG* pcBindings, DBBINDING** prgBindings,
ULONG* pcbRowSize);
HRESULT myDisplayRowset(IUnknown* pUnkRowset, LPCWSTR pwszColToReturn,
ULONG cchBuffer, LPWSTR pwszBuffer);
HRESULT myDisplayColumnNames(IUnknown* pUnkRowset, ULONG* rgDispSize);
HRESULT myDisplayRow(ULONG iRow, ULONG cBindings, DBBINDING* rgBindings,
void* pData, ULONG * rgDispSize);
HRESULT myInteractWithRowset(IRowset* pIRowset, LONG* pcRows, ULONG
cRowsObtained, BOOL fCanFetchBackwards,
void* pData, ULONG cbRowSize, DBBINDING*
pBinding, ULONG cchBuffer, LPWSTR
pwszBuffer);
HRESULT myFindColumn(IUnknown * pUnkRowset, LPCWSTR pwszName, LONG*
plIndex);
HRESULT myUpdateDisplaySize(ULONG cBindings, DBBINDING* rgBindings, void*
pData, ULONG* rgDispSize);
void myFreeBindings(ULONG cBindings, DBBINDING* rgBindings);
void myAddRowsetProperties(DBPROPSET* pPropSet, ULONG cProperties,
DBPROP* rgProperties);
// Error
HRESULT myHandleResult(HRESULT hrReturned, LPCWSTR pwszFile, ULONG
ulLine);
HRESULT myDisplayErrorRecord(HRESULT hrReturned, ULONG iRecord,
IErrorRecords* pIErrorRecords, LPCWSTR
pwszFile, ULONG ulLine);
HRESULT myDisplayErrorInfo(HRESULT hrReturned, IErrorInfo* pIErrorInfo,
LPCWSTR pwszFile, ULONG ulLine);
HRESULT myGetSqlErrorInfo(ULONG iRecord, IErrorRecords* pIErrorRecords,
BSTR* pBstr, LONG* plNativeError);
#endif // __PRSAMPLE_H__
//---------------------------------------------------------------------------
// Microsoft OLE DB Programmer's Reference Sample.
// Copyright (C) 1998 By Microsoft Corporation.
//
// @doc
//
// @module MAIN.CPP
//
//---------------------------------------------------------------------------
/////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////
#define DBINITCONSTANTS // Store all OLE DB consts inside
// this .obj file.
#include "prsample.h" // Programmer's Reference Sample includes
/////////////////////////////////////////////////////////////////
// Globals
//
/////////////////////////////////////////////////////////////////
DWORD g_dwFlags = USE_PROMPTDATASOURCE | DISPLAY_METHODCALLS;
/////////////////////////////////////////////////////////////////
// main
//
// This is a simple OLE DB application that will display a
// rowset and will allow basic navigation of that rowset by the
// user.
//
// In the sample, functions that begin with 'my' are implemented
// in the sample code; all other functions are either OLE DB
// methods or standard system methods. In addition, two
// macros are used repeatedly throughout the sample:
// - CHECK_HR(hr) - this macro goes to the CLEANUP label if
// FAILED(hr), where hr is usually a method call.
// - XCHECK_HR(hr) - this macro prints the string
// representation of hr to stderr and if FAILED(hr), attempts
// to obtain and display any extended error information
// posted by the last method call and then jumps to the CLEANUP
// label.
//
// This is the entry point for the sample. This function will:
// - parse command line arguments passed to the sample.
// - display appropriate instructions based on these arguments.
// - create an OLE DB data source object for a user-chosen
// provider.
// - create an OLE DB session object from the provider's
// data source object.
// - create an OLE DB rowset object, over a table specified by
// the user, from the provider's session object.
// - display the rowset data and will allow the user to
// navigate over the rowset.
//
/////////////////////////////////////////////////////////////////
int main()
{
HRESULT hr;
IUnknown * pUnkDataSource = NULL;
IUnknown * pUnkSession = NULL;
IUnknown * pUnkRowset = NULL;
// Parse command line arguments, if any; this will update
// the value of g_dwFlags as appropriate for the arguments.
if( !myParseCommandLine() )
return EXIT_FAILURE;
// Display instructions for the given command line arguments.
myDisplayInstructions();
// Initialize OLE
hr = CoInitialize(NULL);
if( FAILED(hr) )
return EXIT_FAILURE;
// Create the data source object using the OLE DB service components.
CHECK_HR(hr = myCreateDataSource(&pUnkDataSource));
// Create a session object from the data source object.
CHECK_HR(hr = myCreateSession(pUnkDataSource, &pUnkSession));
// Create a rowset object from the session object, either directly
// from the session or through a command object.
CHECK_HR(hr = myCreateRowset(pUnkSession, &pUnkRowset));
// Display the rowset object data to the user.
CHECK_HR(hr = myDisplayRowset(pUnkRowset, NULL, 0, NULL));
CLEANUP:
if( pUnkRowset )
pUnkRowset->Release();
if( pUnkSession )
pUnkSession->Release();
if( pUnkDataSource )
pUnkDataSource->Release();
CoUninitialize();
if( FAILED(hr) )
return EXIT_FAILURE;
return EXIT_SUCCESS;
}
/////////////////////////////////////////////////////////////////
// myParseCommandLine
//
// This function parses the application's command line arguments
// and sets the appropriate bits in g_dwFlags. If an invalid
// argument is encountered, a usage message is displayed and
// the function returns FALSE; otherwise, TRUE is returned.
//
/////////////////////////////////////////////////////////////////
BOOL myParseCommandLine
(
void
)
{
int iArg;
CHAR * psz;
// Set the locale for all C run-time functions.
setlocale(LC_ALL, ".ACP");
// Go through each command line argument and set the appropriate
// bits in g_dwFlags, depending on the chosen options.
for( iArg = 1; iArg < __argc; iArg++ )
{
// Inspect the current argument string.
psz = __argv[iArg];
// Valid options begin with '-' or '/'.
if( psz[0] == '-' || psz[0] == '/' )
{
// The next character is the option.
switch( tolower(psz[1]) )
{
case 'u':
// Use the service components UI to prompt for and create
// the data source object; the enumerator is not used.
g_dwFlags |= USE_PROMPTDATASOURCE;
g_dwFlags &= ~USE_ENUMERATOR;
continue;
case 'e':
// Use the enumerator to select the provider, and then use
// IDataInitialize to create the data source object.
// Don't use the UI to prompt for the data source.
g_dwFlags |= USE_ENUMERATOR;
g_dwFlags &= ~USE_PROMPTDATASOURCE;
continue;
case 'c':
// Use ICommand instead of IOpenRowset.
g_dwFlags |= USE_COMMAND;
continue;
case 'b':
// Use ISequentialStream to fetch BLOB column data.
g_dwFlags |= USE_ISEQSTREAM;
continue;
case 'n':
// Don't display method call strings as part of
// the extended error checking macro.
g_dwFlags &= ~DISPLAY_METHODCALLS;
continue;
}
}
// Invalid argument; show the usage flags to the user.
fprintf(stderr, "Usage: %s [-u] [-e] [-c] [-b] [-n]\n\nWhere:\n\t" \
"u = Use the Microsoft Data Links UI " \
"to create the DataSource\n\t" \
"e = Use the Enumerator and IDataInitialize " \
"to create the DataSource\n\t" \
"c = Use ICommand instead of IOpenRowset to create the Rowset\n\t" \
"b = Use ISequentialStream for BLOB columns\n\t" \
"n = Don't display method call strings\n",
__argv[0]);
return FALSE;
}
return TRUE;
}
/////////////////////////////////////////////////////////////////
// myDisplayInstructions
//
// This function asks the user whether they would like
// instructions displayed for the application. If so, it
// displays the instructions appropriate to the flags set
// in g_dwFlags.
//
/////////////////////////////////////////////////////////////////
void myDisplayInstructions
(
void
)
{
CHAR ch;
// Display header and ask the user if they want instructions.
printf("\nOLE DB Programmer's Reference Sample\n" \
"====================================\n\n");
printf("Display instructions [Y or N]? ");
do
{
ch = myGetChar();
}
while( ch != 'y' && ch != 'n' );
printf("%c\n\n", ch);
// No instructions, so we're done.
if( ch == 'n' )
return;
// Display basic instructions.
printf("This application is a simple OLE DB sample that will display\n" \
"a rowset and will allow basic navigation of that rowset by\n" \
"the user. The application will perform the following\n" \
"steps:\n\n");
// Display data source creation instructions.
if( g_dwFlags & USE_PROMPTDATASOURCE )
{
printf(" - Creates a data source object through the Microsoft
Data\n" \ " Links UI. This allows the user to select
the OLE DB\n" \ " provider to use and to set connection
properties.\n");
}
else
{
printf(" - Creates a data source object through
IDataInitialize::\n" \
" CreateDBInstance, which allows the OLE DB service\n" \
" component manager to add additional functionality to\n"\
" the provider as requested. The user will select the\n" \
" provider to use from a rowset obtained from the OLE
DB\n" \ " enumerator.\n");
}
// Display session creation and table-selection instructions.
printf(" - Creates a session object from the data source object.\n");
printf(" - If the provider supports the schema rowset interface,\n" \
" creates a TABLES schema rowset and allows the user to\n" \
" select a table name from this rowset.\n");
// Display rowset creation instructions
if( g_dwFlags & USE_COMMAND )
{
printf(" - Creates a cxommand object from the session object and\n" \
" allows the user to specify command text for this Command,\n" \
" then executes the command to create the final rowset.\n");
}
else
{
printf(" - Creates the final rowset over the table specified by the\n" \
" user.\n");
}
printf(" - Displays this rowset and allows the user to perform basic\n" \
" navigation of that rowset.\n\n");
// Wait for the user to press a key before continuing.
printf("Press a key to continue...");
myGetChar();
printf("\n\n");
}
/////////////////////////////////////////////////////////////////
// myGetInputFromUser
//
// This function prompts the user with the contents of pwszFmt
// and any accompanying variable arguments and then gets a string
// as input from the user. If the string is non-empty, it is
// copied into pwszInput and the function returns TRUE;
// otherwise, this function returns FALSE.
//
/////////////////////////////////////////////////////////////////
BOOL myGetInputFromUser
(
LPWSTR pwszInput,
LPCWSTR pwszFmt,
...
)
{
va_list vargs;
WCHAR wszBuffer[MAX_NAME_LEN + 1] = {0};
// Create the string with variable arguments...
va_start(vargs, pwszFmt);
_vsnwprintf(wszBuffer, MAX_NAME_LEN, pwszFmt, vargs);
va_end(vargs);
// Output the string...
wprintf(wszBuffer);
// Now get the Input from the user....
_getws(wszBuffer);
if( wszBuffer[0] )
{
wcscpy(pwszInput, wszBuffer);
return TRUE;
}
return FALSE;
}
/////////////////////////////////////////////////////////////////
// myGetChar
//
// This function gets a character from the keyboard and
// converts it to lowercase before returning it.
//
/////////////////////////////////////////////////////////////////
CHAR myGetChar
(
void
)
{
CHAR ch;
// Get a character from the keyboard.
ch = _getch();
// Re-read for the actual key value if necessary.
if( !ch || ch == 0xE0 )
ch = _getch();
return tolower(ch);
}
//---------------------------------------------------------------------------
// Microsoft OLE DB Programmer's Reference Sample.
// Copyright (C) 1998 By Microsoft Corporation.
//
// @doc
//
// @module ENUM.CPP
//
//---------------------------------------------------------------------------
/////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////
#include "prsample.h" // Programmer's Reference Sample includes
/////////////////////////////////////////////////////////////////
// myCreateEnumerator
//
// This function creates an enumerator, obtains a sources rowset
// from it, displays the rowset to the user, and allows the user
// to specify the ProgID of a provider. The CLSID that matches
// this ProgID is retuned to the caller in *pCLSID.
//
/////////////////////////////////////////////////////////////////
HRESULT myCreateEnumerator
(
REFCLSID clsidEnumerator,
CLSID * pCLSID
)
{
HRESULT hr;
IUnknown * pIUnkEnumerator = NULL;
ISourcesRowset * pISourcesRowset = NULL;
IRowset * pIRowset = NULL;
IDBInitialize * pIDBInitialize = NULL;
WCHAR wszProgID[MAX_NAME_LEN + 1] = {0};
const ULONG cProperties = 2;
DBPROP rgProperties[cProperties];
DBPROPSET rgPropSets[1];
// Create the enumerator object. We ask for IUnknown when creating
// the enumerator because some enumerators may require initialization
// before we can obtain a sources rowset from the enumerator. This is
// indicated by whether the enumerator object exposes IDBInitialize
// or not. (We don't want to ask for IDBInitialize, since enumerators
// that don't require initialization will cause the CoCreateInstance
// to fail.)
XCHECK_HR(hr = CoCreateInstance(
clsidEnumerator, // clsid -- enumerator
NULL, // pUnkOuter
CLSCTX_INPROC_SERVER, // dwClsContext
IID_IUnknown, // riid
(void**)&pIUnkEnumerator // ppvObj
));
// If the enumerator exposes IDBInitialize, we need to initialize it.
if( SUCCEEDED(hr = pIUnkEnumerator->QueryInterface(IID_IDBInitialize,
(void**)&pIDBInitialize)) )
{
CHECK_HR(hr = myDoInitialization(pIUnkEnumerator));
}
// Set properties on the rowset, to request additional functionality.
myAddRowsetProperties(rgPropSets, cProperties, rgProperties);
// Obtain a sources rowset from the enumerator. This rowset contains
// all of the OLE DB providers that this enumerator is able to list.
XCHECK_HR(hr = pIUnkEnumerator->QueryInterface(IID_ISourcesRowset,
(void**)&pISourcesRowset));
XCHECK_HR(hr = pISourcesRowset->GetSourcesRowset(
NULL, // pUnkOuter
IID_IRowset, // riid
1, // cPropSets
rgPropSets, // rgPropSets
(IUnknown**)&pIRowset // ppRowset
));
// Display the rowset to the user. This will allow the user to
// perform basic navigation of the rowset and will allow the user
// to select a row containing a desired provider.
CHECK_HR(hr = myDisplayRowset(pIRowset,
L"SOURCES_NAME", MAX_NAME_LEN, wszProgID));
// Obtain the ProgID for the provider to use from the user.
// The default value for this is the value of the SOURCES_NAME
// column in the row selected by the user previously.
myGetInputFromUser(wszProgID, L"\nType the ProgID of a provider"
L" to use [Enter = `%s`]: ", wszProgID);
XCHECK_HR(hr = CLSIDFromProgID(wszProgID, pCLSID));
CLEANUP:
if( pIUnkEnumerator )
pIUnkEnumerator->Release();
if( pISourcesRowset )
pISourcesRowset->Release();
if( pIRowset )
pIRowset->Release();
if( pIDBInitialize )
pIDBInitialize->Release();
return hr;
}
//---------------------------------------------------------------------------
// Microsoft OLE DB Programmer's Reference Sample.
// Copyright (C) 1998 By Microsoft Corporation.
//
// @doc
//
// @module DATASOURCE.CPP
//
//---------------------------------------------------------------------------
/////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////
#include "prsample.h" // Programmer's Reference Sample includes
/////////////////////////////////////////////////////////////////
// myCreateDataSource
//
// This function creates an OLE DB data source object for a
// provider selected by the user, sets initialization properties
// for the data source, and initializes the data source. The
// function returns a pointer to the data source object's
// IUnknown in *ppUnkDataSource.
//
/////////////////////////////////////////////////////////////////
HRESULT myCreateDataSource
(
IUnknown ** ppUnkDataSource
)
{
HRESULT hr;
IDataInitialize * pIDataInitialize = NULL;
IDBPromptInitialize * pIDBPromptInitialize = NULL;
IDBInitialize * pIDBInitialize = NULL;
CLSID clsid = CLSID_MSDASQL;
// Use the Microsoft Data Links UI to create the data source
// object. This will allow the user to select the provider
// to connect to and to set the initialization properties
// for the data source object, which will be created by the
// Data Links UI.
if( g_dwFlags & USE_PROMPTDATASOURCE )
{
// Create the Data Links UI object, and obtain the
// IDBPromptInitialize interface from it
XCHECK_HR(hr = CoCreateInstance(
CLSID_DataLinks, // clsid -- Data Links UI
NULL, // pUnkOuter
CLSCTX_INPROC_SERVER, // dwClsContext
IID_IDBPromptInitialize, // riid
(void**)&pIDBPromptInitialize // ppvObj
));
// Invoke the Data Links UI to allow the user to select
// the provider and set initialization properties for
// the data source object that this will create.
XCHECK_HR(hr = pIDBPromptInitialize->PromptDataSource(
NULL, // pUnkOuter
GetDesktopWindow(), // hWndParent
DBPROMPTOPTIONS_PROPERTYSHEET, // dwPromptOptions
0, // cSourceTypeFilter
NULL, // rgSourceTypeFilter
NULL, // pwszszzProviderFilter
IID_IDBInitialize, // riid
(IUnknown**)&pIDBInitialize // ppDataSource
));
// We've obtained a data source object from the Data Links UI. This
// object has had its initialization properties set, so all we
// need to do is Initialize it.
XCHECK_HR(hr = pIDBInitialize->Initialize());
}
// We are not using the Data Links UI to create the data source
// object. Instead, we will enumerate the providers installed on this
// system through the OLE DB enumerator and will allow the user to
// select the ProgID of the provider for which we will create a
// data source object.
else
{
// Use the OLE DB enumerator to obtain a rowset of installed
// providers, and then allow the user to select a provider from
// this rowset.
CHECK_HR(hr = myCreateEnumerator(CLSID_OLEDB_ENUMERATOR, &clsid));
// We will create the data source object through the OLE DB service
// component IDataInitialize interface, so we need to create an
// instance of the data initialization object.
XCHECK_HR(hr = CoCreateInstance(
CLSID_MSDAINITIALIZE, // clsid -- data initialize
NULL, // pUnkOuter
CLSCTX_INPROC_SERVER, // dwClsContext
IID_IDataInitialize, // riid
(void**)&pIDataInitialize // ppvObj
));
// Use IDataInitialize::CreateDBInstance to create an uninitialized
// data source object for the chosen provider. By using this
// service component method, the service component manager can
// provide additional functionality beyond what is natively
// supported by the provider if the consumer requests that
// functionality.
XCHECK_HR(hr = pIDataInitialize->CreateDBInstance(
clsid, // clsid -- provider
NULL, // pUnkOuter
CLSCTX_INPROC_SERVER, // dwClsContext
NULL, // pwszReserved
IID_IDBInitialize, // riid
(IUnknown**)&pIDBInitialize // ppDataSource
));
// Initialize the data source object by setting any required
// initialization properties and calling IDBInitialize::Initialize.
CHECK_HR(hr = myDoInitialization(pIDBInitialize));
}
CLEANUP:
*ppUnkDataSource = pIDBInitialize;
if( pIDataInitialize )
pIDataInitialize->Release();
if( pIDBPromptInitialize )
pIDBPromptInitialize->Release();
return hr;
}
/////////////////////////////////////////////////////////////////
// myDoInitialization
//
// This function sets initialization properties that tell the
// provider to prompt the user for any information required to
// initialize the provider and then calls the provider's
// initialization function.
//
/////////////////////////////////////////////////////////////////
HRESULT myDoInitialization
(
IUnknown * pIUnknown
)
{
HRESULT hr;
IDBInitialize * pIDBInitialize = NULL;
IDBProperties * pIDBProperties = NULL;
HWND hWnd = GetDesktopWindow();
const ULONG cProperties = 2;
DBPROP rgProperties[cProperties];
DBPROPSET rgPropSets[1];
// To initialize the data source object, most providers require
// some initialization properties to be set by the consumer. For
// example, these might include the data source to connect to and the
// user ID and password to use to establish identity. We will ask the
// provider to prompt the user for this required information by
// setting the following properties:
myAddProperty(&rgProperties[0],DBPROP_INIT_PROMPT,VT_I2,DBPROMPT_COMPLETE);
myAddProperty(&rgProperties[1],DBPROP_INIT_HWND, VT_I4,(LONG)hWnd);
rgPropSets[0].rgProperties = rgProperties;
rgPropSets[0].cProperties = cProperties;
rgPropSets[0].guidPropertySet = DBPROPSET_DBINIT;
// Obtain the needed interfaces.
XCHECK_HR(hr = pIUnknown->QueryInterface(IID_IDBProperties,
(void**)&pIDBProperties));
XCHECK_HR(hr = pIUnknown->QueryInterface(IID_IDBInitialize,
(void**)&pIDBInitialize));
// If a provider requires initialization properties, it must support
// the properties that we are setting (_PROMPT and _HWND). However,
// some providers do not need initialization properties and may
// therefore not support the _PROMPT and _HWND properties. Because of
// this, we will not check the return value from SetProperties.
hr = pIDBProperties->SetProperties(1, rgPropSets);
// Now that we've set our properties, initialize the provider.
XCHECK_HR(hr = pIDBInitialize->Initialize());
CLEANUP:
if( pIDBProperties )
pIDBProperties->Release();
if( pIDBInitialize )
pIDBInitialize->Release();
return hr;
}
/////////////////////////////////////////////////////////////////
// myGetProperty
//
// This function gets the BOOL value for the specified property
// and returns the result in *pbValue.
//
/////////////////////////////////////////////////////////////////
HRESULT myGetProperty
(
IUnknown * pIUnknown,
REFIID riid,
DBPROPID dwPropertyID,
REFGUID guidPropertySet,
BOOL * pbValue
)
{
HRESULT hr;
DBPROPID rgPropertyIDs[1];
DBPROPIDSET rgPropertyIDSets[1];
ULONG cPropSets = 0;
DBPROPSET * rgPropSets = NULL;
IDBProperties * pIDBProperties = NULL;
ISessionProperties * pISesProps = NULL;
ICommandProperties * pICmdProps = NULL;
IRowsetInfo * pIRowsetInfo = NULL;
// Initialize the output value
*pbValue = FALSE;
// Set up the property ID array
rgPropertyIDs[0] = dwPropertyID;
// Set up the Property ID Set
rgPropertyIDSets[0].rgPropertyIDs = rgPropertyIDs;
rgPropertyIDSets[0].cPropertyIDs = 1;
rgPropertyIDSets[0].guidPropertySet = guidPropertySet;
// Get the property value for this property from the provider, but
// don't try to display extended error information, since this may
// not be a supported property. A failure is, in fact, expected if
// the property is not supported.
if( riid == IID_IDBProperties )
{
XCHECK_HR(hr = pIUnknown->QueryInterface(IID_IDBProperties,
(void**)&pIDBProperties));
CHECK_HR(hr = pIDBProperties->GetProperties(
1, // cPropertyIDSets
rgPropertyIDSets, // rgPropertyIDSets
&cPropSets, // pcPropSets
&rgPropSets // prgPropSets
));
}
else if( riid == IID_ISessionProperties )
{
XCHECK_HR(hr = pIUnknown->QueryInterface(IID_ISessionProperties,
(void**)&pISesProps));
CHECK_HR(hr = pISesProps->GetProperties(
1, // cPropertyIDSets
rgPropertyIDSets, // rgPropertyIDSets
&cPropSets, // pcPropSets
&rgPropSets // prgPropSets
));
}
else if( riid == IID_ICommandProperties )
{
XCHECK_HR(hr = pIUnknown->QueryInterface(IID_ICommandProperties,
(void**)&pICmdProps));
CHECK_HR(hr = pICmdProps->GetProperties(
1, // cPropertyIDSets
rgPropertyIDSets, // rgPropertyIDSets
&cPropSets, // pcPropSets
&rgPropSets // prgPropSets
));
}
else
{
XCHECK_HR(hr = pIUnknown->QueryInterface(IID_IRowsetInfo,
(void**)&pIRowsetInfo));
CHECK_HR(hr = pIRowsetInfo->GetProperties(
1, // cPropertyIDSets
rgPropertyIDSets, // rgPropertyIDSets
&cPropSets, // pcPropSets
&rgPropSets // prgPropSets
));
}
// Return the value for this property to the caller if
// it's a VT_BOOL type value, as expected.
if( V_VT(&rgPropSets[0].rgProperties[0].vValue) == VT_BOOL )
*pbValue = V_BOOL(&rgPropSets[0].rgProperties[0].vValue);
CLEANUP:
if( rgPropSets )
{
CoTaskMemFree(rgPropSets[0].rgProperties);
CoTaskMemFree(rgPropSets);
}
if( pIDBProperties )
pIDBProperties->Release();
if( pISesProps )
pISesProps->Release();
if( pICmdProps )
pICmdProps->Release();
if( pIRowsetInfo )
pIRowsetInfo->Release();
return hr;
}
/////////////////////////////////////////////////////////////////
// myAddProperty
//
// This function initializes the property structure pProp.
//
/////////////////////////////////////////////////////////////////
void myAddProperty
(
DBPROP * pProp,
DBPROPID dwPropertyID,
VARTYPE vtType,
LONG lValue,
DBPROPOPTIONS dwOptions
)
{
// Set up the property structure.
pProp->dwPropertyID = dwPropertyID;
pProp->dwOptions = dwOptions;
pProp->dwStatus = DBPROPSTATUS_OK;
pProp->colid = DB_NULLID;
V_VT(&pProp->vValue) = vtType;
// Since VARIANT data is a union, we can place the value in any
// member (except for VT_DECIMAL, which is a union with the whole
// VARIANT structure -- but we know we're not passing VT_DECIMAL).
V_I4(&pProp->vValue) = lValue;
}
//---------------------------------------------------------------------------
// Microsoft OLE DB Programmer's Reference Sample.
// Copyright (C) 1998 By Microsoft Corporation.
//
// @doc
//
// @module SESSION.CPP
//
//---------------------------------------------------------------------------
/////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////
#include "prsample.h" // Programmer's Reference Sample includes
/////////////////////////////////////////////////////////////////
// myCreateSession
//
// Create an OLE DB session object from the given data source
// object. The IDBCreateSession interface is mandatory, so this
// is a simple operation.
//
/////////////////////////////////////////////////////////////////
HRESULT myCreateSession
(
IUnknown * pUnkDataSource,
IUnknown ** ppUnkSession
)
{
HRESULT hr;
IDBCreateSession * pIDBCreateSession = NULL;
//Create a session object from a data source object
XCHECK_HR(hr = pUnkDataSource->QueryInterface(
IID_IDBCreateSession, (void**)&pIDBCreateSession));
XCHECK_HR(hr = pIDBCreateSession->CreateSession(
NULL, // pUnkOuter
IID_IOpenRowset, // riid
ppUnkSession // ppSession
));
CLEANUP:
if( pIDBCreateSession )
pIDBCreateSession->Release();
return hr;
}
/////////////////////////////////////////////////////////////////
// myCreateSchemaRowset
//
// If the provider supports IDBSchemaRowset, this function will
// obtain the tables schema rowset, will display this rowset to
// the user, and will allow the user to select a row in the
// rowset containing the name of a table of interest.
//
/////////////////////////////////////////////////////////////////
HRESULT myCreateSchemaRowset
(
GUID guidSchema,
IUnknown * pUnkSession,
ULONG cchBuffer,
LPWSTR pwszBuffer
)
{
HRESULT hr = S_OK;
IDBSchemaRowset * pIDBSchemaRowset = NULL;
IUnknown * pUnkRowset = NULL;
const ULONG cProperties = 2;
DBPROP rgProperties[cProperties];
DBPROPSET rgPropSets[1];
// Attempt to obtain the IDBSchemaRowset interface on the session
// object. This is not a mandatory interface; if it is not supported,
// we are done.
CHECK_HR(pUnkSession->QueryInterface(
IID_IDBSchemaRowset, (void**)&pIDBSchemaRowset));
// Set properties on the rowset, to request additional functionality.
myAddRowsetProperties(rgPropSets, cProperties, rgProperties);
// Get the requested schema rowset. If IDBSchemaRowset is supported,
// the following schema rowsets are required to be supported:
// DBSCHEMA_TABLES, DBSCHEMA_COLUMNS, and DBSCHEMA_PROVIDERTYPES
// We know that we will be asking for one of these, so it is not
// necessary to call IDBSchemaRowset::GetSchemas in this case.
XCHECK_HR(hr = pIDBSchemaRowset->GetRowset(
NULL, // pUnkOuter
guidSchema, // guidSchema
0, // cRestrictions
NULL, // rgRestrictions
IID_IRowset, // riid
1, // cPropSets
rgPropSets, // rgPropSets
&pUnkRowset // ppRowset
));
// Display the rowset to the user. This will allow the user to
// perform basic navigation of the rowset and will allow the user
// to select a row containing a desired table name (taken from the
// TABLE_NAME column).
CHECK_HR(hr = myDisplayRowset(pUnkRowset,
L"TABLE_NAME", cchBuffer, pwszBuffer));
CLEANUP:
if( pIDBSchemaRowset )
pIDBSchemaRowset->Release();
if( pUnkRowset )
pUnkRowset->Release();
return hr;
}
_
//---------------------------------------------------------------------------
// Microsoft OLE DB Programmer's Reference Sample.
// Copyright (C) 1998 By Microsoft Corporation.
//
// @doc
//
// @module ROWSET.CPP
//
//---------------------------------------------------------------------------
/////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////
#include "prsample.h" // Programmer's Reference Sample includes
/////////////////////////////////////////////////////////////////
// myCreateRowset
//
// This function creates an OLE DB rowset object from the given
// provider's session object. It first obtains a default table
// name from the user through the tables schema rowset, if
// supported, and then creates a rowset object by one of two methods:
//
// - If the user requested that the rowset object be created
// from a command object, it creates a command object and then
// obtains command text from the user, sets properties and
// the command text, and finally executes the command to
// create the rowset object.
// - Otherwise, the function obtains a table name from the user
// and calls IOpenRowset::OpenRowset to create a rowset object
// over that table that supports the requested properties.
//
/////////////////////////////////////////////////////////////////
HRESULT myCreateRowset
(
IUnknown * pUnkSession,
IUnknown ** ppUnkRowset
)
{
HRESULT hr;
IUnknown * pUnkCommand = NULL;
IOpenRowset * pIOpenRowset = NULL;
WCHAR wszTableName[MAX_NAME_LEN + 1] = {0};
const ULONG cProperties = 2;
DBPROP rgProperties[cProperties];
DBPROPSET rgPropSets[1];
// Obtain a default table name from the user by displaying the
// tables schema rowset if schema rowsets are supported.
CHECK_HR(hr = myCreateSchemaRowset(DBSCHEMA_TABLES, pUnkSession,
MAX_NAME_LEN, wszTableName));
// Set properties on the rowset, to request additional functionality.
myAddRowsetProperties(rgPropSets, cProperties, rgProperties);
// If the user requested that the rowset be created from a
// Ccommand object, create a command, set its properties and
// text, and execute it to create the rowset object.
if( g_dwFlags & USE_COMMAND )
{
WCHAR wszCommandText[MAX_NAME_LEN + 1];
// Attempt to create the command object from the provider's
// session object. Note that commands are not supported by
// all providers, and this will fail in that case.
CHECK_HR(hr = myCreateCommand(pUnkSession, &pUnkCommand));
// From the user, get the command text that we will execute.
if( !myGetInputFromUser(wszCommandText, L"\nType the command "
L"to execute [Enter = `select * from %s`]: ", wszTableName) )
{
swprintf(wszCommandText, L"select * from %s", wszTableName);
}
// And execute the command the user entered.
CHECK_HR(hr = myExecuteCommand(pUnkCommand, wszCommandText,
1, rgPropSets, ppUnkRowset));
}
// Otherwise, the user gets the default behavior, which is to use
// IOpenRowset to create the rowset object from the session object.
// IOpenRowset is supported by all providers; it takes a TableID
// and creates a rowset containing all rows in that table. It is
// similar to using SQL command text of "SELECT * FROM TableID".
else
{
DBID TableID;
// Create the TableID.
TableID.eKind = DBKIND_NAME;
TableID.uName.pwszName = wszTableName;
// Obtain the table name from the user.
myGetInputFromUser(wszTableName, L"\nType the name of the table "
L"to use [Enter = `%s`]: ", wszTableName);
// Get the IOpenRowset interface, and create a rowset object
// over the requested table through OpenRowset.
XCHECK_HR(hr = pUnkSession->QueryInterface(
IID_IOpenRowset, (void**)&pIOpenRowset));
XCHECK_HR(hr = pIOpenRowset->OpenRowset(
NULL, // pUnkOuter
&TableID, // pTableID
NULL, // pIndexID
IID_IRowset, // riid
1, // cPropSets
rgPropSets, // rgPropSets
ppUnkRowset // ppRowset
));
}
CLEANUP:
if( pIOpenRowset )
pIOpenRowset->Release();
if( pUnkCommand )
pUnkCommand->Release();
return hr;
}
/////////////////////////////////////////////////////////////////
// mySetupBindings
//
// This function takes an IUnknown pointer from a rowset object
// and creates a bindings array that describes how we want the
// data we fetch from the rowset to be laid out in memory. It
// also calculates the total size of a row so that we can use
// this to allocate memory for the rows that we will fetch
// later.
//
// For each column in the rowset, there will be a corresponding
// element in the bindings array that describes how the
// provider should transfer the data, including length and
// status, for that column. This element also specifies the data
// type that the provider should return the column as. We will
// bind all columns as DBTYPE_WSTR, with a few exceptions
// detailed below, as providers are required to support the
// conversion of their column data to this type in the vast
// majority of cases. The exception to our binding as
// DBTYPE_WSTR is if the native column data type is
// DBTYPE_IUNKNOWN or if the user has requested that BLOB
// columns be bound as ISequentialStream objects, in which case
// we will bind those columns as ISequentialStream objects.
//
/////////////////////////////////////////////////////////////////
HRESULT mySetupBindings
(
IUnknown * pUnkRowset,
ULONG * pcBindings,
DBBINDING ** prgBindings,
ULONG * pcbRowSize
)
{
HRESULT hr;
ULONG cColumns;
DBCOLUMNINFO * rgColumnInfo = NULL;
LPWSTR pStringBuffer = NULL;
IColumnsInfo * pIColumnsInfo = NULL;
ULONG iCol;
ULONG dwOffset = 0;
DBBINDING * rgBindings = NULL;
ULONG cStorageObjs = 0;
BOOL fMultipleObjs = FALSE;
// Obtain the column information for the rowset; from this, we can
// find out the following information that we need to construct the
// bindings array:
// - the number of columns
// - the ordinal of each column
// - the precision and scale of numeric columns
// - the OLE DB data type of the column
XCHECK_HR(hr = pUnkRowset->QueryInterface(
IID_IColumnsInfo, (void**)&pIColumnsInfo));
XCHECK_HR(hr = pIColumnsInfo->GetColumnInfo(
&cColumns, // pcColumns
&rgColumnInfo, // prgColumnInfo
&pStringBuffer // ppStringBuffer
));
// Allocate memory for the bindings array; there is a one-to-one
// mapping between the columns returned from GetColumnInfo and our
// bindings.
rgBindings = (DBBINDING*)CoTaskMemAlloc(cColumns * sizeof(DBBINDING));
CHECK_MEMORY(hr, rgBindings);
memset(rgBindings, 0, cColumns * sizeof(DBBINDING));
// Determine if the rowset supports multiple storage object bindings.
// If it does not, we will bind only the first BLOB column or IUnknown
// column as an ISequentialStream object, and we will bind the rest as
// DBTYPE_WSTR.
myGetProperty(pUnkRowset, IID_IRowset, DBPROP_MULTIPLESTORAGEOBJECTS,
DBPROPSET_ROWSET, &fMultipleObjs);
// Construct the binding array element for each column.
for( iCol = 0; iCol < cColumns; iCol++ )
{
// This binding applies to the ordinal of this column.
rgBindings[iCol].iOrdinal = rgColumnInfo[iCol].iOrdinal;
// We are asking the provider to give us the data for this column
// (DBPART_VALUE), the length of that data (DBPART_LENGTH), and
// the status of the column (DBPART_STATUS).
rgBindings[iCol].dwPart = DBPART_VALUE|DBPART_LENGTH|DBPART_STATUS;
// The following values are the offsets to the status, length, and
// data value that the provider will fill with the appropriate
// values when we fetch data later. When we fetch data, we will
// pass a pointer to a buffer that the provider will copy column
// data to, in accordance with the binding we have provided for
// that column; these are offsets into that future buffer.
rgBindings[iCol].obStatus = dwOffset;
rgBindings[iCol].obLength = dwOffset + sizeof(DBSTATUS);
rgBindings[iCol].obValue = dwOffset + sizeof(DBSTATUS) + sizeof(ULONG);
// Any memory allocated for the data value will be owned by us, the
// client. Note that no data will be allocated in this case, as the
// DBTYPE_WSTR bindings we are using will tell the provider to
// simply copy data directly into our provided buffer.
rgBindings[iCol].dwMemOwner = DBMEMOWNER_CLIENTOWNED;
// This is not a parameter binding.
rgBindings[iCol].eParamIO = DBPARAMIO_NOTPARAM;
// We want to use the precision and scale of the column.
rgBindings[iCol].bPrecision = rgColumnInfo[iCol].bPrecision;
rgBindings[iCol].bScale = rgColumnInfo[iCol].bScale;
// Bind this column as DBTYPE_WSTR, which tells the provider to
// copy a Unicode string representation of the data into our
// buffer, converting from the native type if necessary.
rgBindings[iCol].wType = DBTYPE_WSTR;
// Initially, we set the length for this data in our buffer to 0;
// the correct value for this will be calculated directly below.
rgBindings[iCol].cbMaxLen = 0;
// Determine the maximum number of bytes required in our buffer to
// contain the Unicode string representation of the provider's
// native data type, including room for the NULL-termination
// character.
switch( rgColumnInfo[iCol].wType )
{
case DBTYPE_NULL:
case DBTYPE_EMPTY:
case DBTYPE_I1:
case DBTYPE_I2:
case DBTYPE_I4:
case DBTYPE_UI1:
case DBTYPE_UI2:
case DBTYPE_UI4:
case DBTYPE_R4:
case DBTYPE_BOOL:
case DBTYPE_I8:
case DBTYPE_UI8:
case DBTYPE_R8:
case DBTYPE_CY:
case DBTYPE_ERROR:
// When the above types are converted to a string, they
// will all fit into 25 characters, so use that plus space
// for the NULL terminator.
rgBindings[iCol].cbMaxLen = (25 + 1) * sizeof(WCHAR);
break;
case DBTYPE_DECIMAL:
case DBTYPE_NUMERIC:
case DBTYPE_DATE:
case DBTYPE_DBDATE:
case DBTYPE_DBTIMESTAMP:
case DBTYPE_GUID:
// Converted to a string, the above types will all fit into
// 50 characters, so use that plus space for the terminator.
rgBindings[iCol].cbMaxLen = (50 + 1) * sizeof(WCHAR);
break;
case DBTYPE_BYTES:
// In converting DBTYPE_BYTES to a string, each byte
// becomes two characters (e.g. 0xFF -> "FF"), so we
// will use double the maximum size of the column plus
// include space for the NULL terminator.
rgBindings[iCol].cbMaxLen =
(rgColumnInfo[iCol].ulColumnSize * 2 + 1) * sizeof(WCHAR);
break;
case DBTYPE_STR:
case DBTYPE_WSTR:
case DBTYPE_BSTR:
// Going from a string to our string representation,
// we can just take the maximum size of the column,
// a count of characters, and include space for the
// terminator, which is not included in the column size.
rgBindings[iCol].cbMaxLen =
(rgColumnInfo[iCol].ulColumnSize + 1) * sizeof(WCHAR);
break;
default:
// For any other type, we will simply use our maximum
// column buffer size, since the display size of these
// columns may be variable (e.g. DBTYPE_VARIANT) or
// unknown (e.g. provider-specific types).
rgBindings[iCol].cbMaxLen = MAX_COL_SIZE;
break;
};
// If the provider's native data type for this column is
// DBTYPE_IUNKNOWN or this is a BLOB column and the user
// has requested that we bind BLOB columns as ISequentialStream
// objects, bind this column as an ISequentialStream object if
// the provider supports our creating another ISequentialStream
// binding.
if( (rgColumnInfo[iCol].wType == DBTYPE_IUNKNOWN ||
((rgColumnInfo[iCol].dwFlags & DBCOLUMNFLAGS_ISLONG) &&
(g_dwFlags & USE_ISEQSTREAM))) &&
(fMultipleObjs || !cStorageObjs) )
{
// To create an ISequentialStream object, we will
// bind this column as DBTYPE_IUNKNOWN to indicate
// that we are requesting this column as an object.
rgBindings[iCol].wType = DBTYPE_IUNKNOWN;
// We want to allocate enough space in our buffer for
// the ISequentialStream pointer we will obtain from
// the provider.
rgBindings[iCol].cbMaxLen = sizeof(ISequentialStream *);
// To specify the type of object that we want from the
// provider, we need to create a DBOBJECT structure and
// place it in our binding for this column.
rgBindings[iCol].pObject =
(DBOBJECT *)CoTaskMemAlloc(sizeof(DBOBJECT));
CHECK_MEMORY(hr, rgBindings[iCol].pObject);
// Direct the provider to create an ISequentialStream
// object over the data for this column.
rgBindings[iCol].pObject->iid = IID_ISequentialStream;
// We want read access on the ISequentialStream
// object that the provider will create for us.
rgBindings[iCol].pObject->dwFlags = STGM_READ;
// Keep track of the number of storage objects
// (ISequentialStream is a storage interface) that we have
// requested, so that we can avoid requesting multiple storage
// objects from a provider that supports only a single storage
// object in our bindings.
cStorageObjs++;
}
// Ensure that the bound maximum length is no more than the
// maximum column size in bytes that we've defined.
rgBindings[iCol].cbMaxLen
= min(rgBindings[iCol].cbMaxLen, MAX_COL_SIZE);
// Update the offset past the end of this column's data so
// that the next column will begin in the correct place in
// the buffer.
dwOffset = rgBindings[iCol].cbMaxLen + rgBindings[iCol].obValue;
// Ensure that the data for the next column will be correctly
// aligned for all platforms, or if we're done with columns,
// that if we allocate space for multiple rows that the data
// for every row is correctly aligned.
dwOffset = ROUNDUP(dwOffset);
}
// Return the row size (the current dwOffset is the size of the row),
// the count of bindings, and the bindings array to the caller.
*pcbRowSize = dwOffset;
*pcBindings = cColumns;
*prgBindings = rgBindings;
CLEANUP:
CoTaskMemFree(rgColumnInfo);
CoTaskMemFree(pStringBuffer);
if( pIColumnsInfo )
pIColumnsInfo->Release();
return hr;
}
/////////////////////////////////////////////////////////////////
// myCreateAccessor
//
// This function takes an IUnknown pointer for a rowset object
// and creates an accessor that describes the layout of the
// buffer we will use when we fetch data. The provider will fill
// this buffer according to the description contained in the
// accessor that we will create here.
//
/////////////////////////////////////////////////////////////////
HRESULT myCreateAccessor
(
IUnknown * pUnkRowset,
HACCESSOR * phAccessor,
ULONG * pcBindings,
DBBINDING ** prgBindings,
ULONG * pcbRowSize
)
{
HRESULT hr;
IAccessor * pIAccessor = NULL;
// An accessor is basically a handle to a collection of bindings.
// To create the accessor, we need to first create an array of
// bindings for the columns in the rowset.
CHECK_HR(hr = mySetupBindings(pUnkRowset, pcBindings, prgBindings,
pcbRowSize));
// Now that we have an array of bindings, tell the provider to
// create the accessor for those bindings. We get back a handle
// to this accessor, which we will use when fetching data.
XCHECK_HR(hr = pUnkRowset->QueryInterface(
IID_IAccessor, (void**)&pIAccessor));
XCHECK_HR(hr = pIAccessor->CreateAccessor(
DBACCESSOR_ROWDATA, // dwAccessorFlags
*pcBindings, // cBindings
*prgBindings, // rgBindings
0, // cbRowSize
phAccessor, // phAccessor
NULL // rgStatus
));
CLEANUP:
if( pIAccessor )
pIAccessor->Release();
return hr;
}
/////////////////////////////////////////////////////////////////
// myDisplayRowset
//
// This function will display data from a rowset object and will
// allow the user to perform basic navigation of the rowset.
//
// The function takes a pointer to a rowset object's IUnknown
// and, optionally, the name of a column and a buffer that will
// receive the value of that column when the user selects a row.
//
/////////////////////////////////////////////////////////////////
HRESULT myDisplayRowset
(
IUnknown * pUnkRowset,
LPCWSTR pwszColToReturn,
ULONG cchBuffer,
LPWSTR pwszBuffer
)
{
HRESULT hr;
IRowset * pIRowset = NULL;
ULONG cBindings;
DBBINDING * rgBindings = NULL;
HACCESSOR hAccessor = DB_NULL_HACCESSOR;
ULONG cbRowSize;
void * pData = NULL;
ULONG * rgDispSize = NULL;
ULONG cRowsObtained;
HROW * rghRows = NULL;
ULONG iRow;
LONG cRows = MAX_ROWS;
LONG iRetCol = -1;
BOOL fCanFetchBackwards;
ULONG iIndex;
void * pCurData;
// Obtain the IRowset interface for use in fetching rows and data
XCHECK_HR(hr = pUnkRowset->QueryInterface(
IID_IRowset, (void**)&pIRowset));
// Determine whether this rowset supports fetching data backwards;
// we use this to determine whether the rowset can support moving
// to the previous set of rows, described in more detail below
myGetProperty(pUnkRowset, IID_IRowset, DBPROP_CANFETCHBACKWARDS,
DBPROPSET_ROWSET, &fCanFetchBackwards);
// If the caller wants us to return the data for a particular column
// from a user-selected row, we need to turn the column name into a
// column ordinal.
if( pwszColToReturn )
CHECK_HR(hr = myFindColumn(pUnkRowset, pwszColToReturn, &iRetCol));
// Create an accessor. An accessor is basically a handle to a
// collection of bindings that describes to the provider how to
// copy (and convert, if necessary) column data into our buffer.
// The accessor that this creates will bind all columns either as
// DBTYPE_WSTR (a Unicode string) or as an ISequentialStream object
// (used for BLOB data). This will also give us the size of the
// row buffer that the accessor describes to the provider.
CHECK_HR(hr = myCreateAccessor(pUnkRowset, &hAccessor,
&cBindings, &rgBindings, &cbRowSize));
// Allocate enough memory to hold cRows rows of data; this is
// where the actual row data from the provider will be placed.
pData = CoTaskMemAlloc(cbRowSize * MAX_ROWS);
CHECK_MEMORY(hr, pData);
// Allocate memory for an array that we will use to calculate the
// maximum display size used by each column in the current set of
// rows.
rgDispSize = (ULONG *)CoTaskMemAlloc(cBindings * sizeof(ULONG));
CHECK_MEMORY(hr, rgDispSize);
// In this loop, we perform the following process:
// - reset the maximum display size array
// - try to get cRows row handles from the provider
// - these handles are then used to actually get the row data from the
// provider copied into our allocated buffer
// - calculate the maximum display size for each column
// - release the row handles to the rows we obtained
// - display the column names for the rowset
// - display the row data for the rows that we fetched
// - get user input
// - free the provider-allocated row handle array
// - repeat unless the user has chosen to quit or has selected a row
while( hr == S_OK )
{
// Clear the maximum display size array.
memset(rgDispSize, 0, cBindings * sizeof(ULONG));
// Attempt to get cRows row handles from the provider.
XCHECK_HR(hr = pIRowset->GetNextRows(
DB_NULL_HCHAPTER, // hChapter
0, // lOffset
cRows, // cRows
&cRowsObtained, // pcRowsObtained
&rghRows // prghRows
));
// Loop over the row handles obtained from GetNextRows,
// actually fetching the data for these rows into our buffer.
for( iRow = 0; iRow < cRowsObtained; iRow++ )
{
// Find the location in our buffer where we want to place
// the data for this row. Note that if we fetched rows
// backwards (cRows < 0), the row handles obtained from the
// provider are reversed from the order in which we want to
// actually display the data on the screen, so we will
// account for this. This ensures that the resulting order
// of row data in the pData buffer matches the order we
// wish to use to display the data.
iIndex = cRows > 0 ? iRow : cRowsObtained - iRow - 1;
pCurData = (BYTE*)pData + (cbRowSize * iIndex);
// Get the data for this row handle. The provider will copy
// (and convert, if necessary) the data for each of the
// columns that are described in our Aaccessor into the given
// buffer (pCurData).
XCHECK_HR(hr = pIRowset->GetData(
rghRows[iRow], // hRow
hAccessor, // hAccessor
pCurData // pData
));
// Update the maximum display size array, accounting for
// this row.
CHECK_HR(hr = myUpdateDisplaySize(cBindings, rgBindings,
pCurData, rgDispSize));
}
// If we obtained rows, release the row handles for the retrieved
// rows and display the names of the rowset columns before we
// display the data.
if( cRowsObtained )
{
// Release the row handles that we obtained.
XCHECK_HR(hr = pIRowset->ReleaseRows(
cRowsObtained, // cRows
rghRows, // rghRows
NULL, // rgRowOptions
NULL, // rgRefCounts
NULL // rgRowStatus
));
// Display the names of the rowset columns.
CHECK_HR(hr = myDisplayColumnNames(pIRowset, rgDispSize));
}
// For each row that we obtained the data for, display this data.
for( iRow = 0; iRow < cRowsObtained; iRow++ )
{
// Get a pointer to the data for this row.
pCurData = (BYTE*)pData + (cbRowSize* iRow);
// And display the row data.
CHECK_HR(hr = myDisplayRow(iRow, cBindings, rgBindings,
pCurData, rgDispSize));
}
// Allow the user to navigate the rowset. This displays the
// appropriate prompts, gets the user's input, may call
// IRowset::RestartPosition, and may copy data from a selected row
// to the selection buffer, if so directed. This will return S_OK
// if the user asked for more rows, S_FALSE if the user selected a
// row, or E_FAIL if the user quits.
hr = myInteractWithRowset(
pIRowset, // IRowset pointer, for RestartPosition
&cRows, // updated with fetch direction value
cRowsObtained, // to indicate selection range
fCanFetchBackwards, // whether [P]revious is supported
pData, // data pointer for copying selection
cbRowSize, // size of rows for copying selection
iRetCol >= 0 ? // bindings for the selection column,
&rgBindings[iRetCol] : // or NULL if no selection column
NULL,
cchBuffer, // size of the selection buffer
pwszBuffer); // pointer to the selection buffer
// Since we are allowing the provider to allocate the memory for
// the row handle array, we will free this memory and reset the
// pointer to NULL. If this is not NULL on the next call to
// GetNextRows, the provider will assume that it points to an
// allocated array of the required size (which may not be the case
// if we obtained less than cRows rows from this last call to
// GetNextRows).
CoTaskMemFree(rghRows);
rghRows = NULL;
}
CLEANUP:
myFreeBindings(cBindings, rgBindings);
CoTaskMemFree(rgDispSize);
CoTaskMemFree(pData);
if( pIRowset )
pIRowset->Release();
return hr;
}
/////////////////////////////////////////////////////////////////
// myInteractWithRowset
//
// This function allows the user to interact with the rowset. It
// prompts the user appropriately, gets the user's input, may
// call IRowset::RestartPosition if the user requests a restart,
// and will copy data from a selected row to the selection
// buffer.
//
/////////////////////////////////////////////////////////////////
HRESULT myInteractWithRowset
(
IRowset * pIRowset,
LONG * pcRows,
ULONG cRowsObtained,
BOOL fCanFetchBackwards,
void * pData,
ULONG cbRowSize,
DBBINDING * pBinding,
ULONG cchBuffer,
LPWSTR pwszBuffer
)
{
HRESULT hr = S_OK;
CHAR ch;
// Let the user know if no rows were fetched.
if( !cRowsObtained )
printf("\n*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*\n" \
"* *\n" \
"* No rows obtained on this fetch! *\n" \
"* *\n" \
"*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*\n");
// Print navigation options.
if( fCanFetchBackwards )
printf("\n[P]revious; [N]ext; [R]estart; ");
else
printf("\n[N]ext; [R]estart; ");
// Print selection options.
if( cRowsObtained && pwszBuffer && pBinding )
printf("[0]-[%d] for a row; ", cRowsObtained - 1);
// User can always quit the program.
printf("[Q]uit? ");
// Get the user's input.
while( TRUE )
{
// Get a character from the console.
ch = myGetChar();
// Look for one of the allowed options; if not found, go
// back around and wait for another input from the user.
// If we're looking for a row selection, allow the user to select
// a row that we fetched, and then copy the data from the requested
// column into the selection buffer we were passed.
if( pwszBuffer && pBinding &&
ch >= '0' && ch < (int)('0' + cRowsObtained) )
{
// Save the data for the selected row.
ULONG nSelection = ch - '0';
_snwprintf(pwszBuffer, cchBuffer, L"%s",
(WCHAR *)((BYTE *)pData + cbRowSize * nSelection +
pBinding->obValue));
pwszBuffer[cchBuffer] = L'\0';
hr = S_FALSE;
}
// If the provider supports fetching backwards, set *pcRows
// to -MAX_ROWS. When GetNextRows is called with this value,
// it will fetch rows backwards from the current position
// until it fetches MAX_ROWS rows or hits the end of the rowset.
else if( fCanFetchBackwards && ch == 'p' )
{
// Fetch backwards.
*pcRows = -MAX_ROWS;
}
// Set *pcRows so that the next call to GetNextRows fetches
// MAX_ROWS rows forward from the current position.
else if( ch == 'n' )
{
// Fetch forward
*pcRows = MAX_ROWS;
}
// Call IRowset::RestartPosition, and fetch the first MAX_ROWS
// rows of the rowset forward from there.
else if( ch == 'r' )
{
// RestartPosition
*pcRows = MAX_ROWS;
XCHECK_HR(hr = pIRowset->RestartPosition(DB_NULL_HCHAPTER));
// Restarting a command may return the DB_S_COMMANDREEXECUTED
// warning. If this happens, we still want the caller to
// continue to display data, so we will reset the result code.
// to S_OK.
hr = S_OK;
}
// Quit the program.
else if( ch == 'q' )
{
hr = E_FAIL;
}
// Invalid option; go back up and get another character from the
// user.
else
{
continue;
}
// Echo the character and stop waiting for input.
printf("%c\n", ch);
break;
}
CLEANUP:
return hr;
}
/////////////////////////////////////////////////////////////////
// myDisplayColumnNames
//
// This function takes an IUnknown pointer to a rowset object
// and displays the names of the columns of that rowset.
//
/////////////////////////////////////////////////////////////////
HRESULT myDisplayColumnNames
(
IUnknown * pUnkRowset,
ULONG * rgDispSize
)
{
HRESULT hr;
IColumnsInfo * pIColumnsInfo = NULL;
ULONG cColumns;
DBCOLUMNINFO * rgColumnInfo = NULL;
LPOLESTR pStringsBuffer = NULL;
WCHAR wszColumn[MAX_DISPLAY_SIZE + 1];
LPWSTR pwszColName;
ULONG iCol;
ULONG cSpaces;
ULONG iSpace;
// Get the IColumnsInfo interface for the rowset.
XCHECK_HR(hr = pUnkRowset->QueryInterface(
IID_IColumnsInfo, (void**)&pIColumnsInfo));
// Get the columns information.
XCHECK_HR(hr = pIColumnsInfo->GetColumnInfo(
&cColumns, // pcColumns
&rgColumnInfo, // prgColumnInfo
&pStringsBuffer // ppStringBuffer
));
// Display the title of the row index column.
wprintf(L" Row | ");
// Display all column names.
for( iCol = 0; iCol < cColumns; iCol++ )
{
pwszColName = rgColumnInfo[iCol].pwszName;
// If the column name is NULL, we'll use a default string.
if( !pwszColName )
{
// Is this the bookmark column?
if( !rgColumnInfo[iCol].iOrdinal )
pwszColName = L"Bmk";
else
pwszColName = L"(null)";
}
// Ensure that the name is no longer than MAX_DISPLAY_SIZE.
wcsncpy(wszColumn, pwszColName, MAX_DISPLAY_SIZE);
wszColumn[min(rgDispSize[iCol], MAX_DISPLAY_SIZE)] = L'\0';
// Figure out how many spaces we need to print after
// this column name.
cSpaces = min(rgDispSize[iCol], MAX_DISPLAY_SIZE) - wcslen(wszColumn);
// Print the column name.
wprintf(L"%s", wszColumn);
// Now print any spaces necessary to align this column.
for(iSpace = 0; iSpace < cSpaces; iSpace++ )
putch(' ');
// Now end the column with a separator marker if necessary.
if( iCol < cColumns - 1 )
wprintf(L" | ");
}
// Done with the header, so print a new line.
wprintf(L"\n");
CLEANUP:
CoTaskMemFree(rgColumnInfo);
CoTaskMemFree(pStringsBuffer);
if( pIColumnsInfo )
pIColumnsInfo->Release();
return hr;
}
/////////////////////////////////////////////////////////////////
// myDisplayRow
//
// This function displays the data for a row.
//
/////////////////////////////////////////////////////////////////
HRESULT myDisplayRow
(
ULONG iRow,
ULONG cBindings,
DBBINDING * rgBindings,
void * pData,
ULONG * rgDispSize
)
{
HRESULT hr = S_OK;
WCHAR wszColumn[MAX_DISPLAY_SIZE + 1];
DBSTATUS dwStatus;
ULONG ulLength;
void * pvValue;
ULONG iCol;
ULONG cbRead;
ISequentialStream * pISeqStream = NULL;
ULONG cSpaces;
ULONG iSpace;
// Display the row number.
wprintf(L" [%d] | ", iRow);
// For each column that we have bound, display the data.
for( iCol = 0; iCol < cBindings; iCol++ )
{
// We have bound status, length, and the data value for all
// columns, so we know that these can all be used.
dwStatus = *(DBSTATUS *)((BYTE *)pData + rgBindings[iCol].obStatus);
ulLength = *(ULONG *)((BYTE *)pData + rgBindings[iCol].obLength);
pvValue = (BYTE *)pData + rgBindings[iCol].obValue;
// Check the status of this column. This decides
// exactly what will be displayed for the column.
switch( dwStatus )
{
// The data is NULL, so don't try to display it.
case DBSTATUS_S_ISNULL:
wcscpy(wszColumn, L"(null)");
break;
// The data was fetched, but may have been truncated.
// Display string data for this column to the user.
case DBSTATUS_S_TRUNCATED:
case DBSTATUS_S_OK:
case DBSTATUS_S_DEFAULT:
{
// We have bound the column either as a Unicode string
// (DBTYPE_WSTR) or as an ISequentialStream object
// (DBTYPE_IUNKNOWN), and we have to do different processing
// for each one of these possibilities.
switch( rgBindings[iCol].wType )
{
case DBTYPE_WSTR:
{
// Copy the string data.
wcsncpy(wszColumn, (WCHAR *)pvValue, MAX_DISPLAY_SIZE);
wszColumn[MAX_DISPLAY_SIZE - 1] = L'\0';
break;
}
case DBTYPE_IUNKNOWN:
{
// We've bound this as an ISequentialStream object,
// therefore the data in our buffer is a pointer
// to the object's ISequentialStream interface.
pISeqStream = *(ISequentialStream**)pvValue;
// We call ISequentialStream::Read to read bytes from
// the stream blindly into our buffer, simply as a
// demonstration of ISequentialStream. To display the
// data properly, the native provider type of this
// column should be accounted for; it could be
// DBTYPE_WSTR, in which case this works, or it could
// be DBTYPE_STR or DBTYPE_BYTES, in which case this
// won't display the data correctly.
CHECK_HR(hr = pISeqStream->Read(
wszColumn, // pBuffer
MAX_DISPLAY_SIZE, // cBytes
&cbRead // pcBytesRead
));
// Since streams don't provide NULL-termination,
// we'll NULL-terminate the resulting string ourselves.
wszColumn[cbRead / sizeof(WCHAR)] = L'\0';
// Release the stream object, now that we're done.
pISeqStream->Release();
pISeqStream = NULL;
break;
}
}
break;
}
// This is an error status, so don't try to display the data.
default:
wcscpy(wszColumn, L"(error status)");
break;
}
// Determine how many spaces we need to add after displaying this
// data to align it with this column in other rows.
cSpaces = min(rgDispSize[iCol], MAX_DISPLAY_SIZE) - wcslen(wszColumn);
// Print the column data.
wprintf(L"%s", wszColumn);
// Now print any spaces necessary.
for(iSpace = 0; iSpace < cSpaces; iSpace++ )
putch(' ');
// Now end the column with a separator marker if necessary.
if( iCol < cBindings - 1 )
wprintf(L" | ");
}
CLEANUP:
if( pISeqStream )
pISeqStream->Release();
// Print the row separator.
wprintf(L"\n");
return hr;
}
/////////////////////////////////////////////////////////////////
// myFreeBindings
//
// This function frees a bindings array and any allocated
// structures contained in that array.
//
/////////////////////////////////////////////////////////////////
void myFreeBindings
(
ULONG cBindings,
DBBINDING * rgBindings
)
{
ULONG iBind;
// Free any memory used by DBOBJECT structures in the array.
for( iBind = 0; iBind < cBindings; iBind++ )
CoTaskMemFree(rgBindings[iBind].pObject);
// Now free the bindings array itself.
CoTaskMemFree(rgBindings);
}
/////////////////////////////////////////////////////////////////
// myAddRowsetProperties
//
// This function sets up the given DBPROPSET and DBPROP
// structures, adding two optional properties that describe
// features that we would like to use on the rowset created
// with these properties applied:
// - DBPROP_CANFETCHBACKWARDS -- the rowset should support
// fetching rows backwards from our current cursor position.
// - DBPROP_IRowsetLocate -- the rowset should support
// the IRowsetLocate interface and its semantics.
//
/////////////////////////////////////////////////////////////////
void myAddRowsetProperties(DBPROPSET* pPropSet, ULONG cProperties, DBPROP* rgProperties)
{
// Initialize the property set array.
pPropSet->rgProperties = rgProperties;
pPropSet->cProperties = cProperties;
pPropSet->guidPropertySet = DBPROPSET_ROWSET;
// Add the following two properties (as OPTIONAL) to the property
// array contained in the property set array in order to request
// that they be supported by the rowset we will create. Because
// these are optional, the rowset we obtain may or may not support
// this functionality. We will check for the functionality that
// we need once the rowset is created and will modify our behavior
// appropriately.
myAddProperty(&rgProperties[0], DBPROP_CANFETCHBACKWARDS);
myAddProperty(&rgProperties[1], DBPROP_IRowsetLocate);
}
/////////////////////////////////////////////////////////////////
// myUpdateDisplaySize
//
// This function updates the rgDispSize array, keeping the
// maximum of the display size needed for the given data and
// the previous maximum size already in the array.
//
/////////////////////////////////////////////////////////////////
HRESULT myUpdateDisplaySize
(
ULONG cBindings,
DBBINDING * rgBindings,
void * pData,
ULONG * rgDispSize
)
{
DBSTATUS dwStatus;
ULONG cchLength;
ULONG iCol;
// Loop through the bindings, comparing the size of each column
// against the previously found maximum size for that column.
for( iCol = 0; iCol < cBindings; iCol++ )
{
dwStatus = *(DBSTATUS *)((BYTE *)pData + rgBindings[iCol].obStatus);
cchLength = ((*(ULONG *)((BYTE *)pData + rgBindings[iCol].obLength))
/ sizeof(WCHAR));
// The length that we need to display depends on the status
// of this column and generally on the data in the column.
switch( dwStatus )
{
case DBSTATUS_S_ISNULL:
cchLength = 6; // "(null)"
break;
case DBSTATUS_S_TRUNCATED:
case DBSTATUS_S_OK:
case DBSTATUS_S_DEFAULT:
if( rgBindings[iCol].wType == DBTYPE_IUNKNOWN )
cchLength = 2 + 8; // "0x%08lx"
// Ensure that the length is at least the minimum
// display size.
cchLength = max(cchLength, MIN_DISPLAY_SIZE);
break;
default:
cchLength = 14; // "(error status)"
break;
}
if( rgDispSize[iCol] < cchLength )
rgDispSize[iCol] = cchLength;
}
return S_OK;
}
/////////////////////////////////////////////////////////////////
// myFindColumn
//
// Find the index of the column described in pwszName, and return
// S_OK or, if not found, S_FALSE.
//
/////////////////////////////////////////////////////////////////
HRESULT myFindColumn
(
IUnknown * pUnkRowset,
LPCWSTR pwszName,
LONG * plIndex
)
{
HRESULT hr;
IColumnsInfo * pIColumnsInfo = NULL;
ULONG cColumns;
DBCOLUMNINFO * rgColumnInfo = NULL;
OLECHAR * pStringsBuffer = NULL;
ULONG iCol;
// Get the IColumnsInfo interface.
XCHECK_HR(hr = pUnkRowset->QueryInterface(
IID_IColumnsInfo, (void**)&pIColumnsInfo));
// Get the columns information.
XCHECK_HR(hr = pIColumnsInfo->GetColumnInfo(
&cColumns, // pcColumns
&rgColumnInfo, // prgColumnInfo
&pStringsBuffer // ppStringBuffer
));
// Assume that we'll find the column.
hr = S_OK;
// Search for the column we need.
for( iCol = 0; iCol < cColumns; iCol++ )
{
// If the column name matches, we've found the column....
if( rgColumnInfo[iCol].pwszName &&
!wcscmp(pwszName, rgColumnInfo[iCol].pwszName) )
{
*plIndex = iCol;
goto CLEANUP;
}
}
// If we didn't find the column, we'll return S_FALSE.
hr = S_FALSE;
CLEANUP:
CoTaskMemFree(rgColumnInfo);
CoTaskMemFree(pStringsBuffer);
if( pIColumnsInfo )
pIColumnsInfo->Release();
return hr;
}
//---------------------------------------------------------------------------
// Microsoft OLE DB Programmer's Reference Sample.
// Copyright (C) 1998 By Microsoft Corporation.
//
// @doc
//
// @module COMMAND.CPP
//
//---------------------------------------------------------------------------
/////////////////////////////////////////////////////////////////
// Includes
//
/////////////////////////////////////////////////////////////////
#include "prsample.h" // Programmer's Reference Sample includes
/////////////////////////////////////////////////////////////////
// myCreateCommand
//
// This function takes an IUnknown pointer on a session object
// and attempts to create a command object using the session's
// IDBCreateCommand interface. Since this interface is optional,
// this may fail.
//
/////////////////////////////////////////////////////////////////
HRESULT myCreateCommand
(
IUnknown * pUnkSession,
IUnknown ** ppUnkCommand
)
{
HRESULT hr;
IDBCreateCommand * pIDBCreateCommand = NULL;
// Attempt to create a command object from the session object
XCHECK_HR(hr = pUnkSession->QueryInterface(
IID_IDBCreateCommand, (void**)&pIDBCreateCommand));
XCHECK_HR(hr = pIDBCreateCommand->CreateCommand(
NULL, // pUnkOuter
IID_ICommand, // riid
ppUnkCommand // ppCommand
));
CLEANUP:
if( pIDBCreateCommand )
pIDBCreateCommand->Release();
return hr;
}
/////////////////////////////////////////////////////////////////
// myExecuteCommand
//
// This function takes an IUnknown pointer on a command object
// and performs the following steps to create a new rowset
// object:
// - sets the given properties on the command object; these
// properties will be applied by the provider to any rowset
// created by this command.
// - sets the given command text for the command.
// - executes the command to create a new rowset object.
//
/////////////////////////////////////////////////////////////////
HRESULT myExecuteCommand
(
IUnknown * pUnkCommand,
WCHAR * pwszCommandText,
ULONG cPropSets,
DBPROPSET* rgPropSets,
IUnknown ** ppUnkRowset
)
{
HRESULT hr;
ICommandText * pICommandText = NULL;
ICommandProperties * pICommandProperties = NULL;
// Set the properties on the command object.
XCHECK_HR(hr = pUnkCommand->QueryInterface(
IID_ICommandProperties, (void**)&pICommandProperties));
XCHECK_HR(hr = pICommandProperties->SetProperties(cPropSets, rgPropSets));
// Set the text for this command, using the default command text
// dialect. All providers that support commands must support this
// dialect, and providers that support SQL must be able to recognize
// an SQL command as SQL when this dialect is specified.
XCHECK_HR(hr = pUnkCommand->QueryInterface(
IID_ICommandText, (void**)&pICommandText));
XCHECK_HR(hr = pICommandText->SetCommandText(
DBGUID_DEFAULT, // guidDialect
pwszCommandText // pwszCommandText
));
// And execute the command. Note that the user could have
// entered a non-row returning command, so we will check for
// that and return failure to prevent the display of the
// nonexistent rowset by the caller.
XCHECK_HR(hr = pICommandText->Execute(
NULL, // pUnkOuter
IID_IRowset, // riid
NULL, // pParams
NULL, // pcRowsAffected
ppUnkRowset // ppRowset
));
if( !*ppUnkRowset )
{
printf("\nThe command executed successfully, but did not " \
"return a rowset.\nNo rowset will be displayed.\n");
hr = E_FAIL;
}
CLEANUP:
if( pICommandText )
pICommandText->Release();
if( pICommandProperties )
pICommandProperties->Release();
return hr;
}
//---------------------------------------------------------------------------
// Microsoft OLE DB Programmer's Reference Sample.
// Copyright (C) 1998 By Microsoft Corporation.
//
// @doc
//
// @module ERROR.CPP
//
//---------------------------------------------------------------------------
////////////////////////////////////////////////////////////////////////
// Includes
//
////////////////////////////////////////////////////////////////////////
#include "prsample.h" // Programmer's Reference Sample includes
////////////////////////////////////////////////////////////////////////
// myHandleResult
//
// This function is called as part of the XCHECK_HR macro; it takes an
// HRESULT, which is returned by the method called in the XCHECK_HR
// macro, and the file and line number where the method call was made.
// If the method call failed, this function attempts to get and display
// the extended error information for the call from the IErrorInfo,
// IErrorRecords, and ISQLErrorInfo interfaces.
//
////////////////////////////////////////////////////////////////////////
HRESULT myHandleResult
(
HRESULT hrReturned,
LPCWSTR pwszFile,
ULONG ulLine
)
{
HRESULT hr;
IErrorInfo * pIErrorInfo = NULL;
IErrorRecords * pIErrorRecords = NULL;
ULONG cRecords;
ULONG iErr;
// If the method called as part of the XCHECK_HR macro failed,
// we will attempt to get extended error information for the call.
if( FAILED(hrReturned) )
{
// Obtain the current error object, if any, by using the
// Automation GetErrorInfo function, which will give
// us back an IErrorInfo interface pointer if successful.
hr = GetErrorInfo(0, &pIErrorInfo);
// We've got the IErrorInfo interface pointer on the error object.
if( SUCCEEDED(hr) && pIErrorInfo )
{
// OLE DB extends the Automation error model by allowing
// error objects to support the IErrorRecords interface. This
// interface can expose information on multiple errors.
hr = pIErrorInfo->QueryInterface(IID_IErrorRecords,
(void**)&pIErrorRecords);
if( SUCCEEDED(hr) )
{
// Get the count of error records from the object.
CHECK_HR(hr = pIErrorRecords->GetRecordCount(&cRecords));
// Loop through the set of error records, and
// display the error information for each one.
for( iErr = 0; iErr < cRecords; iErr++ )
{
myDisplayErrorRecord(hrReturned, iErr, pIErrorRecords,
pwszFile, ulLine);
}
}
// The object didn't support IErrorRecords; display
// the error information for this single error.
else
{
myDisplayErrorInfo(hrReturned, pIErrorInfo, pwszFile, ulLine);
}
}
// There was no error object, so just display the HRESULT
// to the user.
else
{
wprintf(L"\nNo Error Info posted; HResult: 0x%08x\n"
L"File: %s, Line: %d\n", hrReturned, pwszFile, ulLine);
}
}
CLEANUP:
if( pIErrorInfo )
pIErrorInfo->Release();
if( pIErrorRecords )
pIErrorRecords->Release();
return hrReturned;
}
////////////////////////////////////////////////////////////////////////
// myDisplayErrorRecord
//
// This function displays the error information for a single error
// record, including information from ISQLErrorInfo, if supported.
//
////////////////////////////////////////////////////////////////////////
HRESULT myDisplayErrorRecord
(
HRESULT hrReturned,
ULONG iRecord,
IErrorRecords * pIErrorRecords,
LPCWSTR pwszFile,
ULONG ulLine
)
{
HRESULT hr;
IErrorInfo * pIErrorInfo = NULL;
BSTR bstrDescription = NULL;
BSTR bstrSource = NULL;
BSTR bstrSQLInfo = NULL;
static LCID lcid = GetUserDefaultLCID();
LONG lNativeError = 0;
ERRORINFO ErrorInfo;
// Get the IErrorInfo interface pointer for this error record.
CHECK_HR(hr = pIErrorRecords->GetErrorInfo(iRecord, lcid, &pIErrorInfo));
// Get the description of this error.
CHECK_HR(hr = pIErrorInfo->GetDescription(&bstrDescription));
// Get the source of this error.
CHECK_HR(hr = pIErrorInfo->GetSource(&bstrSource));
// Get the basic error information for this record.
CHECK_HR(hr = pIErrorRecords->GetBasicErrorInfo(iRecord, &ErrorInfo));
// If the error object supports ISQLErrorInfo, get this information.
myGetSqlErrorInfo(iRecord, pIErrorRecords, &bstrSQLInfo, &lNativeError);
// Display the error information to the user.
if( bstrSQLInfo )
{
wprintf(L"\nErrorRecord: HResult: 0x%08x\nDescription: %s\n"
L"SQLErrorInfo: %s\nSource: %s\nFile: %s, Line: %d\n",
ErrorInfo.hrError,
bstrDescription,
bstrSQLInfo,
bstrSource,
pwszFile,
ulLine);
}
else
{
wprintf(L"\nErrorRecord: HResult: 0x%08x\nDescription: %s\n"
L"Source: %s\nFile: %s, Line: %d\n",
ErrorInfo.hrError,
bstrDescription,
bstrSource,
pwszFile,
ulLine);
}
CLEANUP:
if( pIErrorInfo )
pIErrorInfo->Release();
SysFreeString(bstrDescription);
SysFreeString(bstrSource);
SysFreeString(bstrSQLInfo);
return hr;
}
////////////////////////////////////////////////////////////////////////
// myDisplayErrorInfo
//
// This function displays basic error information for an error object
// that doesn't support the IErrorRecords interface.
//
////////////////////////////////////////////////////////////////////////
HRESULT myDisplayErrorInfo
(
HRESULT hrReturned,
IErrorInfo * pIErrorInfo,
LPCWSTR pwszFile,
ULONG ulLine
)
{
HRESULT hr;
BSTR bstrDescription = NULL;
BSTR bstrSource = NULL;
// Get the description of the error.
CHECK_HR(hr = pIErrorInfo->GetDescription(&bstrDescription));
// Get the source of the error -- this will be the window title.
CHECK_HR(hr = pIErrorInfo->GetSource(&bstrSource));
// Display this error information.
wprintf(L"\nErrorInfo: HResult: 0x%08x, Description: %s\nSource:
%s\n" L"File: %s, Line: %d\n",
hrReturned,
bstrDescription,
bstrSource,
pwszFile,
ulLine);
CLEANUP:
SysFreeString(bstrDescription);
SysFreeString(bstrSource);
return hr;
}
////////////////////////////////////////////////////////////////////////
// myGetSqlErrorInfo
//
// If the error object supports ISQLErrorInfo, get the SQL error
// string and native error code for this error.
//
////////////////////////////////////////////////////////////////////////
HRESULT myGetSqlErrorInfo
(
ULONG iRecord,
IErrorRecords * pIErrorRecords,
BSTR * pBstr,
LONG * plNativeError
)
{
HRESULT hr;
ISQLErrorInfo * pISQLErrorInfo = NULL;
LONG lNativeError = 0;
// Attempt to get the ISQLErrorInfo interface for this error
// record through GetCustomErrorObject. Note that ISQLErrorInfo
// is not mandatory, so failure is acceptable here.
CHECK_HR(hr = pIErrorRecords->GetCustomErrorObject(
iRecord, // iRecord
IID_ISQLErrorInfo, // riid
(IUnknown**)&pISQLErrorInfo // ppISQLErrorInfo
));
// If we obtained the ISQLErrorInfo interface, get the SQL
// error string and native error code for this error.
if( pISQLErrorInfo )
hr = pISQLErrorInfo->GetSQLInfo(pBstr, &lNativeError);
CLEANUP:
if( plNativeError )
*plNativeError = lNativeError;
if( pISQLErrorInfo )
pISQLErrorInfo->Release();
return hr;
}