The following sections discuss how to bind, get, and set BLOB data using storage objects.
To bind to BLOB data as a storage object, a consumer creates an accessor that includes a binding to the BLOB column. The consumer performs the following actions:
To get BLOB data using a storage object, a consumer performs the following actions:
The provider creates a storage object over the BLOB's data and returns a pointer to the requested storage interface (ISequentialStream, IStream, IStorage, or ILockBytes) on this object. If the provider supports only a single open storage object at a time and another storage object is open, the method returns a status of DBSTATUS_E_CANTCREATE for the column.
If the consumer calls GetData, GetLastVisibleData, or GetOriginalData multiple times for the BLOB column, the provider returns distinct pointers to storage interfaces on each call. This is similar to opening a file multiple times and returning a different file handle each time. It is the consumer's responsibility to call Release on each of these storage interfaces separately.
For example, the following code binds to a BLOB column and uses ISequentialStream::Read to get the data:
#include<oledb.h>
IRowset* pIRowset;
IAccessor* pIAccessor;
IMalloc* pIMalloc;
int main() {
// Assume the consumer has a pointer (pIRowset) that points to the rowset. Create an
// accessor for the BLOB column. Assume it is column 1.
HACCESSOR hAccessor;
DBBINDSTATUS rgStatus[1];
DBOBJECT ObjectStruct;
DBBINDING rgBinding[1] = {
1, // Column 1
0, // Offset to data
0, // Ignore length field
sizeof(IUnknown *), // Offset to status field
NULL, // No type info
&ObjectStruct, // Object structure
NULL, // Ignore binding extensions
DBPART_VALUE|DBPART_STATUS, // Bind value and status
DBMEMOWNER_CLIENTOWNED, // Consumer owned memory
DBPARAMIO_NOTPARAM, // Not a parameter
0, // Ignore size of data
0, // Reserved
DBTYPE_IUNKNOWN, // Type DBTYPE_IUNKNOWN
0, // Precision not applicable
0 // Scale not applicable
} ;
// Set the elements in the object structure so that the provider creates a readable
// ISequentialStream object over the column. The consumer will read data from this
// object.
ObjectStruct.dwFlags = STGM_READ;
ObjectStruct.iid = IID_ISequentialStream;
pIRowset->QueryInterface(IID_IAccessor, (void**) &pIAccessor);
pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, 1, rgBinding,
sizeof(IUnknown *) + sizeof(ULONG), &hAccessor, rgStatus);
pIAccessor->Release();
// Allocate memory for the returned pointer and the status field. The first
// sizeof(IUnknown*) bytes are for the pointer to the object and the next
// sizeof(ULONG) bytes are for the status.
void * pData = pIMalloc->Alloc(sizeof(IUnknown *) + sizeof(ULONG));
// Get the next row, get the pointer to ISequentialStream.
HROW * rghRows = NULL;
ULONG cRows;
pIRowset->GetNextRows(NULL, 0, 1, &cRows, &rghRows);
pIRowset->GetData(rghRows[0], hAccessor, pData);
// Read and process 5000 bytes at a time.
BYTE rgBuffer[5000];
ULONG cb;
if ((ULONG)((BYTE*)pData)[rgBinding[0].obStatus] == DBSTATUS_S_ISNULL) {
// Process NULL data
} else if ((ULONG)((BYTE *)pData)[rgBinding[0].obStatus] == DBSTATUS_S_OK) {
do {
(*((ISequentialStream **)pData))->Read(rgBuffer, sizeof(rgBuffer), &cb);
if (cb > 0) {
; // Process data
}
} while (cb >= sizeof(rgBuffer));
};
pIMalloc->Free(rghRows);
};
There are two ways a consumer can set BLOB data using a storage object:
To write data directly to the provider's storage object, the consumer:
The provider creates a storage object over the BLOB's data and returns a pointer to the requested storage interface (ISequentialStream, IStream, IStorage, or ILockBytes) on this object. If the provider supports only a single open storage object at a time and another storage object is open, the method returns a status of DBSTATUS_E_CANTCREATE for the column.
If the storage object is transacted (that is, the STGM_TRANSACTED flag is set in the dwFlags element of the DBOBJECT structure in the binding), the storage object does not publish the changes to the containing rowset until the consumer calls Commit on the storage interface. If the storage object is not transacted (that is, the STGM_DIRECT flag is set), the storage object publishes the changes to the containing rowset when the consumer calls a method on the storage interface to set the changes.
For a similar code example, see "Getting BLOB Data with Storage Objects" earlier in this section. The code for setting BLOB data is essentially the same, except that the consumer must set the dwFlags element of the ObjectStruct structure so that it can write to the storage object. Also, the consumer calls ISequentialStream::Write instead of ISequentialStream::Read.
To pass a pointer to its own storage object, the consumer:
If the consumer's storage object exposes ISequentialStream and the provider needs to know the number of bytes of BLOB data that will be sent before any of the data is sent, this accessor must also bind the length of the BLOB column. For more information, see "Limitations of Storage Objects" earlier in this chapter.
Comments
If the provider already has a storage object open over the BLOB's data, the method returns a status of DBSTATUS_E_CANTCREATE for the column. Otherwise, the provider copies the data from the consumer's storage object to the BLOB column. This is equivalent to the provider destroying the existing data, creating a new (empty) storage object over the BLOB column, repeatedly calling methods to read data from the consumer's storage object and write data to the storage object over the BLOB column, and, if the storage mode is transacted, committing the changes on the storage object over the BLOB column. Character data set in this manner is not null terminated.
If the provider needs to know the number of bytes of BLOB data that will be sent before any of the data is sent, it retrieves this number with the IStream::Stat, IStorage::Stat, or ILockBytes::Stat method on the consumer's storage object (if the consumer's storage object exposes IStream, IStorage, or ILockBytes) or from the length part of the binding (if the consumer's storage object exposes ISequentialStream). If the consumer binds the length and the provider does not need to know this information or the consumer's storage object exposes IStream, IStorage, or ILockBytes, the provider ignores the bound length value.
When the provider has finished using the consumer's storage object, it calls IUnknown::Release to release the pointer. After SetData or InsertRow returns, the provider must not hold any pointers or reference counts on the consumer's storage object. If the consumer wants to ensure access to its storage object after SetData returns, it must call IUnknown::AddRef on the pointer before calling SetData.
To set the BLOB column to a zero-length value, the consumer sets the status flag to DBSTATUS_S_OK. It then either passes a null pointer (instead of a pointer to its own storage object) or passes a pointer to a storage object that contains no data. The provider sets the column to a zero-length value. This is different from setting the status value for the column to DBSTATUS_S_ISNULL. If the consumer sets the column value to NULL and then calls GetData, GetData returns a status value of DBSTATUS_S_ISNULL. If the consumer sets the column to a zero-length value and then calls GetData, GetData returns a pointer to a storage object that contains no data.
For example, the following code passes a pointer to a different ISequentialStream object to SetData to overwrite the existing value:
#include<oledb.h>
IRowsetChange * pIRowsetChange;
IAccessor * pIAccessor;
ISequentialStream * pISeqStr;
HROW hrow;
int main() {
// Assume the consumer has a pointer (pIRowsetChange) that points to the rowset and
// a pointer (pISeqStr) that points to an ISequentialStream object not in the
// rowset. Create an accessor for the BLOB column. Assume it is column 1.
HACCESSOR hAccessor;
DBBINDSTATUS rgStatus[1];
DBOBJECT ObjectStruct;
DBBINDING rgBinding[1] = {
1, // Column 1
0, // Offset to data
sizeof(IUnknown*), // obLength length field
0, // Ignore status field
NULL, // No type info
ObjectStruct, // Object structure
NULL, // Ignore binding extension
DBPART_VALUE|DBPART_LENGTH, // Bind value and length
DBMEMOWNER_CLIENTOWNED, // Consumer owned memory
DBPARAMIO_NOTPARAM, // Not a parameter binding
0, // Ignore maxlength
0, // Reserved
DBTYPE_IUNKNOWN, // Type DBTYPE_IUNKNOWN
0, // Precision not applicable
0, // Scale not applicable
};
// Set the elements in the object structure so that the provider creates a writable
// ISequentialStream object over the column. The provider will read data from the
// ISequentialStream object passed to SetData and write it to this object.
ObjectStruct.dwFlags = STGM_READ ;
ObjectStruct.iid = IID_ISequentialStream;
pIRowsetChange->QueryInterface(IID_IAccessor, (void**) &pIAccessor);
pIAccessor->CreateAccessor(DBACCESSOR_ROWDATA, 1, rgBinding, sizeof(IUnknown *),
pIAccessor->Release();
//Setup pData row buffer
BYTE* pData=(BYTE*)pIMalloc->Alloc(sizeof(IUnknown*)+sizeof(ULONG));
//Value - pass ISequentialStream pointer to the provider
*(ISequentialStream**)(pData+rgBinding[0].obVaule)=pISeqStr;
//LENGTH - Some providers need to know the length of the stream ahead of time...
*(ULONG*)(pData+rgBinding[0].oblength)=5000;
//SetData - The provider will then do a ISequentialStream::Read
//on the pISeqStr pointer passed in... pIRowsetChange->SetData(hrow, hAccessor, &pISeqStr);
return 0;
} ;
Consumers get and set BLOBs and OLE objects in command parameters in a manner similar to accessing BLOB columns.
To get BLOB data using a storage object as output parameters, a consumer performs the following actions:
To set BLOB data using a storage object as input parameters, a consumer performs the following actions: