书籍:《Visual C++ 2017从入门到精通》的4.2 按钮控件
环境:visual studio 2022
内容:[例 4.2]制作图片按钮
说明:以上内容大部分来自腾讯元宝。
函数原型与功能定位
GlobalLock()
- 核心作用:锁定全局内存块,获取可访问的指针
- 参数:
hMem
由GlobalAlloc
或GlobalReAlloc
返回 - 返回值:成功返回内存指针,失败返回
NULL
- 所属头文件:
winbase.h
GlobalUnlock()
BOOL GlobalUnlock(
HGLOBAL hMem // 全局内存句柄
);
- 核心作用:解除内存锁定,使指针失效
- 参数:
hMem
需与GlobalLock
使用的句柄一致 - 返回值:非零表示成功,零表示失败(通过
GetLastError
获取错误码) - 所属头文件:
winbase.h
锁定机制解析
1. 锁定计数器
- 内部机制:每个内存对象维护一个锁定计数器(初始为 0)
GlobalLock
每次调用计数器 +1GlobalUnlock
每次调用计数器 -1
- 锁定条件:只有计数器 > 0 时内存保持锁定状态
2. 内存移动控制
- 可移动内存(GMEM_MOVEABLE):
- 未锁定时,系统可合并/移动内存块优化空间
- 锁定后内存地址固定,直到计数器归零
- 固定内存(GMEM_FIXED):
- 锁定计数器始终为 0
- 返回的句柄即内存地址(无需锁定/解锁)
3. 多线程安全性
- 线程间竞争:不同线程对同一内存块的锁定操作需通过计数器同步
- 死锁风险:需确保
GlobalLock
和GlobalUnlock
成对出现
典型应用场景
1. 跨进程数据交换
// 剪贴板操作示例
HGLOBAL hClipData = GlobalAlloc(GMEM_MOVEABLE, dataSize);
LPVOID pData = GlobalLock(hClipData); // 锁定获取指针
CopyMemory(pData, sourceData, dataSize);
SetClipboardData(CF_BITMAP, hClipData);
GlobalUnlock(hClipData); // 解锁后剪贴板可被其他进程访问
2. 动态数据流处理
// 实现 IStream 接口的读写
STDMETHODIMP CMyStream::Read(void* pv, ULONG cb, ULONG* pcbRead) {
HGLOBAL hMem = GlobalLock(hGlobal);
memcpy(pv, (BYTE*)hMem + currentPosition, cb);
*pcbRead = cb;
GlobalUnlock(hMem);
currentPosition += cb;
return S_OK;
}
3. COM 对象内存管理
// 通过全局内存共享数据
HGLOBAL hSharedData = GlobalAlloc(GMEM_MOVEABLE, sizeof(DataStruct));
DataStruct* pData = (DataStruct*)GlobalLock(hSharedData);
pData->version = 2;
GlobalUnlock(hSharedData);
错误处理与调试
1. 常见错误码
错误码 | 含义 |
---|---|
NULL (GlobalLock) | 句柄无效或内存已释放 |
ERROR_NOT_LOCKED | 尝试解锁未锁定的内存 |
ERROR_INVALID_HANDLE | 无效的内存句柄 |
2. 调试技巧
- 锁定计数验证:
LONG lockCount = 0; do { lockCount = GlobalHandle(hMem)->dwLockCount; Sleep(10); } while (lockCount > 0); // 检测锁定泄漏
- 内存转储分析:
// 使用 WinDbg 查看锁定状态 !heap -p -a <hMem地址>
性能优化建议
- 最小化锁定时间:
- 批量数据处理:
HGLOBAL hBuffer = GlobalAlloc(GMEM_MOVEABLE, 1024 * 1024); LPVOID pBuffer = GlobalLock(hBuffer); // 一次性处理大块数据 ProcessData(pBuffer, 1024 * 1024); GlobalUnlock(hBuffer);
特殊场景注意事项
1. 内存映射文件
// 将文件映射到全局内存
HANDLE hFile = CreateFile("data.bin", GENERIC_READ, ...);
HGLOBAL hMap = GlobalAlloc(GMEM_MOVEABLE, fileSize);
LPVOID pMap = GlobalLock(hMap);
ReadFile(hFile, pMap, fileSize, &bytesRead, NULL);
GlobalUnlock(hMap);
2. 多语言支持
// 锁定包含多语言字符串的资源
HRSRC hRes = FindResource(NULL, MAKEINTRESOURCE(IDR_STRINGS), RT_STRING);
HGLOBAL hString = LoadResource(NULL, hRes);
LPVOID pString = GlobalLock(hString);
// 根据语言选择字符串偏移量
CString str = *(LPCTSTR)((BYTE*)pString + 0x100);
GlobalUnlock(hString);
与 GlobalAlloc() 的协作关系
分配标志 | 锁定要求 | 典型场景 |
---|---|---|
GMEM_FIXED | 无需锁定,直接使用句柄作为指针 | 长期驻留内存的数据结构 |
GMEM_MOVEABLE | 必须锁定后才能访问 | 临时缓冲区、跨进程数据交换 |
GMEM_DISCARDABLE | 锁定时禁止系统丢弃内存 | 可恢复的缓存数据 |
源码实现原理
(基于 Windows 内核实现)
// ntddk.h
typedef struct _HEAP_LOCK_ENTRY {
ULONG LockCount; // 锁定计数器
PVOID pAddress; // 内存起始地址
} HEAP_LOCK_ENTRY;
// win32k.sys 内部实现
LPVOID APIENTRY GlobalLock(HGLOBAL hMem) {
HEAP_LOCK_ENTRY* pEntry = GetLockEntry(hMem);
if (InterlockedIncrement(&pEntry->LockCount) == 1) {
return pEntry->pAddress;
}
return NULL; // 锁定失败
}
BOOL APIENTRY GlobalUnlock(HGLOBAL hMem) {
HEAP_LOCK_ENTRY* pEntry = GetLockEntry(hMem);
if (InterlockedDecrement(&pEntry->LockCount) >= 0) {
return TRUE;
}
return FALSE; // 计数器溢出
}
总结
GlobalLock()
和 GlobalUnlock()
是 Windows 内存管理的核心接口,其设计体现了以下特性:
- 安全性:通过锁定计数器防止内存竞争
- 灵活性:支持可移动内存的动态管理
- 兼容性:为旧系统 API 提供基础支持
最佳实践:
- 优先使用
GMEM_FIXED
减少锁定开销 - 锁定后确保在异常处理中解锁
- 大内存操作时结合
VirtualLock
提升性能
通过合理运用这两个函数,可在跨进程通信、动态数据流处理等场景中实现高效安全的内存管理。