联合体定义

预计阅读时间: 5 分钟

源文档 - Union values

联合体定义

新增于 Koffi 2.5

你可以使用类似于结构体的语法,通过 koffi.union() 函数声明联合体。该函数接受两个参数:第一个是类型的名称,第二个是一个包含联合体成员名称和类型的对象。你可以省略第一个参数来声明一个匿名联合体。

以下示例展示了如何在 C 和 JS 中使用 Koffi 声明相同的联合体:

typedef union IntOrDouble {
    int64_t i;
    double d;
} IntOrDouble;
const IntOrDouble = koffi.union("IntOrDouble", {
	i: "int64_t",
	d: "double",
});

输入联合体

将联合体值传递给 C

你可以通过 koffi.Union(type) 实例化一个联合体对象。这将创建一个最多包含一个活动成员的特殊对象。

创建联合体实例后,你可以像操作普通对象一样,使用点运算符设置成员。然后,将联合体值传递给所需的 C 函数即可。

const U = koffi.union("U", { i: "int", str: "char *" });

const DoSomething = lib.func("void DoSomething(const char *type, U u)");

const u1 = new koffi.Union("U");
u1.i = 42;
const u2 = new koffi.Union("U");
u2.str = "Hello!";

DoSomething("int", u1);
DoSomething("string", u2);

为了简化操作,Koffi 还接受仅有一个属性的对象字面量(不多不少),用于设置相应的联合体成员。以下示例使用此方法简化了上述代码:

const U = koffi.union("U", { i: "int", str: "char *" });

const DoSomething = lib.func("void DoSomething(const char *type, U u)");

DoSomething("int", { i: 42 });
DoSomething("string", { str: "Hello!" });

Win32 示例

以下示例使用 Win32 API 的 SendInput 函数发送 Win+D 快捷键,以隐藏窗口(显示桌面)。

// ES6 语法:import koffi from 'koffi';
const koffi = require("koffi");

// Win32 类型和函数

const user32 = koffi.load("user32.dll");

const INPUT_MOUSE = 0;
const INPUT_KEYBOARD = 1;
const INPUT_HARDWARE = 2;

const KEYEVENTF_KEYUP = 0x2;
const KEYEVENTF_SCANCODE = 0x8;

const VK_LWIN = 0x5b;
const VK_D = 0x44;

const MOUSEINPUT = koffi.struct("MOUSEINPUT", {
	dx: "long",
	dy: "long",
	mouseData: "uint32_t",
	dwFlags: "uint32_t",
	time: "uint32_t",
	dwExtraInfo: "uintptr_t",
});
const KEYBDINPUT = koffi.struct("KEYBDINPUT", {
	wVk: "uint16_t",
	wScan: "uint16_t",
	dwFlags: "uint32_t",
	time: "uint32_t",
	dwExtraInfo: "uintptr_t",
});
const HARDWAREINPUT = koffi.struct("HARDWAREINPUT", {
	uMsg: "uint32_t",
	wParamL: "uint16_t",
	wParamH: "uint16_t",
});

const INPUT = koffi.struct("INPUT", {
	type: "uint32_t",
	u: koffi.union({
		mi: MOUSEINPUT,
		ki: KEYBDINPUT,
		hi: HARDWAREINPUT,
	}),
});

const SendInput = user32.func("unsigned int __stdcall SendInput(unsigned int cInputs, INPUT *pInputs, int cbSize)");

// 使用 Win+D 快捷键显示/隐藏桌面

let events = [
	make_keyboard_event(VK_LWIN, true),
	make_keyboard_event(VK_D, true),
	make_keyboard_event(VK_D, false),
	make_keyboard_event(VK_LWIN, false),
];

SendInput(events.length, events, koffi.sizeof(INPUT));

// 辅助函数

function make_keyboard_event(vk, down) {
	let event = {
		type: INPUT_KEYBOARD,
		u: {
			ki: {
				wVk: vk,
				wScan: 0,
				dwFlags: down ? 0 : KEYEVENTF_KEYUP,
				time: 0,
				dwExtraInfo: 0,
			},
		},
	};

	return event;
}

输出联合体

与结构体不同,Koffi 无法自动解码联合体,因为它无法确定哪个成员是有效的。然而,你可以使用特殊的 koffi.Union 对象作为输出参数,并在调用后解码内存。

要解码输出联合体指针参数,请使用 new koffi.Union(type) 创建一个占位对象,并将该对象传递给函数。

调用后,你可以解引用此对象上的所需成员值,Koffi 将在此时进行解码。

以下示例展示了如何在调用后使用 koffi.Union() 解码输出联合体。

#include <stdint.h>

typedef union IntOrDouble {
    int64_t i;
    double d;
} IntOrDouble;

void SetUnionInt(int64_t i, IntOrDouble *out)
{
    out->i = i;
}

void SetUnionDouble(double d, IntOrDouble *out)
{
    out->d = d;
}

const IntOrDouble = koffi.union('IntOrDouble', {
    i: 'int64_t',
    d: 'double',
    raw: koffi.array('uint8_t', 8)
});

const SetUnionInt = lib.func('void SetUnionInt(int64_t i, _Out_ IntOrDouble *out)');
const SetUnionDouble = lib.func('void SetUnionDouble(double d, _Out_ IntOrDouble *out)');

let u1 = new koffi.Union('IntOrDouble');
let u2 = new koffi.Union('IntOrDouble');

SetUnionInt(123, u1);
SetUnionDouble(123, u2);

console.log(u1.i, '---', u1.raw); // 输出 123 --- Uint8Array(8) [123, 0, 0, 0, 0, 0, 0, 0]
console.log(u2.d, '---', u2.raw); // 输出 123 --- Uint8Array(8) [0, 0, 0, 0, 0, 0, 69, 64]