65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.41/5 (8投票s)

2004年11月29日

5分钟阅读

viewsIcon

52181

downloadIcon

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
}
© . All rights reserved.