Binding and Accessor Example

The code in this example shows how to set up bindings and use them to create an accessor.

/////////////////////////////////////////////////////////////////
// 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 only bind the first BLOB column or IUnknown
   // column as an ISequentialStream object, and 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 only supports 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;
}

This topic is a part of: