KB5000808/KB5000802 causes bitmaps/text objects to render invisibly - c++

We've been dealing with an issue in our software ever since Microsoft released KB5000802/808 in early March. I've seen a similar post about it here, and the code we use is virtually the same (and hasn't changed in many years):
Calling Windows SelectObject in a printer-compatible device context returns NULL
In our case, we are actually printing OK, but our objects (bitmaps and text boxes) render invisibly on the screen (only "resize handles" appear) until we de-select and re-select the object. Rolling back the security update is a workaround but it's becoming more difficult to do this. We also have tested with Microsoft's hotfix KB5001649, but the issue persists. Has anyone else encountered this?
'''
hBitmap = (HBITMAP)LoadImage(NULL,szFileName,IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION|LR_DEFAULTSIZE|LR_LOADFROMFILE|LR_VGACOLOR);
// [...]
HDC prn = GetPrinterDC(hwnd);
hdcMem = CreateCompatibleDC(prn);
HBITMAP hbmOld = (HBITMAP)SelectObject(hdcMem, hBitmap);
// hbmOld is NULL at this point, indicating SelectObject failure
'''

Related

Manipulate system/visible clipping region in Windows 1809

Apparently, Microsoft has changed the way clipping works with Windows update 1809, released in late 2018. Before that update, GetClipBox() returned the full client rectangle of a window, even when it was (partially) offscreen.
After the update, the same function returns a clipped rectangle, only containing the parts that are still onscreen. This leads to the Device Context contents not being updated for the offscreen area, which prevents me from taking screenshots from these windows.
The question is: can I somehow manipulate the clipping region?
I have researched a bit and it seems that the final clipping region is influenced by the window region, the update rectangle, and the system region - as far as I understand the "global clipping region". I've checked the window region with GetWindowRgn() and GetRgnBox(), both return the same values for Windows 1809 and older versions. GetUpdateRect() also returns the full client rectangle, so that cannot be the issue either. I've also tried to hook the BeginPaint() method and see if changing the PAINTSTRUCT.rcPaint does anything, without success.
So what I am left with is trying to adjust the system region, or sometimes called the visible region. However, I have no idea if and how that is possible. MSDN suggests that it's not, but I thought maybe someone does have an idea for a solution!?
EDIT: To make this more clear, I don't think the clipping is done by the application itself, because offscreen screenshots of the same application version work prior to Windows 1809 and don't work with the updated Windows version. Instead, Windows itself seems to clip any offscreen surfaces.
EDIT2: Here's a minimal working code example for taking the screenshot.
// Get the client size.
RECT crect;
GetClientRect(hwnd, &crect);
int width = crect.right - crect.left;
int height = crect.bottom - crect.top;
// Create DC and Bitmap.
HDC windowDC = GetDC(hwnd);
HDC memoryDC = CreateCompatibleDC(windowDC);
BITMAPINFO bitmapInfo;
ZeroMemory(&bitmapInfo, sizeof(BITMAPINFO));
bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmapInfo.bmiHeader.biWidth = width;
bitmapInfo.bmiHeader.biHeight = -height;
bitmapInfo.bmiHeader.biPlanes = 1;
bitmapInfo.bmiHeader.biBitCount = 32;
bitmapInfo.bmiHeader.biCompression = BI_RGB;
bitmapInfo.bmiHeader.biSizeImage = width * height * 4;
char* pixels;
HBITMAP bitmap = CreateDIBSection(windowDC, &bitmapInfo, DIB_RGB_COLORS, (void**)&pixels, 0, 0);
HGDIOBJ previousObject = SelectObject(memoryDC, bitmap);
// Take the screenshot. Neither BitBlt nor PrintWindow work.
BitBlt(memoryDC, 0, 0, width, height, windowDC, 0, 0, SRCCOPY);
// ..or..
// PrintWindow(hwnd, memoryDC, PW_CLIENTONLY);
// Save the image.
BITMAPFILEHEADER bitmapFileHeader;
bitmapFileHeader.bfType = 0x4D42;
bitmapFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
std::fstream hFile("./screenshot.bmp", std::ios::out | std::ios::binary);
if(hFile.is_open())
{
hFile.write((char*)&bitmapFileHeader, sizeof(bitmapFileHeader));
hFile.write((char*)&bitmapInfo.bmiHeader, sizeof(bitmapInfo.bmiHeader));
hFile.write(pixels, (((32 * width + 31) & ~31) / 8) * height);
hFile.close();
}
// Free Resources
ReleaseDC(hwnd, windowDC);
SelectObject(memoryDC, previousObject);
DeleteDC(memoryDC);
DeleteObject(bitmap);
You can download a compiled executable from Google Drive here. Usage is Screenshot.exe <HWND>, where HWND is the hex address of the window handle as it is shown in Spy++ for example. It will save a screenshot of the target window in the working directory as screenshot.bmp (make sure you're allowed to write to the directory). The screenshot will work for almost all windows (even if they are hidden behind other windows), but as soon as you partially move the window offscreen, the screenshot will continue to show the old window contents for the offscreen part of the window (resize it while it's offscreen for example, to see the effect). This only happens on Windows 1809, it still shows up-to-date contents on earlier Windows versions.
EDIT3: I did some more research on this. Regarding the AdobeAir application for which the WS_EX_LAYERED style did not work: I found that it uses BitBlt internally do render the back buffer to the window dc. The rendering steps are:
GetDC(hwnd) on the window to obtain hdcWin
CreateCompatibleDC(hdcWin) to create a hdcMem
Call SelectObject(hdcMem, bmp) to select an HBITMAP into hdcMem
BitBlt from hdcMem to hdcWin.
During the BitBlt call, the hdcMem contains valid pixel data even in the offscreen regions, but that data is never copied to the hdcWin.
I looked at the system regions during the BitBlt call. For hdcMem the system region is a NULLREGION, but for the hdcWin the region is always clipped at the screen edges. I also tried to adjust the system region, by replacing all calls to GetDC with GetDCEx(hwnd, hrgn, DCX_CACHE | DCX_INTERSECTRGN) (as mentioned in this article), but that doesn't work and doesn't seem to provide options for extending the region. I really think the secret to solving the problem lies in manipulating the system region for the window dc, but I have no idea how to do that.
If found that the CreateDC function takes a pointer to a DEVMODE struct as the last argument (msdn). That in turn has fields dmPelsWidth, dmPelsHeight and dmPosition. I believe that these make up the system region and maybe if I could manipulate them, the DC would no longer get clipped, but I wasn't able to hook the CreateDC function, yet.
If you have any new ideas based on my new insights, please share them. I'd appreciate any help!
If we take ReactOS as an example, the clipping region is at dc->dclevel.prgnClip and the system region is at dc->prgnVis. When you call BeginPaint on a window, it calls NtUserBeginPaint stub which traps to its kernel counterpart through the win32k SSDT, which calls IntBeginPaint, which passes the window's update region (Window->hrgnUpdate) to UserGetDCEx, which copies this to Dce->hrgnClip and calls DceUpdateVisRgn, which then gets the visible region by calling DceGetVisRgn which calculates the visible region using VIS_ComputeVisibleRegion, which develops a complex region by traversing all child windows, all parent windows and all siblings at each level (a top level window has a parent as the desktop (((PCLIENTINFO)(NtCurrentTeb()->Win32ClientInfo))->pDeskInfo->spwnd) and all top level windows are siblings; the desktop's parent is NULL and removing the parts they cover up – this does not appear to perform any special handling for the desktop window when it gets to it like clipping to the client area, and is treated like any other window in the z order, where only what it is covering is removed). DceGetVisRgn then combines this returned visible region and combines it wil the clipping region Dce->hrgnClip and combines them into RgnVisible using IntGdiCombineRgn(RgnVisible, RgnVisible, RgnClip, RGN_AND), which is then copied into dc->prgnVis using GdiSelectVisRgn(Dce->hDC, RgnVisible). DC is the device context and DCE is the device context entry for the DC in the DC cache. Therefore, the system region of the DC is now the intersection of the visible region and the update region of the window. IntBeginPaint also calls GdiGetClipBox(Ps->hdc, &Ps->rcPaint), which calls REGION_GetRgnBox(pdc->prgnVis, prc) to copy the bound of the region pdc->prgnVis (pdc->prgnVis->rdh.rcBound) to Ps->rcPaint and then GdiGetClipBox calls IntDPtoLP(pdc, (LPPOINT)prc, 2) to convert the bound from physical coordinates to logical coordinates, which DPI-unaware apps use. The paintstruct now contains the smallest logical rectangle that contains the complex intersection of the update region and the visible region.
GetClipRgn calls NtGdiGetRandomRgn, which returns pdc->dclevel.prgnClip when called with CLIPRGN, which is application defined using SetClipRgn
An application-defined clipping region is a clipping region identified by the SelectClipRgn function. It is not a clipping region created when the application calls the BeginPaint function.
There are 2 clipping regions. One is an application defined one created by the application using SelectClipRgn and the pointer is stored in pdc->dclevel.prgnClip, and the other clipping region is system region, after it has been updated to the intersection of the system region and the update region by a BeginPaint call, where it is presented to the application as a logical clipping rectangle in the PAINTSTRUCT.
GetClipBox calls NtGdiGetAppClipBox, which calls GdiGetClipBox, which of course returns the smallest logical rect boundary of the current system region, which may be the visible region if GetDC was used, or it may be the system region intersected with a custom clipping region with GetDCEx, or it may be the system region intersected with the window update region when using BeginPaint. Your issue would imply that the system region, when calculated, is now performing special handling for the desktop window in VIS_ComputeVisibleRegion
To actually access the DC directly, and hence the System region, you'd have to start and interact with a driver to do it from the application.
This seems to be a bug in the relevant versions of Windows which has apparently been fixed in more recent versions.
For anybody sailing through countless google pages, blog posts, SO answers... calling:
SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_LAYERED);
seems to do the trick. This doesn't answer how to extend the clipping region over the area restricted by windows, but allows to capture screen correctly which is pretty much the goal anyway. This also is a solution to many other issues, like the taskbar thumbnail not updating, parts of window filckering with black when dragging or bugs when capturing to video file. MSDN doesn't specifically explain that anywhere.
One more thing worth pointing out is that Discord is able to stream a partially offscreen window WITHOUT modifying any of window's attributes, so there's probably more to that...
GDI is not the best way for doing screenshots, often it can't get even completely visible window.
Few months ago I found Youtube video with DWM hacking, which allows you to take screenshot of any window.
Here are sources. Personally I, didn't try to compile and run it.

LoadBitmap fails after multiple calls

In this function, after about 90 calls (it's called in a loop and the idea is to load in a separate image each time, but I have kept it to one image for simplicity).The global variables now changed to local ones.
void CDLP_Printer_ControlDlg::DisplayBMPfromSVG(CString& strDsiplayFile)
{
HBITMAP hbmp_temp = (HBITMAP)::LoadImage(0, strDsiplayFile, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (!hbmp_temp)
{
//hbmp_temp = ::LoadBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_BITMAP1));
ActionList.AddString(L"Bitmap Load Failure: GetBMPromSVG");
ActionList.UpdateWindow();
if (!hbmp_temp)
return;
}
CBitmap bmp_temp;
bmp_temp.Attach(hbmp_temp);
mProjectorWindow.m_picControl.ModifyStyle(0xF, SS_BITMAP, SWP_NOSIZE);
mProjectorWindow.m_picControl.SetBitmap(bmp_temp);
return;
}
I hope someone can come up with an Idea whats wrong. GetLastError returns "8" which means nothing to me.
Detach will destroy the previous handle.
Note that if you call Detach after calling SetBitmap, the picture control's bitmap is destroyed. The result is that the picture control is painted once, but it won't be repainted. For example picture control goes blank if dialog is resized.
EDIT
To destroy the old bitmap, call Detach followed by DestroyObject. Example
HGDIOBJ hbitmap_detach = m_bitmap.Detach();
if (hbitmap_detach)
DeleteObject(hbitmap_detach);
m_bitmap.Attach(hbitmap);
If it's a temporary CBitmap then DeleteObject is not necessary, because DeleteObject is called automatically when CBitmap goes out of scope.
Note that if you destroy the bitmap after calling SetBitmap, the picture control's bitmap is destroyed. The result is that the picture control is painted once, but it won't be repainted. For example picture control goes blank if dialog is resized.
It's the same problem if you declare a temporary CBitmap on stack and attach the bitmap handle. That bitmap handle will be destroyed and picture control can't repaint itself.
In addition, Windows XP sometimes makes duplicate bitmap which needs to be destroyed as well. SetBitmap returns a handle to previous bitmap. In Vista+ the returned bitmap is the same one which was save in m_bitmap, we already destroy that with Detach. But in XP we need to destroy this copy if it is a different handle.
void CMyDialog::foo()
{
HBITMAP save = m_bitmap;
HBITMAP hbitmap = (HBITMAP)::LoadImage(0, filename,
IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
if (hbitmap)
{
HGDIOBJ hbitmap_detach = m_bitmap.Detach();
//Edit ****************************************
//Delete old handle, otherwise program crashes after 10,000 calls
if (hbitmap_detach)
DeleteObject(hbitmap_detach);
//*********************************************
m_bitmap.Attach(hbitmap);
HBITMAP oldbmp = m_picControl.SetBitmap(m_bitmap);
//for Windows XP special case where there might be 2 copies:
if (oldbmp && (oldbmp != save))
DeleteObject(oldbmp);
}
}
Also, SetBitmap takes HBITMAP parameter and returns HBITMAP, so you can avoid using CBitmap altogether. The following example works in Vista+
void foo()
{
HBITMAP temp = (HBITMAP)::LoadImage(0,filename,IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
if (temp)
{
HBITMAP oldbmp = m_picControl.SetBitmap(temp);
if (oldbmp)
DeleteObject(oldbmp);
DeleteObject(temp);
}
}
Your code has several issues, some minor, others are fatal (and the implementation really only appears to work, because the OS is prepared to deal with those common bugs). Here is an annotated listing of the original code:
void CDLP_Printer_ControlDlg::DisplayBMPfromSVG(CString& strDsiplayFile) {
// ^ should be const CString&
HBITMAP hbmp_temp = (HBITMAP)::LoadImage(0, strDsiplayFile, IMAGE_BITMAP, 0, 0,
LR_LOADFROMFILE);
if (!hbmp_temp) {
//hbmp_temp = ::LoadBitmap(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDB_BITMAP1));
ActionList.AddString(L"Bitmap Load Failure: GetBMPromSVG");
ActionList.UpdateWindow();
if (!hbmp_temp)
// You already know, that the condition is true (unless your commented out code
// is supposed to run).
return;
}
CBitmap bmp_temp;
bmp_temp.Attach(hbmp_temp);
// ^ This should immediately follow the LoadImage call, to benefit from automatic
// resource management. (What's the point of using MFC when you decide to implement
// manual resource management on top of it?)
mProjectorWindow.m_picControl.ModifyStyle(0xF, SS_BITMAP, SWP_NOSIZE);
// ^ Use named constants. No one is going to
// look up the documentation just to find
// out, what you are trying to do.
mProjectorWindow.m_picControl.SetBitmap(bmp_temp);
// The GDI object (hbmp_temp) now has two owners, the CBitmap instance bmp_temp, and
// the picture control. At the same time, you are throwing away the handle previously
// owned by the control. This is your GDI resource leak.
return;
// ^ Superfluous. This is merely confusing readers. Remove it.
}
// This is where things go fatal: The bmp_temp d'tor runs, destroying the GDI resource
// hbmp_temp, that's also owned by the control. This should really blow up in your face
// but the OS knows that developers cannot be trusted anymore, and covers your ass.
The two major issues are:
Failure to delete GDI resources owned by your code (the return value of SetBitmap). This eventually causes any attempt to create additional GDI resources to fail.
The dangling pointer (HBITMAP) caused by assigning two owners to the same resource (hbmp_temp).
There's really another issue here as well. Since you assigned two owners to the bitmap resource, this would lead to a double-delete. It doesn't, because you opted against resource cleanup. (The fact that you don't know what you're doing saved you here. Keep that in mind the next time you celebrate your "can do" attitude.)
Following is a version with fixed-up resource management. This won't make any sense to you, since you don't know MFC (or C++ for that matter) well enough to understand, how either one aids in automatic resource management. Anyhow:
void CDLP_Printer_ControlDlg::DisplayBMPfromSVG(CString& strDsiplayFile) {
HBITMAP hbmp_temp = (HBITMAP)::LoadImage(0, strDsiplayFile, IMAGE_BITMAP, 0, 0,
LR_LOADFROMFILE);
// Immediately attach a C++ object, so that resources will get cleaned up
// regardless how the function is exited.
CBitmap bmp_temp;
if (!bmp_temp.Attach(hbmp_temp)) {
// Log error/load placeholder image
return;
}
mProjectorWindow.m_picControl.ModifyStyle(0xF, SS_BITMAP, SWP_NOSIZE);
// Swap the owned resource of bmp_temp with that of the control:
bmp_temp.Attach(mProjectorWindow.m_picControl.SetBitmap(bmp_temp.Detach()));
}
The last line is the critical part. It implements the canonical way to swap raw Windows API resources with resource management wrappers. This is the sequence of operations:
bmp_temp.Detach() releases ownership of the GDI resource.
SetBitmap() passes ownership of the GDI resource to the control, and returns the previous GDI object (if any).
bmp_temp.Attach() acquires ownership of the returned GDI resource. This ensures that the previous resource gets cleaned up, when bmp_temp goes out of scope (at the end of the function).

Copy contents of one DeviceContext to another DeviceContext

I've never done any GDI programming and despite taking several shots in the dark and searching the documentation I haven't found the correct way to do copy the contents of one DC to another DC.
The code I have at the moment is below. I don't understand why it's not working (the window remains just remains blank after creation).
SIZE srcSize;
// ... Get size of source DC
HDC destDC = ...; // from GetDC(myWindow), myWindow was
// sized before this to properly contain source
HDC sourceDC = ...;
HBITMAP buffer = CreateCompatibleBitmap(sourceDC, srcSize.cx, srcSize.cy);
HGDIOBJ oldObj = SelectObject(destDC, buffer);
BitBlt(destDC, 0, 0, srcSize.cx, srcSize.cy, sourceDC, 0, 0, SRCCOPY);
SelectObject(destDC, oldObj);
DeleteObject(buffer);
//... ReleaseDC()s here
What's the proper way this is done?
The only thing necessary to copy from one DC to another is a BitBlt. Code that works is below.
SIZE srcSize;
// ... Get size of source DC
HDC destDC = ...; // from GetDC(myWindow), myWindow was
// sized before this to properly contain source
HDC sourceDC = ...;
BitBlt(destDC, 0, 0, srcSize.cx, srcSize.cy, sourceDC, 0, 0, SRCCOPY);
//... ReleaseDC()s here
It's not very clear to me what you are trying to do. First off, why create the new bitmap and select it into the window (sorry, "client area") DC? All you want is paint/draw the window, isn't it? This is not needed then. The destDC is exactly the window's client area surface.
Does sourceDC really contain anything? For example, does it have a bitmap slected into it?
And of course, you SHOULD process WM_PAINT. If you process this message the window is validated, and you are not required to validate it explicitly. Using GetDC()/ReleaseDC() is called "drawing", as opposed to "painting". In an application I made in the past I had to use both methods, painting (processing WM_PAINT) for responding to resizing, exiting from minimized state and bringing the window to foreground (if previously obscured by another) and drawing, for making certain changes immediately visible (instead of invalidating the window and waiting for the application to nearly enter the idle state first - pls note that WM_PAINT is a low-priority message).
Hope this helps

How to use LoadImage and DeleteObject properly?

I am working on a windows application with C++. I load a bmp file to a DC using LoadImage, and it shows up properly. However, when I call DeleteObject, the memory doesn't seem to be freed. (I use windows task manager to track the memory usage)
In the WM_INITDIALOG part I do this:
static HBITMAP hBitmap = 0;
char* tempPath = "tabView.bmp";
hBitmap = (HBITMAP)LoadImage(NULL,
tempPath, // file containing bitmap
IMAGE_BITMAP, // type = bitmap
0, 0, // original size
LR_LOADFROMFILE); // get image from a file
if(hBitmap)
{
SendMessage(GetDlgItem(hwndDlg, IDC_PICTURE),
STM_SETIMAGE, // message to send
(WPARAM)IMAGE_BITMAP, // bitmap type
(LPARAM)hBitmap); // bitmap handle
}
So the picture shows up in the DC, and memory increases. And in a button I do:
int result = DeleteObject(hBitmap);
When I press the button, I checked the result and it's a non-zero value, which is success. But IDC_PICTURE will still show the picture, and memory stays the same. I am wondering if the SendMessage() may increase the ref count on the hBitmap...
So my question is: What is the proper way to clean up?
You didn't mentioned what version of Windows you are using. Anyway, if you read "Important" part of STM_SETIMAGE, you will see next:
With Windows XP, if the bitmap passed in the STM_SETIMAGE message contains pixels with nonzero alpha, the static control takes a copy of the bitmap. This copied bitmap is returned by the next STM_SETIMAGE message. The client code may independently track the bitmaps passed to the static control, but if it does not check and release the bitmaps returned from STM_SETIMAGE messages, the bitmaps are leaked.
Maybe this applies not only for Windows XP, but for later version of Windows. Hope this will help you.

Why the image disappears?

i have the following code ...
case WM_PAINT:
{
hdc = BeginPaint(hwnd,&paintSt);
temphdc = hdc;
GetClientRect(hwnd,&aRect);
if(hBitmap!=NULL)
{
HDC memDC = CreateCompatibleDC(hdc);
if(memDC!=NULL)
{
BITMAP bmp;
GetObject(hBitmap,sizeof(bmp),&bmp);
SelectObject(memDC,hBitmap);
SetStretchBltMode(hdc,HALFTONE);
StretchBlt(hdc,0,0,aRect.right,aRect.bottom,
memDC,0,0,bmp.bmWidth,bmp.bmHeight,
SRCCOPY);
DeleteObject(&bmp);
ReleaseDC(hwnd,memDC);
}
}
// the code for painting
EndPaint(hwnd,&paintSt);
}
break;
hBitmap is a global variable which is assigned at some place in the code.... Image is displayed but disappears whenever I minimize the window....
can anyone explain this ?
thanks in advance,
Your cleanup code is all wrong, you are leaking handles badly. Should be readily visible in TaskMgr.exe, Processes tab. View + Select Columns and tick GDI Objects. This code stops working when the GDI object handle count reaches 10,000. Yes, likely to happen when you resize the window since there will be a flurry of paint requests.
Don't delete the BITMAP, it is just as struct. Restore the old bitmap handle you got back from SelectObject() before you delete the memDC. Don't use ReleaseDC, DeleteDC is required. Pay attention to the return value of these functions, they tell you when you messed up but that can't work if you never check with an assert.
GDI programming is painful with these explicit cleanup rules. Consider a class library to take care of this kind of drudgery, they never get it wrong.
I guess somehow hBitmap is changing to null while minimize.
Posting the code where you are assigning and referring hBitmap will help to identify the issue I think.