Click here to Skip to main content
15,885,757 members
Articles / Programming Languages / C++/CLI
Article

CAutoMate<>: a tiny utility class to run a mate function automatically in code block

Rate me:
Please Sign up or sign in to vote.
4.41/5 (10 votes)
29 Nov 20045 min read 51.3K   563   20   11
An article on a tiny utility class to run a mate function (Win32 API and CRT functions) automatically in code block.

Introduction

Getting clean-up code executed automatically using destructor of user-defined auto variable when it is getting out of its scope is a good technique since it increases readability as well as maintenance of code. Recently, I have been using HANDLE-related Win32 API functions a lot, and happened to realize that many of those functions are using only one clean-up function, CloseHandle. If you have blocks of code which are nested multiple times, and if you are returning from some of its execution path in its nesting sub-block after manipulating HANDLE, then calling CloseHandle() function in the right place to clean-up HANDLE is getting really messy and somewhat complicated.

One can immediately write down a simple class in whose destructor the cleanup code (function) gets called automatically as shown below:

class CAutoCloseHandle
{
public:
  // c'tor
  CAutoCloseHandle(HANDLE hObject) : m_hObject(hObject) { }
  // d'tor
  ~CAutoCloseHandle()
  {
    ::CloseHandle(m_hObject);
  }

private:
  HANDLE m_hObject;
};
// in the some function
BOOL FooFile()
{
  HANDLE hFile = ::CreateFile("test.dat", ...);
  CAutoCloseHandle ach(hFile);
  // ::CloseHandle(hFile) will get called when FooFile() function returns
  // IOW, when local variable ach get out of its scope
  // even in the exception case
  if(...)
  {
    ...
  }
  else if(...)
  {
    while(...)
    {
      switch(...)
      {
      case 1:
        // do something with hFile
        ...

        return TRUE;
        break;

      case 2:
        break;

      default:
        return FALSE;

      break;
      }

      if(...)
      {
        try
        {
          // do something with hFile
          ...
          return TRUE;
        }
        catch(...)
        {
          return FALSE;
        }
      }
      else
        continue;
    }
  }

  return TRUE;
}

Hurrah! This works perfectly, and as far as I know, many people use a similar technique in their code. Good thing in this implementation is that you can not use CAutoCloseHandle class only with CreateFile() and file object, but also with many other CreateXXX() Win32 API functions and its related objects since they share the same cleanup function, CloseHandle() as I mentioned earlier. If you look at MSDN, such objects are listed as below:

  • Access token
  • Communications device
  • Console input
  • Console screen buffer
  • Event
  • File
  • File mapping
  • Job
  • Mailslot
  • Mutex
  • Named pipe
  • Process
  • Semaphore
  • Socket
  • Thread

But later, you will probably be confronted with a similar situation while you are working on GDI objects. There are many GDI object creation functions but they share a common cleanup function, DeleteObject() again, and, of course, you can immediately write another simple class which calls DeleteObject() in its destructor, maybe named CAutoDeleteObject.

You can write such a new class over and over again, as many times as you want. But wait! I will suggest you one solid class here.

Implementation Note

If you look at MSDN:

BOOL WINAPI CloseHandle(HANDLE hObject);
BOOL WINAPI DeleteObject(HGDIOBJ hObject);

I think you can now guess what I am going to do. If you look further into MSDN, you can find many similar others.

BOOL WINAPI CloseHandle(HANDLE);
BOOL WINAPI CloseDesktop(HDESK);
BOOL WINAPI CloseEventLog(HANDLE);
BOOL WINAPI ClosePrinter(HANDLE);
BOOL WINAPI CloseServiceHandle(SC_HANDLE);
BOOL WINAPI CloseWindowStation(HWINSTA);
BOOL WINAPI DeleteDC(HDC);
BOOL WINAPI DeleteObject(HGDIOBJ);
BOOL WINAPI DeletePrinter(HANDLE);
BOOL WINAPI DeleteTimerQueue(HANDLE);
BOOL WINAPI DeregisterEventSource(HANDLE);
BOOL WINAPI DestroyAcceleratorTable(HACCEL);
BOOL WINAPI DestroyCursor(HCURSOR);
BOOL WINAPI DestroyIcon(HICON);
BOOL WINAPI DestroyMenu(HMENU);
BOOL WINAPI DestroyWindow(HWND);
BOOL WINAPI FreeLibrary(HMODULE);
BOOL WINAPI ReleaseMutex(HANDLE);
BOOL WINAPI ReleaseEvent(HANDLE);

Right! They all take void * as an input parameter and return BOOL. But at this moment, calling these functions as cleanup functions doesn't sound right anymore. So I decided to call them as mate functions, and that is the reason I named my class as CAutoMate.

To generalize my CAutoMate class, CAutoMate class' constructor takes one more parameter which is a pointer to a Win32 API function which gets called in CAutoMate class' destructor.

MC++
typedef BOOL (WINAPI *__stdcall_fnMate)(void *);

class CAutoMate
{
public:
  // c'tor
  CAutoMate(__stdcall_fnMate fnMate, void *parameter)
    : m_fnMate(fnMate), m_parameter(parameter)
  {
  }

  // d'tor
  {
    // mate function get called in destructor
    m_fnMate(m_parameter);
  }

private:
  __stdcall_fnMate m_fnMate;
  void *m_parameter;
};
// in the some function
BOOL FooFile()
{
  HANDLE hFile = ::CreateFile("test.dat", ...);
  CAutoMate am_CloseHandle(::CloseHandle, hFile);
  if(...)
  {
    ...
  }
  else if(...)
  {
    ...
  }

  return TRUE;
}

Be aware of that WINAPI macro is defined as __stdcall, therefore all Win32 API functions follow __stdcall calling convention. I will bring up this issue again later when I extend CAutoMate class.

Usually, CAutoMate class implemented above would be enough in most cases, and we can control the time at which the mate function gets called by using dummy sub-block. But if code gets nested in multiple level blocks, you can't control the time anymore using dummy sub-block. So I decided to add one public function, named RunMateNow().

MC++
typedef BOOL (WINAPI *__stdcall_fnMate)(void *);

class CAutoMate
{
public:
  // c'tor
  CAutoMate(__stdcall_fnMate fnMate, void *parameter)
    : m_fnMate(fnMate), m_parameter(parameter)
  {
  }

  // d'tor
  {
    // mate function get called in destructor
    RunMateNow();
  }

  BOOL RunMateNow()
  {
    BOOL bRet = FALSE;

    if(m_fnMate)
    {
      bRet = m_fnMate(m_parameter);
      m_fnMate = NULL;
      m_parameter = NULL;
    }

    return bRet;
  }

private:
  __stdcall_fnMate m_fnMate;
  void *m_parameter;
};
// in the some function
BOOL FooFile()
{
  HANDLE hFile = ::CreateFile("test.dat", ...);
  CAutoMate am_CloseHandle(::CloseHandle, hFile);

  {  // START of dummy sub-block
    HANDLE hFile2 = ::CreateFile("test2.dat", ...);
    CAutoMate am_CloseHandle2(::CloseHandle, hFile2);
  }  // END of dummy sub-block, ::CloseHandle(hFile2) gets called here

  HANDLE hFile3 = ::CreateFile("test3.dat", ...);
  CAutoMate am_CloseHandle3(::CloseHandle, hFile3);

   if(...)
  {
    BOOL bRet = am_CloseHandle3.RunMateNow();
    // ::CloseHandle(hFile3) gets called here,
    // and it will not be called again in destructor
    // since its member variables are initialized to NULL
    // in RunMateNow() function after calling ::CloseHandle(hFile3)
    if(!bRet)
    {
      // ::CloseHandle() failed
      ...
    }
    ...
  }
  else if(...)
  {
    ...
  }

  return TRUE;
}

Supporting more mate functions

DWORD WINAPI CloseNtmsNotification(HANDLE);
DWORD WINAPI CloseNtmsSession(HANDLE);
LONG WINAPI InterlockedDecrement(LPLONG volatile);
void WINAPI LeaveCriticalSection(LPCRITICAL_SECTION);
void WINAPI DeleteCriticalSection(LPCRITICAL_SECTION);

Win32 API functions listed above (including BOOL (WINAPI *fn)(void *) functions) have completely different signatures in terms of C++, but they are very similar if you look at them in assembly (machine) language level. They all have four bytes length of an input parameter and follow the same calling convention, __stdcall. They all go similar to what is shown below in assembly (machine) language level.

ASM
; in the caller
mov         eax,dword ptr [parameter]
push        eax
call        ___stdcall_fnMate@4
ASM
; in the callee
; __stdcall_fnMate
; prolog
push        ebp
mov         ebp,esp
sub         esp,40h

...

; epilog
mov         esp,ebp
pop         ebp
ret         4

Now, we can extend CAutoMate class to accept all the Win32 API functions listed above seamlessly by using reinterpret_cast, the power of C++ polymorphism (function overloading), and template.

MC++
typedef BOOL  (__stdcall *__stdcall_fnMate1)(void *);
typedef DWORD (__stdcall *__stdcall_fnMate2)(void *);
typedef LONG  (__stdcall *__stdcall_fnMate3)(long volatile *);
typedef void  (__stdcall *__stdcall_fnMate4)(CRITICAL_SECTION *);

template<class TMateReturnType = BOOL>
class CAutoMate
{
public:
  // c'tor
  CAutoMate(__stdcall_fnMate1 fnMate, void *parameter)
  {
    m__stdcall_fnMate = fnMate;
    m_parameter = parameter;
    m_parameter3 = NULL;
    m_parameter4 = NULL;
  }

  CAutoMate(__stdcall_fnMate2 fnMate, void *parameter)
  {
    m__stdcall_fnMate = reinterpret_cast<__stdcall_fnMate1>(fnMate);
    m_parameter = parameter;
    m_parameter3 = NULL;
    m_parameter4 = NULL;
  }

  CAutoMate(__stdcall_fnMate3 fnMate, long volatile *parameter)
  {
    m__stdcall_fnMate = reinterpret_cast<__stdcall_fnMate1>(fnMate);
    m_parameter = NULL;
    m_parameter3 = parameter;
    m_parameter4 = NULL;
  }

  CAutoMate(__stdcall_fnMate4 fnMate, CRITICAL_SECTION *parameter)
  {
    m__stdcall_fnMate = reinterpret_cast<__stdcall_fnMate1>(fnMate);
    m_parameter = NULL;
    m_parameter3 = NULL;
    m_parameter4 = parameter;
  }

  // d'tor
  ~CAutoMate()
  {
    // mate function get called in destructor
    RunMateNow();
  }

  TMateReturnType RunMateNow()
  {
    TMateReturnType ret = 0;

    if(m__stdcall_fnMate)
    {
      if(m_parameter)
      {
        ret = m__stdcall_fnMate(m_parameter);
        m_parameter = NULL;
      }
      else if(m_parameter3)
      {
        ret = reinterpret_cast<__stdcall_fnMate3>(m__stdcall_fnMate)(m_parameter3);
        m_parameter3 = NULL;
      }
      else if(m_parameter4)
      {
        reinterpret_cast<__stdcall_fnMate4>(m__stdcall_fnMate)(m_parameter4);
        m_parameter4 = NULL;
      }
      m__stdcall_fnMate = NULL;
    }

    return ret;
  }

private:
  __stdcall_fnMate1 m__stdcall_fnMate;

  void *m_parameter;
  long volatile *m_parameter3;
  CRITICAL_SECTION *m_parameter4;
};
BOOL Bar()
{
  LONG lLock = 10L;
  ::InterlockedIncrement(&lLock);  // lLock == 11L
  CAutoMate<LONG> am_InterlockedDecrement(::InterlockedDecrement, &lLock);
  // lLock == 11L;

  LONG lLock2 = am_InterlockedDecrement.RunMateNow();
  // lLock == lLock2 == 10L

  CRITICAL_SECTION cs;
  ::InitializeCriticalSection(&cs);
  CAutoMate<> am_DeleteCriticalSection(::DeleteCriticalSection, &cs);

  {
    ::EnterCriticalSection(&cs);
    CAutoMate<> am_LeaveCriticalSection(::LeaveCriticalSection, &cs);
  }
}

Now, CAutoMate class becomes a template class in order to deal with different return types of mate functions. The destructor can not return any value but RunMateNow() function can, and it requires to have a way of distinguishing the difference of the mate function's return type. For the void function, you can just ignore to fill in the template type parameter, and the default type value of template parameter (BOOL) will be used since void can not be used as type value. And you can just ignore return value of RunMateNow() since it will return FALSE always.

A simple idea has been getting well extended to a small but pretty useful class so far. But I decided to extend the CAutoMate class a little bit more since I happened to find that there is one more suitable place to apply for my class.

MC++
void __cdecl free(void *);
void __cdecl operator delete(void *);
void __cdecl operator delete[](void *);

These three functions belong to CRT and they follow __cdecl calling convention. Since there are many good articles which explain the differences between calling conventions, I will not cover it here. We just need to know that __cdecl function pointer is not compatible with __stdcall function pointer. You can not use reinterpret_cast to convert __cdecl function pointer to __stdcall function pointer.

MC++
typedef BOOL  (__stdcall *__stdcall_fnMate1)(void *);
typedef DWORD (__stdcall *__stdcall_fnMate2)(void *);
typedef LONG  (__stdcall *__stdcall_fnMate3)(long volatile *);
typedef void  (__stdcall *__stdcall_fnMate4)(CRITICAL_SECTION *);
typedef void  (__cdecl *__cdecl_fnMate1)(void *);

template<class TMateReturnType = BOOL>
class CAutoMate
{
public:
  // c'tor
  CAutoMate(__stdcall_fnMate1 fnMate, void *parameter)
  {
    m__stdcall_fnMate = fnMate;
    m__cdecl_fnMate = NULL;
    m_parameter = parameter;
    m_parameter3 = NULL;
    m_parameter4 = NULL;
  }

  CAutoMate(__stdcall_fnMate2 fnMate, void *parameter);
  CAutoMate(__stdcall_fnMate3 fnMate, long volatile *parameter);
  CAutoMate(__stdcall_fnMate4 fnMate, CRITICAL_SECTION *parameter);

  CAutoMate(__cdecl_fnMate1 fnMate, void *parameter)
  {
    m__stdcall_fnMate = NULL;
    m__cdecl_fnMate = fnMate;
    m_parameter = parameter;
    m_parameter3 = NULL;
    m_parameter4 = NULL;
  }

  // d'tor
  ~CAutoMate()
  {
    // mate function get called in destructor
    RunMateNow();
  }

  TMateReturnType RunMateNow()
  {
    TMateReturnType ret = 0;

    if(m__stdcall_fnMate)
    {
      if(m_parameter)
      {
        ret = m__stdcall_fnMate(m_parameter);
        m_parameter = NULL;
      }
      else if(m_parameter3)
      {
        ret = reinterpret_cast<__stdcall_fnMate3>(m__stdcall_fnMate)(m_parameter3);
        m_parameter3 = NULL;
      }
      else if(m_parameter4)
      {
        reinterpret_cast<__stdcall_fnMate4>(m__stdcall_fnMate)(m_parameter4);
        m_parameter4 = NULL;
      }
      m__stdcall_fnMate = NULL;
    }
    else if(m__cdecl_fnMate)
    {
      m__cdecl_fnMate(m_parameter);
      m__cdecl_fnMate = NULL;
      m_parameter = NULL;
    }

    return ret;
  }

  operator void * ()
  {
    return m_parameter;
  }

  operator long volatile * ()
  {
    return m_parameter3;
  }

  operator CRITICAL_SECTION * ()
  {
    return m_parameter4;
  }

private:
  __stdcall_fnMate1 m__stdcall_fnMate;
  __cdecl_fnMate1 m__cdecl_fnMate;

  void *m_parameter;
  long volatile *m_parameter3;
  CRITICAL_SECTION *m_parameter4;
};
typedef struct tagTestStruct
{
  LONG lTest;
  CHAR szTest[10];
} TestStruct;

BOOL Bar()
{
  // TEST 1
  LPVOID pTest1 = ::malloc(10);
  CAutoMate<> am_free(::free, pTest1);

  // TEST 2
  PCHAR pTest2 = NULL;
  {  // START of sub-block
    CAutoMate<> am_free2(::free, ::malloc(10));
    pTest2 = (PCHAR)(LPVOID)am_free2;

    pTest2[0] = 0x41;        // OK.
  }  // END of sub-block (pTest2 get freed here!)

  CHAR chTest = pTest2[0];    // Memory access violation !

  // TEST 3 (unnamed object -  not practical usage)
  LPVOID pTest3 = (LPVOID)CAutoMate<>(::free, ::malloc(10));
  // memory allocated then deallocated right away
  // memory location pointed by pTest3 will only be valid within the same line

  TestStruct *pTestStruct = (TestStruct *)new TestStruct;
  CAutoMate<> am_operator_delete(::operator delete, pTestStruct);

  LPSTR pszTest = new CHAR[10];
  // CAutoMate<> am_operator_array_delete(::operator delete[], pszTest);
  //
  // Unfortunately we can't use CAutoMate class in this manner in MSVC6.0
  // since ::operator delete[] () doesn't exist.
  //
  delete [] pszTest;
}

Unfortunately, I found that there is no operator new[] () nor operator delete[] () existing in MSVC6.0. When I looked at assembly code generated for operator new[] () or operator delete[] () in MSVC6.0, they are actually calling the scalar form of their counterpart which are operator new () and operator delete (), internally. I don't know exactly how MSVC6.0 allocates memory for array form but because there is no operator delete[] (), you can not use CAutoMate class with it.

But I read in MSDN that .NET version has operator delete[] (), therefore CAutoMate class should work with it in .NET version. Someone can try and let me know.

Using the code

We went through a long story but its usage is very simple. First, include "AutoMate.h", then define CAutoMate variable on the heap, and supply the pointer to mate function and input parameter of the mate function, and give return type of mate function as template parameter.

#include "AutoMate.h"

using codeproject::CAutoMate;

void MyFunction()
{
  HANDLE hThread = ::CreateThread(...);
  CAutoMate<BOOL> am_CloseHandleThread(::CloseHandle, hThread);
  // BOOL CloseHandle(hFile);

  HMODULE hMod = ::LoadLibrary("MyDll.dll");
  CAutoMate<BOOL> am_FreeLibrary(::FreeLibrary, hMod);
  // BOOL FreeLibrary(hMod);

  HMENU hMenu = ::CreateMenu();
  CAutoMate<BOOL> am_DestroyMenu(::DestroyMenu, hMenu);
  // BOOL DestroyMenu(hMenu);

  // and many other mate functions as long as it has a 4 bytes or less length
  // of input parameter
}

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
Other
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralIt's good idea! Pin
jansenc9-Dec-04 18:33
professionaljansenc9-Dec-04 18:33 
GeneralMate! Pin
Don Clugston8-Dec-04 16:12
Don Clugston8-Dec-04 16:12 
GeneralRe: Mate! Pin
JaeWook Choi11-Dec-04 7:09
JaeWook Choi11-Dec-04 7:09 
GeneralRe: Mate! Pin
Don Clugston12-Dec-04 12:17
Don Clugston12-Dec-04 12:17 
GeneralRe: Mate! Pin
JaeWook Choi13-Dec-04 5:18
JaeWook Choi13-Dec-04 5:18 
GeneralRe: Mate! Pin
Don Clugston13-Dec-04 11:40
Don Clugston13-Dec-04 11:40 
GeneralYou have a beautiful mind !! Pin
WREY2-Dec-04 6:12
WREY2-Dec-04 6:12 
GeneralNice! Pin
peterchen29-Nov-04 11:26
peterchen29-Nov-04 11:26 
GeneralRe: Nice! Pin
JaeWook Choi2-Dec-04 10:53
JaeWook Choi2-Dec-04 10:53 
GeneralA simple but very good idea Pin
Rodrigo Strauss29-Nov-04 1:59
Rodrigo Strauss29-Nov-04 1:59 
GeneralRe: A simple but very good idea Pin
JaeWook Choi2-Dec-04 10:50
JaeWook Choi2-Dec-04 10:50 

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.