Microsoft KB Archive/242956

= PRB: DB_E_BADBINDINFO Returned from MoveNext/GetData When Using BYREF with Column Type =

Article ID: 242956

Article Last Modified on 5/12/2003

-

APPLIES TO


 * Microsoft OLE DB 1.1
 * Microsoft OLE DB 1.5
 * Microsoft OLE DB 2.0
 * Microsoft OLE DB 2.5
 * Microsoft OLE DB 2.6
 * Microsoft OLE DB 2.7
 * Microsoft Data Access Components 2.5
 * Microsoft Data Access Components 2.6
 * Microsoft Data Access Components 2.7

-



This article was previously published under Q242956



SYMPTOMS
When using the ATL OLE DB Consumer Templates and specifying DBTYPE_BYREF for the data type for a column entry, MoveNext returns DB_E_BADBINDINFO. More specifically, internally IRowset::GetData is called and it returns the error. The column map may resemble the following: BEGIN_COLUMN_MAP(CMyTableAccessor) COLUMN_ENTRY(1, m_field1) COLUMN_ENTRY_EX(2, DBTYPE_STR | DBTYPE_BYREF, sizeof(char *),                                                                    0, 0, m_pszField2, m_nField2Len, m_Field2Status) END_COLUMN_MAP



CAUSE
The OLE DB provider is doing deferred accessor validation and is finding values of the DBBINDING structure, which it doesn't support. Specifically, Active Template Library (ATL) is setting the dwMemOwner member of the DBBINDING structure to DBMEMOWNER_PROVIDEROWNED whenever the developer specifies DBTYPE_BYREF. This is done so that the consumer doesn't have the responsibility to free the memory returned by the provider. When ReleaseRows is called in the MoveNext method, the memory is automatically freed by the provider. However, according to the OLE DB specification:

For bindings in row accessors, consumer-owned memory must be used unless wType is DBTYPE_BSTR, X | DBTYPE_BYREF, X | DBTYPE_ARRAY, or X | DBTYPE_VECTOR, in which cases either consumer-owned or provider-owned memory can be used. However, the provider might not support using provider-owned memory to retrieve columns for which IColumnsInfo::GetColumnInfo returns DBCOLUMNFLAGS_ISLONG for the column.

ODBC OLE DB Provider is one such provider that doesn't support DBMEMOWNER_PROVIDEROWNED on DBCOLUMNFLAGS_ISLONG columns. As a result, the error is returned when a SQL Server column type of "text" is used, for example, and DBTYPE_BYREF is specified (which causes ATL to set dwMemOwner to DBMEMOWNER_PROVIDEROWNED).



RESOLUTION
The type DBTYPE_BYREF can still be used, but modifications must be made to the ATL code to prevent it from setting the the DBBINDING structure to DBMEMOWNER_PROVIDEROWNED. Furthermore, if you do specify DBMEMOWNER_CLIENTOWNED and use DBTYPE_BYREF, the consumer is responsible for freeing the memory referenced by using IMalloc::Free/CoTaskMemFree.

The easiest way to work around this problem is to copy the Atldbcli.h file into your project so that you have your own custom build of the header. Modify the Bind method of CAccessorBase by commenting the code that sets the dwMemOwner member to DBMEMOWNER_PROVIDEROWNED. For example: static void Bind(DBBINDING* pBinding, ULONG nOrdinal, DBTYPE wType, ULONG nLength, BYTE nPrecision, BYTE nScale, DBPARAMIO eParamIO, ULONG nDataOffset, ULONG nLengthOffset = NULL, ULONG nStatusOffset = NULL, DBOBJECT* pdbobject = NULL) {  ATLASSERT(pBinding != NULL); // If we are getting a pointer to the data, then let the provider // own the memory. // if (wType & DBTYPE_BYREF) // pBinding->dwMemOwner = DBMEMOWNER_PROVIDEROWNED; // else pBinding->dwMemOwner = DBMEMOWNER_CLIENTOWNED; Use this header file instead of the Visual C++ copy.

NOTE: You must free the memory returned to you by the provider. You may want to create a function called FreeRecordMemory in your class where you call CoTaskMemFree on any of the pointers you get back with BYREF. For example: void FreeRecordMemory { CoTaskMemFree((BYTE *)m_pszField2); } If you don't want to modify the header file, there is a more difficult approach to work around this problem. You can write your own macro that calls your own Bind function, and that always sets pBinding->dwMemOwner = DBMEMOWNER_CLIENTOWNED. The code for your data class might resemble the following: if (pBuffer != NULL) \ { \        CAccessorBase::FreeType(wType, pBuffer + dataOffset); \ } \    else if (pBinding != NULL) \ { \        Bind(pBinding, nOrdinal, wType, nLength, nPrecision, nScale, eParamIO, \             dataOffset, lengthOffset, statusOffset); \ pBinding++; \ } \    nColumns++; _COLUMN_ENTRY_CODE_BYREF(nOrdinal, wType, nLength, nPrecision, nScale, offsetbuf(data), offsetbuf(length), offsetbuf(status))
 * 1) define _COLUMN_ENTRY_CODE_BYREF(nOrdinal, wType, nLength, nPrecision, nScale, dataOffset, lengthOffset, statusOffset) \
 * 1) define COLUMN_ENTRY_EX_BYREF(nOrdinal, wType, nLength, nPrecision, nScale, data, length, status) \

class CdbotexttestAccessor { public: TCHAR m_szfield1[11]; char * m_pszField2; long m_nField2Len; DBSTATUS m_Field2Status;

static void Bind(DBBINDING* pBinding, ULONG nOrdinal, DBTYPE wType,        ULONG nLength, BYTE nPrecision, BYTE nScale, DBPARAMIO eParamIO,         ULONG nDataOffset, ULONG nLengthOffset = NULL, ULONG nStatusOffset = NULL,         DBOBJECT* pdbobject = NULL) {     ATLASSERT(pBinding != NULL); // If we are getting a pointer to the data, then let the provider // own the memory. // (wType & DBTYPE_BYREF) // pBinding->dwMemOwner = DBMEMOWNER_PROVIDEROWNED; // else

pBinding->dwMemOwner = DBMEMOWNER_CLIENTOWNED; pBinding->pObject = pdbobject; pBinding->eParamIO = eParamIO; pBinding->iOrdinal = nOrdinal; pBinding->wType = wType; pBinding->bPrecision = nPrecision; pBinding->bScale = nScale; pBinding->dwFlags = 0; pBinding->obValue = nDataOffset; pBinding->obLength = 0; pBinding->obStatus = 0; pBinding->pTypeInfo = NULL; pBinding->pBindExt = NULL; pBinding->cbMaxLen = nLength; pBinding->dwPart = DBPART_VALUE; if (nLengthOffset != NULL) {        pBinding->dwPart |= DBPART_LENGTH; pBinding->obLength = nLengthOffset; }     if (nStatusOffset != NULL) {        pBinding->dwPart |= DBPART_STATUS; pBinding->obStatus = nStatusOffset; }   }

void FreeRecordMemory {     CoTaskMemFree((BYTE *)m_pField2); }

BEGIN_COLUMN_MAP(CdbotexttestAccessor) COLUMN_ENTRY(1, m_szField1) COLUMN_ENTRY_EX_BYREF(2, DBTYPE_STR | DBTYPE_BYREF, sizeof(char *), 0, 0, m_pszField2, m_szField2Len, m_szField2Status) END_COLUMN_MAP }; You still can use BYREF, however, you must specify client owned by modifying the ATL code. You are also responsible for freeing the memory using IMalloc::Free/CoTaskMemFree.



STATUS
This behavior is by design.



Steps to Reproduce Behavior

 * 1) Create a SQL Server 7.0 table that has a "text" datatype field and insert some records.
 * 2) Create an MFC application and use the ATL Consumer wizard to generate a CCommand-derived class for the SQL Server table you just created. Use the OLE DB provider for ODBC.
 * 3) Modify the column map entry for the "text" field to use COLUMN_ENTRY_EX so that it uses DBTYPE_STR | DBTYPE_BYREF.
 * 4) Add code to the project that creates an instance of the CCommand-derived class and that calls Open followed by a call to MoveNext.
 * 5) Build and run the application and check the return value of the MoveNext method (it should have a value of DB_E_BADBINDINFO).

Keywords: kbconsumer kbdatabase kbprb KB242956

-

[mailto:TECHNET@MICROSOFT.COM Send feedback to Microsoft]

© Microsoft Corporation. All rights reserved.