Подчеркивания не отображаются с помощью SendInput On C++

Это мой первый вопрос, который я разместил в stackoverflow. Я искал SendInput для С++, чтобы моя программа «вводилась» в другую программу. Я решил начать с «ввода» пары слов с подчеркиванием в терминал. Я не обнаружил проблем с вводом заглавных и строчных букв, а также точки. Но после того, как вы дошли до символа подчеркивания, введя числовой идентификатор 95 для буквы подчеркивания, подчеркивание не отображалось и действовало так, как будто эта буква никогда не нажималась. Вот код, который я получил от cplusplus.com, на основе которого я его основал, он полностью функционален:

#include <iostream>
#include <windows.h>
using namespace std;

/* HWND = "Window Handle" */
HWND GameWindow = FindWindow(0, "Command Prompt");

/* This is a function to simplify usage of sending keys */
void GenerateKey(int vk, BOOL bExtended) {

    KEYBDINPUT  kb = {0};
    INPUT       Input = {0};

    /* Generate a "key down" */
    if (bExtended) { kb.dwFlags  = KEYEVENTF_EXTENDEDKEY; }
    kb.wVk  = vk;
    Input.type  = INPUT_KEYBOARD;
    Input.ki  = kb;
    SendInput(1, &Input, sizeof(Input));

    /* Generate a "key up" */
    ZeroMemory(&kb, sizeof(KEYBDINPUT));
    ZeroMemory(&Input, sizeof(INPUT));
    kb.dwFlags  =  KEYEVENTF_KEYUP;
    if (bExtended) { kb.dwFlags |= KEYEVENTF_EXTENDEDKEY; }
    kb.wVk = vk;
    Input.type = INPUT_KEYBOARD;
    Input.ki = kb;
    SendInput(1, &Input, sizeof(Input));

    return;
}

int main() {

    /*
       SetForegroundWindow will give the window focus for the
       keyboard/mouse! In other words, you don't have to have
       the game opened upfront in order to emulate key/mouse
       presses, it's very helpful if it's a game that runs
       in fullscreen mode, like StarCraft: Brood War does
    */

    SetForegroundWindow(GameWindow);

    GenerateKey(VK_CAPITAL, TRUE);
    GenerateKey('I', FALSE);
    GenerateKey(' ', FALSE);

    GenerateKey(VK_CAPITAL, TRUE);
    GenerateKey('A', FALSE);
    GenerateKey('M', FALSE);
    GenerateKey(' ', FALSE);

    GenerateKey('C', FALSE);
    GenerateKey('O', FALSE);
    GenerateKey('O', FALSE);
    GenerateKey('L', FALSE);
    GenerateKey('E', FALSE);
    GenerateKey('R', FALSE);
    GenerateKey(' ', FALSE);

    GenerateKey('T', FALSE);
    GenerateKey('H', FALSE);
    GenerateKey('A', FALSE);
    GenerateKey('N', FALSE);
    GenerateKey(' ', FALSE);

    GenerateKey('Y', FALSE);
    GenerateKey('O', FALSE);
    GenerateKey('U', FALSE);
    GenerateKey(' ', FALSE);

    GenerateKey('W', FALSE);
    GenerateKey('I', FALSE);
    GenerateKey('L', FALSE);
    GenerateKey('L', FALSE);
    GenerateKey(' ', FALSE);

    GenerateKey('E', FALSE);
    GenerateKey('V', FALSE);
    GenerateKey('E', FALSE);
    GenerateKey('R', FALSE);
    GenerateKey(' ', FALSE);

    GenerateKey('B', FALSE);
    GenerateKey('E', FALSE);
    GenerateKey('n', FALSE);
    GenerateKey(' ', FALSE);

    GenerateKey(0x3A, FALSE); /* period key */
    GenerateKey(0x0D, FALSE); /* enter key */

    return 0;
}

И это код, который я сделал, который работал неправильно:

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

using namespace std;

/* HWND = "Window Handle" */
HWND GameWindow = FindWindow(0, "Command Prompt");

/* This is a function to simplify usage of sending keys */
void GenerateKey(int vk, BOOL bExtended) {

    KEYBDINPUT  kb = {0};
    INPUT       Input = {0};

    /* Generate a "key down" */
    if (bExtended) { kb.dwFlags  = KEYEVENTF_EXTENDEDKEY; }
    kb.wVk  = vk;
    Input.type  = INPUT_KEYBOARD;
    Input.ki  = kb;
    SendInput(1, &Input, sizeof(Input));

    /* Generate a "key up" */
    ZeroMemory(&kb, sizeof(KEYBDINPUT));
    ZeroMemory(&Input, sizeof(INPUT));
    kb.dwFlags  =  KEYEVENTF_KEYUP;
    if (bExtended) { kb.dwFlags |= KEYEVENTF_EXTENDEDKEY; }
    kb.wVk = vk;
    Input.type = INPUT_KEYBOARD;
    Input.ki = kb;
    SendInput(1, &Input, sizeof(Input));

    return;
}

int main() {

    /*
       SetForegroundWindow will give the window focus for the
       keyboard/mouse! In other words, you don't have to have
       the game opened upfront in order to emulate key/mouse
       presses, it's very helpful if it's a game that runs
       in fullscreen mode, like StarCraft: Brood War does
    */

    SetForegroundWindow(GameWindow);

    GenerateKey(VK_CAPITAL, TRUE);
    GenerateKey('N', FALSE);
    GenerateKey(VK_CAPITAL, TRUE);
    GenerateKey('I', FALSE);
    GenerateKey('N', FALSE);
    GenerateKey('J', FALSE);
    GenerateKey('A', FALSE);
    GenerateKey(0xBE, FALSE);   // GenerateKey(0x3A, FALSE); did not work
    GenerateKey(' ', FALSE);
    GenerateKey(VK_CAPITAL, TRUE);
    GenerateKey('H', FALSE);
    GenerateKey(VK_CAPITAL, TRUE);
    GenerateKey('I', FALSE);
    GenerateKey('1', FALSE);
    GenerateKey('2', FALSE);
    GenerateKey('3', FALSE);
    GenerateKey(95 , FALSE);   // GenerateKey('_', FALSE); did not work either
    GenerateKey('4', FALSE);
    GenerateKey('5', FALSE);
    GenerateKey('6', FALSE);
    return 0;
}

Это выводит Ninja. Hi123456 вместо Ninja. Hi123_456.

Другие вещи, достойные внимания:

1). Для периода ('.'), который был "введен", рабочий идентификатор был 0xBE вместо 0x3A.

2). Это было скомпилировано в Windows 10 с использованием Mingw.

Я надеюсь, что это было достаточно тщательно, спасибо заранее!


person Gavin Downard    schedule 23.09.2018    source источник
comment
95 — это VK_SLEEP, виртуальный код клавиши для спящего режима. Почему вы снова ожидаете, что он вызовет подчеркивание? Напомним, что SendInput отправляет нажатия клавиш, а не символы. Какие символы генерируются этими нажатиями клавиш, зависит от раскладки клавиатуры принимающего потока. Нажатие клавиши сразу справа от Tab не всегда приводит к Q.   -  person Igor Tandetnik    schedule 23.09.2018
comment
Подчеркивание не является ключом. Это символ, который вы получаете, удерживая Shift и нажимая клавишу дефиса (при американской раскладке клавиатуры).   -  person Sid S    schedule 23.09.2018
comment
Проверьте Spy++, чтобы увидеть, какие сообщения на самом деле отправляются в окно, когда вы используете клавиатуру. Он поставляется с Visual Studio.   -  person Sid S    schedule 23.09.2018


Ответы (1)


Код виртуального ключа 0x3A не является символом точки. Фактически, согласно документации Microsoft, 0x3A ДАЖЕ НЕ ОПРЕДЕЛЯЕТСЯ вообще. Вместо символа точки вы должны использовать VK_OEM_PERIOD:

VK_OEM_PERIOD
0xBE

Для любой страны/региона '.' ключ

При этом вызов SendInput() с cInputs=1 обычно является логической ошибкой. Конечно, ВСЕГДА ошибка, когда вы отправляете несколько входных событий подряд, как это делает ваш пример кода. Вся причина, по которой SendInput() вообще существует, заключается в том, чтобы заменить keybd_event()mouse_event()), которые могут отправлять только одно входное событие за раз. При моделировании нескольких событий вы не хотите, чтобы другие события вводились между вашими событиями, и наоборот. SendInput() является атомарным с другими механизмами ввода, но при отправке нескольких событий эта атомарность гарантируется только в том случае, если вы отправляете все свои события одновременно.

Вы должны поместить свои INPUT в массив и вызвать SendInput() ОДИН РАЗ, при этом cInputs будет равно общему количеству INPUT, которое вы отправляете.

Кроме того, при отправке ввода с клавиатуры для текстовых символов необходимо использовать VkKeyScan/Ex(), чтобы получить правильный код виртуальной клавиши и состояние сдвига, хотя намного проще вместо этого используйте флаг KEYEVENTF_UNICODE, чтобы вы могли отправлять фактические символы Unicode вместо виртуальных кодов клавиш.

Вместо этого попробуйте что-то вроде этого:

#include <iostream>
#include <vector>
#include <string>
#include <windows.h>

/* HWND = "Window Handle" */
HWND GameWindow = FindWindow(NULL, TEXT("Command Prompt"));

void GenerateKeyDown(std::vector<INPUT> &inputQueue, int vk, bool bExtended = false)
{
    INPUT in = {};

    in.type = INPUT_KEYBOARD;
    in.ki.wVk = vk;
    if (bExtended) in.ki.dwFlags = KEYEVENTF_EXTENDEDKEY;

    inputQueue.push_back(in);
}

void GenerateKeyUp(std::vector<INPUT> &inputQueue, int vk, bool bExtended = false)
{
    INPUT in = {};

    in.type = INPUT_KEYBOARD;
    in.ki.wVk = vk;
    if (bExtended) in.ki.dwFlags = KEYEVENTF_EXTENDEDKEY;
    in.ki.dwFlags |= KEYEVENTF_KEYUP;

    inputQueue.push_back(in);
}

void GenerateKey(std::vector<INPUT> &inputQueue, int vk, bool bExtended = false)
{
    INPUT in[2] = {};

    in[0].type = INPUT_KEYBOARD;
    in[0].ki.wVk = vk;
    if (bExtended) in[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY;

    in[1] = in[0];
    in[1].ki.dwFlags |= KEYEVENTF_KEYUP;

    inputQueue.insert(inputQueue.end(), in, in+1);
}

void GenerateString(std::vector<INPUT> &inputQueue, const std::wstring &str)
{
    int len = str.length();
    if (len == 0) return;

    inputQueue.reserve(inputQueue.size()+(len*2));

    INPUT in = {};
    in.type = INPUT_KEYBOARD;
    in.ki.dwFlags = KEYEVENTF_UNICODE;

    int i = 0;
    while (i < len)
    {
        WORD ch = (WORD) str[i++];

        if ((ch < 0xD800) || (ch > 0xDFFF))
        {
            in.ki.wScan = ch;
            in.ki.dwFlags &= ~KEYEVENTF_KEYUP;
            inputQueue.push_back(in);

            in.ki.dwFlags |= KEYEVENTF_KEYUP;
            inputQueue.push_back(in);
        }
        else
        {
            WORD ch2 = (WORD) str[i++];

            in.ki.wScan = ch;
            in.ki.dwFlags &= ~KEYEVENTF_KEYUP;
            inputQueue.push_back(in);

            in.ki.wScan = ch2;
            inputQueue.push_back(in);

            in.ki.wScan = ch;
            in.ki.dwFlags |= KEYEVENTF_KEYUP;
            inputQueue.push_back(in);

            in.ki.wScan = ch2;
            inputQueue.push_back(in);
        }
    }
 }

int main()
{
    /*
       SetForegroundWindow will give the window focus for the
       keyboard/mouse! In other words, you don't have to have
       the game opened upfront in order to emulate key/mouse
       presses, it's very helpful if it's a game that runs
       in fullscreen mode, like StarCraft: Brood War does
    */

    SetForegroundWindow(GameWindow);

    std::vector<INPUT> inputQueue;

    /*
    GenerateString(inputQueue, L"I Am cooler than you will ever ben .");
    GenerateKey(inputQueue, VK_RETURN);
    */

    GenerateString(inputQueue, L"NInja. HI123_456");

    /* alternatively:

    GenerateString(inputQueue, L"NInja");
    GenerateKey(inputQueue, VK_OEM_PERIOD);
    GenerateString(inputQueue, L" HI123");

    // see why using KEYEVENTF_UNICODE is easier?
    SHORT ret = VkKeyScanW(L'_');
    BYTE vk = LOBYTE(ret);
    BYTE shift = HIBYTE(ret);
    if (vk != -1)
    {
        SHORT state = GetKeyState(VK_SHIFT);
        bool bIsDown = (state & 0x800);

        if (shift & 1)
        {
            if (!bIsDown)
                GenerateKeyDown(inputQueue, VK_SHIFT);
        }
        else
        {
            if (bIsDown)
                GenerateKeyUp(inputQueue, VK_SHIFT);
        }

        GenerateKey(inputQueue, vk);

        if (shift & 1) 
        {
            if (!bIsDown)
                GenerateKeyUp(inputQueue, VK_SHIFT);
        }
        else
        {
            if (bIsDown)
                GenerateKeyDown(inputQueue, VK_SHIFT);
        }
    }

    GenerateString(inputQueue, L"456");
    */

    SendInput(inputQueue.size(), &inputQueue[0], sizeof(INPUT));

    return 0;
}
person Remy Lebeau    schedule 23.09.2018
comment
Спасибо! Я очень ценю это. Надеюсь, вы не возражаете, что я опубликовал второй ответ на свой вопрос, просто исправив ошибки в вашем коде. Похоже, мой код, на котором я основывал свои вещи, был ошибочным :( - person Gavin Downard; 24.09.2018
comment
@GavinDownard, хотя вы можете опубликовать свой собственный ответ на свой вопрос, было бы более вежливо просто указать на ошибки в моем ответе, чтобы я мог их исправить (что я сейчас и сделал), и вы могли бы тогда принять Это. - person Remy Lebeau; 24.09.2018