Introduction
MFC provides the CFileFind class to encapsulate the ::FindFirstFile
Win32 calls for searching the filesystem. The member function CFileFind::GetLength()
returns the length in bytes of the current file, using a DWORD
. Since a DWORD
is 32bits long, the maximum file length this can cope with is 4 gigabytes.
The documentation states that for files larger than 4 gigabytes, there is the CFileFind::GetLength64()
function that returns the size as an __int64
.
However, in versions of MFC prior to the MFC7 that comes with Visual Studio.NET, this function does not return correct values for large files, due to a bug in the way the 64-bit arithmetic is done. The CFileFind::GetLength
method in MFC7 now returns a ULONGLONG
with the correct filesize, and the GetLength64()
member function has been removed.
This is the offending code for earlier versions of MFC as used in Visual Studio 6.0 and earlier:
__int64 CFileFind::GetLength64() const
{
ASSERT(m_hContext != NULL);
ASSERT_VALID(this);
if (m_pFoundInfo != NULL)
return ((LPWIN32_FIND_DATA) m_pFoundInfo)->nFileSizeLow +
(((LPWIN32_FIND_DATA) m_pFoundInfo)->nFileSizeHigh << 32);
else
return 0;
}
Whilst this appears fine at a glance, the problem lies in the shift operation for the high DWORD
. It should have been cast to a 64-bit integer before the shift. As the code stands, it performs a 32-bit shift on a 32-bit value.
Whilst you may expect that to always be zero, in fact it gives the same value as before the shift. The compiler uses the x86 opcode shl
, which shifts a 32-bit value a given number of places modulo 32.
Therefore the value returned for any file greater than 4 gigabytes is the value of the low 2 bytes added to the high 2 bytes, which is "more incorrect" than the value returned by GetLength()
.
Solution
The correct expression to get the filesize as a 64-bit integer from the WIN32_FIND_DATA
structure pointed to by the m_pFoundInfo
member is:
((LPWIN32_FIND_DATA)pFinder->m_pFoundInfo)->nFileSizeLow +
((unsigned __int64)((LPWIN32_FIND_DATA)pFinder->m_pFoundInfo)->nFileSizeHigh << 32);
However, this is a protected member, and altering MFC code is not an option, particularly if you are linking dynamically.
The solutions are:
- Don't use
CFileFind
; only use the Win32 API functions ::FindFirstFile
etc.
- Create a derived class from
CFileFind
, implementing the GetLength64()
member function correctly, and use this derived class instead of CFileFind
.
- Alter the Afx.h header file to make the
m_pFoundInfo
member public, so that you can perform the above calculation from the calling code, rather than calling GetLength64()
. This is not recommended.