Microsoft KB Archive/238539

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

Article ID: 238539

Article Last Modified on 10/15/2002

-

APPLIES TO

 Microsoft OLE DB 2.7, when used with:  Microsoft Visual C++ 6.0 Enterprise Edition

 Microsoft Visual C++ 6.0 Professional Edition

 Microsoft Visual C++ 6.0 Standard Edition 

-

<div class="notice_section">

This article was previously published under Q238539

<div class="symptoms_section">

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.

<div class="cause_section">

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

<div class="resolution_section">

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; } };

<div class="status_section">

STATUS
Microsoft has confirmed that this is a bug in the Microsoft products that are listed at the beginning of this article.

This problem was corrected in Microsoft Visual C++ .NET.

<div class="moreinformation_section">

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: kbbug kbfix kbnoupdate kbconsumer kbdatabase kbdtl kbmdacnosweep KB238539

-

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

© Microsoft Corporation. All rights reserved.