Multithreading a Windows callback function - c

I have a method to set up a window in Windows, written in C. It uses the standard code which looks like this:
WNCDCLASS w1 = {0};
w1.lpszClassName = TEXT( "the_window" );
w1.lpfnWndProc = methodIWantOnItsOwnThread;
// snip
Now, I have been multithreading with the &ltprocess.h> threads in a method such as:
HANDLE h;
h = (HANDLE) _beginthread(methodIWantOnItsOwnThread, 0, 0);
How can I multithread like this in the windows callback function, such as with w1.lpfnWndProc = methodIWantOnItsOwnThread;?
More code.
HWND SetupWindow(int device)
{
HWND hwnd;
WNDCLASS w1 = {0};
if (device == 1)
{
w1.lpszClassName = TEXT( "window 1" );
w1.lpfnWndProc = methodIWantThreaded;
}
else
{
w1.lpszClassName = TEXT( "window 2" );
w1.lpfnWndProc = otherMethodIWantThreaded;
}
w1.hInstance = 0;
w1.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
w1.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClass(&w1);
hwnd = CreateWindow( w1.lpszClassName, TEXT("The Window"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 520, 650, 520, NULL, NULL, 0, NULL);
SetTimer(hwnd, 0,30,NULL);
ShowWindow(hwnd, SW_SHOW);
UpdateWindow(hwnd);
return hwnd;
}

You're going about this backward, and it isn't going to work.
You should be having each window create a thread, not each thread creating a window. Then you simply create multiple instances of the WNDCLASS classname, and each has its own thread that can post and receive messages.
This also gives you a window handle that your main thread's window can use to send and receive messages, allowing you to communicate things to the threads (and vice versa).

Why do you want to do that?
The window proc runs on the thread that created the window handle. Of course, by extension, every thread that creates window handles must have a message loop.

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.

Magnification API: flickering during repaint

I created an application by slightly modifying the Magnification API sample provided by Microsoft to implement a custom transformation of each frame captured and displayed in the Magnifier window.
I used the MagSetImageScalingCallback to set the my function as a callback. The callback is invoked without problem and the source and destination image can be easily manipulated because the raw bits are passed to the callback as pointers (srcdata and destdata).
The window is refreshed with a timer which is set to 16 ms (~60Hz). The refresh is invoked by using the InvalidateRect API. The problem is that when the magnifier window suffer from flickering. This happens especially when the start menu appears, if "Peek" is enabled or every time there is a window on foreground which has dynamic content.
I tried to intercept the WM_ERASEBKGND and invoke the InvalidateRect with FALSE as third parameter but this didn't help. I tried to add an UpdateWindow invocation before the invalidate but nothing changed.
The Magnifier application provided with Windows 10 doesn't have the same problem. I'm wondering why this is happening and how can I get rid of flickering.
In order to reproduce che problem download the Magnification API sample from the link above then replace the content in the file MagnifierSample.cpp with this source code:
// Ensure that the following definition is in effect before winuser.h is included.
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501
#endif
#include <windows.h>
#include <wincodec.h>
#include <magnification.h>
// For simplicity, the sample uses a constant magnification factor.
#define RESTOREDWINDOWSTYLES WS_POPUP
// Global variables and strings.
HINSTANCE hInst;
const TCHAR WindowClassName[]= TEXT("MagnifierWindow");
const TCHAR WindowTitle[]= TEXT("Screen Magnifier Sample");
const UINT timerInterval = 16; // close to the refresh rate #60hz
HWND hwndMag;
HWND hwndHost;
RECT magWindowRect;
RECT hostWindowRect;
// Forward declarations.
ATOM RegisterHostWindowClass(HINSTANCE hInstance);
BOOL SetupMagnifier(HINSTANCE hinst);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void CALLBACK UpdateMagWindow(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
BOOL isFullScreen = FALSE;
//
// FUNCTION: WinMain()
//
// PURPOSE: Entry point for the application.
//
int APIENTRY WinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE /*hPrevInstance*/,
_In_ LPSTR /*lpCmdLine*/,
_In_ int nCmdShow)
{
if (FALSE == MagInitialize())
{
return 0;
}
if (FALSE == SetupMagnifier(hInstance))
{
return 0;
}
ShowWindow(hwndHost, nCmdShow);
UpdateWindow(hwndHost);
// Create a timer to update the control.
UINT_PTR timerId = SetTimer(hwndHost, 0, timerInterval, UpdateMagWindow);
// Main message loop.
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Shut down.
KillTimer(NULL, timerId);
MagUninitialize();
return (int) msg.wParam;
}
//
// FUNCTION: HostWndProc()
//
// PURPOSE: Window procedure for the window that hosts the magnifier control.
//
LRESULT CALLBACK HostWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_ERASEBKGND:
return TRUE;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
//
// FUNCTION: RegisterHostWindowClass()
//
// PURPOSE: Registers the window class for the window that contains the magnification control.
//
ATOM RegisterHostWindowClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex = {};
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = HostWndProc;
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(1 + COLOR_BTNFACE);
wcex.lpszClassName = WindowClassName;
return RegisterClassEx(&wcex);
}
static BOOL CALLBACK myCallBack(HWND hwnd, void *srcdata, MAGIMAGEHEADER srcheader, void *destdata, MAGIMAGEHEADER destheader, RECT unclipped, RECT clipped, HRGN dirty) {
UINT row;
UINT column;
BYTE *matrix;
memset(destdata, 0, destheader.cbSize);
matrix = (BYTE *) destdata;
//Set alpha bits for the resulting image to 255 (fully opaque)
for (row = 0; row < destheader.height; row++) {
for (column = 3; column < destheader.width; column += 4) {
matrix[(row * destheader.width) + column] = 0xFF;
}
}
Sleep(20);
return TRUE;
}
//
// FUNCTION: SetupMagnifier
//
// PURPOSE: Creates the windows and initializes magnification.
//
BOOL SetupMagnifier(HINSTANCE hinst)
{
// Set bounds of host window according to screen size.
hostWindowRect.top = 0;
hostWindowRect.bottom = GetSystemMetrics(SM_CYSCREEN);
hostWindowRect.left = 0;
hostWindowRect.right = GetSystemMetrics(SM_CXSCREEN);
// Create the host window.
RegisterHostWindowClass(hinst);
hwndHost = CreateWindowEx(WS_EX_TOPMOST | WS_EX_LAYERED, WindowClassName, WindowTitle, RESTOREDWINDOWSTYLES, 0, 0, hostWindowRect.right, hostWindowRect.bottom, NULL, NULL, hInst, NULL);
if (!hwndHost)
{
return FALSE;
}
// Make the window opaque.
SetLayeredWindowAttributes(hwndHost, 0, 255, LWA_ALPHA);
SetWindowLong(hwndHost, GWL_EXSTYLE, GetWindowLong(hwndHost, GWL_EXSTYLE) | WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOPMOST | WS_EX_NOACTIVATE);
// Create a magnifier control that fills the client area.
magWindowRect = hostWindowRect;
hwndMag = CreateWindow(WC_MAGNIFIER, TEXT("MagnifierWindow"), WS_CHILD | WS_VISIBLE, magWindowRect.left, magWindowRect.top, magWindowRect.right, magWindowRect.bottom, hwndHost, NULL, hInst, NULL );
if (!hwndMag)
{
return FALSE;
}
MagSetImageScalingCallback(hwndMag, &myCallBack);
// Set the source rectangle for the magnifier control.
MagSetWindowSource(hwndMag, magWindowRect);
return 1;
}
//
// FUNCTION: UpdateMagWindow()
//
// PURPOSE: Sets the source rectangle and updates the window. Called by a timer.
//
void CALLBACK UpdateMagWindow(HWND /*hwnd*/, UINT /*uMsg*/, UINT_PTR /*idEvent*/, DWORD /*dwTime*/)
{
// Reclaim topmost status, to prevent unmagnified menus from remaining in view.
SetWindowPos(hwndHost, HWND_TOPMOST, 0, 0, magWindowRect.right, magWindowRect.bottom, SWP_SHOWWINDOW | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOREDRAW);
// Force redraw.
InvalidateRect(hwndMag, NULL, FALSE);
}
Notice that I added the code to set the callback
BOOL ret = MagSetImageScalingCallback(hwndMag, &myCallBack);
then I created this callback:
static BOOL CALLBACK myCallBack(HWND hwnd, void *srcdata, MAGIMAGEHEADER srcheader, void *destdata, MAGIMAGEHEADER destheader, RECT unclipped, RECT clipped, HRGN dirty)
The Sleep statement "simulate" the time taken by my custom transformation.
If you open a youtube video with Edge and then you run the MagnifierSample.exe executable you should see a black screen flickering sometime and when the screen flickers you should see the content behind the magnifier window (the black screen). This is exactly what is happening in my application. The sleep value is set to 20 but I don't known how long the callback is actually taking. I just guessed that it might take more than 16 ms.
To reproduce the problem perform the following steps:
Run the MagnifierSample.exe manually (don't use the debugger to run it)
You should see a black screen
Press the window key
Set a window with a video or a dynamic content in foreground
Click on the black window to let the taskbar disappear
You should see the screen flashing sometime
Is it possible to set a callback similar to the MagSetImageScalingCallback for a normal window handle ? I know I can use the WindowProc callback to intercept the messages sent to the window however I don't have access to the rawbits unless I use CreateDIBSection, SelectObject etc ...but at that time the image has been already sent to the destination window and I don't have the chance to transform it.

ShellExecuteEx() force window to top z order

I looking for a method for forcing windows to set the program I launch with ShellExecuteEx() in first in z order.
The base of the problem is an issue with a machine (only one) and Google Chrome
When I launch Chrome from ShellExecuteEx() with this parameters :
http://www.google.fr --kiosk --fast-start --chrome-frame
It doesn't display the window, but I see the Chrome icon flashing in the bar.
I've tried to force the window by using EnumWindows() and BringWindowToTop() then SetWindowPos() with *HWND_TOPMOST* flag but this doesn't work for all programs I've tried.
Actualy I get a HWND by comparing pids
BOOL CALLBACK SetWindowToTopFromhProcess(HWND hwnd, LPARAM lParam)
{
HANDLE hProcess = (HANDLE)lParam;
DWORD win_pid = 0;
DWORD pid = 0;
char buf[512];
pid = GetProcessId(hProcess);
GetWindowThreadProcessId(hwnd, &win_pid);
if (pid == win_pid && pid != 0 && win_pid != 0)
{
//SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE);
BringWindowToTop(hwnd);
memset(&buf, 0, sizeof(buf));
GetWindowText(hwnd, buf, sizeof(buf));
printf("%s\n", buf);
return (FALSE);
}
return (TRUE);
}
int my_function()
{
SHELLEXECUTEINFO ExecuteInfo;
...
ExecuteInfo.nShow = SW_SHOWNORMAL;
...
if (ShellExecuteEx(&ExecuteInfo))
EnumWindows(SetWindowToTopFromhProcess, (LPARAM)ExecuteInfo.hProcess);
...
}
By the way, I've realized that the window handle I getting from the pid is not always the handle I want. For example with mspaint.exe the window name I retrieve is "GDI+ Window", so it's not the good window because it's not the good pid.
I've also tried a lot of nShow flags in the SHELLEXECUTEINFO structure who doesn't work.
So how to get the good window handle ? Is there another way to force the program I run to display its main window?
Or any idea ?

catching the WM_DEVICECHANGE

How to become aware of WM_DEVICECHANGE arrival?
WndProc's overwritten. I catch the whole bunch of messages but none of them are of type WM_DEVICECHANGE. RegisterDeviceNotification makes the linker to complain that it can't find the function! So I'm stuck in this voodoo magic. Kindly help.
P.S.: of course I have been googling and stackoverflowing (lol) all this stuff for about 8 hours.
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
LPTSTR lolclassname = "lolclass";
WNDCLASS lolclass;
HWND lolwindow;
MSG lolmsg;
UINT msgstatus;
lolclass.style = CS_VREDRAW;
lolclass.lpfnWndProc = &lol_wnd_proc;
lolclass.cbClsExtra = 0;
lolclass.cbWndExtra = 0;
lolclass.hInstance = hInstance;
lolclass.hIcon = NULL;
lolclass.hCursor = NULL;
lolclass.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
lolclass.lpszMenuName = NULL;
lolclass.lpszClassName = lolclassname;
if(!RegisterClass(&lolclass)) fail("RegisterClassEx");
lolwindow = CreateWindow("lolclass", NULL, WS_MINIMIZE, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
HWND_MESSAGE, NULL, hInstance, NULL);
if(lolwindow == NULL) fail("CreateWindowEx");
/*ShowWindow(lolwindow, nCmdShow);
UpdateWindow(lolwindow);*/
do {
/* if(!SetWindowPos(lolwindow, HWND_TOPMOST, 1, 1, 1, 1,
SWP_HIDEWINDOW))
fail("SetWindowPos");*/
msgstatus = GetMessage(&lolmsg, lolwindow, 0, 0);
if(!msgstatus) break;
if(msgstatus == - 1) fail("GetMessage");
TranslateMessage(&lolmsg);
DispatchMessage(&lolmsg);
Sleep(1000);
} while(1);
return lolmsg.wParam;
}
lol_wnd_proc is executed but never when it supposed to (on device change of course, am I clear?)
The problem is that you are creating a message-only window which does not receive broadcasts:
A message-only window enables you to send and receive messages. It is not visible, has no z-order, cannot be enumerated, and does not receive broadcast messages. The window simply dispatches messages.
So, you cannot use a message-only window and instead need to make a top-level window that is never shown. That's trivial to achieve — stop passing HWND_MESSAGE to CreateWindow and make sure that you never call ShowWindow.
As an aside, Sleep(1000) in the middle of a message loop is going to be a disaster. You need to pump messages in a timely fashion, not fall asleep on the job. You must get rid of that Sleep. Note that GetMessage will block if the queue is empty, so you don't need to worry about your application running hot.
Your message loop should look like this:
while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

Displaying a bitmap on a "BUTTON" class window in WIN32

Edit: I think the WM_CREATE message isn't sent during the creation of child windows (namely my button). So by calling SendMessage during WM_CREATE, I'm sending a message to a window that hasn't been created yet. The solution for now is to call SendMessage() during the WM_SHOWWINDOW message. Do child windows send WM_CREATE messages at creation?
Why isn't the bitmap displaying on the button? The bitmap is 180x180 pixels.
I have a resource file with:
Bit BITMAP bit.bmp
I then create the main window and a child "BUTTON" window with:
HWND b, d;
b = CreateWindow(L"a", NULL, WS_OVERLAPPEDWINDOW, 0, 0, 500, 500, 0, 0,
hInstance, 0);
d = CreateWindow(L"BUTTON", NULL, WS_CHILD | WS_VISIBLE | BS_BITMAP,
10, 10, 180, 180, b, 200, hInstance, 0);
Then, in my windows procedure, I send the "BUTTON" window the "BM_SETIMAGE" message with:
HBITMAP hbit;
case WM_CREATE: // It works if I change this to: case WM_SHOWWINDOW
hbit = LoadBitmap(hInstance, L"Bit");
SendMessage(d, BM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)hbit);
LoadBitmap() is returning a valid handle because It isn't returning NULL, and I'm able to display the bitmap in the client area using the BitBlt() function. So I'm either not sending the message correctly, or I'm not creating the "BUTTON" window correctly.
What am I doing wrong?
Thanks!
The window procedure for for your window class "a" gets called with WM_CREATE when a window of that class is created. This is during your first call to CreateWindow, which is before you create the child BUTTON window. WM_CREATE means "you are being created" - it doesn't mean "a child is being created".
The solution is to call d = CreateWindow(L"BUTTON"...) in the WM_CREATE handler for class "a":
case WM_CREATE:
d = CreateWindow(L"BUTTON", NULL, WS_CHILD | WS_VISIBLE | BS_BITMAP,
10, 10, 180, 180, hwnd, 200, hInstance, 0);
hbit = LoadBitmap(hInstance, L"Bit");
SendMessage(d, BM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)hbit);
How are you verifying that WM_CREATE isn't getting called? Since BUTTON isn't your window class (but rather defined by the OS) it owns the WndProc for the window, not you - therefore WM_CREATE shouldn't be called for the button in your code, because BUTTON isn't your class.
If you want to receive messages for the button, you'll have to subclass it, and then provide your own WndProc.

Resources