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