quickwin 1.3.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +18 -24
- package/README.md +17 -23
- package/examples/PdfCanvas.js +8 -37
- package/examples/PdfCanvas.tsx +11 -41
- package/examples/exec_server.js +20155 -1420
- package/examples/exec_server.tsx +489 -0
- package/examples/exec_server_worker.js +383 -33
- package/examples/exec_server_worker.ts +408 -30
- package/examples/pdf_preview2.js +25 -82
- package/examples/pdf_preview2.ts +27 -84
- package/examples/pdf_viewer.js +1424 -1180
- package/examples/pdf_viewer.tsx +16 -67
- package/examples/setres.js +22 -47
- package/examples/setres.ts +23 -57
- package/examples/test_gallery.js +1418 -1075
- package/examples/test_react_lazy.js +1391 -1059
- package/examples/test_react_listbox.js +1390 -1058
- package/examples/test_root_window.js +1384 -1052
- package/examples/test_tab.js +1391 -1059
- package/lib/abort.js +70 -0
- package/lib/abort.ts +69 -0
- package/lib/certs.js +219 -0
- package/lib/certs.ts +229 -0
- package/lib/exec-log.js +15 -0
- package/lib/exec-log.ts +30 -0
- package/lib/fetch.js +88 -7
- package/lib/fetch.ts +73 -7
- package/lib/ffi/bind.js +441 -0
- package/lib/ffi/bind.ts +539 -0
- package/lib/ffi/ctype.js +194 -0
- package/lib/ffi/ctype.ts +369 -0
- package/lib/ffi/index.js +6 -0
- package/lib/ffi/index.ts +6 -0
- package/lib/ffi/struct.js +490 -0
- package/lib/ffi/struct.ts +753 -0
- package/lib/http-common.js +43 -5
- package/lib/http-common.ts +48 -6
- package/lib/http-server.js +187 -29
- package/lib/http-server.ts +177 -26
- package/lib/react-qw/components/DateTimePicker.js +18 -37
- package/lib/react-qw/components/Input.js +3 -10
- package/lib/react-qw/components/Link.js +6 -19
- package/lib/react-qw/components/ListView.js +50 -154
- package/lib/react-qw/components/PathPicker.js +28 -88
- package/lib/react-qw/components/ScrollView.js +15 -8
- package/lib/react-qw/components/Slider.js +2 -1
- package/lib/react-qw/components/Tab.js +6 -22
- package/lib/react-qw/components/Tooltip.js +10 -21
- package/lib/react-qw/components/TreeView.js +8 -31
- package/lib/react-qw/index.js +5 -3
- package/lib/react-qw/index.ts +8 -3
- package/lib/react-qw/nmhdr.js +3 -24
- package/lib/react-qw/nmhdr.ts +5 -30
- package/lib/react-qw/props.js +7 -2
- package/lib/react-qw/props.ts +7 -3
- package/lib/react-qw/reconciler.js +23 -13
- package/lib/react-qw/reconciler.ts +22 -13
- package/lib/text-measure.js +20 -23
- package/lib/text-measure.ts +22 -26
- package/lib/websocket.js +44 -4
- package/lib/websocket.ts +40 -4
- package/lib/windows/advapi32.js +53 -0
- package/lib/windows/advapi32.ts +64 -0
- package/lib/windows/comctl32.js +18 -0
- package/lib/windows/comctl32.ts +19 -0
- package/lib/windows/comdlg32.js +8 -0
- package/lib/windows/comdlg32.ts +10 -0
- package/lib/windows/gdi32.js +392 -0
- package/lib/windows/gdi32.ts +420 -0
- package/lib/windows/kernel32.js +262 -0
- package/lib/windows/kernel32.ts +291 -0
- package/lib/windows/msimg32.js +38 -0
- package/lib/windows/msimg32.ts +45 -0
- package/lib/windows/ntdll.js +9 -0
- package/lib/windows/ntdll.ts +11 -0
- package/lib/windows/ole32.js +7 -0
- package/lib/windows/ole32.ts +9 -0
- package/lib/windows/psapi.js +32 -0
- package/lib/windows/psapi.ts +37 -0
- package/lib/windows/shell32.js +13 -0
- package/lib/windows/shell32.ts +16 -0
- package/lib/windows/structs.js +519 -0
- package/lib/windows/structs.ts +566 -0
- package/lib/windows/user32.js +572 -0
- package/lib/windows/user32.ts +607 -0
- package/lib/windows/version.js +29 -0
- package/lib/windows/version.ts +35 -0
- package/lib/windows/winspool.js +71 -0
- package/lib/windows/winspool.ts +80 -0
- package/package.json +1 -1
- package/quickwin.d.ts +31 -40
- package/quickwin_const.d.ts +137 -0
- package/qwin-nowasm-x86.exe +0 -0
- package/qwin-nowasm.exe +0 -0
- package/qwin-x86.exe +0 -0
- package/qwin.exe +0 -0
- package/test/bench_ffi_callraw.js +236 -0
- package/test/bench_ffi_callraw.ts +236 -0
- package/test/bench_ffi_overhead.js +139 -0
- package/test/bench_ffi_overhead.ts +141 -0
- package/test/bench_sendmessage.js +104 -0
- package/test/bench_sendmessage.ts +100 -0
- package/test/bench_sendmessage_nonnull.js +187 -0
- package/test/bench_sendmessage_nonnull.ts +188 -0
- package/test/run.js +19 -4
- package/test/run.ts +20 -5
- package/test/test_basic_fetch.js +29 -0
- package/test/test_basic_fetch.ts +26 -0
- package/test/test_ffi.js +27 -45
- package/test/test_ffi.ts +35 -54
- package/test/test_ffi_abi.js +290 -0
- package/test/test_ffi_abi.ts +274 -0
- package/test/test_ffi_array.js +116 -0
- package/test/test_ffi_array.ts +141 -0
- package/test/test_ffi_bind.js +620 -8
- package/test/test_ffi_bind.ts +630 -8
- package/test/test_ffi_struct.js +632 -88
- package/test/test_ffi_struct.ts +666 -91
- package/test/test_ffi_struct_layout.js +495 -0
- package/test/test_ffi_struct_layout.ts +543 -0
- package/test/test_ffi_user32.js +103 -0
- package/test/test_ffi_user32.ts +113 -0
- package/test/test_ffi_windows.js +101 -0
- package/test/test_ffi_windows.ts +113 -0
- package/test/test_files.js +102 -0
- package/test/test_files.ts +121 -0
- package/test/test_gdi_draw.js +681 -0
- package/test/test_gdi_draw.ts +725 -0
- package/test/test_helper.js +8 -1
- package/test/test_helper.ts +9 -1
- package/test/test_http_import.js +18 -0
- package/test/test_http_import.ts +19 -0
- package/test/test_http_server.js +73 -1
- package/test/test_http_server.ts +75 -1
- package/test/test_ipv6.js +2 -2
- package/test/test_ipv6.ts +2 -2
- package/test/test_net_fetch.js +74 -11
- package/test/test_net_fetch.ts +73 -10
- package/test/test_net_websocket.js +1 -1
- package/test/test_net_websocket.ts +1 -1
- package/test/test_printer.js +137 -0
- package/test/test_printer.ts +148 -0
- package/test/test_proc.js +141 -0
- package/test/test_proc.ts +163 -0
- package/test/test_react_complex.js +1401 -1069
- package/test/test_react_counter.js +1391 -1059
- package/test/test_react_flex.js +1411 -1072
- package/test/test_react_hidden.js +1394 -1062
- package/test/test_react_listview_link.js +1391 -1059
- package/test/test_react_notify.js +18638 -0
- package/test/test_react_render.js +1391 -1059
- package/test/test_registry.js +74 -0
- package/test/test_registry.ts +95 -0
- package/test/test_sysinfo.js +122 -0
- package/test/test_sysinfo.ts +141 -0
- package/test/test_wolfssl_read.js +45 -43
- package/test/test_wolfssl_read.ts +42 -40
- package/test/test_worker_net.js +35 -17
- package/test/test_worker_net.ts +27 -15
- package/tsconfig.json +1 -1
- package/examples/exec_server.ts +0 -80
- package/lib/ffi-bind.js +0 -85
- package/lib/ffi-bind.ts +0 -170
- package/lib/ffi-struct.js +0 -224
- package/lib/ffi-struct.ts +0 -239
- package/lib/win-common-structs.js +0 -39
- package/lib/win-common-structs.ts +0 -41
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
import * as ffi from 'ffi';
|
|
2
|
+
import '../text-codec.js';
|
|
3
|
+
import { isCPtrToken, normToken, PTR_SIZE, PtrArrayBuffer, readScalar, SizeAlign, writeScalar, } from './ctype.js';
|
|
4
|
+
// ============================================================
|
|
5
|
+
// Layout IR —— computeStructLayout/lower 把 CType AST IR lower 到此。
|
|
6
|
+
// 自包含:子布局在 lowering 时内嵌、offset 相对本层起点,运行期 read/write 不回查 AST IR。
|
|
7
|
+
// ============================================================
|
|
8
|
+
/** 向上对齐(a 必须是 2 的幂)。 */
|
|
9
|
+
function alignUp(v, a) {
|
|
10
|
+
return (v + a - 1) & ~(a - 1);
|
|
11
|
+
}
|
|
12
|
+
// 归一后 token 的档位检查:'void'/指针 token 查表为 undefined(SizeAlign 只收 C_Number)。
|
|
13
|
+
const isCNumberToken = (t) => SizeAlign[t] !== undefined;
|
|
14
|
+
// 值 → { size, align, FieldType }:字符串先试字符串糖/数组糖/位域糖,再按 token 归一;
|
|
15
|
+
// 对象值一律按 '#' 分派 —— 无「裸嵌套 map 默认 struct」,漏写 '#' 直接报错。
|
|
16
|
+
// 字符串无 '#' 声明形式(词汇只有糖,见 ctype.ts §4):旧写法落到末尾 unknown '#' 报错。
|
|
17
|
+
function lower(v) {
|
|
18
|
+
if (typeof v === 'string') {
|
|
19
|
+
const st = /^(u8|u16)\[(\d+)\]@(utf-8|utf-16le)$/.exec(v); // 'u16[128]@utf-16le' → 字符串糖
|
|
20
|
+
if (st) {
|
|
21
|
+
const length = Number(st[2]);
|
|
22
|
+
if (!Number.isInteger(length) || length < 1)
|
|
23
|
+
throw new Error(`ffi-struct: string length must be an integer >= 1, got ${length}`);
|
|
24
|
+
// 布局 = unit × length(与 encoding 无关),encoding 只是读写时的解释 ——
|
|
25
|
+
// 'u8[128]@utf-16le'(128 字节按 utf-16le 解出 64 字符)之类的错配语义自洽。
|
|
26
|
+
const s = st[1] === 'u8' ? 1 : 2;
|
|
27
|
+
return { size: s * length, align: s, type: { tag: 'string', encoding: st[3] } };
|
|
28
|
+
}
|
|
29
|
+
const a = /^([^\[\]]+)\[(\d+)\]$/.exec(v); // 'u32[4]' / 'i3z[4]' → 数组糖(元素递归归一)
|
|
30
|
+
if (a) {
|
|
31
|
+
const length = Number(a[2]);
|
|
32
|
+
if (!Number.isInteger(length) || length < 1)
|
|
33
|
+
throw new Error(`ffi-struct: array length must be an integer >= 1, got ${length}`);
|
|
34
|
+
const el = lower(a[1]);
|
|
35
|
+
if (el.type.tag === 'bitfield')
|
|
36
|
+
throw new Error('ffi-struct: bitfields cannot be array elements');
|
|
37
|
+
return {
|
|
38
|
+
size: el.size * length,
|
|
39
|
+
align: el.align,
|
|
40
|
+
type: { tag: 'array', elementType: el.type, elementSize: el.size },
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const b = /^([A-Za-z_]\w*):(\d+)$/.exec(v); // 'u32:3' → 位域糖('f32:3' 归一后非整数档被拒)
|
|
44
|
+
if (b) {
|
|
45
|
+
const rawUnit = b[1];
|
|
46
|
+
const unit = normToken(rawUnit);
|
|
47
|
+
// 'void'/'<>ptr' 查表为 undefined、'float' 等别名 normToken 直接抛 —— 都不是合法位域单元。
|
|
48
|
+
if (!isCNumberToken(unit) || unit === 'f32' || unit === 'f64')
|
|
49
|
+
throw new Error(`ffi-struct: bitfield unit must be an integer type, got "${rawUnit}"`);
|
|
50
|
+
const width = Number(b[2]);
|
|
51
|
+
const s = SizeAlign[unit];
|
|
52
|
+
const cap = s * 8;
|
|
53
|
+
if (!Number.isInteger(width) || width < 1)
|
|
54
|
+
throw new Error(`ffi-struct: bitfield width must be an integer >= 1, got ${width}`);
|
|
55
|
+
if (width > cap)
|
|
56
|
+
throw new Error(`ffi-struct: bitfield width ${width} exceeds unit width ${cap}`);
|
|
57
|
+
// bit 是单元内偏移,由 computeStructLayout 的位域状态机填写;这里占位 0。
|
|
58
|
+
return { size: s, align: s, type: { tag: 'bitfield', unit, bit: 0, width } };
|
|
59
|
+
}
|
|
60
|
+
// 规范 token / 指针 token(别名机制已移除:'DWORD' 等 normToken 直接抛)。
|
|
61
|
+
// 归一后必须先判指针形,否则落 unknown kind;'void' 归一后无大小、
|
|
62
|
+
// 裸 'ptr'/'i3z' 未知 —— 都在这里抛(原先由 SizeAlign 查表兜住,现归一并入)。
|
|
63
|
+
const k = normToken(v);
|
|
64
|
+
if (isCPtrToken(k))
|
|
65
|
+
return { size: PTR_SIZE, align: PTR_SIZE, type: { tag: 'basic', kind: 'ptr' } };
|
|
66
|
+
if (!isCNumberToken(k))
|
|
67
|
+
throw new Error(`ffi-struct: unknown kind "${v}"`);
|
|
68
|
+
const s = SizeAlign[k];
|
|
69
|
+
return { size: s, align: s, type: { tag: 'basic', kind: k } };
|
|
70
|
+
}
|
|
71
|
+
if (v && typeof v === 'object') {
|
|
72
|
+
const o = v;
|
|
73
|
+
const tag = o['#'];
|
|
74
|
+
if (tag === undefined)
|
|
75
|
+
throw new Error(`ffi-struct: nested object value requires a '#' key ('struct' | 'union' | 'array')`);
|
|
76
|
+
if (tag === 'array' && '#pack' in o)
|
|
77
|
+
throw new Error(`ffi-struct: '#pack' is only allowed on struct/union, got '${tag}'`);
|
|
78
|
+
if (tag === 'array') {
|
|
79
|
+
const length = o['length'];
|
|
80
|
+
if (!Number.isInteger(length) || length < 1)
|
|
81
|
+
throw new Error(`ffi-struct: array length must be an integer >= 1, got ${String(length)}`);
|
|
82
|
+
const el = lower(o['element']);
|
|
83
|
+
if (el.type.tag === 'bitfield')
|
|
84
|
+
throw new Error('ffi-struct: bitfields cannot be array elements');
|
|
85
|
+
return {
|
|
86
|
+
size: el.size * length,
|
|
87
|
+
align: el.align,
|
|
88
|
+
type: { tag: 'array', elementType: el.type, elementSize: el.size },
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (tag === 'struct' || tag === 'union') {
|
|
92
|
+
const l = computeStructLayout(v);
|
|
93
|
+
return { size: l.size, align: l.maxEffectiveAlign, type: { tag, fields: l.fields } };
|
|
94
|
+
}
|
|
95
|
+
throw new Error(`ffi-struct: unknown '#' declaration ${JSON.stringify(String(tag))}`);
|
|
96
|
+
}
|
|
97
|
+
throw new Error(`ffi-struct: invalid member value: ${String(v)}`);
|
|
98
|
+
}
|
|
99
|
+
export function computeArray(t) {
|
|
100
|
+
const { size, align } = lower(t);
|
|
101
|
+
return { align, size };
|
|
102
|
+
}
|
|
103
|
+
export function computeStructLayout(t) {
|
|
104
|
+
const isStruct = t['#'] === 'struct';
|
|
105
|
+
const pack = t['#pack'] ?? 8;
|
|
106
|
+
let maxEffectiveAlign = 1;
|
|
107
|
+
let maxMemberSize = 0;
|
|
108
|
+
const fields = [];
|
|
109
|
+
let cursor = 0;
|
|
110
|
+
// 位域组状态:unitBase 是当前存储单元的字节基址(-1 = 不在位域组内),
|
|
111
|
+
// unitBits 是单元内已用位数,unitSize 是单元宽(字节)。
|
|
112
|
+
// MSVC 规则(mingw 交叉实测 DCB/COMSTAT 验证):
|
|
113
|
+
// - 连续同宽位域共单元,LSB→MSB(签别不参与分组)
|
|
114
|
+
// - 放不下的位域开新单元,单元剩余位废弃
|
|
115
|
+
// - 非位域成员打断位域组,游标跳到完整单元边界(不是按字节截断)
|
|
116
|
+
// 联合体位域不分组:每位域成员独立从 offset 0/bit 0 起算——完全别名
|
|
117
|
+
// (mingw 16 x64+ia32 实测:a=1→b=1、溢出成员回 offset 0、sizeof=单元宽)。
|
|
118
|
+
let unitBase = -1;
|
|
119
|
+
let unitBits = 0;
|
|
120
|
+
let unitSize = 0;
|
|
121
|
+
// 落定当前位域组:游标 = 单元起点 + 已用字节数,向上取整到单元宽。
|
|
122
|
+
const flushUnit = () => {
|
|
123
|
+
if (unitBase < 0)
|
|
124
|
+
return;
|
|
125
|
+
cursor = alignUp(unitBase + Math.ceil(unitBits / 8), unitSize);
|
|
126
|
+
unitBase = -1;
|
|
127
|
+
};
|
|
128
|
+
for (const [rawName, v] of Object.entries(t)) {
|
|
129
|
+
if (rawName.startsWith('#'))
|
|
130
|
+
continue; // '#' 指令键('#'/'#pack')不是字段
|
|
131
|
+
const m = /^([^@]+)@(\d+)$/.exec(rawName); // 键尾 '@N' = alignas(字段名不含 '@')
|
|
132
|
+
const name = m ? m[1] : rawName;
|
|
133
|
+
const alignas = m ? Number(m[2]) : 0;
|
|
134
|
+
const { size, align: natural, type } = lower(v);
|
|
135
|
+
// '!' 非空担保标记(原文 `<X>ptr!`):lower 里 normToken 已拒掉 'u32!'/'HANDLE!' 等
|
|
136
|
+
// 非法尾缀,能走到这里的 '!' 尾必是指针 token —— 记到 Field 上供 decode 的 0 断言。
|
|
137
|
+
const nonnull = typeof v === 'string' && v.endsWith('!');
|
|
138
|
+
let align = natural;
|
|
139
|
+
if (pack > 0)
|
|
140
|
+
align = Math.min(pack, align);
|
|
141
|
+
if (alignas)
|
|
142
|
+
align = Math.max(align, alignas);
|
|
143
|
+
if (type.tag === 'bitfield') {
|
|
144
|
+
if (name.startsWith('$'))
|
|
145
|
+
throw new Error('ffi-struct: bitfield members must be named');
|
|
146
|
+
// union:成员同址(C 标准),每位域成员独立从 offset 0/bit 0 起算。
|
|
147
|
+
// 不顺序分组、不开新单元、不动游标(flushUnit 因 unitBase 恒 -1 而
|
|
148
|
+
// no-op,普通/匿名成员的 offset 也因此保持 0)——曾因游标推进致
|
|
149
|
+
// 溢出单元/后续成员落在 union 声明 size 之外(越界读写)。
|
|
150
|
+
if (!isStruct) {
|
|
151
|
+
fields.push({ name: name, offset: 0, size, type: { ...type, bit: 0 } });
|
|
152
|
+
maxEffectiveAlign = Math.max(maxEffectiveAlign, align);
|
|
153
|
+
maxMemberSize = Math.max(maxMemberSize, size);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const width = type.width;
|
|
157
|
+
// 新单元:无活动单元 / 单元宽不同 / 放不下。
|
|
158
|
+
if (unitBase < 0 || size !== unitSize || unitBits + width > unitSize * 8) {
|
|
159
|
+
// 单元宽变化或溢出时先把旧单元落定,新单元从落定点对齐。
|
|
160
|
+
if (unitBase >= 0)
|
|
161
|
+
flushUnit();
|
|
162
|
+
const offset = alignUp(cursor, align);
|
|
163
|
+
unitBase = offset;
|
|
164
|
+
unitBits = 0;
|
|
165
|
+
unitSize = size;
|
|
166
|
+
}
|
|
167
|
+
fields.push({
|
|
168
|
+
name: name,
|
|
169
|
+
offset: unitBase, // 单元基址(不是位域自己的地址)
|
|
170
|
+
size,
|
|
171
|
+
type: { ...type, bit: unitBits },
|
|
172
|
+
});
|
|
173
|
+
unitBits += width;
|
|
174
|
+
maxEffectiveAlign = Math.max(maxEffectiveAlign, align);
|
|
175
|
+
maxMemberSize = Math.max(maxMemberSize, size);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
flushUnit();
|
|
179
|
+
const offset = alignUp(cursor, align);
|
|
180
|
+
if (!name.startsWith('$')) {
|
|
181
|
+
fields.push({ name: name, offset, size, type, ...(nonnull ? { nonnull: true } : {}) });
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
// 匿名聚合:内嵌 fields 已 lower 好,offset 平移到本成员起点。
|
|
185
|
+
if (type.tag !== 'struct' && type.tag !== 'union')
|
|
186
|
+
throw new Error(`ffi-struct: anonymous member "${name}" must be a struct/union`);
|
|
187
|
+
for (const cf of type.fields)
|
|
188
|
+
fields.push({ ...cf, offset: cf.offset + offset });
|
|
189
|
+
}
|
|
190
|
+
if (isStruct)
|
|
191
|
+
cursor = offset + size;
|
|
192
|
+
maxEffectiveAlign = Math.max(maxEffectiveAlign, align);
|
|
193
|
+
maxMemberSize = Math.max(maxMemberSize, size);
|
|
194
|
+
}
|
|
195
|
+
flushUnit();
|
|
196
|
+
return {
|
|
197
|
+
size: isStruct ? alignUp(cursor, maxEffectiveAlign) : alignUp(maxMemberSize, maxEffectiveAlign),
|
|
198
|
+
maxEffectiveAlign,
|
|
199
|
+
fields,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function readUnitRaw(dv, off, unit) {
|
|
203
|
+
const s = SizeAlign[unit];
|
|
204
|
+
if (s === 1)
|
|
205
|
+
return BigInt(dv.getUint8(off));
|
|
206
|
+
if (s === 2)
|
|
207
|
+
return BigInt(dv.getUint16(off, true));
|
|
208
|
+
if (s === 4)
|
|
209
|
+
return BigInt(dv.getUint32(off, true));
|
|
210
|
+
return dv.getBigUint64(off, true);
|
|
211
|
+
}
|
|
212
|
+
function writeUnitRaw(dv, off, unit, v) {
|
|
213
|
+
const s = SizeAlign[unit];
|
|
214
|
+
if (s === 1) {
|
|
215
|
+
dv.setUint8(off, Number(v & 0xffn));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (s === 2) {
|
|
219
|
+
dv.setUint16(off, Number(v & 0xffffn), true);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (s === 4) {
|
|
223
|
+
dv.setUint32(off, Number(v & 0xffffffffn), true);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
dv.setBigUint64(off, v & 0xffffffffffffffffn, true);
|
|
227
|
+
}
|
|
228
|
+
function readBitfield(dv, off, t) {
|
|
229
|
+
const w = BigInt(t.width);
|
|
230
|
+
let v = (readUnitRaw(dv, off, t.unit) >> BigInt(t.bit)) & ((1n << w) - 1n);
|
|
231
|
+
if (t.unit[0] === 'i' && (v & (1n << (w - 1n)))) // 有符号单元且符号位为 1 → 先符号扩展
|
|
232
|
+
v -= 1n << w;
|
|
233
|
+
if (t.width > 53)
|
|
234
|
+
return v; // 值域装不进安全整数 → bigint(与类型层 BitShape 同界)
|
|
235
|
+
return Number(v);
|
|
236
|
+
}
|
|
237
|
+
function writeBitfield(dv, off, t, val) {
|
|
238
|
+
const w = BigInt(t.width);
|
|
239
|
+
const bit = BigInt(t.bit);
|
|
240
|
+
const widthMask = (1n << w) - 1n;
|
|
241
|
+
const posMask = widthMask << bit;
|
|
242
|
+
const raw = readUnitRaw(dv, off, t.unit);
|
|
243
|
+
const bits = (typeof val === 'bigint' ? val : BigInt(Math.trunc(val))) & widthMask;
|
|
244
|
+
writeUnitRaw(dv, off, t.unit, (raw & ~posMask) | (bits << bit));
|
|
245
|
+
}
|
|
246
|
+
// 字符串读写统一走 TextEncoder/TextDecoder(polyfill 见 lib/text-codec.js)。
|
|
247
|
+
const stringEncoders = {
|
|
248
|
+
'utf-8': new TextEncoder('utf-8'),
|
|
249
|
+
'utf-16le': new TextEncoder('utf-16le'),
|
|
250
|
+
};
|
|
251
|
+
const stringDecoders = {
|
|
252
|
+
'utf-8': new TextDecoder('utf-8', { ignoreBOM: true }),
|
|
253
|
+
'utf-16le': new TextDecoder('utf-16le', { ignoreBOM: true }),
|
|
254
|
+
};
|
|
255
|
+
function readString(dv, off, size, enc) {
|
|
256
|
+
const bytes = new Uint8Array(dv.buffer, dv.byteOffset + off, size);
|
|
257
|
+
const unit = enc === 'utf-16le' ? 2 : 1;
|
|
258
|
+
let end = size;
|
|
259
|
+
for (let i = 0; i + unit <= size; i += unit) {
|
|
260
|
+
if (bytes[i] === 0 && (unit === 1 || bytes[i + 1] === 0)) {
|
|
261
|
+
end = i;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return stringDecoders[enc].decode(bytes.subarray(0, end));
|
|
266
|
+
}
|
|
267
|
+
function writeString(dv, off, size, enc, v) {
|
|
268
|
+
const bytes = new Uint8Array(dv.buffer, dv.byteOffset + off, size);
|
|
269
|
+
bytes.fill(0);
|
|
270
|
+
// 始终给 NUL 终止符留一个存储单元(宽度由 encoding 决定,见 readString):
|
|
271
|
+
// 末尾保持 0,payload 最多 size - unit 字节。这样 C 侧 strlen/wcslen 都不会越界。
|
|
272
|
+
const unit = enc === 'utf-16le' ? 2 : 1;
|
|
273
|
+
const cap = size - unit;
|
|
274
|
+
if (cap <= 0)
|
|
275
|
+
return;
|
|
276
|
+
const src = stringEncoders[enc].encode(String(v ?? ''));
|
|
277
|
+
bytes.set(src.subarray(0, Math.min(src.length, cap)));
|
|
278
|
+
}
|
|
279
|
+
function readArray(dv, off, size, t) {
|
|
280
|
+
const count = t.elementSize > 0 ? size / t.elementSize : 0;
|
|
281
|
+
const out = [];
|
|
282
|
+
for (let i = 0; i < count; i++)
|
|
283
|
+
out.push(readElement(dv, off + i * t.elementSize, t.elementType, t.elementSize));
|
|
284
|
+
return out;
|
|
285
|
+
}
|
|
286
|
+
function writeArray(dv, off, size, t, v) {
|
|
287
|
+
const count = t.elementSize > 0 ? size / t.elementSize : 0;
|
|
288
|
+
const list = (v ?? []);
|
|
289
|
+
for (let i = 0; i < count; i++)
|
|
290
|
+
writeElement(dv, off + i * t.elementSize, t.elementType, t.elementSize, list[i]);
|
|
291
|
+
}
|
|
292
|
+
function readElement(dv, off, t, size) {
|
|
293
|
+
switch (t.tag) {
|
|
294
|
+
case 'basic': return readScalar(dv, off, t.kind);
|
|
295
|
+
case 'bitfield': return readBitfield(dv, off, t);
|
|
296
|
+
case 'string': return readString(dv, off, size, t.encoding);
|
|
297
|
+
case 'struct':
|
|
298
|
+
case 'union': return doDecode(dv, off, t.fields);
|
|
299
|
+
case 'array': return readArray(dv, off, size, t);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function writeElement(dv, off, t, size, val) {
|
|
303
|
+
switch (t.tag) {
|
|
304
|
+
// TODO: remove this any
|
|
305
|
+
case 'basic':
|
|
306
|
+
writeScalar(dv, off, { k: t.kind, v: val });
|
|
307
|
+
break;
|
|
308
|
+
// 直传(不 Number()):writeBitfield 的 val 是 number | bigint,
|
|
309
|
+
// 64 位单元走 bigint,先 Number() 会把 >2^53 的位域值截断。
|
|
310
|
+
case 'bitfield':
|
|
311
|
+
writeBitfield(dv, off, t, (val ?? 0));
|
|
312
|
+
break;
|
|
313
|
+
case 'string':
|
|
314
|
+
writeString(dv, off, size, t.encoding, val);
|
|
315
|
+
break;
|
|
316
|
+
case 'struct':
|
|
317
|
+
case 'union':
|
|
318
|
+
doEncode(dv, off, t.fields, (val ?? {}));
|
|
319
|
+
break;
|
|
320
|
+
case 'array':
|
|
321
|
+
writeArray(dv, off, size, t, val);
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function doDecode(dv, base, fields) {
|
|
326
|
+
const out = {};
|
|
327
|
+
for (const f of fields) {
|
|
328
|
+
const v = readElement(dv, base + f.offset, f.type, f.size);
|
|
329
|
+
// '!' 非空担保的后置兜底:C 没填(忘判成功就 decode)或拿未填充槽(decode(alloc()))
|
|
330
|
+
// 读出 0 —— 当场 throw,不让 0 带 Ptr 品牌流到下游 '! 消费位'。成功路径必非零,不触发。
|
|
331
|
+
if (f.nonnull && v === 0)
|
|
332
|
+
throw new Error(`ffi-struct: decode: '!' field "${f.name}" got 0 ` +
|
|
333
|
+
`(C did not fill it, or decode was called on an unfilled slot)`);
|
|
334
|
+
out[f.name] = v;
|
|
335
|
+
}
|
|
336
|
+
return out;
|
|
337
|
+
}
|
|
338
|
+
function doEncode(dv, base, fields, v) {
|
|
339
|
+
for (const f of fields) {
|
|
340
|
+
// DeepPartial 缺省:不动该字段(新建 buffer 上保持 0、复用 buffer 上保留旧值);
|
|
341
|
+
// 嵌套/数组字段整个缺失也在这里拦下(writeElement 的 struct 分支回到本函数)。
|
|
342
|
+
if (v[f.name] === undefined)
|
|
343
|
+
continue;
|
|
344
|
+
writeElement(dv, base + f.offset, f.type, f.size, v[f.name]);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// ============================================================
|
|
348
|
+
// 内部构造器
|
|
349
|
+
// ============================================================
|
|
350
|
+
function createStruct(t) {
|
|
351
|
+
const { fields, size, maxEffectiveAlign } = computeStructLayout(t);
|
|
352
|
+
return {
|
|
353
|
+
__struct: t,
|
|
354
|
+
size,
|
|
355
|
+
structAlign: maxEffectiveAlign,
|
|
356
|
+
decode: (p) => {
|
|
357
|
+
// number = native 拥有的品牌指针(Codec 的 decode(p) 形态):readBytes 一次
|
|
358
|
+
// native memcpy 拷出 size 字节再解码(替代逐字节 readByte 循环,N 次跨语言
|
|
359
|
+
// 调用降为 1 次);ArrayBuffer = 调用方持有的 out buffer 直读。
|
|
360
|
+
// 顶层不收 offset——out 参数恒从起点读,嵌套偏移由字段布局(doDecode 的 base)承担。
|
|
361
|
+
if (typeof p === 'number')
|
|
362
|
+
return doDecode(new DataView(ffi.readBytes(p, size)), 0, fields);
|
|
363
|
+
return doDecode(new DataView(p), 0, fields);
|
|
364
|
+
},
|
|
365
|
+
alloc: ((n) => {
|
|
366
|
+
// fail-loud:n < 1 / 非整数绝不静默产出 PAB(0)(其 .ptr 是"非 0 的 1 字节
|
|
367
|
+
// 堆指针",会骗过 C 的 NULL 防御)—— 空位语义由调用方显式传 NULL。
|
|
368
|
+
if (n !== undefined && (!Number.isInteger(n) || n < 1))
|
|
369
|
+
throw new Error(`ffi-struct: alloc(n) requires an integer n >= 1 ` +
|
|
370
|
+
`(PAB(0) never escapes — pass NULL for an empty pointer position)`);
|
|
371
|
+
return new PtrArrayBuffer(size * (n ?? 1));
|
|
372
|
+
}),
|
|
373
|
+
encode: ((v, buf, offset) => {
|
|
374
|
+
// fail-loud:无参 = 分配语义,走 alloc();这里绝不静默零填充 —— 类型层由
|
|
375
|
+
// EncodeIn 拦 '!' 字段省略,动态/JS 调用方由本 throw 兜住。顶层默认从起点写,
|
|
376
|
+
// offset 显式给写入点(数组物化原语,见接口注释)。
|
|
377
|
+
if (v === undefined || v === null)
|
|
378
|
+
throw new Error(`ffi-struct: encode(v) requires an argument; ` +
|
|
379
|
+
`use alloc() for a zeroed out-param slot`);
|
|
380
|
+
const out = buf ?? new PtrArrayBuffer(size);
|
|
381
|
+
doEncode(new DataView(out), offset ?? 0, fields, v);
|
|
382
|
+
return out;
|
|
383
|
+
}),
|
|
384
|
+
offsetOf: (name) => {
|
|
385
|
+
for (const f of fields)
|
|
386
|
+
if (f.name === name)
|
|
387
|
+
return f.offset;
|
|
388
|
+
throw new Error(`ffi-struct: no field "${name}"`);
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
/** 平铺字段表 → '#' 声明:'#'/`#pack` 由构造器写入(顶层 kind 由函数名表明,字段表本身
|
|
393
|
+
* 不收指令键)。member 里混进的 '#' 前缀键在此剔除 —— 类型层已禁(fresh literal excess
|
|
394
|
+
* check),这里运行时兜底,避免用户键覆盖构造器写入的 '#'/`#pack`。 */
|
|
395
|
+
function buildDecl(kind, member, pack) {
|
|
396
|
+
const decl = { '#': kind };
|
|
397
|
+
if (pack !== undefined)
|
|
398
|
+
decl['#pack'] = pack;
|
|
399
|
+
for (const [k, v] of Object.entries(member))
|
|
400
|
+
if (!k.startsWith('#'))
|
|
401
|
+
decl[k] = v;
|
|
402
|
+
return decl;
|
|
403
|
+
}
|
|
404
|
+
/** 结构/联合体的数组编解码对工厂:返回 `{ encode, decode }`。
|
|
405
|
+
* encode —— 单值 encoder 的数组版:收元素数组 → 按本布局 size 步长紧密排列
|
|
406
|
+
* (stride = size,已含尾部对齐 padding),返回带品牌 .ptr 的 PAB —— 与 C 的
|
|
407
|
+
* `const T apt[]` 同构。值域刻意收普通数组(调用方持有的 `const pts = [...]` 变量
|
|
408
|
+
* 直传,非空元组会拒之);[] / 非数组 / 单对象运行时 throw —— 任何路径都不产出
|
|
409
|
+
* PAB(0)(其 .ptr 是"非 0 的 1 字节堆指针",会骗过 C 的 NULL 防御;空位语义由
|
|
410
|
+
* 调用方显式传 NULL)。注册进 bind 的 encoders 表(`{ POINT: structArray(POINT) }`)
|
|
411
|
+
* 后,普通 `<POINT>ptr` 位的值域即元素数组(值域由 encoder 推导,token/引擎/类型层
|
|
412
|
+
* 零数组概念);直接调用 `.encode(els).ptr` 亦可。
|
|
413
|
+
* decode —— 出参数组读回(与 def.decode 的 ArrayBuffer/地址双态对称):
|
|
414
|
+
* - buffer 形(含 PAB):count? 缺省自算 byteLength/size —— 要求整数倍,% ≠ 0
|
|
415
|
+
* throw(混合缓冲如 EnumPrinters 的"结构 + 尾部字符串"必须显式 count);显式
|
|
416
|
+
* count 校验 count*size ≤ byteLength(写错 count → 越界读的防线,buf 形可校才
|
|
417
|
+
* 校;不验品牌,与 def.decode 的 buffer 形同款)。
|
|
418
|
+
* - 裸地址形:count 必填(地址无长度信息),逐字节拷读(def.decode 的 number 形
|
|
419
|
+
* 同机制);长度不可校 → 信任 C 的 count。
|
|
420
|
+
* - count = 0 → [](读 0 个真空安全,与 encode 禁空的 PAB(0) 理由刻意不对称)。
|
|
421
|
+
* 实现:encode 走纯公开 API(alloc + encode offset 循环);decode 在本文件内复用
|
|
422
|
+
* computeStructLayout/doDecode。 */
|
|
423
|
+
export function structArray(def) {
|
|
424
|
+
const { fields, size } = computeStructLayout(def.__struct);
|
|
425
|
+
const readN = (dv, n) => {
|
|
426
|
+
const out = [];
|
|
427
|
+
for (let i = 0; i < n; i++)
|
|
428
|
+
out.push(doDecode(dv, i * size, fields));
|
|
429
|
+
return out;
|
|
430
|
+
};
|
|
431
|
+
return {
|
|
432
|
+
encode: (els) => {
|
|
433
|
+
if (!Array.isArray(els) || els.length === 0)
|
|
434
|
+
throw new Error(`ffi-struct: structArray(def).encode(els) requires a ` +
|
|
435
|
+
`non-empty element array (pass NULL for an empty pointer position)`);
|
|
436
|
+
const out = def.alloc(els.length);
|
|
437
|
+
for (let i = 0; i < els.length; i++)
|
|
438
|
+
def.encode(els[i], out, i * def.size);
|
|
439
|
+
return out;
|
|
440
|
+
},
|
|
441
|
+
decode: ((p, count) => {
|
|
442
|
+
// fail-loud 校验矩阵(见 JSDoc):显式 count 信调用方但拦越界;缺省只对
|
|
443
|
+
// 有 byteLength 的 buffer 形自算;裸地址必带 count。
|
|
444
|
+
if (count !== undefined && (!Number.isInteger(count) || count < 0))
|
|
445
|
+
throw new Error(`ffi-struct: structArray.decode(p, count) requires a ` +
|
|
446
|
+
`non-negative integer count, got ${count}`);
|
|
447
|
+
if (typeof p === 'number') {
|
|
448
|
+
if (count === undefined)
|
|
449
|
+
throw new Error(`ffi-struct: structArray.decode(address, count) ` +
|
|
450
|
+
`requires an explicit count (a bare address carries no length)`);
|
|
451
|
+
if (count === 0)
|
|
452
|
+
return [];
|
|
453
|
+
// readBytes 一次 native memcpy 拷出整段再解码(def.decode 的 number 形同机制)。
|
|
454
|
+
return readN(new DataView(ffi.readBytes(p, count * size)), count);
|
|
455
|
+
}
|
|
456
|
+
let n;
|
|
457
|
+
if (count !== undefined) {
|
|
458
|
+
n = count;
|
|
459
|
+
// 越界防线:写错 count 是越界读的唯一入口,buf 形有 byteLength 就校死。
|
|
460
|
+
if (n * size > p.byteLength)
|
|
461
|
+
throw new Error(`ffi-struct: structArray.decode: count ${n} × ` +
|
|
462
|
+
`size ${size} = ${n * size} exceeds buffer byteLength ${p.byteLength}`);
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
// 自算形态:byteLength 必须是纯元素序列(size = 0 的退化布局同样落此
|
|
466
|
+
// throw —— 0 % 0 = NaN),混合缓冲显式传 count。
|
|
467
|
+
if (p.byteLength % size !== 0)
|
|
468
|
+
throw new Error(`ffi-struct: structArray.decode: byteLength ` +
|
|
469
|
+
`${p.byteLength} is not a multiple of size ${size} ` +
|
|
470
|
+
`(mixed buffer? pass count explicitly)`);
|
|
471
|
+
n = p.byteLength / size;
|
|
472
|
+
}
|
|
473
|
+
if (n === 0)
|
|
474
|
+
return [];
|
|
475
|
+
return readN(new DataView(p), n);
|
|
476
|
+
}),
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
export function struct(a, b, c) {
|
|
480
|
+
const isNamed = typeof a === 'string';
|
|
481
|
+
const raw = (isNamed ? b : a);
|
|
482
|
+
const opts = (isNamed ? c : b);
|
|
483
|
+
return createStruct(buildDecl('struct', raw, opts?.pack));
|
|
484
|
+
}
|
|
485
|
+
export function union(a, b, c) {
|
|
486
|
+
const isNamed = typeof a === 'string';
|
|
487
|
+
const raw = (isNamed ? b : a);
|
|
488
|
+
const opts = (isNamed ? c : b);
|
|
489
|
+
return createStruct(buildDecl('union', raw, opts?.pack));
|
|
490
|
+
}
|