如何退出/中断控制台应用程序中的 Windows 消息循环和 Windows 桌面应用程序的尊重?

问题描述

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

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

1.我为什么要这样做?

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

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

2.我尝试了什么。

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

3.我的实验结果

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

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

4.实验设置

我使用的是 VS2019、C++、Windows 10 家庭版 1909。

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

5.我的代码

在这里提供了玩具示例代码大部分都是由 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)) {
            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 https://stackoverflow.com/a/60913531/9758790
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,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 也按照https://stackoverflow.com/a/60913531/9758790 的建议由 Visual Studio 创建后稍作修改,以获取用于 dll 注入的 dll 的 hinstance。

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

// refer to https://stackoverflow.com/a/60913531/9758790
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) 我的桌面应用程序。

在使用 Visual Studio 创建解决方案时,我选择了“创建 Windows 桌面应用程序”。大多数代码是由 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,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,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,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,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,0))
    //{
    //    if (!TranslateAccelerator(msg.hwnd,hAccelTable,&msg))
    //    {
    //        TranslateMessage(&msg);
    //        dispatchMessage(&msg);
    //    }
    //}
    while (recieve_quit == false)
    {
        if (GetMessage(&msg,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,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,hInstance,nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd,nCmdshow);
   UpdateWindow(hWnd);

   return TRUE;
}

//
//  Function: WndProc(HWND,LParaM)
LRESULT CALLBACK WndProc(HWND hWnd,UINT message,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,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,lParam);
    }
    return 0;
}

// "About"
INT_PTR CALLBACK About(HWND hDlg,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。向该解决方添加一个新项目,选择创建新项目 -> DLL,将其命名为Dll1。创建 dll_func.h、dll_func.c,并将我的代码复制进去。不要忘记修改 dllmain.cpp。设置 AdditionalIncludeDirectories 的路径,将 dll1.lib 添加到 AdditionalDependencies 中。还要设置dll和lib文件的路径。

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

解决方法

这篇文章解释了为什么控制台应用程序不接收键盘消息以及它们如何处理它们: does not go inside the Windows GetMessage loop on console application

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

这个答案是关于处理在控制台应用程序中获取按键的: https://stackoverflow.com/a/6479673/4240951

一般情况下 - 向您的控制台应用添加一个隐藏窗口或检查 GetAsyncState 所需的密钥。

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

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

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...