如何从控制台应用程序中的 windows 消息循环 quit/break 以及与 windows 桌面应用程序的区别?

How to quit/break from windows message loop in console app and deferences from windows desktop app?

我想跳出 windows 消息循环。就像 C++ how to break message loop in windows hook 一样。我想出了一个解决方案,它适用于 Window 桌面应用程序,但不适用于控制台应用程序。怎么会这样?

编辑:我将我的代码上传到 https://github.com/hellohawaii3/Experiment ,克隆它然后你可以快速重现我的问题。谢谢!

1.Why我要这样做?

我正在编写控制台应用程序。首先,要求用户设置一些决定应用程序行为的选项。然后,应用程序启动消息循环并监视 keyboard/mouse 的输入。用户可能想要更改设置,所以我想让用户能够按下一些热键,退出消息循环并返回到应用程序的开头。

如果您知道实现此功能而不用担心中断消息循环的任何方法,请告诉我!但是,我也想知道为什么我的解决方案对控制台应用程序失败而对桌面应用程序运行良好。感谢您的帮助!

2.What我试过了吗

我使用布尔变量“recieve_quit”。当按下键盘上的某个键时,钩子回调函数会将变量设置为 True。对于每个循环获取消息,首先检查变量 'recieve_quit' 并在变量为 False 时退出。

3.Result 我的实验

对于Console APP,变量'recieve_quit'在按下某个键时设置正确,但是,消息循环继续。

对于Windows带有GUI的桌面APP,我可以按预期退出消息循环。

4.Experiment 设置

我用的是VS2019,C++,windows10家1909.

我使用 dll 注入为我的控制台应用程序设置挂钩。

5.My代码

我这里提供了玩具示例代码,大部分都是Visual Studio自动生成的,如果觉得我的代码太啰嗦就不要打扰.

(a)我的控制台应用程序

控制台:

// Console.cpp

#include <iostream>
#include "dll_func.h"
#include <windows.h>

int main()
{
    MSG msg;
    HHOOK hhook_tmp2 = SetWindowsHookEx(WH_KEYBOARD_LL, HandleKeyboardEvent, hDllModule, 0);
    while (recieve_quit == false)
    {
        if (GetMessage(&msg, nullptr, 0, 0)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    MessageBox(NULL, TEXT("APP QUIT"), TEXT(" "), MB_OK);
}

(b)我的dll包含钩子函数

我的 dll_func.h 文件,遵循文档 https://docs.microsoft.com/en-us/cpp/build/walkthrough-creating-and-using-a-dynamic-link-library-cpp?view=msvc-160

#pragma once

#ifdef DLL1_EXPORTS
#define DLLFUNC_API __declspec(dllexport)
#else
#define DLLFUNC_API __declspec(dllimport)
#endif

#include "framework.h"

extern "C" DLLFUNC_API bool recieve_quit;
extern "C" DLLFUNC_API LRESULT CALLBACK HandleKeyboardEvent(int nCode, WPARAM wParam, LPARAM lParam);// refer to 
extern "C" DLLFUNC_API HMODULE hDllModule;//or whatever name you like 

我的dll_func.cpp文件,包含钩子函数的定义。

#include "pch.h"
#include <windows.h>
#include "dll_func.h"

bool recieve_quit = false;
LRESULT CALLBACK HandleKeyboardEvent(int nCode, WPARAM wParam, LPARAM lParam)
{
    PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT)lParam;
    if (wParam == WM_KEYDOWN) {
        if (p->vkCode == VK_F8) {
            recieve_quit = true;
        }
    }

    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

根据的建议,dllmain.cpp在Visual Studio创建后也进行了一些修改,以获取用于dll注入的dll实例。

// dllmain.cpp
#include "pch.h"
#include "dll_func.h"

// refer to 
HMODULE hDllModule;//or whatever name you like 
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    hDllModule = hModule;
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

(c) 我的桌面应用程序。

我 select 'Creating Windows Desktop Application' 当用 Visual Studio 创建解决方案时。大部分代码都是VS自动生成的。我添加和修改的代码在代码中被*****包围了。

// GUI.cpp
//

#include "framework.h"
#include "GUI.h"

#define MAX_LOADSTRING 100

HINSTANCE hInst;
WCHAR szTitle[MAX_LOADSTRING];
WCHAR szWindowClass[MAX_LOADSTRING];

// *********************** ADDED BY ME! ***********************
// same as dll_func.cpp
bool recieve_quit = false;
LRESULT CALLBACK HandleKeyboardEvent(int nCode, WPARAM wParam, LPARAM lParam)
{
    //FILE* file;
    //fopen_s(&file, "./temp0210222.txt", "a+");
    //fprintf(file, "Function keyboard_hook called.n");
    //fclose(file);
    PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT)lParam;
    if (wParam == WM_KEYDOWN) {
        if (p->vkCode == VK_F8) {
            recieve_quit = true;
        }
    }

    return CallNextHookEx(NULL, nCode, wParam, lParam);
}
// *********************** END OF MY CODES ***********************

ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // *********************** ADDED BY ME! ***********************
    HHOOK hhook_tmp2 = SetWindowsHookEx(WH_KEYBOARD_LL, HandleKeyboardEvent, hInst, 0);
    // *********************** END OF MY CODES ***********************

    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_GUI, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_GUI));

    MSG msg;

    // main message loop:
    // *********************** MODIFIED BY ME! ***********************
    //while (GetMessage(&msg, nullptr, 0, 0))
    //{
    //    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
    //    {
    //        TranslateMessage(&msg);
    //        DispatchMessage(&msg);
    //    }
    //}
    while (recieve_quit == false)
    {
        if (GetMessage(&msg, nullptr, 0, 0)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    MessageBox(NULL, TEXT("APP QUIT"), TEXT(" "), MB_OK);
    // *********************** END OF MODIFICATION ***********************

    return (int) msg.wParam;
}



//
//  Function: MyRegisterClass()
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_GUI));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_GUI);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

//
//   Function: InitInstance(HINSTANCE, int)
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance;

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

//
//  Function: WndProc(HWND, UINT, WPARAM, LPARAM)
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// "About"
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

(d)重现我的结果的提示

Console APP:新建一个名为Console的控制台应用解决方案,复制我的代码替换Console.cpp。在此解决方案中添加一个新项目,select 新建项目 -> DLL,命名为Dll1。创建dll_func.h、dll_func.c,将我的代码复制进去。别忘了修改dllmain.cpp。设置AdditionalIncludeDirectories的路径,在AdditionalDependencies中添加dll1.lib。也设置dll和lib文件的路径。

对于 Windows 桌面应用程序:构建一个现在的 Windows 桌面应用程序解决方案,将其命名为 GUI。将我的代码复制到 GUI.cpp 并简单地 运行 它。按 F8 键,应用程序将退出并按预期弹出消息框。

此 post 解释了为什么控制台应用程序不接收键盘消息以及它们如何处理它们:

在您的控制台案例中,执行进入 GetMessage 并且永远不会退出。挂钩收到通知并正确设置 recieve_quit。由于执行永远不会退出 GetMessage,因此未检查 recieve_quit。

这个答案是关于在控制台应用程序中处理获取按键的:

通常 - 添加一个隐藏的 window 到您的控制台应用程序或检查 GetAsyncState 所需的密钥。

有消息循环时不需要设置钩子。您可以按如下方式检查按键:

while (recieve_quit == false)
{
    if (GetMessage(&msg, nullptr, 0, 0)) {
        if (msg.message == WM_KEYDOWN && msg.wParam == VK_F8)
            recieve_quit = true;
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}