Handle tab stop using DispatchMessage() without blocking the thread through message loop? - c++

I have a function export OpenUI() in a DLL for UI view which creates the modeless main dialog and also has a modeless child dialog.
I am calling the function export OpenUI() from a separate DLL which is my controller.
How can I possibly execute more code after the function call if the message loop in OpenUI() prevents the function to return unless the dialog is closed?
I cannot remove the message loop because tab stop will not work without it.
I need the function export to return immediately after execution therefore I cannot use a modal dialog. Creating a subthread is also not an option because it caused issues in my application.
Any help is highly appreciated.
Thank you.
Pseudocode for my controller dll
typedef int(*DLL_OPENUI)();
int func()
{
HINSTANCE hinst_dll = LoadLibrary(dll_path);
DLL_OPENUI DllOpenUI = (DLL_OPENUI)GetProcAddress(hinst_dll, "OpenUI");
int ret = DllOpenUI();
//...execute more code here
return ret;
}
Pseudocode for my UI view dll
__declspec(dllexport) OpenUI()
{
hwnd_main = CreateDialog(hinst, IDD_MAIN, MainDlgProc);
ShowWindow(hwnd_main, SW_SHOW);
MSG msg;
while ((GetMessage(&msg, NULL, 0, 0) > 0))
{
if (!IsDialogMessage(hwnd, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return 0;
}
LRESULT CALLBACK MainDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_INITDIALOG:
OnInitDialog();
break;
}
}
void OnInitDialog()
{
CreateDialog(hinst, IDD_NAV_PANE, hwnd_main, NavPaneProc);
CreateDialog(hinst, IDD_NAV_TABS, hwnd_main, NavTabsProc);
CreateDialog(hinst, IDD_TAB_1, hwnd_main, TabOneProc);
CreateDialog(hinst, IDD_TAB_2, hwnd_main, TabTwoProc);
CreateDialog(hinst, IDD_TAB_3, hwnd_main, TabThreeProc);
CreateDialog(hinst, IDD_DETAILS_PANE_BG, hwnd_main, BackgroundProc);
CreateDialog(hinst, IDD_DETAILS_PANE, hwnd_main, DetailsPaneProc);
//...execute more code below
}

You have to have an active message loop, there's no way around that. One way to do it is to have one new function that would PeekMessage and execute the loop code only once. PeekMessage returns non-zero value if subsequent call to GetMessage would actually get a message and not block instead. ProcessOneMessage may look something like this:
BOOL ProcessOneMessage(HWND hwnd)
{
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
return TRUE;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return FALSE;
}
So, in your main code you have to call this function often (once every 10 ms should be fine). Whatever the code does it must call the function once in 10 ms. You will have a live window and run your code at the same time. But once the function returns TRUE the window was closed and you must not call the function again. For more info search for PeekMessage. The code is taken from this link: https://jeffpar.github.io/kbarchive/kb/074/Q74042/

Related

PeekMessage always returns FALSE

I have written a small test application that inserts files (with hardcoded paths) into the currently active folder/application via delayed rendering. It works as expected. But I have a question - why does PeekMessage always return FALSE? But if you remove the PeekMessage call, Wndproc will never be called. I read a similar post, but I'm creating a window in the same thread in which I'm trying to process messages.
Code:
static LRESULT CALLBACK WindProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
switch (Msg) {
case WM_RENDERALLFORMATS: {
OpenClipboard(hWnd);
EmptyClipboard();
}
case WM_RENDERFORMAT: {
printf("WM_RENDERFORMAT received");
<Here the file paths are copied to the clipboard>
if (Msg == WM_RENDERALLFORMATS)
CloseClipboard();
return 0;
}
case WM_DESTROYCLIPBOARD:
return 0;
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
HWND hwnd_;
void thread_(void* ignored) {
WNDCLASSEX wcx = { 0 };
wcx.cbSize = sizeof(WNDCLASSEX);
wcx.lpfnWndProc = WindProc;
wcx.hInstance = GetModuleHandle(NULL);
wcx.lpszClassName = TEXT("my_class");
RegisterClassEx(&wcx);
hwnd_ = CreateWindowEx(0, TEXT("my_class"), TEXT(""), 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, GetModuleHandle(NULL), NULL);
MSG msg;
while (true) {
if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
printf("PeekMessage returned TRUE\n");
TranslateMessage(&msg);
DispatchMessage(&msg);
break;
}
Sleep(1000);
}
}
void main() {
CloseHandle((HANDLE)_beginthread(thread_, 0, NULL));
// let's give some time to thread to create msg window
Sleep(100);
if (OpenClipboard(hwnd_)) {
EmptyClipboard();
SetClipboardData(CF_HDROP, NULL);
CloseClipboard();
}
while (true) {
Sleep(100);
}
}
PeekMessage() (and GetMessage()) only returns messages that are posted via PostMessage() or PostThreadMessage() to the calling thread's message queue. PeekMessage() returns FALSE when there is no posted message available at the moment it is called. GetMessage() blocks until a posted message becomes available.
However, the messages in question (WM_RENDERFORMAT and WM_RENDERALLFORMATS) are instead being sent via SendMessage...() directly to the target window. But, the messages are being sent by another thread, so PeekMessage() (or GetMessage()) is still needed, as they internally dispatch messages that are sent across thread boundaries.
This is stated in PeekMessage()'s documentation:
Dispatches incoming nonqueued messages, checks the thread message queue for a posted message, and retrieves the message (if any exist).
...
During this call, the system dispatches (DispatchMessage) pending, nonqueued messages, that is, messages sent to windows owned by the calling thread using the SendMessage, SendMessageCallback, SendMessageTimeout, or SendNotifyMessage function. Then the first queued message that matches the specified filter is retrieved. The system may also process internal events. If no filter is specified, messages are processed in the following order:
Sent messages
Posted messages
Input (hardware) messages and system internal events
Sent messages (again)
WM_PAINT messages
WM_TIMER messages
As well as the GetMessage() documentation:
Retrieves a message from the calling thread's message queue. The function dispatches incoming sent messages until a posted message is available for retrieval.
...
During this call, the system delivers pending, nonqueued messages, that is, messages sent to windows owned by the calling thread using the SendMessage, SendMessageCallback, SendMessageTimeout, or SendNotifyMessage function. Then the first queued message that matches the specified filter is retrieved. The system may also process internal events. If no filter is specified, messages are processed in the following order:
Sent messages
Posted messages
Input (hardware) messages and system internal events
Sent messages (again)
WM_PAINT messages
WM_TIMER messages
And SendMessage()'s documentation:
If the specified window was created by the calling thread, the window procedure is called immediately as a subroutine. If the specified window was created by a different thread, the system switches to that thread and calls the appropriate window procedure. Messages sent between threads are processed only when the receiving thread executes message retrieval code. The sending thread is blocked until the receiving thread processes the message. However, the sending thread will process incoming nonqueued messages while waiting for its message to be processed. To prevent this, use SendMessageTimeout with SMTO_BLOCK set. For more information on nonqueued messages, see Nonqueued Messages.
With that said, your handling of the WM_RENDERALLFORMATS message is wrong. For one thing, it must not call EmptyClipboard(), and for another, it is not checking whether your app is still the clipboard owner before rendering its data. See What is the proper handling of WM_RENDERFORMAT and WM_RENDERALLFORMATS? for why these points are important.
Also, you have a race condition in your main thread. Before it calls OpenClipboard(), it sleeps for only 100ms to wait for the window to be created first, but there is no guarantee that hwnd_ will have been assigned within that 100ms. It could take that long just for the worker thread to start running, for instance.
A better option is to have the worker thread signal an event when the window is created, and then have the main thread wait on that event (even better would be to simply move the initial SetClipboardData() call into the worker thread itself after creating the window, but you have dismissed that option).
Try something more like this instead:
static LRESULT CALLBACK WindProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
/*
case WM_CREATE:
if (OpenClipboard(hWnd)) {
EmptyClipboard();
SetClipboardData(CF_HDROP, NULL);
CloseClipboard();
}
return 0;
}
*/
case WM_RENDERALLFORMATS: {
if (OpenClipboard(hWnd)) {
if (GetClipboardOwner() == hWnd) {
SendMessage(hWnd, WM_RENDERFORMAT, CF_HDROP, 0);
}
CloseClipboard();
}
return 0;
}
case WM_RENDERFORMAT: {
printf("WM_RENDERFORMAT received");
if (wParam == CF_HDROP) {
// <Here the file paths are copied to the clipboard>
}
return 0;
}
case WM_DESTROYCLIPBOARD:
return 0;
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
HWND hwnd_ = NULL;
void thread_(void* arg) {
HANDLE hEvent = (HANDLE)arg;
WNDCLASSEX wcx = { 0 };
wcx.cbSize = sizeof(WNDCLASSEX);
wcx.lpfnWndProc = WindProc;
wcx.hInstance = GetModuleHandle(NULL);
wcx.lpszClassName = TEXT("my_class");
RegisterClassEx(&wcx);
hwnd_ = CreateWindowEx(0, TEXT("my_class"), TEXT(""), 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, GetModuleHandle(NULL), NULL);
SetEvent(hEvent);
if (hwnd_ == NULL) {
return 0;
}
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
printf("GetMessage returned a message\n");
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
int main() {
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!hEvent)
return 1;
uintptr_t res = _beginthread(thread_, 0, hEvent);
if (res == -1L) {
CloseHandle(hEvent);
return 1;
}
WaitForSingleObject(hEvent, INFINITE);
CloseHandle(hEvent);
if (hwnd_ != NULL) {
if (OpenClipboard(hwnd_)) {
EmptyClipboard();
SetClipboardData(CF_HDROP, NULL);
CloseClipboard();
}
}
HANDLE hThread = (HANDLE)res;
WaitForSingleObject(hThread, INIFINTE);
CloseHandle(hThread);
return 0;
}
why does PeekMessage always return FALSE?
Assuming that the window handle that you pass to PeekMessage is valid, then the reason for PeekMessage returning FALSE is simply that there are no messages in the queue.
That this is the case can be discerned from the documentation which says:
If a message is available, the return value is nonzero.
If no messages are available, the return value is zero.

How to handle WM_ENDSESSION in a console app

I wrote a program to register mouse events, and I want it to terminate when the computer is shutdown (and then perform a flush and a final print).
I tried with a CtrlHandler, but it works only with Ctrl-C and not when the system is shutdown, because I am using a Win32 library, according to MSDN:
If a console application loads the gdi32.dll or user32.dll library, the HandlerRoutine function that you specify when you call SetConsoleCtrlHandler does not get called for the CTRL_LOGOFF_EVENT and CTRL_SHUTDOWN_EVENT events. The operating system recognizes processes that load gdi32.dll or user32.dll as Windows applications rather than console applications. This behavior also occurs for console applications that do not call functions in gdi32.dll or user32.dll directly, but do call functions such as Shell functions that do in turn call functions in gdi32.dll or user32.dll.
To receive events when a user signs out or the device shuts down in these circumstances, create a hidden window in your console application, and then handle the WM_QUERYENDSESSION and WM_ENDSESSION window messages that the hidden window receives. You can create a hidden window by calling the CreateWindowEx method with the dwExStyle parameter set to 0.
So, first I have to create a hidden window, and then I have to intercept the the WM_ENDSESSION message. But how?
I tried to read some examples, but I can't figure out how to do this.
Here is my code:
BOOL WINAPI CtrlHandler(DWORD fdwCtrlType)
{
switch (fdwCtrlType)
{
// Handle the CTRL-C signal.
/*case CTRL_C_EVENT:
printf("Ctrl-C event\n\n");
Beep(750, 300);
return FALSE; //TRUE
// CTRL-CLOSE: confirm that the user wants to exit.
case CTRL_CLOSE_EVENT:
Beep(600, 200);
printf("Ctrl-Close event\n\n");
return FALSE; //TRUE
// Pass other signals to the next handler.
case CTRL_BREAK_EVENT:
Beep(900, 200);
printf("Ctrl-Break event\n\n");
return FALSE;
*/case CTRL_LOGOFF_EVENT:
Beep(1000, 200);
printf("Ctrl-Logoff event\n\n");
myfile << "totale :" << tot;
myfile.flush();
myfile.close();
return TRUE; //FALSE
case CTRL_SHUTDOWN_EVENT:
Beep(750, 500);
printf("Ctrl-Shutdown event\n\n");
myfile << "totale :" << tot;
myfile.flush();
myfile.close();
return TRUE; //FALSE
default:
return FALSE;
}
}
int main(){
if (SetConsoleCtrlHandler(CtrlHandler, TRUE))
{
printf("\nThe Control Handler is installed.\n");
for(;;)
{
code that print the mouse event(........)
}
}
else
{
printf("\nERROR: Could not set control handler");
return 1;
}
return 0;
}
You can just create a hidden GUI window in console application and handle WM_ENDSESSION in the window procedure as shown below
#include <Windows.h>
HWND g_hidden_window = nullptr;
LRESULT CALLBACK wnd_proc(HWND, UINT, WPARAM, LPARAM);
// Main entry point of your app
int main() {
HMODULE current_instance = ::GetModuleHandle(L"");
// Register the window class
WNDCLASSEX window_class_ex = { 0 };
window_class_ex.cbSize = sizeof(WNDCLASSEX);
window_class_ex.lpfnWndProc = wnd_proc;
window_class_ex.lpszClassName = L"Foo";
window_class_ex.hInstance = current_instance;
if (!::RegisterClassEx(&window_class_ex)) {
return 1;
}
// Create an overlapped window
g_hidden_window = ::CreateWindow(
L"Foo",
L"",
WS_OVERLAPPED,
0, 0, 0, 0,
nullptr,
nullptr,
current_instance,
0);
if (!g_hidden_window) {
return 1;
}
MSG message;
// Main message loop
while (::GetMessage(&message, nullptr, 0, 0)) {
::DispatchMessage(&message);
}
}
Now, in your main window procedure, you should handle WM_ENDSESSION. In your case, I see no reason to handle WM_QUERYENDSESSION. You should also handle WM_CLOSE and/or WM_DESTROY to quit the main message loop:
// Main window procedure
LRESULT CALLBACK wnd_proc(HWND window_handle, UINT window_message, WPARAM wparam, LPARAM lparam) {
switch (window_message) {
case WM_ENDSESSION:
if(wparam) {
// According to MSDN this value will be 1 when the system is about to shut down: https://learn.microsoft.com/en-us/windows/win32/shutdown/wm-endsession
// Invoke your function here
CtrlHandler(CTRL_SHUTDOWN_EVENT);
}
break;
case WM_CLOSE:
DestroyWindow(window_handle);
break;
case WM_DESTROY:
::PostQuitMessage(0);
break;
default:
return ::DefWindowProc(window_handle, window_message, wparam, lparam);
}
return 0;
}
To gracefully shut down the app, you will have to break that message loop. To do so, you will have to send a WM_CLOSE message:
SendMessage(g_hidden_window, WM_CLOSE, 0, 0);
Or, explicitly destroy the window by calling:
DestroyWindow(g_hidden_window);
Let me know if it works. I have not tested it because I'm on a Mac right now, but it should work.

SendNotifyMessage do not send correct messages

From one thread I send the message to main thread in window procedure.
But it is unsuccessful. When I send messages from the same thread - all is ok
include "stdafx.h"
#include <Windows.h>
#include <atlbase.h>
#define MAX_THREADS 1
HWND m_wnd;
enum
{
EVENT_CALL = (WM_APP + 0x30),
};
static LRESULT CALLBACK function_call()
{
//some code
int test = 0;
return 0;
}
static LRESULT CALLBACK http_message_proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case EVENT_CALL:
function_call();
return 0;
}
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
void CreateNotifyWnd()
{
WNDCLASSEX w = { 0 };
w.cbSize = sizeof(w);
w.hInstance = (HINSTANCE)&__ImageBase;
w.lpszClassName = L"uistone_http_event_wnd";
w.lpfnWndProc = http_message_proc;
::RegisterClassEx(&w);
int error = GetLastError();
m_wnd = ::CreateWindowEx(0, w.lpszClassName, NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, w.hInstance, 0);
error = GetLastError();
}
DWORD WINAPI SendThread(void* request_param)
{
::SendNotifyMessage(m_wnd, EVENT_CALL, 11, 12);
int error = GetLastError();
return 0;
}
int main()
{
CreateNotifyWnd();
HANDLE hThreadArray[MAX_THREADS];
hThreadArray[0] = CreateThread(nullptr, 0, SendThread, nullptr, 0, nullptr);
//::SendNotifyMessage(m_wnd, EVENT_CALL, 11, 12);
WaitForMultipleObjects(MAX_THREADS, hThreadArray, TRUE, INFINITE);
return 0;
}
Why I can not catch messages from another thread?
Thanks.
This is documented behavior. This is the relevant part from the SendNotifyMessage documentation:
If the window was created by the calling thread, SendNotifyMessage calls the window procedure for the window and does not return until the window procedure has processed the message. If the window was created by a different thread, SendNotifyMessage passes the message to the window procedure and returns immediately; it does not wait for the window procedure to finish processing the message.
This appears to work when used with a window created on the same thread, because when you call SendNotifyMessage, the function synchronously calls into the window procedure associated with the target window before returning.
If the call crosses threads, on the other hand, you'd have to run a message loop for the - now queued - message to get picked up and passed to the window procedure1). Your application doesn't run a message loop, and it exits before the message ever reaches the target window.
To fix this you'd have to run a message loop. This may or may not be the right approach to your problem. Since we don't know, what problem you are trying to solve, we cannot suggest potentially superior approaches and solutions.
1) See About Messages and Message Queues: Message Routing.

Can't Remove WM_TIMER message

I have a problem with my code, and after several hours, I can't seem to figure it out...
Problem: I'm trying to attempt to connect to a server every ten seconds. When the timer first elapses, the callback function is called just fine, but then it is called again immediately (without waiting ten seconds), and it keeps being called again and again and again repeatedly, as if the timer message is not getting removed from the queue. Can anyone help?
The timer is set here:
SConnect::SConnect()
{
hSimConnect = NULL;
Attempt();
SetTimer(hMainWindow, reinterpret_cast<UINT_PTR>(this), 10000, (TIMERPROC)TimerProc);
}
The (only) application message loop is here:
while (true)
{
PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
if (msg.message == WM_QUIT)
break;
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//UPDATES
pSCObj->Update();
manager.Update();
Sleep(50);
}
And the timer callback function is here:
void CALLBACK SConnect::TimerProc(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime){
SConnect *pSC = reinterpret_cast<SConnect *>(idEvent);
MSG wMsg;
if (!pSC->connected)
pSC->Attempt();
else{
pSC->connected = false;
}
}
I really appreciate any help... Please let me know if you need more info...
Sincerely,
Farley
I think this is happening because you are calling PeekMessage() in your message loop instead of GetMessage().
PeekMessage() will return FALSE if there are no messages in the queue. I don't know what the implementation of PeekMessage() is, but I could see it leaving the contents of the msg parameter alone if there are no messages in the queue. This means that when PeekMessage() returns FALSE, msg will contain the previous message in the queue. msg is then blindly passed to DispatchMessage(), which then dutifully will pass that to your window's window procedure. So as long as the WM_TIMER message is the last processed message, your timer callback will be called until another message is added to the queue.
You can fix this by using a more traditional message loop:
BOOL bRet;
while( (bRet = GetMessage( &msg, nullptr, 0, 0 )) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
pSCObj->Update();
manager.Update();
}
(Message loop adapted from the sample in the GetMessage() documentation.)
Since GetMessage() will block until there is a message in the queue, you don't need to call Sleep(), and your process will not use any CPU when there is nothing for it to do.
Problem is that the return value of PeekMessage() is not checked. On each loop, if there is no new message, PeekMessage leaves the contents of msg unchanged, and it is simply dispatched again.
Dispatching the messages only when PeekMessage() returns true fixes the problem:
while (true)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
if (msg.message == WM_QUIT)
break;
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
//UPDATES
pSCObj->Update();
manager.Update();
Sleep(50);
}

Browse For Folder dialog window handle C++

How to get the handle HWND of the dialog which user open when clicking on a button.
I'm using Spy++ to find the window class and tittle, but it says that no such window is found. And how then to get the handle of that dialog in C++ using Win API ?
I hope that I will be able to do that using simple functions as FindWindow, GetParent, any WIN APi function. I do not like to inject something or load DLL. Thanks
UPDATE:
the folder browser dialog is opened by other program. I want to get it's handle from different program , my program. Thanks.
The closest to want i need is for now the function WindowFromPoint
Accessibility will let you capture window creation events from other processes without DLL injection. You can modify the example to accommodate for the browsing window specifically. Here's an example I made previously to test that is based on the one from the article. Modify it however you wish:
#include <iostream>
#include <windows.h>
void CALLBACK proc(HWINEVENTHOOK hook, DWORD event, HWND hwnd, LONG obj, LONG child, DWORD thr, DWORD time) {
if (hwnd && obj == OBJID_WINDOW && child == CHILDID_SELF) {
switch (event) {
case EVENT_OBJECT_CREATE: {
std::cout << "Window created!\n";
break;
}
case EVENT_OBJECT_DESTROY: {
std::cout << "Window destroyed!\n";
break;
}
}
}
}
int main() {
HWINEVENTHOOK hook = SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_DESTROY, nullptr, proc, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (hook) {
UnhookWinEvent(hook);
}
}