Introduction to the

Electron uses Node-ffi to call the SendMessageW method in the Windows DLL library (user32.dll) to send a Windows message to a Windows window.

To prepare

What is a node – ffi

FFI (Foreign Function Interface) is a cross-language call scheme, in short, my program written in Java can directly call your C++ written Function. Notice that this is a direct call, rather than my Java process sending a message to a C++ process, which calls some function to process my message.

Node-ffi is the implementation of ffI standard under Node

What is a DLL

DLL (Dynamic Link Library) is a shared function Library in Windows. It can be interpreted as a collection of functions packed together in this file, so that the executable file can dynamically Link certain functions.

node-ffi

Let’s start with an official Node-ffi example

var ffi = require('ffi'); Var libm = ffi.library ()'libm', {
  'ceil': [ 'double'['double']]}); / / call libm. Ceil (1.5); / / 2Copy the code

The core is the ffi.library () method: The first argument “libm” is the name of the DLL library to load. The second argument is {” function name “:[” return value type “,[” parameter 1 type “, “parameter 2 type “,” parameter n type “]]}, from left to right. The corresponding function declaration in C++ is double ceil(double x);

Once loaded, ceil is a method of the libm variable that can be called directly.

The most core point here is to describe C++ functions in JS, what type is the return value, what type is the input parameter, because there are many types in C++ and can be customized, it is impossible to correspond with JS type one by one, so we need to introduce a library (ref) to help us describe the type in C++.

ref

With the introduction of the ref library, basic types in C++ can be described in JS, and variables holding a type can be obtained directly from 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
Copy the code

In addition, we can describe the pointer type in JS, and we can get the pointer of that type by refType, for example

const CHAR_P = ref.refType(ref.types.char);
Copy the code

This chestnut gets a pointer to type char, which is char *

OK, so now we have primitive types, now we have Pointers, now we can describe most types in C++, but notice that in C/C++ there’s also such a thing as structs, where you can combine multiple types to make new types, for example

typedef struct Sample{
    int value;
    char* name;
} Node;
Copy the code

For such types, we also need the help of a library

The attached:The document address

ref-struct

The library provides support for the types of structs. See the official examples for a good understanding of structs in C++

struct timeval {
  time_t       tv_sec;   /* seconds since Jan. 1, 1970 */
  suseconds_t  tv_usec;  /* and microseconds */
};
Copy the code

Declaration in node

var ref = require('ref'Var StructType = require(StructType = require(ref))'ref-struct-di'Var suseconds_t = ref.types. Long var suseconds_t = ref.types. Long var suseconds_t = ref.types. Tv_sec: time_t, tv_usec: suseconds_t}) var TV = new timevalCopy the code

Windows messaging

Windows messages can be used as a means of communication between multiple processes. When the user clicks the mouse or presses the keyboard, a specific message is generated, which is placed in the message queue of the application, and the application comes to consume the message and process it accordingly.

The actual operation

Install dependencies

node-ffi

Details: https://github.com/node-ffi-napi/node-ffi-napi

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

  2. Poke link to download and install Windows build tools check at https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools Visual C++ build tools Visual C++ build tools

  3. node-ffi

npm config set msvs_version 2019

npm install ffi-napi
Copy the code

ref-napi

npm install ref-napi

ref-struct-di

npm install ref-struct-di

ref-wchar-napi

npm install ref-wchar-napi

Use SendMessageW and FindWindowW in JS

OK, now that we know how to call a dynamic library (DLL) function in JS, let’s see how to send a Windows message.

Here we can find a function called SendMessageW, which is defined as follows

LRESULT SendMessageW(
  HWND   hWnd,
  UINT   Msg,
  WPARAM wParam,
  LPARAM lParam
);
Copy the code

The return value is of LRESULT type, and there are four input parameters: HWND,UINT,WPARAM, and LPARAM.

You can see many types of definitions here

The definition of LRESULT is typedef LONG LRESULT; So we can use LONG for LRESULT and HWND is defined as DECLARE_HANDLE(HWND); DECLARE_HANDLE is a macro

#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
Copy the code

This concludes with a pointer of type void *. There’s nothing to say about UINT because ref has a definition of WPARAM and the definition of WPARAM is typedef UINT WPARAM; The definition of UINT LPARAM is typedef LONG LPARAM; , is of type LONG

At this point we can describe this function in 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]]
});
Copy the code

Before sending, we also need to find the handle to the target window. Here we use another function, FindWindowW, to find the window method defined as follows

HWND FindWindowW(
  LPCWSTR lpClassName,
  LPCWSTR lpWindowName
);
Copy the code

The return value is of type HWND, and the arguments are of type LPCWSTR. The definition is typedef const wchar_t* LPCWSTR; The ref-wchar-nAPI we use provides this type.

Finally, there is an implicit type, COPYDATA, which is a Message structure for Windows

typedef struct tagCOPYDATASTRUCT {
  ULONG_PTR dwData;
  DWORD     cbData;
  PVOID     lpData;
} COPYDATASTRUCT, *PCOPYDATASTRUCT;
Copy the code

DwData of ULONG_PTR type is 32-bit custom data; lpData of PVOID type is a pointer to data; cbData of DWORD type is an integer representing the length of lpData. ULONG_PRT is the pointer type of ULONG,PVOID is the pointer type of void, and DWORD is typedef unsigned long DWORD, which is the type of ULONG

We can describe this data type in JS

const COPYDATA = new Struct({
    dwData: ULONG_P,
    cbData: ULONG,
    lpData: VOID_P
});
Copy the code

At this point, our JS part is ready

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("Test message") + '\ 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())
Copy the code

We can directly use user32.findWindoww () to find window handles and user32.sendMessagew () to send messages

Create a window

Create a Win window directly in Python for testing

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()
Copy the code

test

Running our JS file directly sends a message to a Python window, which prints out the length and content of the message

1181360
15
"Test message"
15
"Test message"
15
"Test message"
31
"Chinese-english mixed message test"
Copy the code