Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
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: