【问题标题】:windows.h INPUT not working outside functionswindows.h INPUT 在函数外不起作用
【发布时间】:2020-05-09 03:55:32
【问题描述】:

规格

  • 操作系统: Windows 10
  • 编程语言: C++14
  • 编译器: MSVC 2019
  • IDE: CLion 2019.3.3

代码:

    #define WINVER 0x0500
    #include <windows.h>
    #include <string>
    
    void press_enter() {
        // This structure will be used to create the keyboard
        // input event.
        INPUT ip;
    
        // Set up a generic keyboard event.
        ip.type = INPUT_KEYBOARD;
        ip.ki.wScan = 0; // hardware scan code for key
        ip.ki.time = 0;
        ip.ki.dwExtraInfo = 0;
    
    
        // Press enter
        ip.ki.wVk = 0x0D;
        ip.ki.dwFlags = 0; // 0 for key press
        SendInput(1, &ip, sizeof(INPUT));
    
        // Release the key
        ip.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release
        SendInput(1, &ip, sizeof(INPUT));
        Sleep(25);
    
    }
    
    void press_keys(std::string& text_to_write) {
        // This structure will be used to create the keyboard
        // input event.
        INPUT ip;
    
        // Load current window's keyboardLayout
        HKL kbl = GetKeyboardLayout(0);
    
        // Set up a generic keyboard event.
        ip.type = INPUT_KEYBOARD;
        ip.ki.wScan = 0; // hardware scan code for key
        ip.ki.time = 0;
        ip.ki.dwExtraInfo = 0;
    
        for (char& c : text_to_write) {
            // Press the corresponding 'c' key
            ip.ki.wVk = VkKeyScanEx(c, kbl);; // virtual-key code for the "a" key
            ip.ki.dwFlags = 0; // 0 for key press
            SendInput(1, &ip, sizeof(INPUT));
    
            // Release the key
            ip.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release
            SendInput(1, &ip, sizeof(INPUT));
            Sleep(25);
        }
    }
    
    void give_100000(std::string& item) {
        for (int i = 0; i < 10; i++) {
            press_keys(item);
            Sleep(25);
            press_enter();
            Sleep(25);
            press_enter();
            Sleep(25);
        }
    }
    
    int main() {
    
        // Pause for 5 seconds.
        Sleep(5000);
    
        std::string lumber = "lumberjack";
        std::string food = "cheese steak jimmy's";
        std::string gold = "robin hood";
        std::string stone = "rock on";
    
        give_100000(lumber);
        give_100000(food);
        give_100000(gold);
        give_100000(stone);
    
        // Exit normally
        return 0;
    }

这个程序的作用

我仍然是 C++ 的初学者。我编写了这个程序作为一个小挑战并练习我的 C++。它模拟键盘按下,专门用于快速输入秘籍,这样我就可以在帝国时代 II 中获得大量资源。

问题

此代码按原样完美运行。它做我想让它做的事情。问题是,press_enter()press_keys() 函数内部都有重复的代码,即:

INPUT ip;

// Set up a generic keyboard event.
ip.type = INPUT_KEYBOARD;
ip.ki.wScan = 0; // hardware scan code for key
ip.ki.time = 0;
ip.ki.dwExtraInfo = 0;

所以我想解决这个问题。

我尝试了什么

我想我可以把那段代码放在所有函数之外(就在#includes 下方)并让它们充当全局变量,这样所有函数都可以访问ip。但是这样做让 CLion 抱怨,编译给了我一个巨大的难以理解的错误列表(如果需要我可以发布)。当我将鼠标悬停在以ip. 开头的 4 行中的任何一行上时,CLion 会说:“未知类型名称 'ip'”。我不明白这一点,因为ip 在上面声明了两行。

我在寻找什么

正如我所说,我仍然是 C++ 的初学者,所以我真的很想了解这意味着什么,如果我缺少一些基本概念,以及一种无需重复代码即可使其工作的方法。

【问题讨论】:

  • 将通用代码放入一个单独的函数中,然后在需要的地方调用该函数。例如,参见this question 中的configure_input() 函数。
  • 乍一看,您的press_enter 基本上等同于press_keys("\r");...
  • 注意:您正在编写一些非常特定于平台的代码。如果你想让你的代码在不同的平台上工作,你我想重新评估你在做什么。
  • 变量可以是全局的,但语句不能。例如,ip.type = INPUT_KEYBOARD; 行必须在某个函数内部。错误消息显示“未知 类型名称 ip。”因为编译器认为您正在尝试声明函数或变量,而声明通常以类型名称开头。它看到ip 并说“好吧,规则说类型名称需要放在此处,但ip 不是类型名称。” (这是一个变量名。)
  • 性能在这里不是问题。尤其是因为那些对Sleep 的调用!无论如何,在获得性能之前,您需要专注于基础知识。当你这样做时,不要认为你可以预测性能,总是衡量。您遇到的一个大问题是您调用SendInput 只传递了一个事件。您的目的是在一个数组中提供所有事件,以便可以原子地插入它们。这就是SendInput 存在的主要原因,因为keybd_event 在它之前就已经存在。您有机会发现std::vector&lt;T&gt;

标签: c++ winapi


【解决方案1】:

问题是,press_enter()press_keys() 函数内部都有重复的代码

如果您想重用相同的 INPUT 实例,请通过引用将其传递给需要它的函数 - 尽量远离全局变量。

另请注意,以这种方式发送单个键盘事件是discouraged。您可以创建一个帮助类来保留您只需要初始化一次的信息,并添加一个函数来发送完整的事件序列(key down/up)。然后可以很容易地为特殊场景扩展其他功能。

例子:

#define WINVER 0x0500
#include <windows.h>
#include <string>
#include <vector>

struct KeyboardInput {
    KeyboardInput() :
        kbl{ GetKeyboardLayout(0) }
    {}

    // the basic function to send a number of keys at once
    void press_keys(const std::string& text_to_write) {
        // create a vector of INPUT structs, double the size of the string
        // to be able to send both key down and key up events
        std::vector<INPUT> INPUTs(text_to_write.size() * 2);
        SHORT wVk;

        // loop though all characters in the string and fill the INPUT structs
        size_t idx = 0;
        for(auto ch : text_to_write) {
            wVk = KeyScan(ch);

            // fill the INPUT struct with data
            fill_input(INPUTs[idx], wVk, 0); // KEYDOWN
            fill_input(INPUTs[idx + 1], wVk, KEYEVENTF_KEYUP);

            idx += 2;
        }

        // Call SendInput once for the complete string
        SendInput(static_cast<UINT>(INPUTs.size()), INPUTs.data(), sizeof(INPUT));
    }

    // a press_keys overload with a delay between keystrokes
    void press_keys(const std::string& text_to_write, DWORD delay_ms) {
        for (auto ch : text_to_write) {
            press_keys(std::string(1, ch));
            Sleep(delay_ms);
        }
    }

    // functions using the above
    void give_100000(const std::string& item) {
        press_keys(item + "\n\n", 250); // sleep 250 ms between each keystroke
    }
    void press_enter() {
        press_keys("\n");
    }
private:
    SHORT KeyScan(TCHAR ch) {
        return VkKeyScanEx(ch, kbl);
    }
    void fill_input(INPUT& i, SHORT wVk, DWORD dwFlags) {
        i.type = INPUT_KEYBOARD;
        i.ki.wVk = wVk;
        i.ki.dwFlags = dwFlags;
        i.ki.time = 0;
        i.ki.dwExtraInfo = 0;
    }

    HKL kbl;
};

int main() {
    Sleep(4000);

    std::vector<std::string> messages{
        "lumberjack",
        "cheese steak jimmy's",
        "robin hood",
        "rock on"
    };

    KeyboardInput ip;

    for (const std::string& msg : messages) {
        ip.give_100000(msg);
    }
} // Exit normally (return 0 is default)

【讨论】:

  • @IInspectable 它仍然是一个太多的反模式吗?我刚刚收到匿名投票,所以我不知道是因为我提出了一种不好的做法,还是因为它没有回答问题。
  • 它正在使用SendInput,因为它本来就是要使用的。我不知道否决票。我看不出有太多理由。尽管如此,我还是不愿意投票。 OP似乎在基础知识上苦苦挣扎。他们要求一个简单的抽象,将通用逻辑分解为一个函数。在此之上分层另一个抽象可能会使 OP 感到困惑,而不是帮助。
  • @IInspectable 谢谢。然后我会留下答案,因为它正确使用了SendInput - 如果不是现在,OP 可能会在以后发现它有用。
  • 谢谢@TedLyngmo。我确实会研究您的代码以改进我的代码,并了解 SendInput 模式的工作原理。
【解决方案2】:

至于为什么你得到错误“未知类型”是因为你试图在只能声明它的地方运行代码。

在函数中,您正在运行代码以及可以更改已初始化类型行为的地方。

【讨论】:

  • 感谢您和@RaymondChen,我现在明白我不能在函数之外运行语句。现在看来很明显。谢谢
【解决方案3】:

因此,根据我在这里和其他网站上收集到的信息,我犯了一个错误,即尝试在函数之外运行语句。这里还有其他很棒的建议,我建议任何与我有类似问题的人阅读它们,但是对于我基于我的代码的人 (Ted Burke) 的the help,我相信我实现了我最初想做的事情。 Ted 建议我全局声明ip 并同时对其进行初始化,如下所示:

INPUT ip = {.type = INPUT_KEYBOARD, .ki.wScan = 0, .ki.time = 0, .ki.dwExtraInfo = 0};

我试过了,但是编译器给了我错误,所以我重写了:

(编译器错误让我很困惑,所以给那些能解释为什么下面的代码工作而 Ted 没有工作的人加分)

TL;DR 解决方案

INPUT ip = {ip.type = INPUT_KEYBOARD,
            ip.ki.wScan = 0,
            static_cast<LONG>(ip.ki.time = 0),
            ip.ki.dwExtraInfo = 0};

static_cast&lt;LONG&gt; 的原因是因为 CLion 检测到以下内容:“在初始化程序中,不能将非常量表达式从类型 'DWORD' (aka 'unsigned long') 缩小到 'LONG' (aka 'long')列表”。

我喜欢这个解决方案,因为这样我们就不需要为每一次击键都实例化一个 INPUT 对象。我知道在这种情况下,时间和内存上的性能提升可能可以忽略不计(甚至可能由于编译器优化了我的第一个代码,甚至没有必要?),但从逻辑的角度来看,对我来说没有必要重复 INPUT 的实例化数百次,而它只能实例化一次。

我可以看到我使用 SendInput 可能不是最好的模式,我可以使用 press_keys("\r") 并摆脱 press_enter()。我将研究这些更改以改进我的代码,但现在,我的主要担忧已经得到满足。谢谢大家!

这是我的代码的最终版本:

#define WINVER 0x0500
#include <windows.h>
#include <string>

INPUT ip = {ip.type = INPUT_KEYBOARD,
            ip.ki.wScan = 0,
            static_cast<LONG>(ip.ki.time = 0),
            ip.ki.dwExtraInfo = 0};
HKL kbl = GetKeyboardLayout(0);

void press_enter() {
    // Press enter
    ip.ki.wVk = 0x0D;
    ip.ki.dwFlags = 0; // 0 for key press
    SendInput(1, &ip, sizeof(INPUT));

    // Release the key
    ip.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release
    SendInput(1, &ip, sizeof(INPUT));
    Sleep(25);
}

void press_keys(std::string& text_to_write) {
    for (char& c : text_to_write) {
        // Press the corresponding 'c' key
        ip.ki.wVk = VkKeyScanEx(c, kbl);; // virtual-key code for the "a" key
        ip.ki.dwFlags = 0; // 0 for key press
        SendInput(1, &ip, sizeof(INPUT));

        // Release the key
        ip.ki.dwFlags = KEYEVENTF_KEYUP; // KEYEVENTF_KEYUP for key release
        SendInput(1, &ip, sizeof(INPUT));
        Sleep(25);
    }
}

void give_100000(std::string& item) {
    for (int i = 0; i < 10; i++) {
        press_keys(item);
        Sleep(25);
        press_enter();
        Sleep(25);
        press_enter();
        Sleep(25);
    }
}

int main() {
    // Pause for 5 seconds.
    Sleep(5000);

    std::string lumber = "lumberjack";
    std::string food = "cheese steak jimmy's";
    std::string gold = "robin hood";
    std::string stone = "rock on";

    give_100000(lumber);
    give_100000(food);
    give_100000(gold);
    give_100000(stone);

    // Exit normally
    return 0;
}

【讨论】:

  • 这很糟糕。全局变量不好。性能不是问题。你能看到那些睡眠吗?即使没有他们初始化一个结构也是微不足道的。您即将进行系统调用!这极大地淹没了它。但是不要再考虑性能了。考虑封装。此外,正如我在 cmets 中解释的那样,这里的大问题是一次发送一个事件。在一个原子调用中发送它们。
  • "Ted 建议我在全球范围内声明 ip" - 如果您再次阅读我的回答,您会发现这与我的建议相反 :-) 回顾:“通过引用需要它的函数来传递它 - 尽量远离全局变量”。如果您不创建包含INPUT 和对其进行操作的函数的class,请在main 中声明ip,并通过引用将其传递给需要它的函数。
猜你喜欢
  • 2022-09-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-01
相关资源
最近更新 更多