C++ 正确关闭WinAPI句柄(避免重复关闭)

C++ 正确关闭WinAPI句柄(避免重复关闭),c++,winapi,handle,hbitmap,C++,Winapi,Handle,Hbitmap,我有一些把手,我需要把它关上。代码中有些地方可能会关闭句柄。这是关闭把手的正确方法吗 HANDLE h; .... if ( h != INVALID_HANDLE_VALUE ) { ::CloseHandle(h); h = INVALID_HANDLE_VALUE; } 位图句柄也有同样的问题: HBITMAP hb; .... if ( hb != INVALID_HANDLE_VALUE ) { ::DeleteObject(hb); hb = INVALID_HAN

我有一些把手,我需要把它关上。代码中有些地方可能会关闭句柄。这是关闭把手的正确方法吗

HANDLE h;
....
if ( h != INVALID_HANDLE_VALUE ) {
  ::CloseHandle(h);
  h = INVALID_HANDLE_VALUE;
}
位图句柄也有同样的问题:

HBITMAP hb;
....
if ( hb != INVALID_HANDLE_VALUE ) {
  ::DeleteObject(hb);
  hb = INVALID_HANDLE_VALUE;
}
编辑:我想,有一些误解。我知道
CloseHandle
用于关闭句柄。我想知道关把手的正确方法。删除指针时也会出现类似的情况

Foo *foo = new Foo();

// for example there is 2 functions that can delete foo
void bar() {
  ....
  delete foo;
}
void duck() {
  ....
  delete foo;
}
因此,以下代码表示问题:

bar();
duck();
这个案子有一些变通办法。我们需要定义如下的
bar
&
duck
函数:

void bar() {
  ....
  if (foo) {
    delete foo;
    foo = NULL;
  }
}
void duck() {
  ....
  if (foo) {
    delete foo;
    foo = NULL;
  }
}
因此,我们避免重复删除foo。问题是关闭把手的正确方法是什么?我的意思是,如何避免重复关闭手柄的问题?

是的

  • 关闭windows内核对象句柄
  • 删除GDI对象
我认为你的困惑是因为它们都被称为“句柄”,但它们是不同的对象“类”。
HBITMAP
中的术语句柄在这里更多地用作“不透明标识符”。还有大量文档假定“句柄”==“windows内核句柄”

一般来说,如果您想知道如何删除某些内容,应该查看构造函数的文档。

使用

将句柄包装到一个类中,该类在构造函数中分配句柄,并在析构函数中销毁它。您可以在MFC中找到一些示例,例如对于GDI对象,如
HBITMAP


另请参见此SO问题:

以下代码可能是您所追求的:

BOOL CloseValidHandle(HANDLE& handle)
{
  if (handle != INVALID_HANDLE_VALUE && handle != 0)
  {
    if (CloseHandle(handle))
    {
      handle = INVALID_HANDLE_VALUE;
      return TRUE;
    }
    else
    {
      return FALSE;
    }
  }

  return TRUE;
}

并非所有使用
HANDLE
的函数都使用
CloseHandle()
,有些函数使用其他关闭函数。此外,也不是所有的
句柄
值都使用
无效的句柄
。有些人使用
NULL

HBITMAP
从不使用
无效的句柄值
,它总是使用
NULL
。对于您不拥有的
HBITMAP
,您不应该调用
DeleteObject()

因此,简单的回答是——如果您试图创建一些通用的句柄管理,不要麻烦。你很可能会弄错。如果你分配/打开某个句柄,你必须知道关闭它的正确方法,你无法猜测

如果您希望控制柄自行管理,那么RAII是最佳选择。我更喜欢使用具有专门特性的模板类来减少不同类型句柄的代码重复,例如:

template< class traits >
class HandleWrapper
{
private:
    traits::HandleType FHandle;

public:
    HandleWrapper()
        FHandle(traits::InvalidValue)
    {
    }

    HandleWrapper(const traits::HandleType value)
        FHandle(value)
    {
    }

    ~HandleWrapper()
    {
        Close();
    }

    void Close()
    {
        if (FHandle != traits::InvalidValue)
        {
            traits::Close(FHandle);
            FHandle = traits::InvalidValue;
        }
    }

    bool operator !() const {
        return (FHandle == traits:::InvalidValue);
    }

    operator bool() const {
        return (FHandle != traits:::InvalidValue);
    }

    operator traits::HandleType() {
        return FHandle;
    }
};
template
类手提式振打器
{
私人:
性状:手型手;
公众:
手拉手()
FHandle(特征::InvalidValue)
{
}
HandleRapper(常量特征::HandleType值)
F手柄(值)
{
}
~HandleWrapper()
{
Close();
}
无效关闭()
{
if(FHandle!=traits::InvalidValue)
{
特征:接近(手);
FHandle=特征::无效值;
}
}
布尔运算符!()常量{
返回(FHandle==traits:::InvalidValue);
}
运算符bool()常量{
返回(FHandle!=traits:::InvalidValue);
}
运算符特征::HandleType(){
返回FHandle;
}
};

struct KernelHandleTraits
{
typedef手柄HandleType;
静态常量句柄无效值=无效句柄值;
静态空心闭合(句柄值)
{
闭合手柄(值);
}
};
HandleRapper hFile(创建文件(…);

struct NullKernelHandleTraits
{
typedef手柄HandleType;
静态常量句柄InvalidValue=NULL;
静态空心闭合(句柄值)
{
闭合手柄(值);
}
};
HandleRapper hMapping(CreateFileMapping(…);

struct FileMapViewTraits
{    
typedef void*HandleType;
静态常量void*InvalidValue=NULL;
静态无效关闭(无效*值)
{
取消存档文件(值);
}
};
手拉手hView(MapViewOfFile(…);

struct GDIBitmapHandleTraits
{    
typedef HBITMAP HandleType;
静态常量HBITMAP InvalidValue=NULL;
静态无效关闭(HBITMAP值)
{
删除对象(值);
}
};
HandleRapper hBmp(创建位图(…);

等等。

这不是RAII,但它有助于删除/关闭处理程序

class HandleDel : boost::notcopyable{
public:
    HandleDel(HANDLE h, HANDLE invalid, BOOL(WINAPI *del)(HANDLE)):
        h(h), invalid(invalid), del(del){
    }
    ~HandleDel(){
        if ( h != invalid ) del(h);
    }
private:
    HANDLE h;
    HANDLE invalid;
    BOOL(WINAPI *del)(HANDLE);
};

答案可能会让您大吃一惊,但如果文档中没有其他说明,那么如果您想关闭句柄,
CloseHandle
确实是正确的函数。但是也有一些例外,一些函数创建句柄时应该以不同的方式关闭。因此,要么向我们展示句柄是如何创建的,要么自己在MSDN上查找它:)关键是内核句柄是用
CloseHandle
关闭的,而其他句柄(通常是GDI句柄)有自己的函数。尽管如此,在MSDN页面上,关于用于获取句柄的函数始终列出了要调用以释放它的相应函数。如果可以从多个线程关闭句柄,则检查和设置无效的\u handle\u值可能毫无意义-您需要准备CloseHandle()“无论如何都要失败。@MartinJames:如果一个句柄可以在没有同步的情况下从多个线程关闭,那么你的问题比CloseHandle失败要严重得多。这个句柄将被内核重用,然后你将关闭一些你不想关闭的东西。这个问题很难追踪,因为它从来不会以相同的方式复制两次。我不能使用RAII,因为有时我需要在宿主类销毁之前关闭句柄。但是我的问题不是关于语言隐喻。@lome:如果你不能使用RAII,你的程序的架构肯定有问题,正如指针的例子所示。带指针的代码只是说明问题。实际上,我的问题也不是关于建筑。无论如何,谢谢你的意见。RAII仍然可以使用,只需向关闭
struct NullKernelHandleTraits
{
    typedef HANDLE HandleType;
    static const HANDLE InvalidValue = NULL;

    static void Close(HANDLE value)
    {
        CloseHandle(value);
    }
};

HandleWrapper<NullKernelHandleTraits> hMapping(CreateFileMapping(...));
struct FileMapViewTraits
{    
    typedef void* HandleType;
    static const void* InvalidValue = NULL;

    static void Close(void *value)
    {
        UnmapViewOfFile(value);
    }
};

HandleWrapper<FileMapViewTraits> hView(MapViewOfFile(...));
struct GDIBitmapHandleTraits
{    
    typedef HBITMAP HandleType;
    static const HBITMAP InvalidValue = NULL;

    static void Close(HBITMAP value)
    {
        DeleteObject(value);
    }
};

HandleWrapper<GDIBitmapTraits> hBmp(CreateBitmap(...));
class HandleDel : boost::notcopyable{
public:
    HandleDel(HANDLE h, HANDLE invalid, BOOL(WINAPI *del)(HANDLE)):
        h(h), invalid(invalid), del(del){
    }
    ~HandleDel(){
        if ( h != invalid ) del(h);
    }
private:
    HANDLE h;
    HANDLE invalid;
    BOOL(WINAPI *del)(HANDLE);
};