FIX: Only the First 64K Is Read from Binary Field
ID: q140535
1.50 1.51
WINDOWS
kbprg kbbuglist kbfixlist
The information in this article applies to:
- The Microsoft Foundation Classes (MFC), included with:
Microsoft Visual C++ for Windows, version 1.5, and 1.51
SYMPTOMS
Only the first 64K of data is read from the binary field.
CAUSE
A bug in the RFX_LongBinary() function prevents the CLongBinary object from
receiving all of the field's data when a field has more than 64K of
information.
In Visual C++ 1.5, starting on line 1627 of the Dbrfx.cpp file, you can see
the following code:
AFX_SQL_ASYNC(pFX->m_prs, ::SQLGetData(pFX->m_prs->m_hstmt,
(unsigned short int)nField, SQL_C_BINARY,
(UCHAR FAR*)lpLongBinary, *plLength, plLength));
::GlobalUnlock(value.m_hData);
if (!pFX->m_prs->Check(nRetCode))
pFX->m_prs->ThrowDBException(nRetCode);
The ODBC function SQLGetData() is called only once. This is incorrect
because SQLGetData() cannot fetch more than 64K of data in a single call.
In Visual C++ 1.51, the equivalent code starts on line 1629 of Dbrfx.cpp:
DWORD dwDataLength = 0;
do
{
DWORD dwChunkSize = value.m_dwDataLength - dwDataLength;
if (dwChunkSize > 0x8000)
dwChunkSize = 0x8000;
// Ignore expected data truncated warnings
AFX_SQL_ASYNC(pFX->m_prs, ::SQLGetData(pFX->m_prs->m_hstmt,
(unsigned short int)nField, SQL_C_BINARY,
(UCHAR FAR*)lpLongBinary, dwChunkSize, plLength));
dwDataLength += *plLength;
lpLongBinary += *plLength;
} while (nRetCode == SQL_SUCCESS ||
nRetCode == SQL_SUCCESS_WITH_INFO);
::GlobalUnlock(value.m_hData);
if (!pFX->m_prs->Check(nRetCode))
pFX->m_prs->ThrowDBException(nRetCode);
}
return;
In this case, the code has a loop that tries to get the long binary data in
32K chunks. The problem with this code is that it uses the last argument to
the ::SQLGetData call as the amount by which to move the data pointer and
increment the data length. This argument is assumed to be set to the amount
of data that was actually transferred by the call.
Unfortunately, this parameter can return with the value SQL_NO_TOTAL
(defined in Sqlext.h as -4), which indicates that the data was truncated or
its length could not be determined and the amount requested should be used
as the amount returned. This causes the incoming data to be corrupted when
it is used to move the data pointer and update the data length.
This problem was corrected in Visual C++ version 1.52, where the version
1.51 code:
dwDataLength += *plLength;
lpLongBinary += *plLength;
was replaced with this code:
dwDataLength += dwChunkSize;
lpLongBinary += dwChunkSize;
RESOLUTION
Follow these steps:
1. Copy the RFX_LongBinary() function to another .cpp file that you will
add to your project. Rename the function to something like
RFX_NewLongBinary().
2. In the RFX_LongBinary() routine from Visual C++ 1.5, look for this code:
const BYTE FAR* lpLongBinary;
lpLongBinary = (const BYTE FAR*)::GlobalLock(value.m_hData);
if (lpLongBinary == NULL)
{
::GlobalFree(value.m_hData);
value.m_hData = NULL;
AfxThrowMemoryException();
}
AFX_SQL_ASYNC(pFX->m_prs, ::SQLGetData(pFX->m_prs->m_hstmt,
(unsigned short int)nField, SQL_C_BINARY,
(UCHAR FAR*)lpLongBinary, *plLength, plLength));
::GlobalUnlock(value.m_hData);
if (!pFX->m_prs->Check(nRetCode))
pFX->m_prs->ThrowDBException(nRetCode);
Or if you are in Visual C++ 1.51, look for this code:
const BYTE _huge* lpLongBinary;
lpLongBinary = (const BYTE _huge*)::GlobalLock(value.m_hData);
if (lpLongBinary == NULL)
{
::GlobalFree(value.m_hData);
value.m_hData = NULL;
AfxThrowMemoryException();
}
DWORD dwDataLength = 0;
do
{
DWORD dwChunkSize = value.m_dwDataLength - dwDataLength;
if (dwChunkSize > 0x8000)
dwChunkSize = 0x8000;
// Ignore expected data truncated warnings
AFX_SQL_ASYNC(pFX->m_prs, ::SQLGetData(pFX->m_prs->m_hstmt,
(unsigned short int)nField, SQL_C_BINARY,
(UCHAR FAR*)lpLongBinary, dwChunkSize, plLength));
dwDataLength += *plLength;
lpLongBinary += *plLength;
} while (nRetCode == SQL_SUCCESS || nRetCode ==
SQL_SUCCESS_WITH_INFO);
::GlobalUnlock(value.m_hData);
if (!pFX->m_prs->Check(nRetCode))
pFX->m_prs->ThrowDBException(nRetCode);
3. In either version, replace the listed code with this code:
const BYTE _huge* lpLongBinary;
lpLongBinary = (const BYTE _huge*)::GlobalLock(value.m_hData);
if (lpLongBinary == NULL)
{
::GlobalFree(value.m_hData);
value.m_hData = NULL;
AfxThrowMemoryException();
}
DWORD dwDataLength = 0;
do
{
DWORD dwChunkSize = value.m_dwDataLength - dwDataLength;
if (dwChunkSize > 0x8000)
dwChunkSize = 0x8000;
// Ignore expected data truncated warnings
AFX_SQL_ASYNC(pFX->m_prs, ::SQLGetData(pFX->m_prs->m_hstmt,
(unsigned short int)nField, SQL_C_BINARY,
(UCHAR FAR*)lpLongBinary, dwChunkSize, plLength));
dwDataLength += dwChunkSize;
lpLongBinary += dwChunkSize;
} while (nRetCode == SQL_SUCCESS || nRetCode ==
SQL_SUCCESS_WITH_INFO);
::GlobalUnlock(value.m_hData);
if (!pFX->m_prs->Check(nRetCode))
pFX->m_prs->ThrowDBException(nRetCode);
4. In the CRecordset's DoFieldExchange() method, move the call to
RFX_LongBinary() outside of the ClassWizard-tagged section (the
section marked with "//{{AFX_FIELD_MAP()" and "//}}AFX_FIELD_MAP").
Rename it RFX_NewLongBinary() -- or whatever you called the function
in step 1.
NOTE: If you are using the SQL Server ODBC Driver, this resolution won't
work because the SQL Server ODBC Driver returns only a maximum of 4K bytes
by default. For more information on this issue, please see the following
article in the Microsoft Knowledge Base:
ARTICLE-ID: Q126264
TITLE : PRB: CLongBinary Field Truncated with SQL Server ODBC Driver
STATUS
Microsoft has confirmed this to be a bug in the Microsoft products listed
at the beginning of this article. This problem was corrected in Microsoft
Visual C++ version 1.52 for Windows.
Additional reference words: 2.50 2.51
KBCategory: kbprg kbfixlist kbbuglist
KBSubcategory: MFCDatabase
Keywords : kbDatabase kbMFC kbODBC kbVC kbbuglist kbfixlist
Version : 1.50 1.51
Platform : WINDOWS
Solution Type : kbfix