electron調用DLL庫發送windows消息

簡介

electron使用node-ffi調用windows系統DLL庫(user32.dll)中的SendMessageW方法實現發送windows消息至windows窗口。node

準備

什麼是node-ffi

FFI(Foreign Function Interface)是一種跨語言調用方案,簡言之就是我Java寫的程序能直接調用你C++寫的函數。注意這裏是直接調用,而不是我Java進程發送一個消息給C++進程,C++調用某個函數處理個人消息。python

node-ffi就是FFI標準在node下的實現git

什麼是DLL

DLL(Dynamic Link Library)動態連接庫是windows系統下的一種共享函數庫,能夠理解爲吧一堆函數打包到一塊兒放在該文件中,供可執行文件動態連接某些函數使用。github

node-ffi

咱們先來看一個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

引入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;
複製代碼

對於這樣的類型,咱們還須要一個庫的幫助

附:文檔地址

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"
複製代碼
相關文章
相關標籤/搜索