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

ID: Q242956


The information in this article applies to:
  • Microsoft OLE DB, versions 1.0, 1.1, 1.5, 2.0


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:

#define _COLUMN_ENTRY_CODE_BYREF(nOrdinal, wType, nLength, nPrecision, nScale, dataOffset, lengthOffset, statusOffset) \ 
	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++;
#define COLUMN_ENTRY_EX_BYREF(nOrdinal, wType, nLength, nPrecision, nScale, data, length, status) \ 
	_COLUMN_ENTRY_CODE_BYREF(nOrdinal, wType, nLength, nPrecision, nScale, offsetbuf(data), offsetbuf(length), offsetbuf(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.


MORE INFORMATION

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).


Additional query words:

Keywords : kbATL kbDatabase kbOLEDB kbSQLServ kbConsumer kbGrpVCDB kbGrpMDAC kbDSupport
Version : WINDOWS:1.0,1.1,1.5,2.0
Platform : WINDOWS
Issue type : kbprb


Last Reviewed: January 7, 2000
© 2000 Microsoft Corporation. All rights reserved. Terms of Use.