1

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

我对 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”,这不应该意味着它有效吗?因为我打开记事本时没有按下键。

4

3 回答 3

6

我终于找到了一种使用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

或者,如果您使用的是密钥代码 npm 包

import keycode from "keycode";
KeyTap(keycode.codes.a);
于 2018-05-18T13:34:34.590 回答
5

这是一个按下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)
}
于 2020-10-16T08:06:06.483 回答
0

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

于 2016-12-27T19:18:47.447 回答