Introduction
The past year I spent three unpleasant days at the customer's site. Fortunately I found the issue in the MFC 6.0 code. This is the (base of the) code that gave me headaches:
void CMyClass::OnGetFiles()
{
...
CInternetSession is;
CFtpConnection *pf;
try
{
pf = is.GetFtpConnection(..., ..., ...);
}
catch(CInternetException* e )
{
...
}
if (!pf->SetCurrentDirectory("Whatever"))
{
...
}
CFtpFileFind fff(pf);
fff.FindFile();
bContinue=TRUE;
while (bContinue)
{
bContinue = fff.FindNextFile();
...
}
pf->Close();
deletepf;
is.Close();
}
When in (debug mode) we break on the last bracket and take
one step further, the destruction of the CFtpFileFind
object "fff" reveals a
(handled) C++ exception.
Reason:
CFtpFileFind
is derived from CFileFind
. So when going out of scope:
- The
CFtpFileFind
destructor is called:
The Inet.cpp MFC code is:
CFtpFileFind::~CFtpFileFind()
{
}
So nothing happens!
Then, the destructor of CFileFind
is called:
CFileFind::~CFileFind()
{
Close();
}
So this calls the following Close
void CFileFind::Close()
{
if (m_pFoundInfo != NULL)
{
delete m_pFoundInfo;
m_pFoundInfo = NULL;
}
if (m_pNextInfo != NULL)
{
delete m_pNextInfo;
m_pNextInfo = NULL;
}
if (m_hContext != NULL &&
m_hContext != INVALID_HANDLE_VALUE)
{
CloseContext();
m_hContext = NULL;
}
}
which on its turn calls the following CFileFind::CloseContext
void CFileFind::CloseContext()
{
::FindClose(m_hContext);
return;
}
The C++ exception occurs when ::FindClose(m_hContext);
is executed.
After some unpleasant days on the customer�s site we
detected that the exception (leading to crashes on Windows NT) could be avoided
by adding one extra line of code in our function:
CFtpFileFind fff(pf);
fff.FindFile();
bContinue=TRUE;
while (bContinue)
{
bContinue = fff.FindNextFile();
...
}
fff.Close();
By adding this line, before the CFtpFileFind
object goes
out of scope the Close()
is called.
CFtpFileFind
has no Close()
function, so the
CFileFind::Close()
is called. But now the CloseContext()
call leads to the
CFtpFileFind::CloseContext()
(the object being a CFtpFileFind
)
void CFtpFileFind::CloseContext()
{
if (m_hContext != NULL && m_hContext != INVALID_HANDLE_VALUE)
{
InternetCloseHandle(m_hContext);
m_hContext = NULL;
}
return;
}
Here we see that - instead of a simple ::FindClose()
- an
InternetCloseHandle()
is performed on the m_hContext
handle.
So that makes the difference!
-
In the bad situation (relying on the destructor) the
CFileFind::CloseContext()
is called.
-
In the workaround situation (calling
fff.Close()
ourselves) the
CFtpFileFind::CloseContext()
is called.
Although the C++ exception (occurring in both NT and W2K)
is handled, our experience is that only in NT it leads further on to severe
problems (=crashes) when accessing files (also using handles). So apparently Win2K handles the exception in a better way
than WinNT.
Our solution is OK, but you have to remember to do the Close() yourself!
In fact it�s an MFC bug (in combination with
bad WinNT exception handling): the problem wouldn�t arise if the MFC code for
the CFtpFileFind
destructor was changed from being empty to containing Close();
So, another safer solution would be to create a
CMyFtpFileFind
class derived from CFtpFileFind
with
CMyFtpFileFind::~CMyFtpFileFind
{
Close();
}
Some extra information:
You could wonder why the CFtpFileFind
destructor calling
the CFileFind
destructor calling CloseContext()
doesn�t automatically lead to
the CFtpFileFind::CloseContext()
(you are deleting a CFtpFileFind
object, aren�t you?) but to the CFileFind::CloseContext()
.
This is why:
- There are three cases in which an invocation of a virtual function is resolved statically at compile time:
"3. When a virtual function is invoked within � the destructor of a base
class. The base class instance of the virtual function is called since the
derived class object is � already destructed." -�C++ Primer 2nd Edition�, Stanley B.
Lippman, p.463.
-
"A similar thing happens with the vtables during destruction.
Before your destruction code executes, the compiler-generated code sets the
vtable to the vtable for the class to which the destructor belongs � Your
code executes, then the compiler-generated code calls the base class
destructor. Once again, virtual function calls inside a destructor behave as if
they were static. Again, this makes sense because once a destructor has
finished, that object doesn't exist any longer, and you wouldn't want to call a
derived virtual function after that object has been destroyed. So the first
thing each destructor does is clobber one level of derivedness by setting the
vtable to its own." - MSDN Magazine, March 2000, C++ Q&A.
-
In my own words: Once you leave the
CFtpFileFind
destructor, you can�t call any
CFtpFileFind
function anymore (the vtable only points to CFileFind
functions).
So when you encounter the Close
function calling the CloseContext
function,
it is the CFileFind::CloseContext
not the CFtpFileFind::CloseContext
.
(Note: I have not checked yet if this bug is solved in MFC 7)