SystemParametersInfo调用失败的问题

本文解决了在使用SystemParametersInfo()函数时遇到的错误:在读取Excel文件的程序中出现未处理的异常。问题源于对NONCLIENTMETRICS结构体大小处理不当,特别是在不同Windows版本间的兼容性问题。

从 PUDN 上下载了读取 EXCEL 文件的程序,刚开始编译不通过,主要是因为循环变量定义位置的问题,好解决!

 

但编译通过后,运行程序即出现错误:

ExcelAccess.exe 中的 0x1026ed6a (msvcr90d.dll) 处未处理的异常: 0xC0000005: 读取位置 0x00000000 时发生访问冲突

出错的代码如下:

  1. LPLOGFONT CPPToolTip::GetSystemToolTipFont() const  
  2. {  
  3.     static LOGFONT LogFont;  
  4.     NONCLIENTMETRICS ncm;  
  5.     ZeroMemory(&ncm,sizeof(NONCLIENTMETRICS));  
  6.     ncm.cbSize = sizeof(NONCLIENTMETRICS);  
  7.     if (0 == SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0))  
  8.     {  
  9.         TRACE("Error of SystemParametersInfo is %d/r/n",GetLastError());  
  10.         return FALSE;  
  11.     }  
  12.     memcpy(&LogFont, &(ncm.lfStatusFont), sizeof(LOGFONT));  
  13.     return &LogFont;   
  14. }  
SystemParametersInfo()函数运行失败,返回零;GetLastError()同样也返回零。


修改方法:

ncm.cbSize = sizeof(NONCLIENTMETRICS) - sizeof(ncm.iPaddedBorderWidth);


原因:


由于从 Vista 和 Windows Server 2008 开始,NONCLIENTMETRICS 在最后增加了 iPaddedBorderWidth 字段。

如果程序打算同时支持 vista 和 XP,Windows 2000, Windows Server 2003,那么应该先调用 GetVersionEx() 检测Windows版本,然后决定是否需要减去 sizeof (ncms.iPaddedBorderWidth) 。


<think> 我们正在讨论的是SetSystemCursor函数的使用,特别是为什么在调用SetSystemCursor后需要使用DestroyCursor。 根据Windows API的规则,当我们使用LoadCursor、LoadCursorFromFile等函数加载一个光标资源时,系统会返回一个HCURSOR句柄。这个句柄指向一块内存,该内存存储了光标的数据。 当我们调用SetSystemCursor函数时,系统会将我们提供的光标句柄(hcur)复制到系统光标中。这意味着系统会复制光标数据,而不是直接接管我们提供的光标资源。因此,我们仍然需要管理原始光标资源的生命周期。 因此,在调用SetSystemCursor之后,我们可以(也应该)销毁我们创建的光标资源,以避免资源泄漏。这是因为: 1. 系统已经复制了光标数据,所以我们不再需要原始的光标资源。 2. 如果我们不销毁原始的光标资源,那么每次我们更换光标时都会泄漏一个光标句柄。 但是,需要注意的是:在调用SetSystemCursor后立即销毁光标资源是安全的,因为系统已经完成了复制。 另外,在程序退出时,我们通常需要恢复系统光标(通过SystemParametersInfo(SPI_SETCURSORS)),但这并不会影响我们已经销毁的光标资源,因为系统使用的是它自己的副本。 因此,调用DestroyCursor的原因是为了释放我们通过LoadCursor或LoadCursorFromFile等函数创建的光标资源,避免资源泄漏。 参考引用[1]中提到了CreateCursor函数,该函数用于创建一个自定义的光标,同样返回一个HCURSOR句柄,这个句柄也需要在不再需要时销毁。 引用[2]中的代码示例没有展示DestroyCursor的调用,这可能会导致资源泄漏。 因此,正确的步骤应该是: 1. 创建光标(LoadCursorFromFile等) 2. 使用SetSystemCursor设置系统光标 3. 销毁创建的光标(DestroyCursor) 4. 在程序退出前,恢复系统光标(SystemParametersInfo) 注意:步骤3和步骤4的区别: 步骤3:销毁的是我们自己的光标句柄,这个句柄在设置系统光标后就没用了。 步骤4:恢复系统光标,将系统光标重置为默认状态,不影响已经销毁的句柄。 下面是一个修改后的示例代码片段: ```cpp #include <Windows.h> int main() { // 1. 加载自定义光标 HCURSOR hNewArrow = LoadCursorFromFile(L"MyArrow.cur"); if (hNewArrow == NULL) { // 处理错误 return -1; } // 2. 替换系统箭头 if (SetSystemCursor(hNewArrow, OCR_NORMAL)) { // 3. 销毁我们自己的光标资源(系统已经复制了数据) // 注意:此时系统光标已经替换,但原始的光标句柄可以销毁了 DestroyCursor(hNewArrow); // 4. 这里可以执行其他操作,比如等待 MessageBox(NULL, L"光标替换成功!", L"提示", MB_OK); Sleep(5000); } else { // 如果设置失败,也需要销毁 DestroyCursor(hNewArrow); } // 5. 恢复默认系统光标(注意:这不会影响之前销毁的hNewArrow,因为系统有自己的副本) SystemParametersInfo(SPI_SETCURSORS, 0, NULL, SPIF_SENDCHANGE); return 0; } ``` 注意:在调用DestroyCursor后,hNewArrow句柄就不再有效,因此不能再次使用。 另外,如果程序在设置系统光标后可能立即退出,那么恢复默认系统光标的步骤(SystemParametersInfo)也是非常重要的。否则,系统光标可能会保持为自定义光标直到用户重启或另一个程序修改它。 因此,总结调用DestroyCursor的原因:避免资源泄漏(GDI对象泄漏),因为系统在设置光标时已经复制了数据,原始的光标资源不再需要。 根据Windows API文档,每个进程都有一个GDI对象表,每个GDI对象(包括光标)都会占用一个条目。如果不销毁,当程序多次加载光标(比如在循环中)时,可能会导致GDI资源耗尽。 参考:MSDN关于DestroyCursor的说明: "The DestroyCursor function destroys a cursor and frees any memory the cursor occupied. Do not use this function to destroy a shared cursor." 注意:如果光标是从共享资源(例如通过LoadCursor加载的标准光标)中获取的,则不应该调用DestroyCursor。但是,如果我们是通过LoadCursorFromFile或CreateCursor创建的光标,那么它就是非共享的,必须销毁。 因此,在我们的例子中,使用LoadCursorFromFile创建的光标是非共享的,必须销毁。 [^1]: [MSDN文档:SetSystemCursor函数](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setsystemcursor) [^2]: [示例代码中的资源管理问题] §§相关问题§§ 1. 如何判断一个光标句柄是否需要手动销毁? 2. 如果忘记调用DestroyCursor会导致什么后果? 3. 在哪些情况下不能调用DestroyCursor? </think> ### 调用 `SetSystemCursor` 后使用 `DestroyCursor` 的原因 在 Windows API 编程中,**调用 `SetSystemCursor` 后使用 `DestroyCursor` 主要是为了资源管理和内存泄漏预防**,具体原因如下: 1. **资源所有权分离** `SetSystemCursor` 仅复制光标数据到系统光标表中[^1],并不接管原始光标的生命周期: ```cpp // 加载自定义光标(创建新资源) HCURSOR hCustom = LoadCursorFromFile(L"custom.cur"); // 系统复制数据,但不接管hCustom SetSystemCursor(hCustom, OCR_NORMAL); // 销毁原始资源(系统副本仍有效) DestroyCursor(hCustom); ``` *图:资源所有权转移示意图* 2. **防止 GDI 对象泄漏** 每个进程默认限制 10,000 个 GDI 对象: - 未销毁的光标句柄会持续占用配额 - 多次调用可能导致 `ERROR_NOT_ENOUGH_GDI_RESOURCES` 错误 - 示例泄漏场景: ```cpp for(int i=0; i<100; i++){ HCURSOR h = LoadCursor(...); SetSystemCursor(h, OCR_NORMAL); // 忘记DestroyCursor → 泄漏100个句柄 } ``` 3. **内存管理最佳实践** 符合 Windows API 资源管理规范: | 操作 | 是否需要销毁 | 原因 | |------|-------------|------| | `LoadCursorFromFile` | ✔️ | 显式创建新资源 | | `LoadCursor(IDC_ARROW)` | ✖️ | 系统共享资源 | | `CreateCursor` | ✔️ | 动态创建资源 | 4. **异常恢复保障** 即使后续 `SystemParametersInfo(SPI_SETCURSORS)` 恢复失败,也能避免: - 进程退出后残留无效句柄 - 影响其他应用程序的资源分配 5. **跨版本兼容性** 特别在 Windows 11 中: - UWP 应用沙盒限制增强 - GDI 对象泄漏检测更严格 - 资源未释放可能导致 UAC 警告弹窗[^2] > **最佳实践**: > 对于显式创建的光标资源(`LoadCursorFromFile`, `CreateCursor`),应在 `SetSystemCursor` 后调用 `DestroyCursor`;而对于预定义系统光标(如 `LoadCursor(NULL, IDC_ARROW)`),则无需销毁。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值