Creating and using fonts / avoiding memory leaks in windows GDI - c

I'm trying to get to the bottom of a memory leak in an application written in C and running on Windows CE 6.0. I suspect that the issue MAY be related to the handling of the paint event of the window. In pseudo code it looks like this.
LRESULT CALLBACK HandlePaint(HWND hWnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
hdc = BeginPaint (hWnd, &ps);
HFONT logfont;
FONTINFO font1, font2;
memset(&logfont, 0, sizeof(LOGFONT));
//set font options for font1.
font1 = CreateFontIndirect(&logfont);
memset(&logfont, 0, sizeof(LOGFONT));
//set font options for font2.
font2 = CreateFontIndirect(&logfont);
for(int i = 0; i <= SOME_NUMBER; i++)
{
DrawStuff(hdc, font1);
DrawStuff(hdc, font2);
}
EndPaint (hWnd, &ps);
}
INT DrawStuff(HDC hdc, HFONT font)
{
HPEN pen = CreatePen(PS_SOLID, borderWidth, bordercolor);
HBRUSH brush = CreateSolidBrush(backcolor);
SelectObject (hdc, pen);
SelectObject (hdc, brush);
SelectObject(hdc, font);
SetTextColor (hdc, forecolor);
SetBkColor (hdc, backcolor);
DrawText (hdc, pChar, wcslen(pChar), prect, DT_CENTER|DT_VCENTER|DT_SINGLELINE|DT_NOPREFIX);
DeleteObject(font);
DeleteObject(brush);
DeleteObject(pen);
}
I've noticed in the examples I've seen for windows graphics that there seems to be a pattern for most grapics objects of:
HBRUSH brush = CreateBrush();
SelectObject(hdc, brush);
// use brush
DeleteObject(brush);
However, as you can see in the example above with the fonts, each font is being created once, and then Selected/Deleted multiple times. I'm not sure what the implications are of doing that. Would there be a reason to suspect a memory leak here?
Thanks!

I agree with #pmg's comment that the creator of the Form should be the destroyer of the font, not the DrawStuff callee.
Also bear in mind that SelectObject returns the original item in the DC and you should always return that object when you're done, e.g.:
HPEN newPen = CreatePen(...);
HPEN oldPen = SelectObject(hdc, newPen);
// do stuff
// clean up
SelectObject(hdc, oldPen); // <-- note this line
DeleteObject(newPen);

Related

Strange coordinates in `WM_MOUSEMOVE`

MSDN describes that lParam of WM_MOUSEMOVE is 2 shorts due to it needing to be compatible with virtual coordinates because it acts as a redirected event if capture is set, which is clear. However, negative coordinates are still received under normal circumstances when moving the mouse slightly outside of the window, to be exact a bonus 5 pixels in all directions except up (which is where the caption is, and it'd go to WM_NCMOUSEMOVE instead).
I initially suspected this was to do with the drop shadow technically being part of the window (like with the output of AdjustWindowRectEx's rectangle including it) since you can receive events in some of the shadow, but the values of that don't match up, and clamping the values to the client area's size doesn't feel intended. Where do the bonus 5 pixels (on my system, Windows 10 Education 2004) come from, especially considering that shouldn't even be a part of the client area to my knowledge, and is there a clean/intended way to dodge unwanted values?
I've seen some discussion about the area you can grab to resize the window potentially being related, but my window isn't resizable (doesn't have a thick frame).
Edit: After tinkering, it seems this is related to the window style. Here's a reproducible sample (hopefully on other machines, too):
#include <Windows.h>
#include <stdio.h>
// Remove `^ WS_THICKFRAME` and the bug vanishes!
#define WINDOW_STYLE ((WS_OVERLAPPEDWINDOW | WS_VISIBLE) ^ WS_THICKFRAME)
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
if (message == WM_MOUSEMOVE) {
POINTS mouse = MAKEPOINTS(lParam);
printf("WM_MOUSEMOVE # x=%hd, y=%hd\n", mouse.x, mouse.y);
} else if (message == WM_DESTROY) {
PostQuitMessage(0);
}
return DefWindowProcW(hWnd, message, wParam, lParam);
}
int main(void) {
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = GetModuleHandle(NULL);
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = L"SO_Example";
wcex.hIconSm = NULL;
RECT windowRect = { 0, 0, 600, 600 };
AdjustWindowRectEx(&windowRect, WINDOW_STYLE, FALSE, 0);
ATOM windowClass = RegisterClassExW(&wcex);
HWND hWnd = CreateWindowExW(
0,
(LPCWSTR)windowClass,
L"hello stackoverflow!",
WINDOW_STYLE,
CW_USEDEFAULT, CW_USEDEFAULT,
windowRect.right - windowRect.left,
windowRect.bottom - windowRect.top,
NULL,
NULL,
GetModuleHandle(NULL),
NULL
);
MSG msg;
while (GetMessageW(&msg, NULL, 0, 0) != 0) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return 0;
}
Here's that sample in action: https://i.imgur.com/LfCe9od.mp4
It appears 5 is the border size (1) and the border padding (4) added up (aka the area the user can use to resize the window), so it's safe to discard values outside of the client rectangle, even if it's a little strange that Win32 is reporting events in that area.

Calling MoveWindow() with bRepaint set to TRUE

The following is taken from the Remarks section of the MoveWindow() documentation:
If the bRepaint parameter is TRUE, the system sends the WM_PAINT
message to the window procedure immediately after moving the window
(that is, the MoveWindow function calls the UpdateWindow function).
So I assumed that when I call MoveWindow() with bRepaint set to TRUE, the window procedure will be called immediately and passed a WM_PAINT message, but this is what my testing shows:
When MoveWindow() is called, the window procedure is called
immediately, but a WM_ERASEBKGND message is passed to it and not a
WM_PAINT message.
The region is still invalid and so when I go back to the message loop and no messages are in the message queue, a WM_PAINT message is sent.
Did I interpret the documentation wrong?
Note: I am talking about calling the MoveWindow() method on the parent window object.
Edit:
This is my test code:
/* Left mouse click on the window to call MoveWindow() */
#include <Windows.h>
HWND hEdit;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_LBUTTONDOWN:
MoveWindow(hWnd, 200, 200, 700, 700, TRUE);
// Do not go back to message loop immediately
Sleep(3000);
break;
case WM_ERASEBKGND:
{
SendMessage(hEdit, WM_CHAR, (WPARAM)'e', 0);
}
break;
case WM_PAINT:
{
SendMessage(hEdit, WM_CHAR, (WPARAM)'p', 0);
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
}
break;
case WM_CLOSE:
DestroyWindow(hWnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
wc.lpszMenuName = NULL;
wc.lpszClassName = "WinClass";
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wc);
HWND hWnd = CreateWindowEx(0, "WinClass", "", WS_OVERLAPPEDWINDOW, 261, 172, 594, 384, NULL, NULL, hInstance, NULL);
hEdit = CreateWindowEx(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, 0, 0, 400, 21, hWnd, NULL, hInstance, NULL);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
MSG msg;
while(GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
Did I interpret the documentation wrong?
Basically, yes. You discovered a little fact about MSDN documentation on winapi functions that is very, very important to know. It is not written to be a tutorial. It assumes a basic understanding of how the winapi works, the kind of knowledge you get from reading Petzold's "Programming Windows" book.
That book can teach you that the Windows painting cycle always includes WM_ERASEBKGND. So the background is painted first, what ever you draw on top of it with WM_PAINT is next.
Several reasons why such implementation details are skipped in the MSDN documentation. First off, there is a lot of it and including everything just makes it hard to plow through the article. Next, it is pretty unusual to actually write a message handler for WM_ERASEBKGND. You normally just pass it on to DefWindowProc(). Which uses the WNDCLASSEX.hbrBackground you selected, 99% of the time good enough to get the job done. Note how your window looks screwed up because that's what you did not do. Since you wrote a message handler, it is now your job to take care of it. Easy to do, just call DefWindowProc() yourself.
Finally, MSDN documentation omits details because nailing them down makes it very difficult to ever improve the way Windows works. There's another implementation detail that you can see from your test program. Quite often, calling MoveWindow with bPaint = TRUE does not paint anything at all. Easy to see by moving the window by dragging it with the title bar after you first clicked it. Note how clicking again makes the window jump back but you get neither WM_ERASEBKGND nor WM_PAINT.
That's an optimization at work, making Windows work better. And not mentioned in the MSDN article. If the window didn't move off the screen and back and the size of the window did not change then it can take a shortcut. It simply copies the pixels in the video frame buffer from the old position to the new position. Much more efficient than letting the app repaint everything. If you run with Aero enabled then it is even more optimized, it doesn't have to copy the pixels at all.
Last but not least, while writing code like this to reverse-engineer Windows is pretty educational and recommended, you don't have to. It is much easier to use the Spy++ utility.
What probably happens is that MoveWindow sends WM_ERASEBKGND using SendMessage (which will call the WndProc callback immediately and wait for its processing) but WM_PAINT via PostMessage (which will just put the message in the queue, so it will be processed after sleeping, when DispatchMessage is called).
I don't know if it's just a test or you're really processing something after using MoveWindow which blocks the message queue. If so, then you should consider moving that work to another thread!
Hope it helps.
Did I interpret the documentation wrong?
Yes and no.
No - the documentation is pretty clear on this.
Yes - like Hans Passant said, you just can't rely on such details from MSDN.
There are many WinAPI functions that have "gotchas", undocumented behaviour or expected environment state (incl. timing) and such. It could be that it did behave as specified in some version of Windows. Maybe it still does, under some circumstances.
In practice, MS will test a lot of applications to see if they work after making a change like this. In this case, since you generally process the WM_PAINT "when it happens", and do only painting then, it is easy to see how in most applications this change would not affect the end-user result.
So, always take MSDN as a "general description". Use your own testing and other sources to get the actual behaviour details. Be happy that you're working with the windows-related API, if you ever work some less used APIs, you'll be far worse off (I suffered a lot with the USB / HID related APIs).
i didn't want to override rony's provided test code so i decided to post my alternative, which i think is better suited to debug Windows Message, the only requirement an external tools to view the debug message DebugView. this of course can be replaced by a listbox if some one wishes.
#include <Windows.h>
/*
get DebugView from here https://download.sysinternals.com/files/DebugView.zip
*/
/*___________________________________________________________________________________
*/
void __cdecl DebugPrint(TCHAR *fmt,...){
va_list args = NULL;
va_start(args, fmt);
static TCHAR _buff[512]="";
TCHAR buff[512];
wvsprintf(buff,fmt,args);
va_end(args);
if(lstrcmp(buff,_buff))
OutputDebugString(buff);
lstrcpy(_buff,buff);
return ;
}
/*___________________________________________________________________________________
*/
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_RBUTTONDOWN:
MoveWindow(hWnd, 200, 200, 700, 700, TRUE);
break;
case WM_MOVE:
DebugPrint("WM_MOVE");
break;
case WM_SIZE:
DebugPrint("WM_SIZE");
break;
case WM_ERASEBKGND:
DebugPrint("WM_ERASEBKGND");
break;
case WM_PAINT:
DebugPrint("WM_PAINT");
if(1){
PAINTSTRUCT ps;
TCHAR buff[]="Right mouse click on the window to call MoveWindow()";
HFONT hf=(HFONT)GetStockObject(DEFAULT_GUI_FONT);
HDC hdc = BeginPaint(hWnd, &ps);
hf=SelectObject(hdc,hf);
TextOut(hdc,8,12,buff, sizeof(buff)-sizeof(TCHAR));
hf=SelectObject(hdc,hf);
EndPaint(hWnd, &ps);
}else{
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
/*___________________________________________________________________________________
*/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wc;
ZeroMemory(&wc,sizeof(wc));
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
wc.lpszClassName = "WinClass";
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
RegisterClassEx(&wc);
HWND hWnd = CreateWindowEx(0, "WinClass", "",WS_OVERLAPPEDWINDOW|WS_VISIBLE,
261, 172, 594, 384, NULL, NULL, hInstance, NULL);
MSG msg;
while(GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}

Displaying String Output in a Window using C (in WIN32 API)

I want a proper way in which I can output a character string and display it on a Window created.
I had been using textout() function, but since it only paints the window, once the window is minimized and restored back, the data displayed on the window disappears.
Also when the data to be displayed is exceeds the size of Window, only the data equal to window size is displayed and other data is truncated.
Is there any other way to output data on a Window?
You can put a Static or an Edit control (Label and a text box) on your window to show the data.
Call one of these during WM_CREATE:
HWND hWndExample = CreateWindow("STATIC", "Text Goes Here", WS_VISIBLE | WS_CHILD | SS_LEFT, 10,10,100,100, hWnd, NULL, hInstance, NULL);
Or
HWND hWndExample = CreateWindow("EDIT", "Text Goes Here", WS_VISIBLE | WS_CHILD | ES_LEFT, 10,10,100,100, hWnd, NULL, hInstance, NULL);
If you use an Edit then the user will also be able to scroll, and copy and paste the text.
In both cases, the text can be updated using SetWindowText():
SetWindowText(hWndExample, TEXT("Control string"));
(Courtesy of Daboyzuk)
TextOut should work perfectly fine, If this is done in WM_PAINT it should be drawn every time. (including on minimizing and re-sizing)
LRESULT CALLBACK MyWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_PAINT:
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
TextOut(hdc, 10, 10, TEXT("Text Out String"),strlen("Text Out String"));
EndPaint(hWnd, &ps);
ReleaseDC(hWnd, hdc);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
You might also be interested in DrawText
LRESULT CALLBACK MyWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_PAINT:
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
RECT rec;
// SetRect(rect, x ,y ,width, height)
SetRect(&rec,10,10,100,100);
// DrawText(HDC, text, text length, drawing area, parameters "DT_XXX")
DrawText(hdc, TEXT("Text Out String"),strlen("Text Out String"), &rec, DT_TOP|DT_LEFT);
EndPaint(hWnd, &ps);
ReleaseDC(hWnd, hdc);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Which will draw the text to your window in a given rectangle,
Draw Text will Word Wrap inside of the given rect.
If you want to have your whole window as the draw area you can use GetClientRect(hWnd, &rec); instead of SetRect(&rec,10,10,100,100);

SetBkMode(hdc, TRANSPARENT) doesn't work

When I use SetBkMode(hdc, TRANSPARENT); in the code below, I got the following effect when I resize the main window (and hence when the child receives the WM_PAINT message):
The problem is : When I resize the main window, The old area of "Find:" shoule be erased, I guess. But it just remains there.
If I don't use SetBkMode(hdc, TRANSPARENT);, I don't have this problem. It looks like:
, i.e it has white background. Furthermore, if I use SetBkMode(hdc, TRANSPARENT);, it looks like the same as above, before I resize the main window. So I don't think SetBkMode(hdc, TRANSPARENT); works here.
the hwnd is a static child with style SS_BITMAP.
Do you know why this issue occurs?
switch (message) {
case WM_PAINT:
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
SelectObject(hdc, gDefaultGuiFont);
SetBkMode(hdc, TRANSPARENT);
RECT rc;
GetClientRect(hwnd, &rc);
DrawText(hdc, _TR("Find:"), -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
return 0;
.............
}
Try to use "fixed" rectangles. For example
RECT rc;
GetClientRect(hwnd, &rc);
rc.left += ...; rc.top += ...; // shift up-left point
DrawText(hdc, _TR("Find:"), -1, &rc, DT_SINGLELINE | DT_LEFT | DT_TOP);
The idea is you draw text in wrong position (once) and in right position (twice) while backgound updated only once. Can't say more on part of code.
The problem is that windows is not updating the control (in time) that's behind your static control, you are now responsible for it's contents. So you want to use the background provided by the parent. Well just ask the parent to draw it for you in the child window:
RECT rc;
GetClientRectRelative(m_hWnd, GetParent(m_hWnd), &rc);
SetWindowOrgEx(m_mdc, rc.left, rc.top, NULL);
SendMessage(GetParent(m_hWnd), WM_PAINT, (WPARAM)(HDC)m_mdc);
SetWindowOrgEx(m_mdc, 0, 0, NULL);
In which
bool GetClientRectRelative(HWND hWnd, HWND hWndRelativeTo, RECT *pRect)
{
RECT rcWnd, rcRelativeTo;
if (!GetClientRect(hWnd, &rcWnd) ||
!ClientToScreen(hWnd, (POINT*)&rcWnd) ||
!ClientToScreen(hWnd, (POINT*)&rcWnd + 1) ||
!GetClientRect(hWndRelativeTo, &rcRelativeTo) ||
!ClientToScreen(hWndRelativeTo, (POINT*)&rcRelativeTo) ||
!ClientToScreen(hWndRelativeTo, (POINT*)&rcRelativeTo + 1))
return false;
pRect->top = rcWnd.top - rcRelativeTo.top;
pRect->left = rcWnd.left - rcRelativeTo.left;
pRect->right = rcWnd.right - rcRelativeTo.left;
pRect->bottom = rcWnd.bottom - rcRelativeTo.top;
return true;
}
Now draw anything you like, I suggest you'd use the TRANSPARENT background mode.
Please create all your child windows with the styles WS_CLIPCHILDREN and WS_CLIPSIBLINGS, then these problems will become apparent immediately and you avoid flicker.

Color of the lines are not changing?

I want to draw a white line in my window:
case WM_PAINT:
{
hdc=GetDC(hWnd);
SelectObject(hdc, GetStockObject(WHITE_BRUSH));
MoveToEx(hdc, 0, 0, 0);
LineTo(hdc, 100, 100);
ReleaseDC(hWnd, hdc);
}
but the color is still black. What's Wrong?
You are trying to set a brush for your line when you should be using a pen. A brush is used to fill the interior of a shape while a pen is used to draw the lines.
MSDN says this about pens:
A pen is a graphics tool that an application can use to draw lines and
curves. Drawing applications use pens to draw freehand lines, straight
lines, and curves.
And this about brushes:
A brush is a graphics tool that applications use to paint the interior
of polygons, ellipses, and paths.
Your code would need to be something more like this:
case WM_PAINT:
{
PAINTSTRUCT ps;
hdc=BeginPaint(hWnd, &ps); // Used instead of GetDC in WM_PAINT
HPEN hPen = CreatePen(PS_SOLID, 1, RGB(255,255,255));
HPEN hOldPen = SelectObject(hdc, hPen);
MoveToEx(hdc, 0, 0, 0);
LineTo(hdc, 100, 100);
SelectObject(hdc, hOldPen);
DeleteObject(hPen);
EndPaint(hWnd, &ps); // Used instead of ReleaseDC in WM_PAINT
}

Resources