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: