BUG: Using CDynamicAccessor with Unicode Columns Causes Memory Overwrites and Other Failures

ID: Q238539


The information in this article applies to:
  • Microsoft OLE DB, used with:
    • Microsoft Visual C++, 32-bit Editions, version 6.0


SYMPTOMS

When you use the CDynamicAccessor class to retrieve or set the values of a Unicode string column (for example, a SQL Server 7.0 nchar field), several possible errors or symptoms can occur, including the following:

  • DB_E_ERRORSOCCURED is returned when you call SetData.


  • The application stops responding (hangs).


  • You receive an access violation.



CAUSE

CDynamicAccessor does not create enough storage to hold the string. CDynamicAccessor uses the ulColumnSize member of the column information to get the length needed for the buffer. However, this method yields the number of characters for the field, not the number of bytes. This value (the number of characters for the field) must be multiplied by 2 [sizeof(WCHAR)] to get the number of bytes needed to hold the Unicode string.

Also, the following functions do not calculate the offset correctly for their parts (length and status):

  • GetLength
  • SetLength
  • SetStatus
  • GetStatus


RESOLUTION

To work around this problem, derive a new class from CDynamicAccessor and correct the necessary functions by multiplying ulColumnSize by 2 whenever a DBTYPE_WSTR column is encountered.

The following sample code is an example of how the class might appear:


class CDynAccessor: public CDynamicAccessor
{
public:

	bool GetStatus(ULONG nColumn, DBSTATUS* pStatus) const
	{
		ATLASSERT(pStatus != NULL);
		if (TranslateColumnNo(nColumn))
		{
			if (DBTYPE_WSTR == m_pColumnInfo[nColumn].wType)
				*pStatus = *(ULONG*)(AddOffset(AddOffset((ULONG)_GetDataPtr(nColumn), m_pColumnInfo[nColumn].ulColumnSize*sizeof(WCHAR)), sizeof(ULONG)));
			else
				*pStatus = *(ULONG*)(AddOffset(AddOffset((ULONG)_GetDataPtr(nColumn), m_pColumnInfo[nColumn].ulColumnSize), sizeof(ULONG)));
			return true;
		}
		else
			return false;
	}
	
	bool GetStatus(TCHAR* pColumnName, DBSTATUS* pStatus) const
	{
		ATLASSERT(pColumnName != NULL);
		ATLASSERT(pStatus != NULL);
		ULONG nColumn;
		if (GetInternalColumnNo(pColumnName, &nColumn))
		{
			if (DBTYPE_WSTR == m_pColumnInfo[nColumn].wType)
				*pStatus = *(ULONG*)(AddOffset(AddOffset((ULONG)_GetDataPtr(nColumn), m_pColumnInfo[nColumn].ulColumnSize*sizeof(WCHAR)), sizeof(ULONG)));
			else
				*pStatus = *(ULONG*)(AddOffset(AddOffset((ULONG)_GetDataPtr(nColumn), m_pColumnInfo[nColumn].ulColumnSize), sizeof(ULONG)));
			return true;
		}
		else
			return false;
	}
	
	bool SetStatus(ULONG nColumn, DBSTATUS status)
	{
		if (TranslateColumnNo(nColumn))
		{
			if (DBTYPE_WSTR == m_pColumnInfo[nColumn].wType)
				*(ULONG*)(AddOffset(AddOffset((ULONG)_GetDataPtr(nColumn), m_pColumnInfo[nColumn].ulColumnSize*sizeof(WCHAR)), sizeof(ULONG))) = status;
			else
				*(ULONG*)(AddOffset(AddOffset((ULONG)_GetDataPtr(nColumn), m_pColumnInfo[nColumn].ulColumnSize), sizeof(ULONG))) = status;
			return true;
		}
		else
			return false;
	}

	bool SetStatus(TCHAR* pColumnName, DBSTATUS status)
	{
		ATLASSERT(pColumnName != NULL);
		ULONG nColumn;
		if (GetInternalColumnNo(pColumnName, &nColumn))
		{
			if (DBTYPE_WSTR == m_pColumnInfo[nColumn].wType)
				*(ULONG*)(AddOffset(AddOffset((ULONG)_GetDataPtr(nColumn), m_pColumnInfo[nColumn].ulColumnSize*sizeof(WCHAR)), sizeof(ULONG))) = status;
			else
				*(ULONG*)(AddOffset(AddOffset((ULONG)_GetDataPtr(nColumn), m_pColumnInfo[nColumn].ulColumnSize), sizeof(ULONG))) = status;
			return true;
		}
		else
			return false;
	}

	bool GetLength(ULONG nColumn, ULONG* pLength) const
	{
		ATLASSERT(pLength != NULL);
		if (TranslateColumnNo(nColumn))
		{
			if (DBTYPE_WSTR == m_pColumnInfo[nColumn].wType)
				*pLength = *(ULONG*)(AddOffset((ULONG)_GetDataPtr(nColumn), m_pColumnInfo[nColumn].ulColumnSize*sizeof(WCHAR)));
			else 
				*pLength = *(ULONG*)(AddOffset((ULONG)_GetDataPtr(nColumn), m_pColumnInfo[nColumn].ulColumnSize));
			return true;
		}
		else
			return false;
	}

	bool GetLength(TCHAR* pColumnName, ULONG* pLength) const
	{
		ATLASSERT(pColumnName != NULL);
		ATLASSERT(pLength != NULL);
		ULONG nColumn;
		if (GetInternalColumnNo(pColumnName, &nColumn))
		{
			if (DBTYPE_WSTR == m_pColumnInfo[nColumn].wType)
				*pLength = *(ULONG*)(AddOffset((ULONG)_GetDataPtr(nColumn), m_pColumnInfo[nColumn].ulColumnSize*sizeof(WCHAR)));
			else 
				*pLength = *(ULONG*)(AddOffset((ULONG)_GetDataPtr(nColumn), m_pColumnInfo[nColumn].ulColumnSize));
			return true;
		}
		else
			return false;
	}

	bool SetLength(ULONG nColumn, ULONG nLength)
	{
		if (TranslateColumnNo(nColumn))
		{
			if (DBTYPE_WSTR == m_pColumnInfo[nColumn].wType)
				*(ULONG*)(AddOffset((ULONG)_GetDataPtr(nColumn), m_pColumnInfo[nColumn].ulColumnSize*sizeof(WCHAR))) = nLength;
			else
				*(ULONG*)(AddOffset((ULONG)_GetDataPtr(nColumn), m_pColumnInfo[nColumn].ulColumnSize)) = nLength;
			return true;
		}
		else
			return false;
	}
	
	
	bool SetLength(TCHAR* pColumnName, ULONG nLength)
	{
		ATLASSERT(pColumnName != NULL);
		ULONG nColumn;
		if (GetInternalColumnNo(pColumnName, &nColumn))
		{
			if (DBTYPE_WSTR == m_pColumnInfo[nColumn].wType)
				*(ULONG*)(AddOffset((ULONG)_GetDataPtr(nColumn), m_pColumnInfo[nColumn].ulColumnSize*sizeof(WCHAR))) = nLength;
			else
				*(ULONG*)(AddOffset((ULONG)_GetDataPtr(nColumn), m_pColumnInfo[nColumn].ulColumnSize)) = nLength;
			return true;
		}
		else
			return false;
	}
	


	HRESULT BindColumns(IUnknown* pUnk)
	{
		ATLASSERT(pUnk != NULL);
		CComPtr<IAccessor> spAccessor;
		HRESULT hr = pUnk->QueryInterface(&spAccessor);
		if (FAILED(hr))
			return hr;

		ULONG   i;
		ULONG   nOffset = 0, nLengthOffset, nStatusOffset;

		// If the user hasn't specifed the column information to bind by calling AddBindEntry then
		// we get it ourselves.
		if (m_pColumnInfo == NULL)
		{
			CComPtr<IColumnsInfo> spColumnsInfo;
			hr = pUnk->QueryInterface(&spColumnsInfo);
			if (FAILED(hr))
				return hr;

			hr = spColumnsInfo->GetColumnInfo(&m_nColumns, &m_pColumnInfo, &m_pStringsBuffer);
			if (FAILED(hr))
				return hr;

			m_bOverride = false;
		}
		else
			m_bOverride = true;

		DBBINDING* pBinding = NULL;
		ATLTRY(pBinding= new DBBINDING[m_nColumns]);
		if (pBinding == NULL)
			return E_OUTOFMEMORY;

		DBBINDING* pCurrent = pBinding;
		DBOBJECT*  pObject;
		for (i = 0; i < m_nColumns; i++)
		{
			// If it's a BLOB or the column size is large enough for us to treat it as
			// a BLOB then we also need to set up the DBOBJECT structure.
			if (m_pColumnInfo[i].ulColumnSize > 1024 || m_pColumnInfo[i].wType == DBTYPE_IUNKNOWN)
			{
				pObject = NULL;
				ATLTRY(pObject = new DBOBJECT);
				if (pObject == NULL)
					return E_OUTOFMEMORY;
				pObject->dwFlags = STGM_READ;
				pObject->iid     = IID_ISequentialStream;
				m_pColumnInfo[i].wType      = DBTYPE_IUNKNOWN;
				m_pColumnInfo[i].ulColumnSize   = sizeof(IUnknown*);
			}
			else
				pObject = NULL;

			// If column is of type STR or WSTR increase length by 1
			// to accommodate the NULL terminator.
			if (m_pColumnInfo[i].wType == DBTYPE_STR ||
				m_pColumnInfo[i].wType == DBTYPE_WSTR)
					m_pColumnInfo[i].ulColumnSize += 1;

			if (m_pColumnInfo[i].wType == DBTYPE_WSTR)
				nLengthOffset = AddOffset(nOffset, m_pColumnInfo[i].ulColumnSize*sizeof(WCHAR));
			else 
				nLengthOffset = AddOffset(nOffset, m_pColumnInfo[i].ulColumnSize);

			nStatusOffset = AddOffset(nLengthOffset, sizeof(ULONG));


			ULONG cbMaxLen;
			if (m_pColumnInfo[i].wType == DBTYPE_WSTR)
				cbMaxLen = m_pColumnInfo[i].ulColumnSize*sizeof(WCHAR);
			else 
				cbMaxLen = m_pColumnInfo[i].ulColumnSize;

			CAccessorBase::Bind(pCurrent, m_pColumnInfo[i].iOrdinal, m_pColumnInfo[i].wType,
				cbMaxLen, m_pColumnInfo[i].bPrecision, m_pColumnInfo[i].bScale,
				DBPARAMIO_NOTPARAM, nOffset,
				nLengthOffset, nStatusOffset, pObject);

			pCurrent++;

			// Note that, because we're not using this for anything else, we're using the
			// pTypeInfo element to store the offset to our data.
			m_pColumnInfo[i].pTypeInfo = (ITypeInfo*)nOffset;

			nOffset = AddOffset(nStatusOffset, sizeof(DBSTATUS));
		}
		// Allocate the accessor memory if we haven't done so yet.
		if (m_pAccessorInfo == NULL)
		{
			hr = AllocateAccessorMemory(1); // We only have one accessor.
			if (FAILED(hr))
			{
				delete [] pBinding;
				return hr;
			}
			m_pAccessorInfo->bAutoAccessor = TRUE;
		}

		// Allocate enough memory for the data buffer and tell the rowset.
		// Note that the rowset will free the memory in its destructor.
		m_pBuffer = NULL;
		ATLTRY(m_pBuffer = new BYTE[nOffset]);
		if (m_pBuffer == NULL)
		{
			delete [] pBinding;
			return E_OUTOFMEMORY;
		}
		hr = BindEntries(pBinding, m_nColumns, &m_pAccessorInfo->hAccessor,
				nOffset, spAccessor);
		delete [] pBinding;

		return hr;
	}
};
 


STATUS

Microsoft has confirmed this to be a bug in the Microsoft products listed at the beginning of this article.


MORE INFORMATION

The following is sample code that fails while inserting an 18-character Unicode string into a SQL table with a 20-character-wide Unicode text column (nvarchar):


	CDataSource ds;
	CSession sn;

	HRESULT		hr;
	CDBPropSet	dbinit(DBPROPSET_DBINIT);

	dbinit.AddProperty(DBPROP_AUTH_USERID, OLESTR("sa"));
	dbinit.AddProperty(DBPROP_INIT_CATALOG, OLESTR("pubs"));
	dbinit.AddProperty(DBPROP_INIT_DATASOURCE, OLESTR("myserver"));
	dbinit.AddProperty(DBPROP_INIT_LCID, (long)1033);
	dbinit.AddProperty(DBPROP_INIT_PROMPT, (short)4);
	hr = ds.Open(_T("SQLOLEDB"), &dbinit);
	ATLASSERT(SUCCEEDED(hr)); //Is the Datasource name and the table name correct, above?	
	sn.Open(ds);
        CCommand< CDynamicAccessor> cmd;

// To fix the problem, remark the above line that uses CDynamicAccessor
// and uncomment the line below that uses the new CDynamicAccessor-derived class.
//	CCommand< CDynAccessor> cmd;  

	cmd.Create(sn, "Select * from TestTable");

	CDBPropSet	propset(DBPROPSET_ROWSET);
	propset.AddProperty(DBPROP_IRowsetChange, true);
	propset.AddProperty(DBPROP_UPDATABILITY, DBPROPVAL_UP_CHANGE | DBPROPVAL_UP_INSERT | DBPROPVAL_UP_DELETE);

	
	hr = cmd.Open(&propset);

	hr = cmd.MoveNext();

	ULONG ul;
	DBSTATUS stat;
       	cmd.GetLength(1, &ul);	// This is probably not correct.
	cmd.GetStatus(1, &stat); // This is probably not correct.
		
	wchar_t * pszValue = (wchar_t *)cmd.GetValue(1);
	wcscpy(pszValue, L"12345678901234567");

	int len = wcslen(pszValue);
	cmd.SetLength(1, len*2 );
	cmd.SetStatus(1, DBSTATUS_S_OK);

	hr = cmd.Insert();

	cmd.GetStatus(1, &stat); 

Additional query words: damage normal block errors occurred corruption

Keywords : kbATL kbDatabase kbDTL kbOLEDB kbVC kbConsumer kbGrpVCDB kbGrpMDAC kbDSupport
Version : WINDOWS:
Platform : WINDOWS
Issue type : kbbug


Last Reviewed: November 20, 1999
© 2000 Microsoft Corporation. All rights reserved. Terms of Use.