electron调用DLL库发送windows消息

简介

electron使用node-ffi调用windows系统DLL库(user32.dll)中的SendMessageW方法实现发送windows消息至windows窗口。

准备

什么是node-ffi

FFI(Foreign Function Interface)是一种跨语言调用方案,简言之就是我Java写的程序能直接调用你C++写的函数。注意这里是直接调用,而不是我Java进程发送一个消息给C++进程,C++调用某个函数处理我的消息。

node-ffi就是FFI标准在node下的实现

什么是DLL

DLL(Dynamic Link Library)动态链接库是windows系统下的一种共享函数库,可以理解为吧一堆函数打包到一起放在该文件中,供可执行文件动态链接某些函数使用。

node-ffi

我们先来看一个node-ffi官方的例子

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);

加载完后,ceil便是变量libm的一个方法,可以直接调用。

这里最核心的一点是将C++函数在JS中描述出来,返回值是什么类型,入参是什么类型,由于C++中的类型颇多而且还可以自定义,不可能与JS的类型一一对应,因此需要引入一个库(ref)来帮我们描述C++中的类型。

ref

引入ref库后,我们便可以在JS中描述C++中的基本类型,直接通过ref.types.XXX便可以得到持有一个类型的变量

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;

对于这样的类型,我们还需要一个库的帮助

附:文档地址

ref-struct

该库提供给我们了结构体的类型的支持,直接看官方例子吧,很好理解
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消息机制

windows消息可以作为一种多进程间的通信方式。当用户点击鼠标、按下键盘都会产生一个特定的消息,放置到应用程序的消息队列中,应用程序过来消费消息,并进行对应的处理。

实际操作

安装依赖

node-ffi

详细步骤:https://github.com/node-ffi-napi/node-ffi-napi

  1. node-gyp
    npm install -g node-gyp

  2. windows build tools
    戳链接下载安装https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools
    勾选 Visual C++ build tools 点击安装,大约4G多

  3. node-ffi

npm config set msvs_version 2019

npm install ffi-napi

ref-napi

npm install ref-napi

ref-struct-di

npm install ref-struct-di

ref-wchar-napi

npm install ref-wchar-napi

在JS中使用SendMessageW和FindWindowW

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"
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335