FIX: Only the First 64K Is Read from Binary Field

Last reviewed: September 19, 1997
Article 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 : MfcDatabase kbbuglist kbfixlist kbprg
Technology : kbMfc
Version : 1.50 1.51
Platform : WINDOWS
Solution Type : kbfix


THE INFORMATION PROVIDED IN THE MICROSOFT KNOWLEDGE BASE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. MICROSOFT DISCLAIMS ALL WARRANTIES, EITHER EXPRESS OR IMPLIED, INCLUDING THE WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL MICROSOFT CORPORATION OR ITS SUPPLIERS BE LIABLE FOR ANY DAMAGES WHATSOEVER INCLUDING DIRECT, INDIRECT, INCIDENTAL, CONSEQUENTIAL, LOSS OF BUSINESS PROFITS OR SPECIAL DAMAGES, EVEN IF MICROSOFT CORPORATION OR ITS SUPPLIERS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME STATES DO NOT ALLOW THE EXCLUSION OR LIMITATION OF LIABILITY FOR CONSEQUENTIAL OR INCIDENTAL DAMAGES SO THE FOREGOING LIMITATION MAY NOT APPLY.

Last reviewed: September 19, 1997
© 1998 Microsoft Corporation. All rights reserved. Terms of Use.