【问题标题】:Whic is correct way to use pair GlobalLock() \ GlobalUnlock()?使用对 GlobalLock() \ GlobalUnlock() 的正确方法是什么?
【发布时间】:2018-08-01 00:17:27
【问题描述】:

关于GlobalLock 的文档说:

返回值
如果函数成功,返回值是指向内存块第一个字节的指针。
如果函数失败返回值为NULL。要获取扩展错误信息,请调用 GetLastError

备注
进程为对象对 GlobalLock 进行的每个成功调用都必须与对 GlobalUnlock 的相应调用相匹配。
....
如果指定的内存块已被丢弃或内存块的大小为零,则此函数返回 NULL。

因此,正如我们所见,如果错误或内存块大小为零字节大小,GlobalLock() 可能会返回 NULL。
另一方面,只有当GlobalLock() 成功时才应该调用GlobalUnlock()。那么,如何正确定义应该调用 GlobalUnlock() 的情况?从以下变体中哪种方法是正确的?为什么?

变体 0:

HGLOBAL hMem = /*some handle on global memory block*/;

// lock block
auto pMem = static_cast<LPBYTE>(::GlobalLock(hMem));
if (pMem!=nullptr)
{
   // ... work with pMem

}

// call unlock in any case
::GlobalUnlock(hMem);

变体 1:

HGLOBAL hMem = /*some handle on global memory block*/;

// lock block
auto pMem = static_cast<LPBYTE>(::GlobalLock(hMem));
if (pMem!=nullptr)
{
   // ... work with pMem

   // unlock block
   ::GlobalUnlock(hMem);
}

变体 2:

HGLOBAL hMem = /*some handle on global memory block*/;

// lock block
auto pMem = static_cast<LPBYTE>(::GlobalLock(hMem));
auto isMemLocked = (pMem!=nullptr);
if (isMemLocked)
{
   // ... work with pMem
}
else
{
   // is it real error?
   isMemLocked = ::GetLastError()==NOERROR;
}

if (isMemLocked)
{
   // unlock block
   ::GlobalUnlock(hMem);
}

更新: 我们假设 hMem 是有效的(句柄不为 NULL)。

P.S.:非常感谢您的回答。

【问题讨论】:

  • @RbMm,谢谢,但为什么不是变体 2?为什么你认为零大小块的 NULL 也不是成功调用?
  • 我强烈建议为此使用 RAII 包装器。 global_locker 接受 HGLOBAL

标签: winapi visual-c++


【解决方案1】:
if (PVOID p = GlobalLock(hGlob))
{
  ...
  GlobalUnlock(hGlob);
}

是正确的模式并由 RbMm 回答,但变体 0 也被 Windows 接受,因为 GlobalUnlock(NULL) 返回 TRUE 而没有做任何其他事情。这当然是一个未记录的实现细节,我只在 Windows NT 4 和 Windows 8 上验证了这一点,但我假设两者之间的一切都是一样的。

这是因为 Windows 使用某些标记位和 alignment 来判断 HGLOBAL 是固定内存还是可移动内存,而 NULL 显然没有设置标记位,所以 GlobalUnlock 只是返回。

没有理由使用这种替代模式,因为:

  1. 您将依赖实施细节。
  2. 您不能省略 GlobalLock 返回值检查,除非您知道 HGLOBAL 是固定内存,在这种情况下,您可以省略所有锁定/解锁,因为如果您只使用固定内存,这将是毫无意义的开销。

【讨论】:

    【解决方案2】:

    来自GlobalLock 文档

    进程向GlobalLock 发出的每个成功调用 对象必须通过对GlobalUnlock的相应调用来匹配。

    如果函数成功,返回值是指向第一个指针 内存块的字节。

    如果函数失败,返回值为NULL

    所以我们需要调用GlobalUnlock,前提是之前调用GlobalLock返回不是NULL

    模式是下一个:

        if (PVOID pv = GlobalLock(hg))
        {
            //...
            GlobalUnlock(hg);
        }
    

    如果我们尝试在具有零字节大小的内存块上执行 GlobalLock - 我们总是得到 0 和 ERROR_DISCARDED。在这种情况下,我们不需要调用 GlobalUnlock - 在这种情况下它只是返回 ERROR_NOT_LOCKED


    如果从 c++ 角度看 GlobalAllocGMEM_MOVEABLE 标志返回 ~ weak_ptr - 所以 HGLOBAL 在这种情况下实际上指向像 weak_ptr 这样的对象。 GlobalLock(hg)weak_ptr::lock 的模拟,它返回 shared_ptr(指向实际内存块的直接指针)。和GlobalLock 是发布此shared_ptr 的模拟。在HGLOBAL hg 上调用GlobalDiscard - shared_ptr(实际内存块)将被销毁。但是HGLOBAL hg (weak_ptr) 仍然有效,只是每个GlobalLock(hg) (weak_ptr::lock) 调用它都会失败并出现错误ERROR_DISCARDED。最后GlobalFree 删除这个weak_ptr。演示代码:

    if (HGLOBAL hg = GlobalAlloc(GMEM_MOVEABLE, 8))
    {
        if (PVOID pv = GlobalLock(hg))
        {
            ASSERT(!GlobalDiscard(hg));
    
            GlobalUnlock(hg);
        }
    
        ASSERT(GlobalDiscard(hg));
    
        ASSERT(!GlobalLock(hg));
    
        ASSERT(GetLastError() == ERROR_DISCARDED);
    
        ASSERT(!GlobalUnlock(hg));
    
        ASSERT(GetLastError() == ERROR_NOT_LOCKED);
    
        GlobalFree(hg);
    }
    
    if (HGLOBAL hg = GlobalAlloc(GMEM_MOVEABLE, 0))
    {
        ASSERT(!GlobalLock(hg));
    
        ASSERT(GetLastError() == ERROR_DISCARDED);
    
        ASSERT(!GlobalUnlock(hg));
    
        ASSERT(GetLastError() == ERROR_NOT_LOCKED);
    
        GlobalFree(hg);
    }
    

    【讨论】:

      猜你喜欢
      • 2011-01-10
      • 2013-05-31
      • 1970-01-01
      • 1970-01-01
      • 2021-11-24
      • 2021-09-23
      • 2017-04-07
      • 2013-01-09
      • 2013-03-19
      相关资源
      最近更新 更多