Click here to Skip to main content
15,886,806 members
Articles / Desktop Programming / MFC
Article

CFtpFileFind empty destructor causes crash in MFC 6, NT

Rate me:
Please Sign up or sign in to vote.
4.25/5 (4 votes)
14 Apr 20023 min read 77.7K   30   7
The case of a virtual destructor not calling the correct Close function

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;
      
  // start an ftp session
  CFtpConnection *pf;

  try
  {
    pf = is.GetFtpConnection(..., ..., ...);  // Get an FTP connection
  }
  catch(CInternetException* e )
  {
    ...
  }

  // Go to the right directory
  if (!pf->SetCurrentDirectory("Whatever"))
  {
    ...
  }

  CFtpFileFind fff(pf);
     fff.FindFile();
  bContinue=TRUE;
  while (bContinue)
  {
    bContinue = fff.FindNextFile();
    ...
  }

  pf->Close();// close the ftp connection
  deletepf;

  is.Close();// close the internet session
}

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);   // <<<<< C++ exception
          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(); // The solution!!!
    

    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();  // At last leading to the CloseContext() of CFtpFileFind
}

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)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
Belgium Belgium
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.

Comments and Discussions

 
GeneralNot really a bug! Pin
Marc Brooks23-Sep-03 15:13
Marc Brooks23-Sep-03 15:13 
GeneralC++ exception from the kernel?! Pin
Mike Nordell15-Apr-02 12:22
Mike Nordell15-Apr-02 12:22 
GeneralRe: C++ exception from the kernel?! Pin
15-Apr-02 23:47
suss15-Apr-02 23:47 
GeneralThanx Pin
KarstenK15-Apr-02 0:12
mveKarstenK15-Apr-02 0:12 
GeneralRe: Thanx Pin
Geert Delmeiren15-Apr-02 0:21
Geert Delmeiren15-Apr-02 0:21 
GeneralRe: Thanx Pin
KarstenK15-Apr-02 0:36
mveKarstenK15-Apr-02 0:36 
GeneralRe: Thanx Pin
Geert Delmeiren15-Apr-02 1:43
Geert Delmeiren15-Apr-02 1:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.