electron使用node-ffi
调用windows系统DLL库(user32.dll
)中的SendMessageW
方法实现发送windows消息至windows窗口。node
FFI(Foreign Function Interface)是一种跨语言调用方案,简言之就是我Java写的程序能直接调用你C++写的函数。注意这里是直接调用,而不是我Java进程发送一个消息给C++进程,C++调用某个函数处理个人消息。python
node-ffi就是FFI标准在node下的实现git
DLL(Dynamic Link Library)动态连接库是windows系统下的一种共享函数库,能够理解为吧一堆函数打包到一块儿放在该文件中,供可执行文件动态连接某些函数使用。github
咱们先来看一个node-ffi
官方的例子npm
var ffi = require('ffi');
// 加载动态库中的ceil取整函数
var libm = ffi.Library('libm', {
'ceil': [ 'double', [ 'double' ] ]
});
// 调用
libm.ceil(1.5); // 2
复制代码
核心在于ffi.Library()
这个方法: 该方法参数分为两部分,第一个参数“libm”
是要加载的DLL库名, 第二个参数从左到右依次是 {"函数名":["返回值类型",["参数1类型","参数2类型","参数n类型"]]}
,对应的就是C++中的函数声明double ceil(double x);
windows
加载完后,ceil
即是变量libm
的一个方法,能够直接调用。api
这里最核心的一点是将C++函数在JS中描述出来,返回值是什么类型,入参是什么类型,因为C++中的类型颇多并且还能够自定义,不可能与JS的类型一一对应,所以须要引入一个库(ref
)来帮咱们描述C++中的类型。bash
引入ref库后,咱们即可以在JS中描述C++中的基本类型,直接经过ref.types.XXX
即可以获得持有一个类型的变量electron
void
int8
uint8
int16
uint16
int32
uint32
int64
uint64
float
double
Object
CString
bool
byte
char
uchar
short
int
uint
long
ulong
longlong
ulonglong
复制代码
除此以外,咱们还能够在JS中描述指针
这个类型,经过ref.refType(类型)
即可以获得该类型的指针,举个栗子函数
const CHAR_P = ref.refType(ref.types.char);
复制代码
上边这个栗子就获得了char类型的指针,也就是char *
OK,咱们如今有了基本类型,有了指针,已经能够描述C++中大部分的类型了,但注意C/C++中还有结构体这种东西,能够将多个类型组合在一块儿成为新的类型,好比
typedef struct Sample{
int value;
char* name;
} Node;
复制代码
对于这样的类型,咱们还须要一个库的帮助
附:文档地址
该库提供给咱们告终构体的类型的支持,直接看官方例子吧,很好理解 C++中的结构体
struct timeval {
time_t tv_sec; /* seconds since Jan. 1, 1970 */
suseconds_t tv_usec; /* and microseconds */
};
复制代码
对应node中的声明
var ref = require('ref')
// 这个引用必定写对,我就是漏了后边的(ref),致使一直有问题,郁闷了好几天
var StructType = require('ref-struct-di')(ref)
// 定义时间类型
var time_t = ref.types.long
var suseconds_t = ref.types.long
// 定义timeval结构体
var timeval = StructType({
tv_sec: time_t,
tv_usec: suseconds_t
})
// 能够直接new一个实例
var tv = new timeval
复制代码
windows消息能够做为一种多进程间的通讯方式。当用户点击鼠标、按下键盘都会产生一个特定的消息,放置到应用程序的消息队列中,应用程序过来消费消息,并进行对应的处理。
详细步骤:https://github.com/node-ffi-napi/node-ffi-napi
node-gyp npm install -g node-gyp
windows build tools 戳连接下载安装https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools
勾选 Visual C++ build tools 点击安装,大约4G多
node-ffi
npm config set msvs_version 2019
npm install ffi-napi
复制代码
npm install ref-napi
npm install ref-struct-di
npm install ref-wchar-napi
OK,咱们已经知道了如何在JS中去调用一个动态库(DLL)中的函数了,接下来让咱们看下如何操做发送一个windows消息。
咱们能够在这里找到一个叫作SendMessageW
的函数,该函数的定义以下
LRESULT SendMessageW(
HWND hWnd,
UINT Msg,
WPARAM wParam,
LPARAM lParam
);
复制代码
返回值是LRESULT
类型,入参有4个,分别是HWND
,UINT
,WPARAM
,LPARAM
类型。
这里能够看到不少类型的定义
LRESULT
的定义是 typedef LONG LRESULT;
,因此咱们能够用LONG类型表示LRESULT HWND
的定义是 DECLARE_HANDLE(HWND);
,这里的DECLARE_HANDLE
是一个宏
#ifdef STRICT
typedef void *HANDLE;
#define DECLARE_HANDLE() struct name##__ { int unused; }; typedef struct name##__ *name
#else
typedef PVOID HANDLE;
#define DECLARE_HANDLE(name) typedef HANDLE name
#endif
复制代码
总结下来是一个void *
类型的指针。 UINT
没什么好说的ref
中有定义 WPARAM
的定义是typedef UINT WPARAM;
,也是UINT
LPARAM
的定义是typedef LONG LPARAM;
,是LONG
类型
至此咱们已经能够在JS中描述这个函数了。
const FFI = require('ffi-napi');
const ref = require('ref-napi');
const Struct = require('ref-struct-di')(ref);
const LRESULT = ref.types.long;
const POINTER = ref.refType(ref.types.void);
const UINT = ref.types.uint;
const WPARAM = ref.types.uint;
const LPARAM = ref.types.long;
let User32 = new FFI.Library('user32.dll', {
'SendMessageW': [LRESULT, [POINTER, UINT, WPARAM, LPARAM]]
});
复制代码
在发送以前,咱们还须要找到目标窗口的句柄,这里使用到另一个函数FindWindowW
寻找窗口 方法定义以下
HWND FindWindowW(
LPCWSTR lpClassName,
LPCWSTR lpWindowName
);
复制代码
返回值是HWND
类型,参数都是LPCWSTR
类型,定义是typedef const wchar_t* LPCWSTR;
, 咱们使用的ref-wchar-napi
提供了该类型。
最后,还有一个隐含的类型, COPYDATA
,这是windows的一种消息结构
typedef struct tagCOPYDATASTRUCT {
ULONG_PTR dwData;
DWORD cbData;
PVOID lpData;
} COPYDATASTRUCT, *PCOPYDATASTRUCT;
复制代码
内含三个属性,分别是ULONG_PTR
类型的dwData
,为32位的自定义数据,PVOID
类型的lpData
,是指向数据的指针,DWORD
类型的cbData
,整形,表明lpData
数据的长度。 ULONG_PRT
就是ulong
的指针类型,PVOID
就是void
的指针类型,DWORD
的定义是typedef unsigned long DWORD
,是ulong
类型
咱们即可以在JS中描述这一数据类型
const COPYDATA = new Struct({
dwData: ULONG_P,
cbData: ULONG,
lpData: VOID_P
});
复制代码
至此,咱们JS部分准备完毕
const FFI = require('ffi-napi');
const ref = require('ref-napi');
const Struct = require('ref-struct-di')(ref);
const ULONG = ref.types.ulong;
const LRESULT = ref.types.long;
const POINTER = ref.refType(ref.types.void);
const UINT = ref.types.uint;
const UINT_P = ref.refType(UINT);
const ULONG_P = ref.refType(ULONG);
const WPARAM = ref.types.uint;
const LPARAM = ref.types.long;
const VOID_P = POINTER;
const CHAR_P = ref.refType(ref.types.char);
const WCHAR_T = require('ref-wchar-napi');
const WCHAR_T_P = ref.refType(WCHAR_T);
const COPYDATA = new Struct({
dwData: ULONG_P,
cbData: ULONG,
lpData: VOID_P
});
let User32 = new FFI.Library('user32.dll', {
'FindWindowW': [POINTER, [WCHAR_T_P, WCHAR_T_P]],
'SendMessageW': [LRESULT, [POINTER, UINT, WPARAM, ref.refType(LPARAM)]]
});
let hwnd = User32.FindWindowW(null, Buffer.from('win32gui\0', 'ucs2'));
let msg = JSON.stringify("测试消息") + '\0';
let length = (new TextEncoder().encode(msg)).length;
data = COPYDATA();
data.dwData = ref.NULL;
data.cbData = length;
data.lpData = ref.allocCString(msg);
User32.SendMessageW(hwnd, 0x004a, null, data.ref())
复制代码
咱们能够直接使用User32.FindWindowW()
来寻找窗口句柄,使用User32.SendMessageW()
来发送消息
直接使用Python建立一个win窗口用来测试
import win32con, win32api, win32gui, ctypes, ctypes.wintypes
from ctypes import *
class COPYDATASTRUCT(Structure):
_fields_ = [('dwData', POINTER(c_uint)),
('cbData', c_uint),
('lpData', c_char_p)]
PCOPYDATASTRUCT = POINTER(COPYDATASTRUCT)
class Listener:
def __init__(self):
message_map = {
win32con.WM_COPYDATA: self.OnCopyData
}
wc = win32gui.WNDCLASS()
wc.lpfnWndProc = message_map
wc.lpszClassName = 'MyWindowClass'
hinst = wc.hInstance = win32api.GetModuleHandle(None)
classAtom = win32gui.RegisterClass(wc)
self.hwnd = win32gui.CreateWindow (
classAtom,
"win32gui",
0,
0,
0,
win32con.CW_USEDEFAULT,
win32con.CW_USEDEFAULT,
0,
0,
hinst,
None
)
print(self.hwnd)
def OnCopyData(self, hwnd, msg, wparam, lparam):
copydata = ctypes.cast(lparam, PCOPYDATASTRUCT).contents
print (copydata.cbData)
data = copydata.lpData[:copydata.cbData - 1]
print (data.decode('utf-8'))
return 1
l = Listener()
win32gui.PumpMessages()
复制代码
直接运行咱们的JS文件,即可以发送一条消息至python窗口,python会打印出消息长度和内容
1181360
15
"测试消息"
15
"测试消息"
15
"测试消息"
31
"中文英文混合消息test"
复制代码