Out-of-context SetWinEventHook() not getting any events

Issue

Why is this program not getting any events ?

This is a global (out-of-context) hook (with its message loop) that’s configured for [EVENT_MIN , EVENT_MAX], it should drown in events.

Tried both Console & Window types :

#include <windows.h>
#include <iostream>
#include <thread>

using namespace std;

HWINEVENTHOOK g_hook;
bool stop = false;

// Callback function that handles events.
void CALLBACK HandleWinEvent(HWINEVENTHOOK hook, DWORD event, HWND hwnd, 
  LONG idObject, LONG idChild, 
  DWORD dwEventThread, DWORD dwmsEventTime)
{
  cout<<"Got event "<<event<<'\n'<<std::flush;
}

int EventHook() {

  cout <<"thread EventHook running\n"<<std::flush;
  g_hook = SetWinEventHook(
    EVENT_MIN , EVENT_MAX,  // Range of events.
    NULL,                   // Handle to DLL.
    HandleWinEvent,         // The callback.
    0, 0,                   // Process and thread IDs of interest (0 = all)
    WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); // Flags.
  cout <<"Hooked\n"<<std::flush;

  MSG msg;
  while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
    if(stop) return 0;
  }
  return 0;
}

// Both console & window are eventless
//int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){
//  int argc;
//  auto *argv = CommandLineToArgvW(GetCommandLine(), &argc);
int main(int argc, char **argv){

  std::thread t1(EventHook); cout<<"start thread EventHook\n"<<std::flush;
  //Sleep(std::wcstoul(argv[1],nullptr,10) * 1000);
  Sleep(std::strtoul(argv[1],nullptr,10) * 1000);

  stop = true;
  t1.join();

  UnhookWinEvent(g_hook);
  return 0;
}

Any help appreciated. Thank you.

** Answer **

Based on ideas by @Remy Lebeau :

  • The hook should be removed by the thread it installed it
  • Use GetMessage, not PeekMessage
  • Not for a message loop though, a less expensive Event (or even lighter WaitOnAddress() from Win8 on) is enough to signal thread stop.
#include <windows.h>
#include <iostream>
#include <thread>

using namespace std;

HANDLE gEventStop;

// Callback function that handles events.
void CALLBACK HandleWinEvent(HWINEVENTHOOK hook, DWORD event, HWND hwnd, 
  LONG idObject, LONG idChild, DWORD dwEventThread, DWORD dwmsEventTime)
{
  cout<<"Got event "<<event<<'\n'<<std::flush;
}

int EventHook() {

  cout <<"thread EventHook running\n"<<std::flush;
  hook = SetWinEventHook(
    EVENT_OBJECT_CREATE , EVENT_OBJECT_CREATE,  // Range of events.
    NULL,                   // Handle to DLL.
    HandleWinEvent,         // The callback.
    0, 0,                   // Process and thread IDs of interest (0 = all)
    WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); // Flags.
  cout <<"Monitoring started\n"<<std::flush;

  // Create a message queue, or the hook won't receive any events
  MSG msg;
  GetMessage(&msg, NULL, 0, 0);  // returns on WM_QUIT
  // Wait for stop order
  if(WAIT_FAILED==WaitForSingleObject(gEventStop, INFINITE)) 
    cerr<<"Error WaitForSingleObject\n";
  UnhookWinEvent(hook); cout<<"Monitoring stopped\n";
  return 0;
}

int main(int argc, char **argv){

  gEventStop = CreateEventA(NULL, TRUE /*manual-reset event*/, FALSE/*nonsignaled*/, "stop hook");
  if(!gEventStop){ cerr<<"Error CreateEventA\n"; return 6; }

  std::thread t1(EventHook);
  Sleep(std::strtoul(argv[1],nullptr,10) * 1000);

  // Make sure GetMessage() doesn't block for ever
  PostThreadMessage(GetThreadId(t1.native_handle()), WM_QUIT, 0, 0);
  
  if(!SetEvent(gEventStop)) cerr<<"Error SetEvent\n"; // terminate t1, CloseHandle, ret
  t1.join();

  CloseHandle(gEventStop);
  return 0;
}

Solution

PeekMessage() is a non-blocking function. It always exits immediately, returning TRUE if a message was retrieved, otherwise returning FALSE. So, as soon as your thread’s message queue is empty (as it likely is initially), PeekMessage() will return FALSE causing your loop to end, and thus your program to exit.

In other words, your program is not likely to run long enough to receive any events.

You need to use GetMessage() instead. It will allow your loop to block your thread waiting for new messages to arrive.

However, that means your stop variable by itself is insufficient to end your thread, as you might not receive new messages for awhile (if at all) after you have changed the variable’s value. The loop may be blocked on GetMessage() and not see the variable’s new value right away (if at all).

You can work around that by using PostMessage() to post a benign message (like WM_NULL) to the thread’s message queue to "wake up" GetMessage() if it is blocked. Or, just get rid of the stop variable altogether and post a WM_QUIT message to your thread’s message queue, which will cause GetMessage() to return 0.

Also, you need to call UnhookWinEvent() in the same thread that calls SetWinEventHook().

Try something more like this:

#include <windows.h>
#include <iostream>
#include <thread>

using namespace std;

HWINEVENTHOOK g_hook;
bool stop = false;
DWORD g_tid = 0;

// Callback function that handles events.
void CALLBACK HandleWinEvent(HWINEVENTHOOK hook, DWORD event, HWND hwnd, 
  LONG idObject, LONG idChild, 
  DWORD dwEventThread, DWORD dwmsEventTime)
{
  cout<<"Got event "<<event<<'\n'<<std::flush;
}

int EventHook() {

  g_tid = GetCurrentThreadId();
  WakeByAddressSingle(&g_tid);

  cout <<"thread EventHook running\n"<<std::flush;
  g_hook = SetWinEventHook(
    EVENT_MIN , EVENT_MAX,  // Range of events.
    NULL,                   // Handle to DLL.
    HandleWinEvent,         // The callback.
    0, 0,                   // Process and thread IDs of interest (0 = all)
    WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); // Flags.
  if (!g_hook) {
    cout <<"Error in Hook\n"<<std::flush;
    return;
  }
  cout <<"Hooked\n"<<std::flush;

  MSG msg;
  while (GetMessage(&msg, NULL, 0, 0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
    if (stop) break;
  }

  UnhookWinEvent(g_hook);
  return 0;
}

// Both console & window are eventless
//int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){
//  int argc;
//  auto *argv = CommandLineToArgvW(GetCommandLine(), &argc);
int main(int argc, char **argv){

  std::thread t1(EventHook);
  cout<<"start thread EventHook\n"<<std::flush;

  DWORD tid = 0;
  WaitOnAddress(&g_tid, &tid, sizeof(DWORD), INFINITE);

  //Sleep(std::wcstoul(argv[1],nullptr,10) * 1000);
  Sleep(std::strtoul(argv[1],nullptr,10) * 1000);

  PostMessage(g_tid, WM_NULL, 0, 0);
  t1.join();

  return 0;
}

Alternatively:

#include <windows.h>
#include <iostream>
#include <thread>

using namespace std;

HWINEVENTHOOK g_hook;
DWORD g_tid = 0;

// Callback function that handles events.
void CALLBACK HandleWinEvent(HWINEVENTHOOK hook, DWORD event, HWND hwnd, 
  LONG idObject, LONG idChild, 
  DWORD dwEventThread, DWORD dwmsEventTime)
{
  cout<<"Got event "<<event<<'\n'<<std::flush;
}

int EventHook() {

  g_tid = GetCurrentThreadId();
  WakeByAddressSingle(&g_tid);

  cout <<"thread EventHook running\n"<<std::flush;
  g_hook = SetWinEventHook(
    EVENT_MIN , EVENT_MAX,  // Range of events.
    NULL,                   // Handle to DLL.
    HandleWinEvent,         // The callback.
    0, 0,                   // Process and thread IDs of interest (0 = all)
    WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS); // Flags.
  if (!g_hook) {
    cout <<"Error in Hook\n"<<std::flush;
    return;
  }
  cout <<"Hooked\n"<<std::flush;

  MSG msg;
  while (GetMessage(&msg, NULL, 0, 0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }

  UnhookWinEvent(g_hook);
  return 0;
}

// Both console & window are eventless
//int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){
//  int argc;
//  auto *argv = CommandLineToArgvW(GetCommandLine(), &argc);
int main(int argc, char **argv){

  std::thread t1(EventHook);
  cout<<"start thread EventHook\n"<<std::flush;

  DWORD tid = 0;
  WaitOnAddress(&g_tid, &tid, sizeof(DWORD), INFINITE);

  //Sleep(std::wcstoul(argv[1],nullptr,10) * 1000);
  Sleep(std::strtoul(argv[1],nullptr,10) * 1000);

  PostMessage(g_tid, WM_QUIT, 0, 0);
  t1.join();

  return 0;
}

Answered By – Remy Lebeau

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published