OLE DB Programmer's Reference |
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 whether 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 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; }
1998-2001 Microsoft Corporation. All rights reserved.