CAutoMate<>:一个小型实用类,用于在代码块中自动运行匹配函数






4.41/5 (8投票s)
2004年11月29日
5分钟阅读

52181

565
一篇关于一个小型实用类的文章,用于在代码块中自动运行匹配函数(Win32 API 和 CRT 函数)。
引言
当用户定义的自动变量超出其作用域时,通过其析构函数自动执行清理代码是一项很好的技术,因为它提高了代码的可读性和可维护性。最近,我大量使用了与 HANDLE
相关的 Win32 API 函数,并意识到其中许多函数只使用一个清理函数 CloseHandle
。如果您有嵌套了多次的代码块,并且在操作 HANDLE
后,从嵌套子块的某个执行路径返回,那么在正确的位置调用 CloseHandle()
函数来清理 HANDLE
会变得非常混乱和复杂。
人们可以立即编写一个简单的类,该类的析构函数会在离开作用域时自动调用清理代码(函数),如下所示:
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; }
太棒了!这完美地解决了问题,据我所知,很多人在他们的代码中都使用了类似的技术。这个实现的好处在于,您不仅可以使用 CAutoCloseHandle
类处理 CreateFile()
和文件对象,还可以处理许多其他 CreateXXX()
Win32 API 函数及其相关对象,因为正如我之前提到的,它们共享同一个清理函数 CloseHandle()
。如果您查阅 MSDN,会发现以下列出的对象:
- 访问令牌
- 通信设备
- 控制台输入
- 控制台屏幕缓冲区
- 事件
- 文件
- 文件映射
- 作业
- 邮件槽
- 互斥体
- 命名管道
- 进程
- 信号量
- Socket(套接字)
- 线程
但是,后来您在处理 GDI 对象时可能会遇到类似的情况。有很多 GDI 对象创建函数,但它们共享一个通用的清理函数 DeleteObject()
,当然,您也可以立即编写另一个简单的类,在其中一个析构函数中调用 DeleteObject()
,也许命名为 CAutoDeleteObject
。
您可以这样一遍又一遍地编写新的类,随心所欲。但是等等!在这里我将为您推荐一个稳健的类。
实现说明
如果您查阅 MSDN
BOOL WINAPI CloseHandle(HANDLE hObject);
BOOL WINAPI DeleteObject(HGDIOBJ hObject);
我想您现在可以猜到我要做什么了。如果您进一步查阅 MSDN,您会发现许多类似的示例。
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);
对了!它们都接受 void *
作为输入参数并返回 BOOL
。但此时,将这些函数用作清理函数似乎不再合适。因此,我决定将它们称为匹配函数,这也是我的类命名为 CAutoMate
的原因。
为了泛化我的 CAutoMate
类,CAutoMate
类的构造函数接受一个额外的参数,即指向在 CAutoMate
类析构函数中调用的 Win32 API 函数的指针。
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; }
请注意,WINAPI
宏定义为 __stdcall
,因此所有 Win32 API 函数都遵循 __stdcall
调用约定。我稍后在扩展 CAutoMate
类时会再次提及这个问题。
通常,上面实现的 CAutoMate
类在大多数情况下已经足够了,我们可以通过使用虚拟子块来控制匹配函数被调用的时间。但是,如果代码嵌套在多个级别的块中,您就无法再通过虚拟子块来控制时间了。因此,我决定添加一个名为 RunMateNow()
的公共函数。
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; }
支持更多匹配函数
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 函数(包括 BOOL (WINAPI *fn)(void *)
函数)在 C++ 中具有完全不同的签名,但如果您从汇编(机器)语言级别查看它们,它们非常相似。它们都具有四个字节长的输入参数,并遵循相同的调用约定 __stdcall
。从汇编(机器)语言级别来看,它们都类似于下面所示。
; in the caller
mov eax,dword ptr [parameter]
push eax
call ___stdcall_fnMate@4
; in the callee
; __stdcall_fnMate
; prolog
push ebp
mov ebp,esp
sub esp,40h
...
; epilog
mov esp,ebp
pop ebp
ret 4
现在,我们可以通过使用 reinterpret_cast
、C++ 多态性(函数重载)和模板来扩展 CAutoMate
类,使其能够无缝地接受上面列出的所有 Win32 API 函数。
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); } }
现在,CAutoMate
类变成了一个模板类,以便处理匹配函数的不同返回类型。析构函数不能返回任何值,但 RunMateNow()
函数可以,并且需要一种方法来区分匹配函数的返回类型的差异。对于 void
函数,您可以忽略填写模板类型参数,将使用模板参数的默认类型值(BOOL
),因为 void
不能用作类型值。您可以忽略 RunMateNow()
的返回值,因为它将始终返回 FALSE
。
一个简单的想法已经很好地扩展成了一个小但相当实用的类。但我决定进一步扩展 CAutoMate
类,因为我偶然发现还有一个更适合应用我的类的地方。
void __cdecl free(void *);
void __cdecl operator delete(void *);
void __cdecl operator delete[](void *);
这三个函数属于 CRT,它们遵循 __cdecl
调用约定。由于有很多很好的文章解释了调用约定之间的差异,我在这里就不赘述了。我们只需要知道 __cdecl
函数指针与 __stdcall
函数指针不兼容。您不能使用 reinterpret_cast
将 __cdecl
函数指针转换为 __stdcall
函数指针。
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; }
不幸的是,我发现在 MSVC6.0 中没有 operator new[] ()
或 operator delete[] ()
。当我查看 MSVC6.0 中为 operator new[] ()
或 operator delete[] ()
生成的汇编代码时,它们实际上内部调用了其对应标量形式的函数,即 operator new ()
和 operator delete ()
。我不确定 MSVC6.0 具体是如何为数组形式分配内存的,但由于没有 operator delete[] ()
,您不能将其与 CAutoMate
类一起使用。
但我从 MSDN 上了解到,.NET 版本有 operator delete[] ()
,因此 CAutoMate
类应该可以在 .NET 版本中使用。有人可以尝试一下并告诉我。
使用代码
我们讲了一个很长的故事,但它的用法非常简单。首先,包含 "AutoMate.h",然后在堆上定义 CAutoMate
变量,并提供匹配函数的指针和匹配函数的输入参数,并将匹配函数的返回类型作为模板参数。
#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 }