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


Last Reviewed: September 22, 1997
© 2000 Microsoft Corporation. All rights reserved. Terms of Use.