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!
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)
The first 5 years of my career I programmed in pure C. (Production automatisation software)
In Q4 of 1997 I switched to Visual C++/MFC. (Headend Management system for cable operators)
In Q1 of 2003 I changed job and started programming in JAVA (and Swing)
Later on I also worked in Adobe Flex, GWT and GXT.
Then, since our products became more web based, I started programming in JavaScript and Sencha ExtJS.
In my current job I program in Java, TypeScript and React.