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"
複製代碼