Search code examples
c++winapiantiviruswindow-messages

Why doesn't my Windows API callback function run?


I've written a code that terminates egui.exe. But my callback function doesn't run after the WM_CLOSE message is processed. Why?

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

using namespace std;

HWND ESETWindow = 0;    //Variable to store a handle to the main window
                        // of ESET Smart Security
HWND ConfirmationWindow = 0;    //Variable to store a handle to the
                                // confirmation dialog box that appears
                                // when a WM_CLOSE message is sent to
                                // the ESET window.
DWORD EGUIPID = 0;  //Variable to store the process identifier of egui.exe
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam);
VOID SendAsyncProc(HWND hwnd, UINT uMsg, ULONG_PTR dwData, LRESULT lResult);

int main()
{
    cout << "Terminating ESET Smart Security..." << endl;

    //Obtain an array of process identifiers including the PID of egui.exe
    DWORD PIDs[1024]; //The array of process identifiers
    DWORD bytesReturned;
    if (!EnumProcesses(PIDs, sizeof(PIDs), &bytesReturned))
    {
        cerr << "Unable to enumerate the processes." << endl;
        return 0;
    }

    //Enumerate the PIDs array to find the process of egui.exe.
    DWORD nProcesses = bytesReturned / sizeof(DWORD);
    for (DWORD i = 0;i < nProcesses;i++)
    {
        //Open the process to examine its executable file name.
        HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, PIDs[i]);
        //Create a buffer for the name of the executable file.
        PSTR processName = (PSTR) VirtualAlloc((LPVOID) NULL, (DWORD) 10, MEM_COMMIT, PAGE_READWRITE);
        //Get the name of the executable file name.
        GetModuleBaseName(process, NULL, processName, 10);
        //Check if the executable file name is egui.exe.
        if (!lstrcmpi(processName, TEXT("egui.exe")))
        {
            EGUIPID = PIDs[i];
            break;
        }
        VirtualFree(processName, 0, MEM_RELEASE);
        CloseHandle(process);
    }

    //Display an error message if the process identifier of egui.exe
    //could not be found.
    if (!EGUIPID)
    {
        cerr << "Unable to find process identifier of egui.exe" << endl;
        return 0;
    }

    //Enumerate the top-level windows to find the main window of
    // ESET Smart Security and store a handle to that window in the
    // ESETWindow variable.
    EnumWindows(EnumWindowsProc, 0);
    //Display an error message if the window could not be found.
    if (!ESETWindow)
    {
        cerr << "Unable to find the primary window of ESET Smart Security." << endl;
        return 0;
    }

    //Send a WM_CLOSE message to the main window of egui.exe in order
    // to display a confirmation dialog box.
    if (!SendMessageCallback(ESETWindow, WM_CLOSE, 0, 0, SendAsyncProc, 0))
    {
        cerr << "Unable to send a WM_CLOSE message to the primary window of ESET Smart Security." << endl;
        return 0;
    }

    //Wait a second for the confirmation dialog box to appear...
    Sleep(1000);
    //Enumerate the windows again to find the confirmation dialog box.
    EnumWindows(EnumWindowsProc, 1);
    //Display an error message if the confirmation dialog box
    // could not be found.
    if (!ConfirmationWindow)
    {
        cerr << "Unable to find confirmation message." << endl;
        cout << "If you have ESET NOD32, it is probably terminated successfully.";
        cin.get();
    }

    //Find the Yes button in the confirmation dialog box and display
    // an error message if failed.
    HWND button = FindWindowEx(ConfirmationWindow, NULL, NULL, TEXT("&Yes"));
    if (!button)
    {
        cerr << "Unable to find Yes button in the message box." << endl;
        return 0;
    }

    //Activate the confirmation dialog box and simulate a mouse click
    // on the Yes button.
    SetActiveWindow(ConfirmationWindow);
    SendMessage(button, BM_CLICK, 0, 0);
    cout << "ESET Smart Security was successfully terminated!";

    //Keep the program running until the user presses Enter
    // in the console window.
    cin.get();
    return 0;
}

//If lParam is 0, the function below finds the main window of
//ESET Smart Security, otherwise, it finds the confirmation
//dialog box.
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
    //Check if hwnd belongs to a window of egui.exe.
    DWORD PID = 0;
    GetWindowThreadProcessId(hwnd, &PID);
    if (PID != EGUIPID)
        return TRUE; //Exit function and continue the enumeration.

    //Check if the title of the window is "ESET Smart Security"
    // or "ESET Node32 Antivirus".
    int len = GetWindowTextLength(hwnd);
    PSTR title = (PSTR) VirtualAlloc((LPVOID) NULL, (DWORD) (len + 1), MEM_COMMIT, PAGE_READWRITE);
    GetWindowText(hwnd, title, (len + 1));
    if ((lstrcmp(title, TEXT("ESET Smart Security"))) && (lstrcmp(title, TEXT("ESET NOD32 Antivirus"))))
        return TRUE; //Exit function and continue the enumeration.

    if (lParam)
    {
        //If lParam is nonzero and hwnd refers to the main
        // window of egui.exe, exit function and continue
        // the enumeration.
        if (hwnd == ESETWindow)
            return TRUE;
        //Otherwise hwnd refers to the confirmation dialog box.
        //So store it in the ConfirmationWindow variable.
        ConfirmationWindow = hwnd;
        return FALSE; //Exit function and stop the enumeration.
    }
    else
    {
        //hwnd refers to the main window of ESET Smart Security.
        //So store it in the ESETWindow variable.
        ESETWindow = hwnd;
        return FALSE; //Exit function and stop the enumeration.
    }
}
VOID SendAsyncProc(HWND hwnd, UINT uMsg, ULONG_PTR dwData, LRESULT lResult)
{
    MessageBox(0, TEXT("ESET Smart Security was successfully terminated."), TEXT("Result"), MB_OK);
}

Although this program accomplishes its task perfectly, the callback function SendAsyncProc doesn't run after the WM_CLOSE message is processed and egui.exe is terminated. Could you tell me why?


Solution

  • The documentation for SendMessageCallback tells you, why your callback will not ever be called:

    If the target window belongs to a different thread from the caller, then the callback function is called only when the thread that called SendMessageCallback also calls GetMessage, PeekMessage, or WaitMessage.

    The target window obviously belongs to a different thread, because it runs in a different process. Your code doesn't perform any message retrieval. No callback.