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:
CAutoCloseHandle(HANDLE hObject) : m_hObject(hObject) { }
~CAutoCloseHandle()
{
::CloseHandle(m_hObject);
}
private:
HANDLE m_hObject;
};
BOOL FooFile()
{
HANDLE hFile = ::CreateFile("test.dat", ...);
CAutoCloseHandle ach(hFile);
if(...)
{
...
}
else if(...)
{
while(...)
{
switch(...)
{
case 1:
...
return TRUE;
break;
case 2:
break;
default:
return FALSE;
break;
}
if(...)
{
try
{
...
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.
typedef BOOL (WINAPI *__stdcall_fnMate)(void *);
class CAutoMate
{
public:
CAutoMate(__stdcall_fnMate fnMate, void *parameter)
: m_fnMate(fnMate), m_parameter(parameter)
{
}
{
m_fnMate(m_parameter);
}
private:
__stdcall_fnMate m_fnMate;
void *m_parameter;
};
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()
.
typedef BOOL (WINAPI *__stdcall_fnMate)(void *);
class CAutoMate
{
public:
CAutoMate(__stdcall_fnMate fnMate, void *parameter)
: m_fnMate(fnMate), m_parameter(parameter)
{
}
{
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;
};
BOOL FooFile()
{
HANDLE hFile = ::CreateFile("test.dat", ...);
CAutoMate am_CloseHandle(::CloseHandle, hFile);
{
HANDLE hFile2 = ::CreateFile("test2.dat", ...);
CAutoMate am_CloseHandle2(::CloseHandle, hFile2);
}
HANDLE hFile3 = ::CreateFile("test3.dat", ...);
CAutoMate am_CloseHandle3(::CloseHandle, hFile3);
if(...)
{
BOOL bRet = am_CloseHandle3.RunMateNow();
if(!bRet)
{
...
}
...
}
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.
mov eax,dword ptr [parameter]
push eax
call ___stdcall_fnMate@4
push ebp
mov ebp,esp
sub esp,40h
...
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.
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:
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;
}
~CAutoMate()
{
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);
CAutoMate<LONG> am_InterlockedDecrement(::InterlockedDecrement, &lLock);
LONG lLock2 = am_InterlockedDecrement.RunMateNow();
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.
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.
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:
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;
}
~CAutoMate()
{
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()
{
LPVOID pTest1 = ::malloc(10);
CAutoMate<> am_free(::free, pTest1);
PCHAR pTest2 = NULL;
{
CAutoMate<> am_free2(::free, ::malloc(10));
pTest2 = (PCHAR)(LPVOID)am_free2;
pTest2[0] = 0x41;
}
CHAR chTest = pTest2[0];
LPVOID pTest3 = (LPVOID)CAutoMate<>(::free, ::malloc(10));
TestStruct *pTestStruct = (TestStruct *)new TestStruct;
CAutoMate<> am_operator_delete(::operator delete, pTestStruct);
LPSTR pszTest = new CHAR[10];
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);
HMODULE hMod = ::LoadLibrary("MyDll.dll");
CAutoMate<BOOL> am_FreeLibrary(::FreeLibrary, hMod);
HMENU hMenu = ::CreateMenu();
CAutoMate<BOOL> am_DestroyMenu(::DestroyMenu, hMenu);
}