quickwin 1.3.0 → 2.0.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 +1 -2
- package/README.md +19 -23
- package/examples/PdfCanvas.js +8 -37
- package/examples/PdfCanvas.tsx +11 -41
- package/examples/exec_server.js +20054 -1423
- 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 +1318 -1178
- package/examples/pdf_viewer.tsx +16 -67
- package/examples/setres.js +22 -47
- package/examples/setres.ts +23 -57
- package/examples/test_gallery.js +1313 -1074
- package/examples/test_react_lazy.js +1284 -1056
- package/examples/test_react_listbox.js +1283 -1055
- package/examples/test_root_window.js +1279 -1051
- package/examples/test_tab.js +1284 -1056
- package/lib/abort.js +70 -0
- package/lib/abort.ts +69 -0
- package/lib/certs.js +214 -0
- package/lib/certs.ts +224 -0
- package/lib/exec-log.js +15 -0
- package/lib/exec-log.ts +30 -0
- package/lib/fetch.js +37 -4
- package/lib/fetch.ts +35 -4
- package/lib/ffi/bind.js +352 -0
- package/lib/ffi/bind.ts +442 -0
- package/lib/ffi/ctype.js +168 -0
- package/lib/ffi/ctype.ts +331 -0
- package/lib/ffi/index.js +6 -0
- package/lib/ffi/index.ts +6 -0
- package/lib/ffi/struct.js +412 -0
- package/lib/ffi/struct.ts +664 -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 +49 -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 +4 -2
- package/lib/react-qw/index.ts +4 -2
- 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 +18 -23
- package/lib/text-measure.ts +20 -25
- package/lib/websocket.js +12 -2
- package/lib/websocket.ts +15 -2
- package/lib/windows/comctl32.js +18 -0
- package/lib/windows/comctl32.ts +20 -0
- package/lib/windows/comdlg32.js +7 -0
- package/lib/windows/comdlg32.ts +9 -0
- package/lib/windows/gdi32.js +90 -0
- package/lib/windows/gdi32.ts +97 -0
- package/lib/windows/kernel32.js +66 -0
- package/lib/windows/kernel32.ts +76 -0
- package/lib/windows/ntdll.js +8 -0
- package/lib/windows/ntdll.ts +10 -0
- package/lib/windows/ole32.js +7 -0
- package/lib/windows/ole32.ts +9 -0
- package/lib/windows/shell32.js +11 -0
- package/lib/windows/shell32.ts +14 -0
- package/lib/windows/structs.js +384 -0
- package/lib/windows/structs.ts +422 -0
- package/lib/windows/user32.js +469 -0
- package/lib/windows/user32.ts +502 -0
- package/package.json +1 -1
- package/quickwin.d.ts +22 -37
- package/quickwin_const.d.ts +1 -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 +12 -4
- package/test/run.ts +13 -5
- package/test/test_basic_fetch.js +8 -0
- package/test/test_basic_fetch.ts +9 -0
- package/test/test_ffi.js +25 -43
- package/test/test_ffi.ts +33 -52
- package/test/test_ffi_abi.js +235 -0
- package/test/test_ffi_abi.ts +218 -0
- package/test/test_ffi_bind.js +538 -8
- package/test/test_ffi_bind.ts +559 -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_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 +70 -8
- package/test/test_net_fetch.ts +69 -7
- package/test/test_net_websocket.js +1 -1
- package/test/test_net_websocket.ts +1 -1
- package/test/test_react_complex.js +1294 -1066
- package/test/test_react_counter.js +1284 -1056
- package/test/test_react_flex.js +1304 -1069
- package/test/test_react_hidden.js +1287 -1059
- package/test/test_react_listview_link.js +1284 -1056
- package/test/test_react_notify.js +18534 -0
- package/test/test_react_render.js +1284 -1056
- package/test/test_wolfssl_read.js +3 -2
- package/test/test_wolfssl_read.ts +3 -1
- 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
package/lib/ffi-struct.js
DELETED
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
import * as os from 'os';
|
|
2
|
-
import { normKind } from './ffi-bind.js';
|
|
3
|
-
const PTR = os.arch === 'x64' ? 8 : 4;
|
|
4
|
-
// kind → [align, size]
|
|
5
|
-
const AS = {
|
|
6
|
-
u8: [1, 1], i8: [1, 1],
|
|
7
|
-
u16: [2, 2], i16: [2, 2],
|
|
8
|
-
u32: [4, 4], i32: [4, 4],
|
|
9
|
-
u64: [8, 8], i64: [8, 8],
|
|
10
|
-
f32: [4, 4], f64: [8, 8],
|
|
11
|
-
ptr: [PTR, PTR],
|
|
12
|
-
wstr: [2, 2], // 仅定长数组元素用(wchar)
|
|
13
|
-
};
|
|
14
|
-
function alignUp(v, a) {
|
|
15
|
-
return (v + a - 1) & ~(a - 1);
|
|
16
|
-
}
|
|
17
|
-
function parseField(f) {
|
|
18
|
-
const arr = /^(.+)\[(\d+)\]$/.exec(f);
|
|
19
|
-
if (arr) {
|
|
20
|
-
const b = normKind(arr[1]);
|
|
21
|
-
if (b === 'void')
|
|
22
|
-
throw new Error(`ffi-struct: "void" cannot be an array base`);
|
|
23
|
-
return { base: b, count: Number(arr[2]) };
|
|
24
|
-
}
|
|
25
|
-
const b = normKind(f);
|
|
26
|
-
if (b === 'void' || b === 'wstr')
|
|
27
|
-
throw new Error(`ffi-struct: invalid scalar field type "${f}"`);
|
|
28
|
-
return { base: b, count: 1 };
|
|
29
|
-
}
|
|
30
|
-
function layout(def) {
|
|
31
|
-
const fields = [];
|
|
32
|
-
let cursor = 0;
|
|
33
|
-
let maxAlign = 1;
|
|
34
|
-
for (const key of Object.keys(def)) {
|
|
35
|
-
const v = def[key];
|
|
36
|
-
let a;
|
|
37
|
-
let w;
|
|
38
|
-
if (typeof v === 'string') {
|
|
39
|
-
const { base, count } = parseField(v);
|
|
40
|
-
const [align, elem] = AS[base];
|
|
41
|
-
a = align;
|
|
42
|
-
w = count >= 1 ? elem * count : elem;
|
|
43
|
-
fields.push({ name: key, base, count, offset: 0 });
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
const child = v;
|
|
47
|
-
a = child.structAlign;
|
|
48
|
-
w = child.size;
|
|
49
|
-
fields.push({ name: key, base: 'u8', count: 0, offset: 0, child });
|
|
50
|
-
}
|
|
51
|
-
const off = alignUp(cursor, a);
|
|
52
|
-
const f = fields[fields.length - 1];
|
|
53
|
-
f.offset = off;
|
|
54
|
-
cursor = off + w;
|
|
55
|
-
if (a > maxAlign)
|
|
56
|
-
maxAlign = a;
|
|
57
|
-
}
|
|
58
|
-
return { fields, size: alignUp(cursor, maxAlign), structAlign: maxAlign };
|
|
59
|
-
}
|
|
60
|
-
const _layoutCache = new WeakMap();
|
|
61
|
-
function buildLayout(def) {
|
|
62
|
-
let hit = _layoutCache.get(def);
|
|
63
|
-
if (hit)
|
|
64
|
-
return hit;
|
|
65
|
-
hit = layout(def);
|
|
66
|
-
_layoutCache.set(def, hit);
|
|
67
|
-
return hit;
|
|
68
|
-
}
|
|
69
|
-
function readScalar(dv, off, k) {
|
|
70
|
-
switch (k) {
|
|
71
|
-
case 'u8': return dv.getUint8(off);
|
|
72
|
-
case 'i8': return dv.getInt8(off);
|
|
73
|
-
case 'u16': return dv.getUint16(off, true);
|
|
74
|
-
case 'i16': return dv.getInt16(off, true);
|
|
75
|
-
case 'u32': return dv.getUint32(off, true);
|
|
76
|
-
case 'i32': return dv.getInt32(off, true);
|
|
77
|
-
case 'u64': return Number(dv.getBigUint64(off, true));
|
|
78
|
-
case 'i64': return Number(dv.getBigInt64(off, true));
|
|
79
|
-
case 'f32': return dv.getFloat32(off, true);
|
|
80
|
-
case 'f64': return dv.getFloat64(off, true);
|
|
81
|
-
case 'ptr': return readPtr(dv, off);
|
|
82
|
-
case 'wstr': return dv.getUint16(off, true);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
function readPtr(dv, off) {
|
|
86
|
-
if (PTR === 8)
|
|
87
|
-
return Number(dv.getBigUint64(off, true));
|
|
88
|
-
return dv.getUint32(off, true);
|
|
89
|
-
}
|
|
90
|
-
function writeScalar(dv, off, k, val) {
|
|
91
|
-
switch (k) {
|
|
92
|
-
case 'u8':
|
|
93
|
-
dv.setUint8(off, val);
|
|
94
|
-
break;
|
|
95
|
-
case 'i8':
|
|
96
|
-
dv.setInt8(off, val);
|
|
97
|
-
break;
|
|
98
|
-
case 'u16':
|
|
99
|
-
dv.setUint16(off, val, true);
|
|
100
|
-
break;
|
|
101
|
-
case 'i16':
|
|
102
|
-
dv.setInt16(off, val, true);
|
|
103
|
-
break;
|
|
104
|
-
case 'u32':
|
|
105
|
-
dv.setUint32(off, val >>> 0, true);
|
|
106
|
-
break;
|
|
107
|
-
case 'i32':
|
|
108
|
-
dv.setInt32(off, val | 0, true);
|
|
109
|
-
break;
|
|
110
|
-
case 'u64':
|
|
111
|
-
dv.setBigUint64(off, BigInt(Math.trunc(val)), true);
|
|
112
|
-
break;
|
|
113
|
-
case 'i64':
|
|
114
|
-
dv.setBigInt64(off, BigInt(Math.trunc(val)), true);
|
|
115
|
-
break;
|
|
116
|
-
case 'f32':
|
|
117
|
-
dv.setFloat32(off, val, true);
|
|
118
|
-
break;
|
|
119
|
-
case 'f64':
|
|
120
|
-
dv.setFloat64(off, val, true);
|
|
121
|
-
break;
|
|
122
|
-
case 'ptr':
|
|
123
|
-
writePtr(dv, off, val);
|
|
124
|
-
break;
|
|
125
|
-
case 'wstr':
|
|
126
|
-
dv.setUint16(off, val, true);
|
|
127
|
-
break;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
function writePtr(dv, off, val) {
|
|
131
|
-
if (PTR === 8)
|
|
132
|
-
dv.setBigUint64(off, BigInt(Math.trunc(val)), true);
|
|
133
|
-
else
|
|
134
|
-
dv.setUint32(off, val >>> 0, true);
|
|
135
|
-
}
|
|
136
|
-
function readField(dv, base, f) {
|
|
137
|
-
const off = base + f.offset;
|
|
138
|
-
if (f.child)
|
|
139
|
-
return readInto(f.child, dv.buffer, off);
|
|
140
|
-
if (f.count > 1) {
|
|
141
|
-
if (f.base === 'wstr') {
|
|
142
|
-
const arr = new Uint16Array(dv.buffer, dv.byteOffset + off, f.count);
|
|
143
|
-
let end = 0;
|
|
144
|
-
while (end < arr.length && arr[end] !== 0)
|
|
145
|
-
end++;
|
|
146
|
-
let s = '';
|
|
147
|
-
for (let i = 0; i < end; i++)
|
|
148
|
-
s += String.fromCharCode(arr[i]);
|
|
149
|
-
return s;
|
|
150
|
-
}
|
|
151
|
-
const out = [];
|
|
152
|
-
const step = AS[f.base][1];
|
|
153
|
-
for (let i = 0; i < f.count; i++)
|
|
154
|
-
out.push(readScalar(dv, off + i * step, f.base));
|
|
155
|
-
return out;
|
|
156
|
-
}
|
|
157
|
-
return readScalar(dv, off, f.base);
|
|
158
|
-
}
|
|
159
|
-
function writeField(dv, base, f, v) {
|
|
160
|
-
const off = base + f.offset;
|
|
161
|
-
if (f.child) {
|
|
162
|
-
writeInto(f.child, v, dv.buffer, off);
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
if (f.count > 1) {
|
|
166
|
-
if (f.base === 'wstr') {
|
|
167
|
-
const s = String(v ?? '');
|
|
168
|
-
const arr = new Uint16Array(dv.buffer, dv.byteOffset + off, f.count);
|
|
169
|
-
for (let i = 0; i < arr.length; i++)
|
|
170
|
-
arr[i] = 0;
|
|
171
|
-
const n = Math.min(s.length, f.count - 1);
|
|
172
|
-
for (let i = 0; i < n; i++)
|
|
173
|
-
arr[i] = s.charCodeAt(i);
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
const step = AS[f.base][1];
|
|
177
|
-
const list = (v ?? []);
|
|
178
|
-
for (let i = 0; i < f.count; i++)
|
|
179
|
-
writeScalar(dv, off + i * step, f.base, list[i] ?? 0);
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
writeScalar(dv, off, f.base, Number(v));
|
|
183
|
-
}
|
|
184
|
-
function doSize(def) {
|
|
185
|
-
return buildLayout(def).size;
|
|
186
|
-
}
|
|
187
|
-
function readInto(child, buf, offset) {
|
|
188
|
-
const r = child;
|
|
189
|
-
return r.read(buf, offset);
|
|
190
|
-
}
|
|
191
|
-
function doRead(_def, fields, buf, offset) {
|
|
192
|
-
const out = {};
|
|
193
|
-
const dv = new DataView(buf);
|
|
194
|
-
for (const f of fields)
|
|
195
|
-
out[f.name] = readField(dv, offset, f);
|
|
196
|
-
return out;
|
|
197
|
-
}
|
|
198
|
-
function doWrite(def, fields, v, buf, offset = 0) {
|
|
199
|
-
buf = buf ?? new ArrayBuffer(doSize(def));
|
|
200
|
-
const dv = new DataView(buf);
|
|
201
|
-
for (const f of fields)
|
|
202
|
-
writeField(dv, offset, f, v[f.name]);
|
|
203
|
-
return buf;
|
|
204
|
-
}
|
|
205
|
-
function writeInto(child, v, buf, offset) {
|
|
206
|
-
const w = child;
|
|
207
|
-
w.write(v, buf, offset);
|
|
208
|
-
}
|
|
209
|
-
export function struct(def) {
|
|
210
|
-
const { fields, size, structAlign } = buildLayout(def);
|
|
211
|
-
return {
|
|
212
|
-
__fields: def,
|
|
213
|
-
size,
|
|
214
|
-
structAlign,
|
|
215
|
-
read: (buf, offset = 0) => doRead(def, fields, buf, offset),
|
|
216
|
-
write: (v, buf, offset = 0) => doWrite(def, fields, v, buf, offset),
|
|
217
|
-
offsetOf: (name) => {
|
|
218
|
-
for (const f of fields)
|
|
219
|
-
if (f.name === name)
|
|
220
|
-
return f.offset;
|
|
221
|
-
throw new Error(`ffi-struct: no field "${String(name)}"`);
|
|
222
|
-
},
|
|
223
|
-
};
|
|
224
|
-
}
|
package/lib/ffi-struct.ts
DELETED
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
import * as os from 'os'
|
|
2
|
-
import { normKind, type Norm } from './ffi-bind.js'
|
|
3
|
-
|
|
4
|
-
// 声明式结构体定义:struct({ field: 'i32', p: 'ptr', ws: 'wstr[32]', child: SubStruct })
|
|
5
|
-
// layout 采用 MSVC 对齐语义:
|
|
6
|
-
// i8/u8→1 i16/u16→2 i32/u32/f32→4 i64/u64/f64→8 ptr→arch 宽(4/8)
|
|
7
|
-
// struct 对齐 = 最大字段对齐,总尺寸末尾补齐
|
|
8
|
-
// 字段类型:
|
|
9
|
-
// 标量 kind(含 ffi-bind 的 C typedef 别名:int/DWORD/LPARAM/HANDLE...)
|
|
10
|
-
// 定长数组 'u16[8]' / 'i32[4]' / 'wstr[32]'(wstr 读 string、写 string,自动截断+补 0)
|
|
11
|
-
// 嵌套 struct:字段值直接传子 struct 定义对象
|
|
12
|
-
|
|
13
|
-
type FieldKind = 'u8' | 'i8' | 'u16' | 'i16' | 'u32' | 'i32' | 'u64' | 'i64' | 'f32' | 'f64' | 'ptr' | 'wstr'
|
|
14
|
-
|
|
15
|
-
const PTR = os.arch === 'x64' ? 8 : 4
|
|
16
|
-
|
|
17
|
-
// kind → [align, size]
|
|
18
|
-
const AS: Record<FieldKind, [number, number]> = {
|
|
19
|
-
u8: [1, 1], i8: [1, 1],
|
|
20
|
-
u16: [2, 2], i16: [2, 2],
|
|
21
|
-
u32: [4, 4], i32: [4, 4],
|
|
22
|
-
u64: [8, 8], i64: [8, 8],
|
|
23
|
-
f32: [4, 4], f64: [8, 8],
|
|
24
|
-
ptr: [PTR, PTR],
|
|
25
|
-
wstr: [2, 2], // 仅定长数组元素用(wchar)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
type KnownScalar = 'u8' | 'i8' | 'u16' | 'i16' | 'u32' | 'i32' | 'u64' | 'i64' | 'f32' | 'f64' | 'ptr'
|
|
29
|
-
|
|
30
|
-
type ValOf<S extends string> = S extends `${infer B}[${number}]`
|
|
31
|
-
? (B extends 'wstr' ? string : Norm<B> extends KnownScalar ? number[] : never)
|
|
32
|
-
: S extends KnownScalar ? number
|
|
33
|
-
: Norm<S> extends KnownScalar ? number : never
|
|
34
|
-
|
|
35
|
-
export type StructShape<T extends Record<string, unknown> = Record<string, unknown>> = {
|
|
36
|
-
readonly __fields: T
|
|
37
|
-
readonly size: number
|
|
38
|
-
readonly structAlign: number
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
type ShapeOf<T extends Record<string, unknown>> = {
|
|
42
|
-
[K in keyof T]: T[K] extends string ? ValOf<T[K]> : T[K] extends StructShape ? ShapeOf<T[K]['__fields']> : never
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export type StructDef<F extends Record<string, string | StructShape>> = StructShape<F> & {
|
|
46
|
-
read(buf: ArrayBuffer, offset?: number): ShapeOf<F>
|
|
47
|
-
write(v: ShapeOf<F>, buf?: ArrayBuffer, offset?: number): ArrayBuffer
|
|
48
|
-
offsetOf(name: keyof F): number
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
type Field = {
|
|
52
|
-
name: string
|
|
53
|
-
base: FieldKind
|
|
54
|
-
count: number
|
|
55
|
-
offset: number
|
|
56
|
-
child?: StructShape
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function alignUp(v: number, a: number): number {
|
|
60
|
-
return (v + a - 1) & ~(a - 1)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function parseField(f: string): { base: FieldKind; count: number } {
|
|
64
|
-
const arr = /^(.+)\[(\d+)\]$/.exec(f)
|
|
65
|
-
if (arr) {
|
|
66
|
-
const b = normKind(arr[1]!)
|
|
67
|
-
if (b === 'void') throw new Error(`ffi-struct: "void" cannot be an array base`)
|
|
68
|
-
return { base: b as FieldKind, count: Number(arr[2]) }
|
|
69
|
-
}
|
|
70
|
-
const b = normKind(f)
|
|
71
|
-
if (b === 'void' || b === 'wstr') throw new Error(`ffi-struct: invalid scalar field type "${f}"`)
|
|
72
|
-
return { base: b as FieldKind, count: 1 }
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function layout<F extends Record<string, string | StructShape>>(def: F): { fields: Field[]; size: number; structAlign: number } {
|
|
76
|
-
const fields: Field[] = []
|
|
77
|
-
let cursor = 0
|
|
78
|
-
let maxAlign = 1
|
|
79
|
-
for (const key of Object.keys(def) as (keyof F)[]) {
|
|
80
|
-
const v = def[key]
|
|
81
|
-
let a: number
|
|
82
|
-
let w: number
|
|
83
|
-
if (typeof v === 'string') {
|
|
84
|
-
const { base, count } = parseField(v)
|
|
85
|
-
const [align, elem] = AS[base]
|
|
86
|
-
a = align
|
|
87
|
-
w = count >= 1 ? elem * count : elem
|
|
88
|
-
fields.push({ name: key as string, base, count, offset: 0 })
|
|
89
|
-
} else {
|
|
90
|
-
const child = v as StructShape
|
|
91
|
-
a = child.structAlign
|
|
92
|
-
w = child.size
|
|
93
|
-
fields.push({ name: key as string, base: 'u8', count: 0, offset: 0, child })
|
|
94
|
-
}
|
|
95
|
-
const off = alignUp(cursor, a)
|
|
96
|
-
const f = fields[fields.length - 1]!
|
|
97
|
-
f.offset = off
|
|
98
|
-
cursor = off + w
|
|
99
|
-
if (a > maxAlign) maxAlign = a
|
|
100
|
-
}
|
|
101
|
-
return { fields, size: alignUp(cursor, maxAlign), structAlign: maxAlign }
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const _layoutCache = new WeakMap<object, { fields: Field[]; size: number; structAlign: number }>()
|
|
105
|
-
|
|
106
|
-
function buildLayout<F extends Record<string, string | StructShape>>(def: F): { fields: Field[]; size: number; structAlign: number } {
|
|
107
|
-
let hit = _layoutCache.get(def)
|
|
108
|
-
if (hit) return hit
|
|
109
|
-
hit = layout(def)
|
|
110
|
-
_layoutCache.set(def, hit)
|
|
111
|
-
return hit
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function readScalar(dv: DataView, off: number, k: FieldKind): number {
|
|
115
|
-
switch (k) {
|
|
116
|
-
case 'u8': return dv.getUint8(off)
|
|
117
|
-
case 'i8': return dv.getInt8(off)
|
|
118
|
-
case 'u16': return dv.getUint16(off, true)
|
|
119
|
-
case 'i16': return dv.getInt16(off, true)
|
|
120
|
-
case 'u32': return dv.getUint32(off, true)
|
|
121
|
-
case 'i32': return dv.getInt32(off, true)
|
|
122
|
-
case 'u64': return Number(dv.getBigUint64(off, true))
|
|
123
|
-
case 'i64': return Number(dv.getBigInt64(off, true))
|
|
124
|
-
case 'f32': return dv.getFloat32(off, true)
|
|
125
|
-
case 'f64': return dv.getFloat64(off, true)
|
|
126
|
-
case 'ptr': return readPtr(dv, off)
|
|
127
|
-
case 'wstr': return dv.getUint16(off, true)
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function readPtr(dv: DataView, off: number): number {
|
|
132
|
-
if (PTR === 8) return Number(dv.getBigUint64(off, true))
|
|
133
|
-
return dv.getUint32(off, true)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function writeScalar(dv: DataView, off: number, k: FieldKind, val: number): void {
|
|
137
|
-
switch (k) {
|
|
138
|
-
case 'u8': dv.setUint8(off, val); break
|
|
139
|
-
case 'i8': dv.setInt8(off, val); break
|
|
140
|
-
case 'u16': dv.setUint16(off, val, true); break
|
|
141
|
-
case 'i16': dv.setInt16(off, val, true); break
|
|
142
|
-
case 'u32': dv.setUint32(off, val >>> 0, true); break
|
|
143
|
-
case 'i32': dv.setInt32(off, val | 0, true); break
|
|
144
|
-
case 'u64': dv.setBigUint64(off, BigInt(Math.trunc(val)), true); break
|
|
145
|
-
case 'i64': dv.setBigInt64(off, BigInt(Math.trunc(val)), true); break
|
|
146
|
-
case 'f32': dv.setFloat32(off, val, true); break
|
|
147
|
-
case 'f64': dv.setFloat64(off, val, true); break
|
|
148
|
-
case 'ptr': writePtr(dv, off, val); break
|
|
149
|
-
case 'wstr': dv.setUint16(off, val, true); break
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function writePtr(dv: DataView, off: number, val: number): void {
|
|
154
|
-
if (PTR === 8) dv.setBigUint64(off, BigInt(Math.trunc(val)), true)
|
|
155
|
-
else dv.setUint32(off, val >>> 0, true)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function readField(dv: DataView, base: number, f: Field): unknown {
|
|
159
|
-
const off = base + f.offset
|
|
160
|
-
if (f.child) return readInto(f.child, dv.buffer as ArrayBuffer, off)
|
|
161
|
-
if (f.count > 1) {
|
|
162
|
-
if (f.base === 'wstr') {
|
|
163
|
-
const arr = new Uint16Array(dv.buffer, dv.byteOffset + off, f.count)
|
|
164
|
-
let end = 0
|
|
165
|
-
while (end < arr.length && arr[end] !== 0) end++
|
|
166
|
-
let s = ''
|
|
167
|
-
for (let i = 0; i < end; i++) s += String.fromCharCode(arr[i]!)
|
|
168
|
-
return s
|
|
169
|
-
}
|
|
170
|
-
const out: number[] = []
|
|
171
|
-
const step = AS[f.base]![1]
|
|
172
|
-
for (let i = 0; i < f.count; i++) out.push(readScalar(dv, off + i * step, f.base))
|
|
173
|
-
return out
|
|
174
|
-
}
|
|
175
|
-
return readScalar(dv, off, f.base)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function writeField(dv: DataView, base: number, f: Field, v: unknown): void {
|
|
179
|
-
const off = base + f.offset
|
|
180
|
-
if (f.child) { writeInto(f.child, v as Record<string, unknown>, dv.buffer as ArrayBuffer, off); return }
|
|
181
|
-
if (f.count > 1) {
|
|
182
|
-
if (f.base === 'wstr') {
|
|
183
|
-
const s = String(v ?? '')
|
|
184
|
-
const arr = new Uint16Array(dv.buffer, dv.byteOffset + off, f.count)
|
|
185
|
-
for (let i = 0; i < arr.length; i++) arr[i] = 0
|
|
186
|
-
const n = Math.min(s.length, f.count - 1)
|
|
187
|
-
for (let i = 0; i < n; i++) arr[i] = s.charCodeAt(i)
|
|
188
|
-
return
|
|
189
|
-
}
|
|
190
|
-
const step = AS[f.base]![1]
|
|
191
|
-
const list = (v ?? []) as number[]
|
|
192
|
-
for (let i = 0; i < f.count; i++) writeScalar(dv, off + i * step, f.base, list[i] ?? 0)
|
|
193
|
-
return
|
|
194
|
-
}
|
|
195
|
-
writeScalar(dv, off, f.base, Number(v))
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function doSize<F extends Record<string, string | StructShape>>(def: F): number {
|
|
199
|
-
return buildLayout(def).size
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function readInto<S>(child: StructShape, buf: ArrayBuffer, offset: number): S {
|
|
203
|
-
const r = child as unknown as { read(b: ArrayBuffer, o: number): S }
|
|
204
|
-
return r.read(buf, offset)
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function doRead<F extends Record<string, string | StructShape>>(_def: F, fields: Field[], buf: ArrayBuffer, offset: number): ShapeOf<F> {
|
|
208
|
-
const out: Record<string, unknown> = {}
|
|
209
|
-
const dv = new DataView(buf)
|
|
210
|
-
for (const f of fields) out[f.name] = readField(dv, offset, f)
|
|
211
|
-
return out as ShapeOf<F>
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function doWrite<F extends Record<string, string | StructShape>>(def: F, fields: Field[], v: ShapeOf<F>, buf?: ArrayBuffer, offset = 0): ArrayBuffer {
|
|
215
|
-
buf = buf ?? new ArrayBuffer(doSize(def))
|
|
216
|
-
const dv = new DataView(buf)
|
|
217
|
-
for (const f of fields) writeField(dv, offset, f, (v as Record<string, unknown>)[f.name])
|
|
218
|
-
return buf
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function writeInto<W>(child: StructShape, v: Record<string, unknown>, buf: ArrayBuffer, offset: number): void {
|
|
222
|
-
const w = child as unknown as { write(i: W, b: ArrayBuffer, o: number): unknown }
|
|
223
|
-
w.write(v as W, buf, offset)
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
export function struct<const F extends Record<string, string | StructShape>>(def: F): StructDef<F> {
|
|
227
|
-
const { fields, size, structAlign } = buildLayout(def)
|
|
228
|
-
return {
|
|
229
|
-
__fields: def,
|
|
230
|
-
size,
|
|
231
|
-
structAlign,
|
|
232
|
-
read: (buf: ArrayBuffer, offset = 0) => doRead(def, fields, buf, offset),
|
|
233
|
-
write: (v, buf?: ArrayBuffer, offset = 0) => doWrite(def, fields, v, buf, offset),
|
|
234
|
-
offsetOf: (name: keyof F) => {
|
|
235
|
-
for (const f of fields) if (f.name === name) return f.offset
|
|
236
|
-
throw new Error(`ffi-struct: no field "${String(name)}"`)
|
|
237
|
-
},
|
|
238
|
-
}
|
|
239
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { struct } from './ffi-struct.js';
|
|
2
|
-
// 跨组件/示例共享的 Windows 结构体(ffi-struct 按本机架构 MSVC 对齐布局)。
|
|
3
|
-
// OPENFILENAMEW(GetOpenFileNameW)。Win2000+ 定义含尾部 pvReserved/dwReserved/FlagsEx(即 XP 系统上的真实 sizeof=152/88):
|
|
4
|
-
export const OPENFILENAMEW = struct({
|
|
5
|
-
lStructSize: 'u32',
|
|
6
|
-
hwndOwner: 'ptr',
|
|
7
|
-
hInstance: 'ptr',
|
|
8
|
-
lpstrFilter: 'ptr',
|
|
9
|
-
lpstrCustomFilter: 'ptr',
|
|
10
|
-
nMaxCustFilter: 'u32',
|
|
11
|
-
nFilterIndex: 'u32',
|
|
12
|
-
lpstrFile: 'ptr',
|
|
13
|
-
nMaxFile: 'u32',
|
|
14
|
-
lpstrFileTitle: 'ptr',
|
|
15
|
-
nMaxFileTitle: 'u32',
|
|
16
|
-
lpstrInitialDir: 'ptr',
|
|
17
|
-
lpstrTitle: 'ptr',
|
|
18
|
-
Flags: 'u32',
|
|
19
|
-
nFileOffset: 'u16',
|
|
20
|
-
nFileExtension: 'u16',
|
|
21
|
-
lpstrDefExt: 'ptr',
|
|
22
|
-
lCustData: 'ptr',
|
|
23
|
-
lpfnHook: 'ptr',
|
|
24
|
-
lpTemplateName: 'ptr',
|
|
25
|
-
pvReserved: 'ptr',
|
|
26
|
-
dwReserved: 'u32',
|
|
27
|
-
FlagsEx: 'u32',
|
|
28
|
-
});
|
|
29
|
-
// BROWSEINFOW(SHBrowseForFolderW):
|
|
30
|
-
export const BROWSEINFOW = struct({
|
|
31
|
-
hwndOwner: 'ptr',
|
|
32
|
-
pidlRoot: 'ptr',
|
|
33
|
-
pszDisplayName: 'ptr',
|
|
34
|
-
lpszTitle: 'ptr',
|
|
35
|
-
ulFlags: 'u32',
|
|
36
|
-
lpfn: 'ptr',
|
|
37
|
-
lParam: 'ptr',
|
|
38
|
-
iImage: 'i32',
|
|
39
|
-
});
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { struct } from './ffi-struct.js'
|
|
2
|
-
|
|
3
|
-
// 跨组件/示例共享的 Windows 结构体(ffi-struct 按本机架构 MSVC 对齐布局)。
|
|
4
|
-
// OPENFILENAMEW(GetOpenFileNameW)。Win2000+ 定义含尾部 pvReserved/dwReserved/FlagsEx(即 XP 系统上的真实 sizeof=152/88):
|
|
5
|
-
export const OPENFILENAMEW = struct({
|
|
6
|
-
lStructSize: 'u32',
|
|
7
|
-
hwndOwner: 'ptr',
|
|
8
|
-
hInstance: 'ptr',
|
|
9
|
-
lpstrFilter: 'ptr',
|
|
10
|
-
lpstrCustomFilter: 'ptr',
|
|
11
|
-
nMaxCustFilter: 'u32',
|
|
12
|
-
nFilterIndex: 'u32',
|
|
13
|
-
lpstrFile: 'ptr',
|
|
14
|
-
nMaxFile: 'u32',
|
|
15
|
-
lpstrFileTitle: 'ptr',
|
|
16
|
-
nMaxFileTitle: 'u32',
|
|
17
|
-
lpstrInitialDir: 'ptr',
|
|
18
|
-
lpstrTitle: 'ptr',
|
|
19
|
-
Flags: 'u32',
|
|
20
|
-
nFileOffset: 'u16',
|
|
21
|
-
nFileExtension: 'u16',
|
|
22
|
-
lpstrDefExt: 'ptr',
|
|
23
|
-
lCustData: 'ptr',
|
|
24
|
-
lpfnHook: 'ptr',
|
|
25
|
-
lpTemplateName: 'ptr',
|
|
26
|
-
pvReserved: 'ptr',
|
|
27
|
-
dwReserved: 'u32',
|
|
28
|
-
FlagsEx: 'u32',
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
// BROWSEINFOW(SHBrowseForFolderW):
|
|
32
|
-
export const BROWSEINFOW = struct({
|
|
33
|
-
hwndOwner: 'ptr',
|
|
34
|
-
pidlRoot: 'ptr',
|
|
35
|
-
pszDisplayName: 'ptr',
|
|
36
|
-
lpszTitle: 'ptr',
|
|
37
|
-
ulFlags: 'u32',
|
|
38
|
-
lpfn: 'ptr',
|
|
39
|
-
lParam: 'ptr',
|
|
40
|
-
iImage: 'i32',
|
|
41
|
-
})
|