让非阻塞线程永久执行 "nothing" 的最佳方法是什么?

What's the best way to have a non-blocked thread permanently doing "nothing"?

我正在使用低级挂钩。 我做了这个 class:

class Kayz {
    static int VKEY;
    static void (*funcDown)();
    static void (*funcUp)();
    static HHOOK TheHook;
    static KBDLLHOOKSTRUCT TheHookStruct;
    static LRESULT _stdcall HookCallback(int, WPARAM, LPARAM);
public:
    bool SetHook(int VKey, void(*FunctionDown)(), void(*FunctionUp)()) {
        if (VKey < 0x07) {
            if (!(TheHook = SetWindowsHookEx(WH_MOUSE_LL, &HookCallback, NULL, 0))) {
                return false;
            }
        }
        else if(VKey > 0x07){
            if (!(TheHook = SetWindowsHookEx(WH_KEYBOARD_LL, &HookCallback, NULL, 0))) {
                return false;
            }
        }
        VKEY = VKey; funcDown = FunctionDown; funcUp = FunctionUp;
        return true;
    }

    void UnSetHook() {
        UnhookWindowsHookEx(TheHook);
    }
};

int Kayz::VKEY;
void(*Kayz::funcDown)();
void(*Kayz::funcUp)();
HHOOK Kayz::TheHook;
KBDLLHOOKSTRUCT Kayz::TheHookStruct;
LRESULT _stdcall Kayz::HookCallback(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode >= 0) {
        if (wParam == WM_KEYDOWN) {
            TheHookStruct = *((KBDLLHOOKSTRUCT*)lParam);
            if (TheHookStruct.vkCode == VKEY) {
                (*funcDown)();
            }
        }
        else if (wParam == WM_KEYUP) 
        {
            TheHookStruct = *((KBDLLHOOKSTRUCT*)lParam);
            if (TheHookStruct.vkCode == VKEY) {
                (*funcUp)();
            }
        }
    }
    return CallNextHookEx(TheHook, nCode, wParam, lParam);
}

我放入 SetHook 的所有功能都是在主程序中更改一个 bool 变量,这样我就可以知道是否按下了键。在我看来,这是最佳方式,因为我不必每次在主程序中循环时都检查密钥的状态。

现在。

在主程序中使用阻塞定时器如Sleep()会阻塞程序,包括

return CallNextHookEx(TheHook, nCode, wParam, lParam);

这意味着,由于这是一个低级挂钩,所有其他程序只会在睡眠结束时获得输入。所以如果我在记事本中按下一个键,它只会在睡眠结束并且程序再次循环时被输入,如果我输入很多,它们很可能一次被输入一个。

我见过的唯一能够"bypass"这是

while(GetMessage(&msgVar, NULL, 0, 0)){}

GetMessage 从不或很少 returns,因此它不占用任何系统资源或处理能力。它不会阻塞,因为 while 正在等待它到达 return。所以基本上,它什么也没做,但也没有阻塞。

我需要一个线程来做类似的事情。该线程将接收按键 "events" 并执行更改主程序中变量的函数。

但这很脏。我不喜欢脏。 所以我很想知道:

我怎样才能以干净的方式实现无阻塞,消耗最少的资源?

谢谢。

编辑:

正如您所问:我正在制作一个记忆瞄准机器人,完全用于学习目的。 我现在花了相当多的时间阅读有关 MsgWaitForMultipleObjectsEx 的内容,显然您可以将前 2 个参数设为 null,这会派上用场。

我也在考虑以错误的方式做这件事,我打算为程序创建一个线程 "hold" 并且仍然从钩子接收异步输入(这就是我不想要的原因它会阻塞),然后另一个(总是-运行)线程将根据钩子调用的函数将改变的布尔值工作。

我现在意识到这是一个相当糟糕的设计,所以我正在考虑在主程序中使用 MsgWaitForMultipleObjectsEx,并用它检查 bool,如果需要暂停或恢复 aimbot 线程。

我现在开始理解@HarryJohnston 所说的意大利面条逻辑,因为我必须组织异步挂钩函数的作用与 MsgWaitForMultipleObjectsEx 之后的代码的作用,这些看起来有些困难决定。

我想跟随这些钩子并全面了解这一切是如何工作的,这就是为什么我不会立即使用原始输入的原因,不过谢谢@nikau6 告诉我这件事,我'当我完成 hooks 时,一定会研究它。

再次感谢大家

你需要在循环中使用 MsgWaitForMultipleObjectsEx 这对你来说是最强大的功能。有了这个,您将等待 windows(和挂钩)消息,对于多个事件(最多 63 个),您还可以接收用户模式 ​​APC 调用并定期(通过超时执行相同的任务)。示例:

void ZApp::Run()
{
    for (;;)
    {
        HANDLE* pHandles;

        DWORD nCount = GetWaitHandles(&pHandles);

        DWORD r = MsgWaitForMultipleObjectsEx(nCount, pHandles, GetTimeout(), QS_ALLINPUT, MWMO_ALERTABLE);

        if (r < nCount)
        {
            OnSignalObject(r);
            continue;
        }

        if (r == nCount)
        {
            BOOL bIdle = FALSE;

            MSG msg;

            while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
            {
                if (!bIdle)
                {
                    bIdle = IsIdleMessage(msg.message);
                }

                if (PreTranslateMessage(&msg)) continue;

                if (msg.message == WM_QUIT) 
                {
                    return ;
                }

                if (!IsDialogMessageEx(&msg))
                {
                    if (msg.message - WM_KEYFIRST <= WM_KEYLAST - WM_KEYFIRST)
                    {
                        TranslateMessage(&msg);
                    }
                    DispatchMessage(&msg);
                }
            }

            if (bIdle)
            {
                OnIdle();
            }

            continue;
        }

        if (r - WAIT_ABANDONED_0 < nCount)
        {
            OnAbandonedObject(r - WAIT_ABANDONED_0);
            continue;
        }
        switch(r)
        {
        case WAIT_TIMEOUT:
            OnTimeout();
            break;
        case WAIT_IO_COMPLETION:
            OnApcAlert();
            break;
        default: __debugbreak();
        }
    }
}

"It seems to me that it's the most optimal way because I don't have to check for the key's state every time I loop in the main program."

有一种比 hooks 更好的方法,但并不为人所知,它可以监视所有系统上的键盘事件。这是Raw Input

使用原始输入,您的应用程序可以直接从 HID(人机设备接口)驱动程序获知每个键盘、鼠标等事件。这比钩子更有效,而且使用起来非常简单。您的应用程序不需要从 DLL 导出过程,并且由于原始输入不是挂钩,因此在处理消息后,无需将消息传递给另一个过程、另一个线程。 (请参阅下面关于 DefRawInputProc 程序的评论之一)。应用程序通过 WM_INPUT 消息获取原始输入。与钩子不同,必须创建一个 window,这是一项义务,需要一个句柄。

以下是我使用原始输入的方式:

编辑:你不会遇到关于非阻塞线程的问题。

#include <Windows.h>

#define HID_ISMOUSE(x)     ((x).header.dwType == RIM_MOUSE)
#define HID_ISKEYBOARD(x)  ((x).header.dwType == RIM_TYPEKEYBOARD)
#define HID_SCODE(x)       ((x).data.keyboard.MakeCode) // scan code
#define HID_VKEY(x)        ((x).data.keyboard.VKey)     // virtual key code
#define HID_WMSG(x)        ((x).data.keyboard.Message)  // corresponding window message, WM_KEYDOWN, WM_SYSKEYDOWN, WM_KEYUP, WM_SYSKEYUP.
#define HID_ISKEYUP(x)     ((x).data.keyboard.Flags & RI_KEY_BREAK)
#define HID_ISKEYDOWN(x)  (((x).data.keyboard.Flags & 0x01) == RI_KEY_MAKE)

#define RAWINPUT_ERROR (UINT)-1

namespace HID
{
    const USHORT MOUSE    = 2;
    const USHORT KEYBOARD = 6;

    // Register a raw input device
    bool RegisterDevice(HWND hTarget, USHORT usage)
    {
        RAWINPUTDEVICE hid;
        hid.usUsagePage = 1;        // generic desktop page
        hid.usUsage = usage;        // device id
        hid.hwndTarget = hTarget;   // window handle
        hid.dwFlags = RIDEV_NOLEGACY | RIDEV_INPUTSINK ; // RIDEV_INPUTSINK to monitor all the system, RIDEV_NOLEGACY if you don't want legacy keyboard events.

        return !!RegisterRawInputDevices(&hid, 1, sizeof(RAWINPUTDEVICE));
    }

    // Unregister a raw input device.
    void UnregisterDevice(USHORT usage)
    {
        RAWINPUTDEVICE hid;
        hid.usUsagePage = 1;
        hid.usUsage = usage;
        hid.dwFlags = RIDEV_REMOVE; // RIDEV_REMOVE to remove a device.
        hid.hwndTarget = NULL;      // NULL to remove a device.

        RegisterRawInputDevices(&hid, 1, sizeof(RAWINPUTDEVICE));
    }

    // Get raw input data
    bool GetInputData(HRAWINPUT hInput, RAWINPUT* RawInput)
    {
        UINT size = sizeof(RAWINPUT);  // size = 40 
        if( GetRawInputData((HRAWINPUT)hInput, RID_INPUT, RawInput, &size, sizeof(RAWINPUTHEADER)) != RAWINPUT_ERROR )
            return true;
        else
            return false;
    }
}


int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR cmd_line, int cmd_show)
{

    WNDCLASSW wc = {0};
    wc.lpfnWndProc = WindowProc;
    ... 
    HWND hwnd = ::CreateWindowW(...);   
    ...

    HID::RegisterDevice(hwnd, HID::KEYBOARD);

    MSG msg;
    while(GetMessageW(&msg, NULL, 0, 0))
    {
        DispatchMessageW(&msg);
    }

    HID::UnregisterDevice(HID::KEYBOARD);

    return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{

    if(msg == WM_INPUT) // Raw input message.
    {
        RAWINPUT Input;

        if(HID::GetInputData((HRAWINPUT)lParam, &Input))
        {
            if(HID_ISKEYBOARD(Input))
            {
                if(HID_ISKEYUP(Input))
                {
                    return 0;
                }
                else // if(HID_ISKEYDOWN(Input))
                { 
                    return 0;
                }        
            }
        }
    }        

    return ::DefWindowProc(hWnd, msg, wParam, lParam);
}    

我已经意识到,在等待挂钩执行其他函数时永久拥有一个线程 "on hold" 只是一种糟糕的方式来实现我正在寻找的东西,您应该始终让每个线程都做某事。如果您遵循相同的路径,我建议您放弃它并以不需要这些的方式组织您的代码 "loose ends".

谢谢大家。主要是@RbMm who informed me of MsgWaitForMultipleObjectsEx and guided me through it, and @nikau6 who informed about RawInput,以后会用到

我还完成了 class 并包含了一个函数 returns 当你的键被按下或释放时(当 MsgWaitForMultipleObjectsEx returns 除了 WAIT_OBJECT_0),我想我会 post 它在这里以防万一有人需要它,因为大部分对话都是在评论中进行的,我经常在浏览 Whosebug 时跳过那些。

class Kayz {
    static bool KDown[2];
    static int VKEY;
    static void (*funcDown)();
    static void (*funcUp)();
    static HHOOK TheHook;
    static KBDLLHOOKSTRUCT TheHookStruct;
    static LRESULT _stdcall HookCallback(int, WPARAM, LPARAM);
public:
    bool SetHook(int VKey, void(*FunctionDown)(), void(*FunctionUp)()) {
        if (VKey < 0x07) {
            if (!(TheHook = SetWindowsHookEx(WH_MOUSE_LL, &HookCallback, NULL, 0))) {
                return false;
            }
        }
        else if(VKey > 0x07){
            if (!(TheHook = SetWindowsHookEx(WH_KEYBOARD_LL, &HookCallback, NULL, 0))) {
                return false;
            }
        }
        VKEY = VKey; funcDown = FunctionDown; funcUp = FunctionUp;
        return true;
    }
    void UnSetHook() {
        UnhookWindowsHookEx(TheHook);
    }
    bool WaitOnKey()
    {
        MSG msg;
        while (true) {
            if (MsgWaitForMultipleObjectsEx(0, 0, INFINITE, QS_ALLINPUT, 0) == WAIT_OBJECT_0) {
                while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
                    if (msg.message != WM_QUIT) return false;
                    TranslateMessage(&msg); DispatchMessage(&msg);
                }
                if(KDown[0] == 0 && KDown[1] == 0){
                    continue;
                }else if (KDown[0] == true) {
                    return true;
                }else{
                    KDown[1] = false;
                    return true;
                }
            } else {
                return false;
            }
        }
    }
};

bool Kayz::KDown[2];
int Kayz::VKEY;
void(*Kayz::funcDown)();
void(*Kayz::funcUp)();
HHOOK Kayz::TheHook;
KBDLLHOOKSTRUCT Kayz::TheHookStruct;
LRESULT _stdcall Kayz::HookCallback(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode >= 0) {
        if (wParam == WM_KEYDOWN) {
            TheHookStruct = *((KBDLLHOOKSTRUCT*)lParam);
            if (TheHookStruct.vkCode == VKEY) {
                KDown[0] = true;
                (*funcDown)();
            }
        }
        else if (wParam == WM_KEYUP) 
        {
            TheHookStruct = *((KBDLLHOOKSTRUCT*)lParam);
            if (TheHookStruct.vkCode == VKEY) {
                KDown[1] = true;
                KDown[0] = false;
                (*funcUp)();
            }
        }
    }
    return CallNextHookEx(TheHook, nCode, wParam, lParam);
}