【问题标题】:using SendInput in Node-FFI在 Node-FFI 中使用 SendInput
【发布时间】:2017-05-12 01:05:47
【问题描述】:

我想在nodejs中使用windows Api中的SendInput函数,使用FFI包。

我对 C 的了解有限,所以我无法真正弄清楚我遇到了什么问题, 我基本上是在尝试虚拟按键盘上的一个键。

这是我的代码:

var ffi = require('ffi');
var ref = require ('ref');
var struct = require ('ref-struct');

var keyboardInput = struct({
    'type': 'int',
    'wVK': 'int',
    'wScan': 'int',
    'dwFlags': 'int',
    'time': 'int',
    'dwExtraInfo': 'int64'
});

var keyboardInputPtr = ref.refType(keyboardInput);
var keyboard = new keyboardInput();
keyboard.type = 1;
keyboard.wVK = 0x41;
keyboard.wScan = 0;
keyboard.dwFlags = 2;
keyboard.time = 0;
keyboard.dwExtraInfo = 0;

var user32 = ffi.Library('user32', {
    'SendInput': [ 'int', [ 'uint', keyboardInputPtr, 'int' ] ]
});

setInterval(function(){
    var r = user32.SendInput(1, keyboard.ref(), 40);
    console.log(r);
}, 500);

它在控制台中记录了一个“1”,这不应该意味着它可以工作吗?因为我打开记事本时没有按键。

【问题讨论】:

  • SendInput 将输入放入硬件输入队列。在拾取此输入事件时,无论哪个窗口(或线程,实际上)在前台都会接收输入。因此,当您启动应用程序时,记事本自然不是前台窗口。无论如何,您所描述的是您提出的解决方案。现在你真正想解决什么问题
  • 没有人将伪造输入到记事本作为最终目标。你真正想做什么。很有可能,即使您可以伪造它,我记事本也不会以同样的方式解决您的真正目标。
  • @IInspectable 我正在尝试编写一个程序,将按钮(在键盘上)单击到当前活动窗口中。有了间隔,它基本上就像一个自动点击器。
  • “点击一个按钮(在键盘上)” - 这没有意义。您是要单击按钮,还是要生成键盘输入。
  • @IInspectable 很抱歉,如果我不清楚,但我想生成键盘输入,就像按下键盘上的按钮“A”一样。

标签: javascript c node.js winapi node-ffi


【解决方案1】:

“1”告诉您插入了 1 个事件,而不是事件实际上是什么。我不知道 FFI,但在我看来,keyboardInput 有一些无效的类型定义。 wVK 和 wScan 必须是 16 位整数(因此 'w' 表示 WORD)。由于它们的类型与 dwFlags(一个“int”)相同,因此会导致输入值无效。

【讨论】:

    【解决方案2】:

    我终于找到了一种使用node-ffi/node-ffi-napi 输入按键的方法,使用SendInput 函数! (下面的当前代码使用node-ffi-napi,因为node-ffi已被取消维护/损坏;请参阅node-ffi版本的编辑历史,api几乎完全相同)

    但是,请注意有两种方法可以调用 SendInput 函数,如下所示:https://autohotkey.com/boards/viewtopic.php?p=213617#p213617

    就我而言,我不得不使用第二种(扫描代码)方法,因为第一种(虚拟键)方法在我需要密钥模拟的程序中不起作用。

    事不宜迟,以下是完整的解决方案:

    import keycode from "keycode";
    import ffi from "ffi-napi";
    import ref from "ref-napi";
    import os from "os";
    import import_Struct from "ref-struct-di";
    
    var arch = os.arch();
    const Struct = import_Struct(ref);
    
    var Input = Struct({
        "type": "int",
    
        // For some reason, the wScan value is only recognized as the wScan value when we add this filler slot.
        // It might be because it's expecting the values after this to be inside a "wrapper" substructure, as seen here:
        //     https://msdn.microsoft.com/en-us/library/windows/desktop/ms646270(v=vs.85).aspx
        "???": "int",
         
        "wVK": "short",
        "wScan": "short",
        "dwFlags": "int",
        "time": "int",
        "dwExtraInfo": "int64"
    });
    
    var user32 = ffi.Library("user32", {
        SendInput: ["int", ["int", Input, "int"]],
        MapVirtualKeyExA: ["uint", ["uint", "uint", "int"]],
    });
    
    const extendedKeyPrefix = 0xe000;
    const INPUT_KEYBOARD = 1;
    const KEYEVENTF_EXTENDEDKEY = 0x0001;
    const KEYEVENTF_KEYUP       = 0x0002;
    const KEYEVENTF_UNICODE     = 0x0004;
    const KEYEVENTF_SCANCODE    = 0x0008;
    //const MAPVK_VK_TO_VSC = 0;
    
    export class KeyToggle_Options {
        asScanCode = true;
        keyCodeIsScanCode = false;
        flags?: number;
        async = false; // async can reduce stutter in your app, if frequently sending key-events
    }
    
    let entry = new Input(); // having one persistent native object, and just changing its fields, is apparently faster (from testing)
    entry.type = INPUT_KEYBOARD;
    entry.time = 0;
    entry.dwExtraInfo = 0;
    export function KeyToggle(keyCode: number, type = "down" as "down" | "up", options?: Partial<KeyToggle_Options>) {
        const opt = Object.assign({}, new KeyToggle_Options(), options);
        
        // scan-code approach (default)
        if (opt.asScanCode) {
            let scanCode = opt.keyCodeIsScanCode ? keyCode : ConvertKeyCodeToScanCode(keyCode);
            let isExtendedKey = (scanCode & extendedKeyPrefix) == extendedKeyPrefix;
    
            entry.dwFlags = KEYEVENTF_SCANCODE;
            if (isExtendedKey) {
                entry.dwFlags |= KEYEVENTF_EXTENDEDKEY;
            }
    
            entry.wVK = 0;
            entry.wScan = isExtendedKey ? scanCode - extendedKeyPrefix : scanCode;
        }
        // (virtual) key-code approach
        else {
            entry.dwFlags = 0;
            entry.wVK = keyCode;
            //info.wScan = 0x0200;
            entry.wScan = 0;
        }
    
        if (opt.flags != null) {
            entry.dwFlags = opt.flags;
        }
        if (type == "up") {
            entry.dwFlags |= KEYEVENTF_KEYUP;
        }
    
        if (opt.async) {
            return new Promise((resolve, reject)=> {
                user32.SendInput.async(1, entry, arch === "x64" ? 40 : 28, (error, result)=> {
                    if (error) reject(error);
                    resolve(result);
                });
            });
        }
        return user32.SendInput(1, entry, arch === "x64" ? 40 : 28);
    }
    
    export function KeyTap(keyCode: number, opt?: Partial<KeyToggle_Options>) {
        KeyToggle(keyCode, "down", opt);
        KeyToggle(keyCode, "up", opt);
    }
    
    export function ConvertKeyCodeToScanCode(keyCode: number) {
        //return user32.MapVirtualKeyExA(keyCode, MAPVK_VK_TO_VSC, 0);
        return user32.MapVirtualKeyExA(keyCode, 0, 0);
    }
    

    要使用它,请调用:

    KeyTap(65); // press the A key
    

    或者,如果您使用的是keycode npm package

    import keycode from "keycode";
    KeyTap(keycode.codes.a);
    

    【讨论】:

    • @truefusion 看起来你是对的,但我似乎无法找到该领域的适当文档。我在代码注释中链接的页面提到了该字段,但根本没有描述它或提供可能的值。 (它只是给出纯文本引用“DUMMYUNIONNAME.mi”)
    • 修复了MapVirtualKeyEx 问题,我改用MapVirtualKeyExA,它起作用了:user32.MapVirtualKeyExA(keyCode, 0, 0);MapVirtualKeyExA: ["uint", ["uint", "uint", "int"]],
    • @yaya 太棒了!谢谢(你的)信息。 (我会在我的电脑上确认后更新我的答案。)
    • 当然。这是我的完整代码,所以如果您遇到任何错误,您也可以检查它:pastebin.pl/view/f527be0e
    • @Venryx 在x64 架构中运行时,我也遇到了对"???": "int"“填充”成员的需求。我相信这是由于结构中的指针需要与指针的大小(x64 架构中的 8 个字节)对齐。通过添加 4 字节间距,您有效地对齐了指针 (ULONG_PTR dwExtraInfo)。我找到了另一种解决方法:使用类型pointer 标记该字段,ref-struct-napi 将为您处理对齐。有关详细信息,请参阅我的答案。
    【解决方案3】:

    这是一个按下a 键的工作示例。它使用ref-struct-napiref-union-napi 来准确表示INPUT 结构。

    const FFI = require('ffi-napi')
    const StructType = require('ref-struct-napi')
    const UnionType = require('ref-union-napi')
    const ref = require('ref-napi')
    
    
    const user32 = new FFI.Library('user32.dll', {
      // UINT SendInput(
      //   _In_ UINT cInputs,                     // number of input in the array
      //   _In_reads_(cInputs) LPINPUT pInputs,  // array of inputs
      //   _In_ int cbSize);                      // sizeof(INPUT)
      'SendInput': ['uint32', ['int32', 'pointer', 'int32']],
    })
    
    // typedef struct tagMOUSEINPUT {
    //   LONG    dx;
    //   LONG    dy;
    //   DWORD   mouseData;
    //   DWORD   dwFlags;
    //   DWORD   time;
    //   ULONG_PTR dwExtraInfo;
    // } MOUSEINPUT;
    const MOUSEINPUT = StructType({
      dx: 'int32',
      dy: 'int32',
      mouseData: 'uint32',
      flags: 'uint32',
      time: 'uint32',
      extraInfo: 'pointer',
    })
    
    // typedef struct tagKEYBDINPUT {
    //   WORD    wVk;
    //   WORD    wScan;
    //   DWORD   dwFlags;
    //   DWORD   time;
    //   ULONG_PTR dwExtraInfo;
    // } KEYBDINPUT;
    const KEYBDINPUT = StructType({
      vk: 'uint16',
      scan: 'uint16',
      flags: 'uint32',
      time: 'uint32',
      extraInfo: 'pointer',
    })
    
    // typedef struct tagHARDWAREINPUT {
    //   DWORD   uMsg;
    //   WORD    wParamL;
    //   WORD    wParamH;
    // } HARDWAREINPUT;
    const HARDWAREINPUT = StructType({
      msg: 'uint32',
      paramL: 'uint16',
      paramH: 'uint16',
    })
    
    // typedef struct tagINPUT {
    //   DWORD   type;
    //   union
    //   {
    //     MOUSEINPUT      mi;
    //     KEYBDINPUT      ki;
    //     HARDWAREINPUT   hi;
    //   } DUMMYUNIONNAME;
    // } INPUT;
    const INPUT_UNION = UnionType({
      mi: MOUSEINPUT,
      ki: KEYBDINPUT,
      hi: HARDWAREINPUT,
    })
    const INPUT = StructType({
      type: 'uint32',
      union: INPUT_UNION,
    })
    
    const pressKey = (scanCode) => {
      const keyDownKeyboardInput = KEYBDINPUT({vk: 0, extraInfo: ref.NULL_POINTER, time: 0, scan: scanCode, flags: 0x0008})
      const keyDownInput = INPUT({type: 1, union: INPUT_UNION({ki: keyDownKeyboardInput})})
      user32.SendInput(1, keyDownInput.ref(), INPUT.size)
    
      const keyUpKeyboardInput = KEYBDINPUT({vk: 0, extraInfo: ref.NULL_POINTER, time: 0, scan: scanCode, flags: 0x0008 | 0x0002})
      const keyUpInput = INPUT({type: 1, union: INPUT_UNION({ki: keyUpKeyboardInput})})
      user32.SendInput(1, keyUpInput.ref(), INPUT.size)
    }
    
    pressKey(0x1E)
    

    如果您想对 SendInput 执行包含多次按键的单次调用,请构造一个 INPUT 结构数组:

    const pressKey = (scanCode) => {
      const inputCount = 2
      const inputArray = Buffer.alloc(INPUT.size * inputCount)
      const keyDownKeyboardInput = KEYBDINPUT({vk: 0, extraInfo: ref.NULL_POINTER, time: 0, scan: scanCode, flags: 0x0008})
      const keyDownInput = INPUT({type: 1, union: INPUT_UNION({ki: keyDownKeyboardInput})})
      keyDownInput.ref().copy(inputArray, 0)
      const keyUpKeyboardInput = KEYBDINPUT({vk: 0, extraInfo: ref.NULL_POINTER, time: 0, scan: scanCode, flags: 0x0008 | 0x0002})
      const keyUpInput = INPUT({type: 1, union: INPUT_UNION({ki: keyUpKeyboardInput})})
      keyUpInput.ref().copy(inputArray, INPUT.size)
      user32.SendInput(inputCount, inputArray, INPUT.size)
    }
    

    【讨论】:

    • 我了解到 SendInput 能够在一个函数调用中发送多个键事件 (docs.microsoft.com/en-us/windows/win32/api/winuser/…)。您知道如何使用上面的代码/结构来完成此操作吗? (这是我无法弄清楚该怎么做的事情)
    • @Venryx 查看我的答案,我已对其进行了更新以包含此内容。
    • 太棒了!非常感谢,会派上用场的。
    猜你喜欢
    • 1970-01-01
    • 2018-12-26
    • 2013-09-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-12
    • 1970-01-01
    相关资源
    最近更新 更多