GetStockObject 理解

转自:http://www.zzzj.com/html/20070514/19402.html

GetStockObject在图形编程技巧


  GetStockObject在图形编程中是常用API之一,对于GetStockObject,我一直认为它获得的"对象"是一个句柄(因为GetStockObject的API参考下面有DeleteObject函数的链接,一般只有图形对象函数的链接中才会有它的),并且认为它是从当前程序本身拥的"对象"中返回的,后来我无意的发现了问题,请大家看看API参考手册中对函数GetStockObject的注意项: 
  ...... 
  It is not necessary (but it is not harmful) to delete stock objects by calling DeleteObject. 
  它说Stock"对象"没有必要删除,可是说如果不删除可能有害,如果是程序本身的"对象",程序在退出时会自己清除的,那么我想我以前的理解是错误的。 
  我开始仍把GetStockObject的返回值当成一个对象,进行了一下测试,我写了一个循环,不停的获得Stock"对象"而不删除,足足运行了50000次(Windows规定一个程序最多只能同时拥有36000个对象),可是程序照样正常运行,系统也没有因此而变慢,我在想,GetStockObject返回的可能是一个长指针,指向系统提供的共享对象的句柄,无论获得多少次,它只有一份,返回的值也是一样的,于是,我写了一个显示句柄值的过程,结果的确是获得同一Stock类型的返回值都是相同的,可是,我看到返回的值竟是148(SYSTEM_FONT),这个怎么会是长指针?不可能,我马上又仔细的从头看了GetStockObject的API参考,看到下面一段: 
  ... 
  If the function succeeds, the return value identifies the logical object requested 
  我靠!它的返回值根本不是一个句柄,只是一个标识ID(真是的,这也是我的大意,总认为操作图形的函数成功返回的是句柄,这类API我只看注意事项的,以后不会了),那么GetStockObject终于有结果了,总结如下: 
  GetStockObject的返回的对象完全不需要删除,因为GetStockObject返回的根本不是句柄,也就是说根本不是一个对象,甚至GetStockObject本身都不需要,因为它的返回值只是一个标识系统对象的ID值,只不过是为了让SelectObject方便选择系统对象,那么,你在使用SelectObject时直接提供ID值就OK,如SYSTEM_FONT的ID值是148,那么就可直接使用Invoke SelectObject,hdc,148就行,只要你记住了常用Stock对象的ID或把它定义成常数,GetStockObject完全可以丢掉! 
  (关于SelectObject,它返回的才是一个对象,不过是前一对象的句柄,所以,如果要删除对象,只有选择新的同类型的对象到hdc中,前一对象才返回,就可以删除了,如果中间有交错,记住SelectObject返回的总是前一次选择的同一类型(是同一类型)的对象,如果一个过程很复杂,可以把经常使用的对象在程序启动时创建好,然后把句柄保存起来,使用起来就方便多了,甚至不需要删除,因为程序在退出时所占用的对象Windows会自动清除。)

#include <windows.h> #include <cmath> // 双缓冲绘图 HDC hdcBuffer; HBITMAP hbmBuffer; RECT rect; // 颜色转换函数 COLORREF HSLtoRGB(float h, float s, float l) { /*...*/ } // 窗口过程 LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); // 获取时间参数 static float t = 0.0f; t += 0.01f; // 初始化缓冲 hdcBuffer = CreateCompatibleDC(hdc); GetClientRect(hwnd, &rect); hbmBuffer = CreateCompatibleBitmap(hdc, rect.right, rect.bottom); SelectObject(hdcBuffer, hbmBuffer); // 绘制背景 FillRect(hdcBuffer, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH)); // 绘制爱心 const float a = 50.0f; // 基础大小 for(float θ = 0; θ < 6.283; θ += 0.001f) { float r = a * (1 - sin(θ)); // 动态参数 float scale = 1.0f + 0.1f * sin(t * 5.0f); // 心跳效果 float phase = t * 2.0f; // 颜色相位 // 坐标变换 int x = scale * r * cos(θ) + rect.right/2; int y = -scale * r * sin(θ) + rect.bottom/2; // 设置彩虹颜色 SetPixel(hdcBuffer, x, y, HSLtoRGB(fmod(θ*30 + phase, 360), 1.0f, 0.5f)); } // 交换缓冲 BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcBuffer, 0, 0, SRCCOPY); // 清理资源 DeleteDC(hdcBuffer); DeleteObject(hbmBuffer); EndPaint(hwnd, &ps); InvalidateRect(hwnd, NULL, FALSE); // 持续重绘 break; } //...其他消息处理 } return DefWindowProc(hwnd, msg, wParam, lParam); } // 主函数 int WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, int nCmdShow) { //...窗口创建代码 } 此代码报错
03-10
<think>好的,我现在需要帮助用户分析他们提供的C++爱心代码中的错误。用户提到代码报错,但具体错误信息没有给出,所以我需要仔细阅读代码,找出可能的错误点。 首先,我会回顾代码结构。代码使用了Windows API来创建窗口和处理图形,这通常需要正确的头文件和库链接。用户已经包含了<windows.h>和<cmath>,这两个头文件应该是足够的,但需要确认是否还有其他遗漏。 接下来,检查变量和函数的声明。代码中声明了全局变量hdcBuffer、hbmBuffer和rect,这些在窗口过程中被使用。然而,在C++中,全局变量可能会导致多线程问题,但在单窗口应用中可能可以接受。不过需要注意是否正确初始化和释放资源。 颜色转换函数HSLtoRGB的实现被注释掉了,这里明显是一个问题。如果函数没有实际实现,编译器会报错未定义的函数。因此,用户可能在编译时遇到链接错误,提示找不到HSLtoRGB的定义。 然后,查看窗口过程WndProc。在WM_PAINT处理中,每次都会创建和销毁双缓冲的DC和位图。这可能存在资源泄漏的问题,因为每次绘制都会创建新的资源,而没有正确检查是否已经存在。此外,DeleteDC和DeleteObject的调用顺序是否正确?需要确认Windows API中关于资源释放的要求。 在坐标变换部分,使用了θ作为变量名。C++中通常使用英文字符,如theta,因为θ可能属于扩展字符集,某些编译器可能不支持,导致编译错误。特别是如果源代码保存的编码不是UTF-8或支持Unicode的话,可能会出现问题。 主函数WinMain中窗口创建的代码被注释掉了,这会导致窗口无法创建,进而程序无法运行。用户可能没有正确注册窗口类或创建窗口,导致编译后的程序没有任何显示,或者直接报错。 另外,代码中使用了InvalidateRect来持续重绘,这可能导致高CPU使用率,但可能不会直接导致编译错误。不过需要注意是否正确处理WM_ERASEBKGND消息,避免闪烁。 还有一些细节需要注意,比如在WM_PAINT处理中,每次都会调用CreateCompatibleDC和CreateCompatibleBitmap,但没有检查之前的资源是否已经释放,可能导致资源泄漏。此外,在每次绘制时,hdcBuffer和hbmBuffer是全局变量,但在每次绘制时被重新赋值,旧的句柄可能没有被正确删除,导致内存泄漏。 另外,HSLtoRGB函数需要正确实现,否则颜色设置会失败。例如,参数h的范围通常是0-360,s和l是0-1,函数内部需要正确转换到RGB的0-255范围。如果函数没有正确实现,可能会导致颜色显示异常,但不会直接导致编译错误。 总结可能的错误点: 1. HSLtoRGB函数未实现,导致链接错误。 2. 使用了非ASCII字符θ作为变量名,导致编译错误。 3. 窗口创建代码缺失,导致程序无法启动窗口。 4. 资源泄漏问题,如未正确释放DC和位图。 5. 可能的编码问题,导致编译器无法识别θ字符。 接下来,我需要逐步解释这些可能的问题,并提供修改建议,同时保持代码结构清晰。需要确保用户能够理解每个错误的原因和解决方法,并指导他们如何修正代码。</think>该代码存在多个需要修正的关键问题,以下是详细解析和解决方案: --- ### **1. 字符编码问题** **错误表现**:编译器提示`θ`未定义 ```cpp for(float θ = 0; θ < 6.283; θ += 0.001f) // 错误:使用希腊字母θ ``` **修正方案**:改用英文字符命名变量 ```cpp for(float theta = 0; theta < 6.283; theta += 0.001f) // 正确 ``` --- ### **2. 缺失HSL转RGB实现** **错误表现**:链接错误`undefined reference to HSLtoRGB` ```cpp COLORREF HSLtoRGB(float h, float s, float l) { /*...*/ } // 缺少具体实现 ``` **补充实现**: ```cpp COLORREF HSLtoRGB(float h, float s, float l) { float c = (1 - fabs(2*l - 1)) * s; float x = c * (1 - fabs(fmod(h/60.0, 2) - 1)); float m = l - c/2; float r, g, b; if(h < 60) { r = c; g = x; b = 0; } else if(h < 120){ r = x; g = c; b = 0; } else if(h < 180){ r = 0; g = c; b = x; } else if(h < 240){ r = 0; g = x; b = c; } else if(h < 300){ r = x; g = 0; b = c; } else { r = c; g = 0; b = x; } return RGB((r+m)*255, (g+m)*255, (b+m)*255); } ``` --- ### **3. 窗口创建代码缺失** **错误表现**:程序无法创建窗口 **补充WinMain实现**: ```cpp int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow) { // 注册窗口类 WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hInst, NULL, LoadCursor(NULL, IDC_ARROW), (HBRUSH)(COLOR_WINDOW+1), NULL, L"HeartWindow", NULL }; RegisterClassEx(&wc); // 创建窗口 HWND hwnd = CreateWindowEx(0, L"HeartWindow", L"Animated Heart", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, NULL, NULL, hInst, NULL); ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // 消息循环 MSG msg; while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } ``` --- ### **4. 双缓冲资源泄漏** **错误表现**:内存持续增长 ```cpp hdcBuffer = CreateCompatibleDC(hdc); // 每次WM_PAINT都创建新DC ``` **优化方案**:改为局部变量 ```cpp case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); // 使用局部缓冲 HDC hdcBuffer = CreateCompatibleDC(hdc); HBITMAP hbmBuffer = CreateCompatibleBitmap(hdc, rect.right, rect.bottom); SelectObject(hdcBuffer, hbmBuffer); // ...绘制代码... // 交换缓冲后立即释放 BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcBuffer, 0, 0, SRCCOPY); DeleteDC(hdcBuffer); DeleteObject(hbmBuffer); } ``` --- ### **5. 编译参数调整** **错误表现**:链接失败提示未定义WinMain **解决方案**: - 对于MinGW编译器,添加编译参数: ```bash -mwindows -lgdi32 ``` - 在Visual Studio中: 1. 项目属性 → 链接器 → 输入 → 附加依赖项添加`gdi32.lib` --- ### **完整修正代码** 整合上述修改后的核心代码段: ```cpp // 修改后的窗口过程 LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); RECT rect; GetClientRect(hwnd, &rect); // 双缓冲初始化 HDC hdcBuffer = CreateCompatibleDC(hdc); HBITMAP hbmBuffer = CreateCompatibleBitmap(hdc, rect.right, rect.bottom); SelectObject(hdcBuffer, hbmBuffer); // 时间参数 static float t = 0.0f; t += 0.01f; // 绘制背景 FillRect(hdcBuffer, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH)); // 绘制爱心 const float a = 50.0f; for(float theta = 0; theta < 6.283f; theta += 0.001f) { float r = a * (1 - sin(theta)); float scale = 1.0f + 0.1f * sin(t * 5.0f); float phase = t * 2.0f; int x = static_cast<int>(scale * r * cos(theta) + rect.right/2); int y = static_cast<int>(-scale * r * sin(theta) + rect.bottom/2); SetPixel(hdcBuffer, x, y, HSLtoRGB(fmod(theta*30 + phase, 360), 1.0f, 0.5f)); } // 交换缓冲 BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcBuffer, 0, 0, SRCCOPY); // 释放资源 DeleteDC(hdcBuffer); DeleteObject(hbmBuffer); EndPaint(hwnd, &ps); InvalidateRect(hwnd, NULL, FALSE); // 持续动画 return 0; } case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, msg, wParam, lParam); } ``` --- ### **代码验证方法** 1. 在Visual Studio中创建空项目 2. 设置字符集为Unicode(项目属性 → 常规 → 字符集) 3. 复制完整修正代码到main.cpp 4. 编译运行应显示: - 黑色背景窗口 - 带彩虹渐变的跳动爱心 - 帧率约60FPS的流畅动画 若仍存在问题,建议: 1. 检查Windows SDK版本 2. 确认没有其他语法错误 3. 查看编译器输出的具体错误信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值