MDAC 2.5 SDK - OLE DB Programmer's Reference
Appendix F: Sample OLE DB Consumer Application


 

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;
}