quickwin 2.1.1 → 2.3.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 +9 -9
- package/README.md +9 -9
- package/constants.ts +1063 -0
- package/enum_map.d.ts +129 -0
- package/examples/PdfCanvas.js +2 -2
- package/examples/PdfCanvas.tsx +3 -2
- package/examples/{exec_server.js → execd/main.js} +1114 -287
- package/examples/execd/tray.js +192 -0
- package/examples/execd/worker.js +122 -0
- package/examples/pdf_preview2.js +46 -46
- package/examples/pdf_preview2.ts +55 -54
- package/examples/pdf_viewer.js +134 -130
- package/examples/pdf_viewer.tsx +6 -5
- package/examples/test_gallery.js +135 -131
- package/examples/test_gallery.tsx +9 -8
- package/examples/test_react_lazy.js +130 -128
- package/examples/test_react_lazy.tsx +7 -6
- package/examples/test_react_listbox.js +130 -128
- package/examples/test_react_listbox.tsx +8 -7
- package/examples/test_root_window.js +130 -128
- package/examples/test_root_window.tsx +5 -5
- package/examples/test_tab.js +130 -128
- package/examples/test_tab.tsx +7 -6
- package/examples/tray_demo.js +8 -8
- package/examples/tray_demo.tsx +9 -8
- package/lib/dumpRects.js +2 -2
- package/lib/dumpRects.ts +3 -2
- package/lib/fetch.js +15 -15
- package/lib/fetch.ts +16 -15
- package/lib/ffi/bind.js +3 -3
- package/lib/ffi/bind.ts +3 -3
- package/lib/ffi/ctype.ts +3 -1
- package/lib/ffi/struct.js +23 -7
- package/lib/ffi/struct.ts +50 -23
- package/lib/http-server.js +5 -5
- package/lib/http-server.ts +6 -5
- package/lib/react-qw/components/Button.js +2 -2
- package/lib/react-qw/components/CheckBox.js +4 -4
- package/lib/react-qw/components/ComboBox.js +10 -10
- package/lib/react-qw/components/DateTimePicker.js +12 -12
- package/lib/react-qw/components/Input.js +7 -7
- package/lib/react-qw/components/Link.js +4 -4
- package/lib/react-qw/components/ListBox.js +12 -12
- package/lib/react-qw/components/ListView.js +35 -35
- package/lib/react-qw/components/PathPicker.js +3 -3
- package/lib/react-qw/components/ProgressBar.js +4 -4
- package/lib/react-qw/components/RadioButton.js +4 -4
- package/lib/react-qw/components/ScrollView.js +28 -28
- package/lib/react-qw/components/Slider.js +11 -11
- package/lib/react-qw/components/Tab.js +10 -10
- package/lib/react-qw/components/Tooltip.js +8 -8
- package/lib/react-qw/components/TreeView.js +20 -20
- package/lib/react-qw/index.js +5 -5
- package/lib/react-qw/index.ts +6 -5
- package/lib/react-qw/props.js +4 -4
- package/lib/react-qw/props.ts +5 -4
- package/lib/react-qw/reconciler.js +10 -10
- package/lib/react-qw/reconciler.ts +11 -10
- package/lib/text-measure.js +3 -3
- package/lib/text-measure.ts +4 -3
- package/lib/websocket.js +17 -17
- package/lib/websocket.ts +18 -17
- package/lib/windows/advapi32.js +7 -7
- package/lib/windows/advapi32.ts +7 -7
- package/lib/windows/gdi32.js +17 -17
- package/lib/windows/gdi32.ts +17 -17
- package/lib/windows/kernel32.js +41 -2
- package/lib/windows/kernel32.ts +45 -2
- package/lib/windows/shell32.js +24 -3
- package/lib/windows/shell32.ts +27 -4
- package/lib/windows/structs.js +230 -92
- package/lib/windows/structs.ts +278 -92
- package/lib/windows/user32.js +18 -6
- package/lib/windows/user32.ts +18 -6
- package/lib/windows/winspool.js +3 -3
- package/lib/windows/winspool.ts +3 -3
- package/package.json +4 -3
- package/quickwin.d.ts +32 -10
- 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/test_ffi_bind.js +12 -10
- package/test/test_ffi_bind.ts +14 -12
- package/test/test_ffi_struct.js +60 -26
- package/test/test_ffi_struct.ts +58 -28
- package/test/test_ffi_windows.js +1 -1
- package/test/test_ffi_windows.ts +1 -1
- package/test/test_files.js +7 -7
- package/test/test_files.ts +8 -8
- package/test/test_gdi_draw.js +51 -51
- package/test/test_gdi_draw.ts +57 -57
- package/test/test_http_server.js +3 -3
- package/test/test_http_server.ts +4 -3
- package/test/test_ipv6.js +3 -3
- package/test/test_ipv6.ts +4 -3
- package/test/test_net_event.js +3 -3
- package/test/test_net_event.ts +4 -3
- package/test/test_printer.js +4 -4
- package/test/test_printer.ts +5 -5
- package/test/test_react_complex.js +132 -130
- package/test/test_react_counter.js +130 -128
- package/test/test_react_flex.js +132 -130
- package/test/test_react_hidden.js +130 -128
- package/test/test_react_listview_link.js +131 -129
- package/test/test_react_notify.js +148 -146
- package/test/test_react_render.js +130 -128
- package/test/test_registry.js +6 -6
- package/test/test_registry.ts +8 -8
- package/test/test_wolfssl_read.js +8 -8
- package/test/test_wolfssl_read.ts +9 -8
- package/examples/exec_server.tsx +0 -488
- package/examples/exec_server_worker.js +0 -395
- package/examples/exec_server_worker.ts +0 -417
- package/examples/setres.js +0 -79
- package/examples/setres.ts +0 -86
- package/quickwin_const.d.ts +0 -1037
package/test/test_ffi_struct.js
CHANGED
|
@@ -13,7 +13,8 @@ const RECT = /* @__PURE__ */ struct('RECT', {
|
|
|
13
13
|
right: 'i32',
|
|
14
14
|
bottom: 'i32',
|
|
15
15
|
});
|
|
16
|
-
const
|
|
16
|
+
const TVITEM_DECL = {
|
|
17
|
+
'#': 'struct',
|
|
17
18
|
mask: 'u32',
|
|
18
19
|
hItem: '<>ptr',
|
|
19
20
|
state: 'u32',
|
|
@@ -24,11 +25,12 @@ const TVITEM = /* @__PURE__ */ struct('TVITEM', {
|
|
|
24
25
|
iSelectedImage: 'i32',
|
|
25
26
|
cChildren: 'i32',
|
|
26
27
|
lParam: '<>ptr',
|
|
27
|
-
}
|
|
28
|
+
};
|
|
29
|
+
const TVITEM = /* @__PURE__ */ struct('TVITEM', TVITEM_DECL);
|
|
28
30
|
const TVINSERTSTRUCT = /* @__PURE__ */ struct('TVINSERTSTRUCT', {
|
|
29
31
|
hParent: '<>ptr',
|
|
30
32
|
hInsertAfter: '<>ptr',
|
|
31
|
-
item:
|
|
33
|
+
item: TVITEM_DECL,
|
|
32
34
|
});
|
|
33
35
|
export const suite = {
|
|
34
36
|
name: 'ffi-struct',
|
|
@@ -228,13 +230,15 @@ export const suite = {
|
|
|
228
230
|
t.check('length 1', 1, one.v.length);
|
|
229
231
|
t.check('[0]', 42, one.v[0]);
|
|
230
232
|
t.section('array of struct roundtrip (stride = child size)');
|
|
231
|
-
const
|
|
233
|
+
const PT_DECL = {
|
|
234
|
+
'#': 'struct',
|
|
232
235
|
x: 'i32',
|
|
233
236
|
y: 'i32',
|
|
234
|
-
}
|
|
237
|
+
};
|
|
238
|
+
const PT = /* @__PURE__ */ struct('PT', PT_DECL);
|
|
235
239
|
const POLY = struct('POLY', {
|
|
236
240
|
count: 'u32',
|
|
237
|
-
pts: { '#': 'array', element:
|
|
241
|
+
pts: { '#': 'array', element: PT_DECL, length: 3 },
|
|
238
242
|
});
|
|
239
243
|
t.check('POLY.size == 28', 28, POLY.size);
|
|
240
244
|
t.check('POLY.offsetOf pts', 4, POLY.offsetOf('pts'));
|
|
@@ -274,9 +278,28 @@ export const suite = {
|
|
|
274
278
|
rejectedMap = true;
|
|
275
279
|
}
|
|
276
280
|
t.check('bare nested map rejected(漏写 #)', true, rejectedMap);
|
|
277
|
-
// 顶层 '#'
|
|
278
|
-
|
|
279
|
-
|
|
281
|
+
// 顶层 '#' 与函数名 kind 不符 → throw(union DECL 误传 struct() 防线);
|
|
282
|
+
// 一致('#': 'struct' 的 DECL 直传形态)→ 按 kind 正常布局
|
|
283
|
+
let rejectedKind = false;
|
|
284
|
+
try {
|
|
285
|
+
struct({ '#': 'union', a: 'u8', b: 'u32' });
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
rejectedKind = true;
|
|
289
|
+
}
|
|
290
|
+
t.check('top-level # kind mismatch rejected', true, rejectedKind);
|
|
291
|
+
const H = struct('H', { '#': 'struct', a: 'u8', b: 'u32' });
|
|
292
|
+
t.check('top-level # matching kind accepted(DECL 直传)', 8, H.size);
|
|
293
|
+
// 类型层 kind 锁(参数交叉 `{ '#'?: 'struct' }` / `{ '#'?: 'union' }`):DECL 的
|
|
294
|
+
// '#' 字面与函数名 kind 不符 → 交叉 never,编译期报错。@ts-expect-error 断言该
|
|
295
|
+
// 报错存在(锁失效则指令未使用、tsc 报错);仅类型期检查,函数不调用
|
|
296
|
+
const _kindLock = () => {
|
|
297
|
+
// @ts-expect-error —— union DECL 传 struct()
|
|
298
|
+
struct('BAD_U', { '#': 'union', a: 'u8', b: 'u32' });
|
|
299
|
+
// @ts-expect-error —— struct DECL 传 union()
|
|
300
|
+
union('BAD_S', { '#': 'struct', a: 'u8', b: 'u32' });
|
|
301
|
+
};
|
|
302
|
+
void _kindLock;
|
|
280
303
|
t.section("pointer member '<>ptr' raw vs '<T>ptr' branded");
|
|
281
304
|
const PTR = struct('PTR', {
|
|
282
305
|
raw: '<>ptr',
|
|
@@ -376,16 +399,18 @@ export const suite = {
|
|
|
376
399
|
t.check('BROWSEINFOW.offsetOf lpszTitle', is64 ? 24 : 12, BROWSEINFOW.offsetOf('lpszTitle'));
|
|
377
400
|
t.check('BROWSEINFOW.offsetOf ulFlags', is64 ? 32 : 16, BROWSEINFOW.offsetOf('ulFlags'));
|
|
378
401
|
t.section('NMHDR(嵌套时 MSVC 尾 padding 传染)');
|
|
379
|
-
const
|
|
402
|
+
const NMHDR_DECL = {
|
|
403
|
+
'#': 'struct',
|
|
380
404
|
hwndFrom: '<>ptr',
|
|
381
405
|
idFrom: '<>ptr',
|
|
382
406
|
code: 'i32',
|
|
383
|
-
}
|
|
407
|
+
};
|
|
408
|
+
const NMHDR = /* @__PURE__ */ struct('NMHDR', NMHDR_DECL);
|
|
384
409
|
t.check('NMHDR.size', is64 ? 24 : 12, NMHDR.size);
|
|
385
410
|
t.check('NMHDR.offsetOf code', is64 ? 16 : 8, NMHDR.offsetOf('code'));
|
|
386
411
|
t.section('NMCUSTOMDRAW / NMLVCUSTOMDRAW (ListView custom draw)');
|
|
387
412
|
const NMCUSTOMDRAW = struct('NMCUSTOMDRAW', {
|
|
388
|
-
hdr:
|
|
413
|
+
hdr: NMHDR_DECL,
|
|
389
414
|
dwDrawStage: 'u32',
|
|
390
415
|
hdc: '<>ptr',
|
|
391
416
|
rc: 'i32[4]',
|
|
@@ -398,7 +423,7 @@ export const suite = {
|
|
|
398
423
|
t.check('NMCUSTOMDRAW.offsetOf hdc', is64 ? 32 : 16, NMCUSTOMDRAW.offsetOf('hdc'));
|
|
399
424
|
t.check('NMCUSTOMDRAW.offsetOf dwItemSpec', is64 ? 56 : 36, NMCUSTOMDRAW.offsetOf('dwItemSpec'));
|
|
400
425
|
const NMLVCUSTOMDRAW = struct('NMLVCUSTOMDRAW', {
|
|
401
|
-
hdr:
|
|
426
|
+
hdr: NMHDR_DECL,
|
|
402
427
|
dwDrawStage: 'u32',
|
|
403
428
|
hdc: '<>ptr',
|
|
404
429
|
rc: 'i32[4]',
|
|
@@ -416,7 +441,7 @@ export const suite = {
|
|
|
416
441
|
t.check('NMLVCUSTOMDRAW.offsetOf iSubItem', is64 ? 88 : 56, NMLVCUSTOMDRAW.offsetOf('iSubItem'));
|
|
417
442
|
t.section('NMLISTVIEW / NMITEMACTIVATE');
|
|
418
443
|
const NMLISTVIEW = struct('NMLISTVIEW', {
|
|
419
|
-
hdr:
|
|
444
|
+
hdr: NMHDR_DECL,
|
|
420
445
|
iItem: 'i32',
|
|
421
446
|
iSubItem: 'i32',
|
|
422
447
|
uNewState: 'u32',
|
|
@@ -471,7 +496,8 @@ export const suite = {
|
|
|
471
496
|
t.check('LVCOLUMNW.offsetOf iSubItem', is64 ? 28 : 20, LVCOLUMNW.offsetOf('iSubItem'));
|
|
472
497
|
t.check('LVCOLUMNW.offsetOf iOrder', is64 ? 36 : 28, LVCOLUMNW.offsetOf('iOrder'));
|
|
473
498
|
t.section('NMDATETIMECHANGE');
|
|
474
|
-
const
|
|
499
|
+
const SYSTEMTIME_DECL = {
|
|
500
|
+
'#': 'struct',
|
|
475
501
|
wYear: 'u16',
|
|
476
502
|
wMonth: 'u16',
|
|
477
503
|
wDayOfWeek: 'u16',
|
|
@@ -480,26 +506,28 @@ export const suite = {
|
|
|
480
506
|
wMinute: 'u16',
|
|
481
507
|
wSecond: 'u16',
|
|
482
508
|
wMilliseconds: 'u16',
|
|
483
|
-
}
|
|
509
|
+
};
|
|
484
510
|
const NMDATETIMECHANGE = struct('NMDATETIMECHANGE', {
|
|
485
|
-
hdr:
|
|
511
|
+
hdr: NMHDR_DECL,
|
|
486
512
|
dwFlags: 'u32',
|
|
487
|
-
st:
|
|
513
|
+
st: SYSTEMTIME_DECL,
|
|
488
514
|
});
|
|
489
515
|
t.check('NMDATETIMECHANGE.size', is64 ? 48 : 32, NMDATETIMECHANGE.size);
|
|
490
516
|
t.check('NMDATETIMECHANGE.offsetOf dwFlags', is64 ? 24 : 12, NMDATETIMECHANGE.offsetOf('dwFlags'));
|
|
491
517
|
t.check('NMDATETIMECHANGE.offsetOf st', is64 ? 28 : 16, NMDATETIMECHANGE.offsetOf('st'));
|
|
492
518
|
t.section('NMLINK szUrl offset');
|
|
493
|
-
const
|
|
519
|
+
const LITEM_DECL = {
|
|
520
|
+
'#': 'struct',
|
|
494
521
|
mask: 'u32',
|
|
495
522
|
iLink: 'i32',
|
|
496
523
|
state: 'u32',
|
|
497
524
|
stateMask: 'u32',
|
|
498
525
|
szID: 'u16[48]@utf-16le',
|
|
499
|
-
}
|
|
526
|
+
};
|
|
527
|
+
const LITEM = /* @__PURE__ */ struct('LITEM', LITEM_DECL);
|
|
500
528
|
const NMLINK = struct('NMLINK', {
|
|
501
|
-
hdr:
|
|
502
|
-
item:
|
|
529
|
+
hdr: NMHDR_DECL,
|
|
530
|
+
item: LITEM_DECL,
|
|
503
531
|
szUrl: 'u16[2084]@utf-16le',
|
|
504
532
|
});
|
|
505
533
|
t.check('LITEM.offsetOf szID', 16, LITEM.offsetOf('szID'));
|
|
@@ -696,14 +724,14 @@ export const suite = {
|
|
|
696
724
|
t.check('数字位 @ 字段归一往返', 4, dev.key);
|
|
697
725
|
// encode 必填判定:0 ∉ 值域的 @ 字段必填(省略 = 新槽写 0 = 静默违约);
|
|
698
726
|
// 0 ∈ 值域可省。下面 { key } 行能编译即 msg/hp 可省的断言。
|
|
699
|
-
EV.encode({ key: 4 /*
|
|
727
|
+
EV.encode({ key: 4 /* MouseKeyFlag.MK_SHIFT */ });
|
|
700
728
|
// @ts-expect-error key 必填:省略即编译错(0 ∉ MouseKeyFlag)
|
|
701
729
|
EV.encode({ msg: 0 });
|
|
702
730
|
if (false) {
|
|
703
731
|
// @ts-expect-error 域外字面量(3 不是任何 MK_ 标志)
|
|
704
732
|
EV.encode({ key: 3 });
|
|
705
733
|
// @ts-expect-error 跨枚举误用(WmMsg 成员不是 MouseKeyFlag)
|
|
706
|
-
EV.encode({ key: 1 /*
|
|
734
|
+
EV.encode({ key: 1 /* WmMsg.CREATE */ });
|
|
707
735
|
}
|
|
708
736
|
// 数组糖元素带 @:lower 递归归一(normToken 剥 @)+ 元素值域
|
|
709
737
|
const EA = struct('EA', { msgs: 'u32@WmMsg[4]' });
|
|
@@ -715,8 +743,14 @@ export const suite = {
|
|
|
715
743
|
t.section('EncodeIn DFS 后序:子树含必填 ⇒ 键必填;值后序合成(非必填兄弟可省)');
|
|
716
744
|
{
|
|
717
745
|
// 嵌套子结构含 '!'(内层强制)+ 标量/字符串糖(内层可省:''/0 是合法零值)
|
|
718
|
-
const
|
|
719
|
-
|
|
746
|
+
const NSUB_DECL = {
|
|
747
|
+
'#': 'struct',
|
|
748
|
+
hp: '<X>ptr!',
|
|
749
|
+
rest: 'u32',
|
|
750
|
+
s: 'u16[4]@utf-8',
|
|
751
|
+
};
|
|
752
|
+
const NSUB = /* @__PURE__ */ struct('NSUB', NSUB_DECL);
|
|
753
|
+
const NOUT = struct('NOUT', { top: 'u32', sub: NSUB_DECL });
|
|
720
754
|
// decode 造全量对象(hp 槽预填非零,绕 '!' 的 decode 0 断言)
|
|
721
755
|
const nbuf = new PtrArrayBuffer(NOUT.size);
|
|
722
756
|
new DataView(nbuf).setUint32(NOUT.offsetOf('sub') + NSUB.offsetOf('hp'), 0x1234, true);
|
package/test/test_ffi_struct.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as os from 'os'
|
|
2
|
-
import * as gui from 'gui'
|
|
3
2
|
import { Tester } from './test_helper.js'
|
|
4
3
|
import { struct, union } from '../lib/ffi/struct.js'
|
|
5
4
|
import { bind, } from '../lib/ffi/bind.js'
|
|
6
5
|
import { type MaybePtr, Ptr, PtrArrayBuffer } from '../lib/ffi/ctype.js'
|
|
7
|
-
import type { C_Union } from '../lib/ffi/ctype.js'
|
|
6
|
+
import type { C_Struct, C_Union } from '../lib/ffi/ctype.js'
|
|
7
|
+
import { MouseKeyFlag, WmMsg } from '../constants'
|
|
8
8
|
|
|
9
9
|
// 编译期断言工具(仅类型层,运行时无开销)
|
|
10
10
|
type Equal<A, B> = (<G>() => G extends A ? 1 : 2) extends (<G>() => G extends B ? 1 : 2) ? true : false
|
|
@@ -22,7 +22,8 @@ const RECT = /* @__PURE__ */ struct('RECT', {
|
|
|
22
22
|
bottom: 'i32',
|
|
23
23
|
})
|
|
24
24
|
|
|
25
|
-
const
|
|
25
|
+
const TVITEM_DECL = {
|
|
26
|
+
'#': 'struct',
|
|
26
27
|
mask: 'u32',
|
|
27
28
|
hItem: '<>ptr',
|
|
28
29
|
state: 'u32',
|
|
@@ -33,11 +34,12 @@ const TVITEM = /* @__PURE__ */ struct('TVITEM', {
|
|
|
33
34
|
iSelectedImage: 'i32',
|
|
34
35
|
cChildren: 'i32',
|
|
35
36
|
lParam: '<>ptr',
|
|
36
|
-
}
|
|
37
|
+
} satisfies C_Struct
|
|
38
|
+
const TVITEM = /* @__PURE__ */ struct('TVITEM', TVITEM_DECL)
|
|
37
39
|
const TVINSERTSTRUCT = /* @__PURE__ */ struct('TVINSERTSTRUCT', {
|
|
38
40
|
hParent: '<>ptr',
|
|
39
41
|
hInsertAfter: '<>ptr',
|
|
40
|
-
item:
|
|
42
|
+
item: TVITEM_DECL,
|
|
41
43
|
})
|
|
42
44
|
|
|
43
45
|
export const suite = {
|
|
@@ -260,13 +262,15 @@ export const suite = {
|
|
|
260
262
|
t.check('[0]', 42, one.v[0])
|
|
261
263
|
|
|
262
264
|
t.section('array of struct roundtrip (stride = child size)')
|
|
263
|
-
const
|
|
265
|
+
const PT_DECL = {
|
|
266
|
+
'#': 'struct',
|
|
264
267
|
x: 'i32',
|
|
265
268
|
y: 'i32',
|
|
266
|
-
}
|
|
269
|
+
} satisfies C_Struct
|
|
270
|
+
const PT = /* @__PURE__ */ struct('PT', PT_DECL)
|
|
267
271
|
const POLY = struct('POLY', {
|
|
268
272
|
count: 'u32',
|
|
269
|
-
pts: { '#': 'array', element:
|
|
273
|
+
pts: { '#': 'array', element: PT_DECL, length: 3 },
|
|
270
274
|
})
|
|
271
275
|
t.check('POLY.size == 28', 28, POLY.size)
|
|
272
276
|
t.check('POLY.offsetOf pts', 4, POLY.offsetOf('pts'))
|
|
@@ -296,9 +300,24 @@ export const suite = {
|
|
|
296
300
|
try { (struct as any)({ outer: { inner: 'u32' } }) } catch { rejectedMap = true }
|
|
297
301
|
t.check('bare nested map rejected(漏写 #)', true, rejectedMap)
|
|
298
302
|
|
|
299
|
-
// 顶层 '#'
|
|
300
|
-
|
|
301
|
-
|
|
303
|
+
// 顶层 '#' 与函数名 kind 不符 → throw(union DECL 误传 struct() 防线);
|
|
304
|
+
// 一致('#': 'struct' 的 DECL 直传形态)→ 按 kind 正常布局
|
|
305
|
+
let rejectedKind = false
|
|
306
|
+
try { (struct as any)({ '#': 'union', a: 'u8', b: 'u32' }) } catch { rejectedKind = true }
|
|
307
|
+
t.check('top-level # kind mismatch rejected', true, rejectedKind)
|
|
308
|
+
const H = (struct as any)('H', { '#': 'struct', a: 'u8', b: 'u32' })
|
|
309
|
+
t.check('top-level # matching kind accepted(DECL 直传)', 8, H.size)
|
|
310
|
+
|
|
311
|
+
// 类型层 kind 锁(参数交叉 `{ '#'?: 'struct' }` / `{ '#'?: 'union' }`):DECL 的
|
|
312
|
+
// '#' 字面与函数名 kind 不符 → 交叉 never,编译期报错。@ts-expect-error 断言该
|
|
313
|
+
// 报错存在(锁失效则指令未使用、tsc 报错);仅类型期检查,函数不调用
|
|
314
|
+
const _kindLock = () => {
|
|
315
|
+
// @ts-expect-error —— union DECL 传 struct()
|
|
316
|
+
struct('BAD_U', { '#': 'union', a: 'u8', b: 'u32' })
|
|
317
|
+
// @ts-expect-error —— struct DECL 传 union()
|
|
318
|
+
union('BAD_S', { '#': 'struct', a: 'u8', b: 'u32' })
|
|
319
|
+
}
|
|
320
|
+
void _kindLock
|
|
302
321
|
|
|
303
322
|
t.section("pointer member '<>ptr' raw vs '<T>ptr' branded")
|
|
304
323
|
const PTR = struct('PTR', {
|
|
@@ -405,17 +424,19 @@ export const suite = {
|
|
|
405
424
|
t.check('BROWSEINFOW.offsetOf ulFlags', is64 ? 32 : 16, BROWSEINFOW.offsetOf('ulFlags'))
|
|
406
425
|
|
|
407
426
|
t.section('NMHDR(嵌套时 MSVC 尾 padding 传染)')
|
|
408
|
-
const
|
|
427
|
+
const NMHDR_DECL = {
|
|
428
|
+
'#': 'struct',
|
|
409
429
|
hwndFrom: '<>ptr',
|
|
410
430
|
idFrom: '<>ptr',
|
|
411
431
|
code: 'i32',
|
|
412
|
-
}
|
|
432
|
+
} satisfies C_Struct
|
|
433
|
+
const NMHDR = /* @__PURE__ */ struct('NMHDR', NMHDR_DECL)
|
|
413
434
|
t.check('NMHDR.size', is64 ? 24 : 12, NMHDR.size)
|
|
414
435
|
t.check('NMHDR.offsetOf code', is64 ? 16 : 8, NMHDR.offsetOf('code'))
|
|
415
436
|
|
|
416
437
|
t.section('NMCUSTOMDRAW / NMLVCUSTOMDRAW (ListView custom draw)')
|
|
417
438
|
const NMCUSTOMDRAW = struct('NMCUSTOMDRAW', {
|
|
418
|
-
hdr:
|
|
439
|
+
hdr: NMHDR_DECL,
|
|
419
440
|
dwDrawStage: 'u32',
|
|
420
441
|
hdc: '<>ptr',
|
|
421
442
|
rc: 'i32[4]',
|
|
@@ -428,7 +449,7 @@ export const suite = {
|
|
|
428
449
|
t.check('NMCUSTOMDRAW.offsetOf hdc', is64 ? 32 : 16, NMCUSTOMDRAW.offsetOf('hdc'))
|
|
429
450
|
t.check('NMCUSTOMDRAW.offsetOf dwItemSpec', is64 ? 56 : 36, NMCUSTOMDRAW.offsetOf('dwItemSpec'))
|
|
430
451
|
const NMLVCUSTOMDRAW = struct('NMLVCUSTOMDRAW', {
|
|
431
|
-
hdr:
|
|
452
|
+
hdr: NMHDR_DECL,
|
|
432
453
|
dwDrawStage: 'u32',
|
|
433
454
|
hdc: '<>ptr',
|
|
434
455
|
rc: 'i32[4]',
|
|
@@ -447,7 +468,7 @@ export const suite = {
|
|
|
447
468
|
|
|
448
469
|
t.section('NMLISTVIEW / NMITEMACTIVATE')
|
|
449
470
|
const NMLISTVIEW = struct('NMLISTVIEW', {
|
|
450
|
-
hdr:
|
|
471
|
+
hdr: NMHDR_DECL,
|
|
451
472
|
iItem: 'i32',
|
|
452
473
|
iSubItem: 'i32',
|
|
453
474
|
uNewState: 'u32',
|
|
@@ -504,7 +525,8 @@ export const suite = {
|
|
|
504
525
|
t.check('LVCOLUMNW.offsetOf iOrder', is64 ? 36 : 28, LVCOLUMNW.offsetOf('iOrder'))
|
|
505
526
|
|
|
506
527
|
t.section('NMDATETIMECHANGE')
|
|
507
|
-
const
|
|
528
|
+
const SYSTEMTIME_DECL = {
|
|
529
|
+
'#': 'struct',
|
|
508
530
|
wYear: 'u16',
|
|
509
531
|
wMonth: 'u16',
|
|
510
532
|
wDayOfWeek: 'u16',
|
|
@@ -513,27 +535,29 @@ export const suite = {
|
|
|
513
535
|
wMinute: 'u16',
|
|
514
536
|
wSecond: 'u16',
|
|
515
537
|
wMilliseconds: 'u16',
|
|
516
|
-
}
|
|
538
|
+
} satisfies C_Struct
|
|
517
539
|
const NMDATETIMECHANGE = struct('NMDATETIMECHANGE', {
|
|
518
|
-
hdr:
|
|
540
|
+
hdr: NMHDR_DECL,
|
|
519
541
|
dwFlags: 'u32',
|
|
520
|
-
st:
|
|
542
|
+
st: SYSTEMTIME_DECL,
|
|
521
543
|
})
|
|
522
544
|
t.check('NMDATETIMECHANGE.size', is64 ? 48 : 32, NMDATETIMECHANGE.size)
|
|
523
545
|
t.check('NMDATETIMECHANGE.offsetOf dwFlags', is64 ? 24 : 12, NMDATETIMECHANGE.offsetOf('dwFlags'))
|
|
524
546
|
t.check('NMDATETIMECHANGE.offsetOf st', is64 ? 28 : 16, NMDATETIMECHANGE.offsetOf('st'))
|
|
525
547
|
|
|
526
548
|
t.section('NMLINK szUrl offset')
|
|
527
|
-
const
|
|
549
|
+
const LITEM_DECL = {
|
|
550
|
+
'#': 'struct',
|
|
528
551
|
mask: 'u32',
|
|
529
552
|
iLink: 'i32',
|
|
530
553
|
state: 'u32',
|
|
531
554
|
stateMask: 'u32',
|
|
532
555
|
szID: 'u16[48]@utf-16le',
|
|
533
|
-
}
|
|
556
|
+
} satisfies C_Struct
|
|
557
|
+
const LITEM = /* @__PURE__ */ struct('LITEM', LITEM_DECL)
|
|
534
558
|
const NMLINK = struct('NMLINK', {
|
|
535
|
-
hdr:
|
|
536
|
-
item:
|
|
559
|
+
hdr: NMHDR_DECL,
|
|
560
|
+
item: LITEM_DECL,
|
|
537
561
|
szUrl: 'u16[2084]@utf-16le',
|
|
538
562
|
})
|
|
539
563
|
t.check('LITEM.offsetOf szID', 16, LITEM.offsetOf('szID'))
|
|
@@ -745,14 +769,14 @@ export const suite = {
|
|
|
745
769
|
|
|
746
770
|
// encode 必填判定:0 ∉ 值域的 @ 字段必填(省略 = 新槽写 0 = 静默违约);
|
|
747
771
|
// 0 ∈ 值域可省。下面 { key } 行能编译即 msg/hp 可省的断言。
|
|
748
|
-
EV.encode({ key:
|
|
772
|
+
EV.encode({ key: MouseKeyFlag.MK_SHIFT })
|
|
749
773
|
// @ts-expect-error key 必填:省略即编译错(0 ∉ MouseKeyFlag)
|
|
750
774
|
EV.encode({ msg: 0 })
|
|
751
775
|
if (false) {
|
|
752
776
|
// @ts-expect-error 域外字面量(3 不是任何 MK_ 标志)
|
|
753
777
|
EV.encode({ key: 3 })
|
|
754
778
|
// @ts-expect-error 跨枚举误用(WmMsg 成员不是 MouseKeyFlag)
|
|
755
|
-
EV.encode({ key:
|
|
779
|
+
EV.encode({ key: WmMsg.CREATE })
|
|
756
780
|
}
|
|
757
781
|
|
|
758
782
|
// 数组糖元素带 @:lower 递归归一(normToken 剥 @)+ 元素值域
|
|
@@ -766,8 +790,14 @@ export const suite = {
|
|
|
766
790
|
t.section('EncodeIn DFS 后序:子树含必填 ⇒ 键必填;值后序合成(非必填兄弟可省)')
|
|
767
791
|
{
|
|
768
792
|
// 嵌套子结构含 '!'(内层强制)+ 标量/字符串糖(内层可省:''/0 是合法零值)
|
|
769
|
-
const
|
|
770
|
-
|
|
793
|
+
const NSUB_DECL = {
|
|
794
|
+
'#': 'struct',
|
|
795
|
+
hp: '<X>ptr!',
|
|
796
|
+
rest: 'u32',
|
|
797
|
+
s: 'u16[4]@utf-8',
|
|
798
|
+
} satisfies C_Struct
|
|
799
|
+
const NSUB = /* @__PURE__ */ struct('NSUB', NSUB_DECL)
|
|
800
|
+
const NOUT = struct('NOUT', { top: 'u32', sub: NSUB_DECL })
|
|
771
801
|
// decode 造全量对象(hp 槽预填非零,绕 '!' 的 decode 0 断言)
|
|
772
802
|
const nbuf = new PtrArrayBuffer(NOUT.size)
|
|
773
803
|
new DataView(nbuf).setUint32(NOUT.offsetOf('sub') + NSUB.offsetOf('hp'), 0x1234, true)
|
package/test/test_ffi_windows.js
CHANGED
|
@@ -74,7 +74,7 @@ export const suite = {
|
|
|
74
74
|
if (himl !== 0)
|
|
75
75
|
t.checkTrue('ImageListDestroy(himl) 成功', ImageListDestroy(himl) !== 0);
|
|
76
76
|
t.section('user32: LoadImage 双变体(string 自动编码 / ordinal MAKEINTRESOURCE)');
|
|
77
|
-
// Win32 IMAGE_* 真值:BITMAP=0 CURSOR=1 ICON=2;LR_SHARED=0x8000(注意
|
|
77
|
+
// Win32 IMAGE_* 真值:BITMAP=0 CURSOR=1 ICON=2;LR_SHARED=0x8000(注意 ImageType d.ts 当前值与此不一致)
|
|
78
78
|
t.checkTrue('LoadImageOrdinal(NULL, IDI_APPLICATION, IMAGE_ICON, LR_SHARED) 非 0', LoadImageOrdinal(NULL, 32512, 2, 0, 0, 0x8000) !== 0);
|
|
79
79
|
t.check('LoadImage(LOADFROMFILE, 不存在文件) → 0', 0, LoadImage(NULL, 'qw_no_such_icon_xyz.ico', 2, 0, 0, 0x10));
|
|
80
80
|
t.section('user32: 滚动条(入参对象直传 / 出参 encode → call → decode)');
|
package/test/test_ffi_windows.ts
CHANGED
|
@@ -83,7 +83,7 @@ export const suite = {
|
|
|
83
83
|
if (himl !== 0) t.checkTrue('ImageListDestroy(himl) 成功', ImageListDestroy(himl) !== 0)
|
|
84
84
|
|
|
85
85
|
t.section('user32: LoadImage 双变体(string 自动编码 / ordinal MAKEINTRESOURCE)')
|
|
86
|
-
// Win32 IMAGE_* 真值:BITMAP=0 CURSOR=1 ICON=2;LR_SHARED=0x8000(注意
|
|
86
|
+
// Win32 IMAGE_* 真值:BITMAP=0 CURSOR=1 ICON=2;LR_SHARED=0x8000(注意 ImageType d.ts 当前值与此不一致)
|
|
87
87
|
t.checkTrue('LoadImageOrdinal(NULL, IDI_APPLICATION, IMAGE_ICON, LR_SHARED) 非 0',
|
|
88
88
|
LoadImageOrdinal(NULL, 32512, 2, 0, 0, 0x8000) !== 0)
|
|
89
89
|
t.check('LoadImage(LOADFROMFILE, 不存在文件) → 0', 0,
|
package/test/test_files.js
CHANGED
|
@@ -40,7 +40,7 @@ export const suite = {
|
|
|
40
40
|
const pathC = `${dir}\\c.txt`;
|
|
41
41
|
const pathCn = `${dir}\\中文文件.txt`;
|
|
42
42
|
t.section('CreateFile + WriteFile: write payload');
|
|
43
|
-
const hW = CreateFile(pathA, GENERIC_WRITE, 0, NULL, 2 /*
|
|
43
|
+
const hW = CreateFile(pathA, GENERIC_WRITE, 0, NULL, 2 /* FileCreation.CREATE_ALWAYS */, 0x80 /* FILE_ATTRIBUTE_NORMAL */, NULL);
|
|
44
44
|
t.checkTrue(`write handle valid (${hW})`, hW !== -1); // 失败 = INVALID_HANDLE_VALUE(-1)
|
|
45
45
|
const payload = 'hello files';
|
|
46
46
|
const wData = toWide(payload);
|
|
@@ -49,34 +49,34 @@ export const suite = {
|
|
|
49
49
|
t.check('bytes written', payload.length * 2, new DataView(pWr).getUint32(0, true));
|
|
50
50
|
t.checkTrue('CloseHandle (writer)', CloseHandle(hW) !== 0);
|
|
51
51
|
t.section('CreateFile(READ) + GetFileSize + SetFilePointer + ReadFile');
|
|
52
|
-
const hR = CreateFile(pathA, GENERIC_READ, 0, NULL, 3 /*
|
|
52
|
+
const hR = CreateFile(pathA, GENERIC_READ, 0, NULL, 3 /* FileCreation.OPEN_EXISTING */, 0x80, NULL);
|
|
53
53
|
t.checkTrue(`read handle valid (${hR})`, hR !== -1);
|
|
54
54
|
t.check('GetFileSize', payload.length * 2, GetFileSize(hR, NULL));
|
|
55
|
-
t.check('SetFilePointer BEGIN → 0', 0, SetFilePointer(hR, 0, NULL, 0 /*
|
|
55
|
+
t.check('SetFilePointer BEGIN → 0', 0, SetFilePointer(hR, 0, NULL, 0 /* FileSeekFrom.BEGIN */));
|
|
56
56
|
const rData = new PtrArrayBuffer(payload.length * 2);
|
|
57
57
|
const pRd = new PtrArrayBuffer(4);
|
|
58
58
|
t.checkTrue('ReadFile ok', ReadFile(hR, rData, payload.length * 2, pRd, NULL) !== 0);
|
|
59
59
|
t.check('read roundtrip', payload, fromWide(rData, payload.length));
|
|
60
60
|
// seek 到 END-4 验证负偏移 + 读尾 4 字节
|
|
61
|
-
t.check('SetFilePointer END-4', payload.length * 2 - 4, SetFilePointer(hR, -4, NULL, 2 /*
|
|
61
|
+
t.check('SetFilePointer END-4', payload.length * 2 - 4, SetFilePointer(hR, -4, NULL, 2 /* FileSeekFrom.END */));
|
|
62
62
|
const rTail = new PtrArrayBuffer(8);
|
|
63
63
|
ReadFile(hR, rTail, 4, pRd, NULL);
|
|
64
64
|
t.check('seeked tail', payload.slice(-2), fromWide(rTail, 2));
|
|
65
65
|
CloseHandle(hR);
|
|
66
66
|
t.section('CopyFile + MoveFileEx + GetFileAttributes');
|
|
67
|
-
const hB = CreateFile(pathB, GENERIC_WRITE, 0, NULL, 2 /*
|
|
67
|
+
const hB = CreateFile(pathB, GENERIC_WRITE, 0, NULL, 2 /* FileCreation.CREATE_ALWAYS */, 0x80, NULL);
|
|
68
68
|
const bData = toWide('bb');
|
|
69
69
|
WriteFile(hB, bData, 4, pWr, NULL);
|
|
70
70
|
CloseHandle(hB);
|
|
71
71
|
t.checkTrue('CopyFile ok', CopyFile(pathB, pathC, 0) !== 0);
|
|
72
|
-
const hC = CreateFile(pathC, GENERIC_READ, 0, NULL, 3 /*
|
|
72
|
+
const hC = CreateFile(pathC, GENERIC_READ, 0, NULL, 3 /* FileCreation.OPEN_EXISTING */, 0x80, NULL);
|
|
73
73
|
t.check('copied size', 4, GetFileSize(hC, NULL));
|
|
74
74
|
t.checkTrue('CloseHandle (copy reader)', CloseHandle(hC) !== 0);
|
|
75
75
|
t.checkTrue('MoveFileEx replace ok', MoveFileEx(pathB, pathC, 0x1 /* MOVEFILE_REPLACE_EXISTING */) !== 0);
|
|
76
76
|
t.check('moved: source gone', INVALID32, GetFileAttributes(pathB));
|
|
77
77
|
t.checkTrue('dest is file (not dir)', (GetFileAttributes(pathC) & FILE_ATTRIBUTE_DIRECTORY) === 0);
|
|
78
78
|
t.section('FindFirstFile/FindNextFile: enumerate *.txt incl. CJK name');
|
|
79
|
-
const hCn = CreateFile(pathCn, GENERIC_WRITE, 0, NULL, 2 /*
|
|
79
|
+
const hCn = CreateFile(pathCn, GENERIC_WRITE, 0, NULL, 2 /* FileCreation.CREATE_ALWAYS */, 0x80, NULL);
|
|
80
80
|
CloseHandle(hCn); // 0 字节中文名文件
|
|
81
81
|
const fd = WIN32_FIND_DATAW.alloc();
|
|
82
82
|
const hFind = FindFirstFile(`${dir}\\a.txt`, fd.ptr);
|
package/test/test_files.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as gui from 'gui'
|
|
2
1
|
import { WCHAR } from '../lib/ffi/bind.js'
|
|
3
2
|
import { NULL, PtrArrayBuffer } from '../lib/ffi/ctype.js'
|
|
4
3
|
import {
|
|
@@ -8,6 +7,7 @@ import {
|
|
|
8
7
|
} from '../lib/windows/kernel32.js'
|
|
9
8
|
import { WIN32_FIND_DATAW } from '../lib/windows/structs.js'
|
|
10
9
|
import { Tester } from './test_helper.js'
|
|
10
|
+
import { FileCreation, FileSeekFrom } from '../constants'
|
|
11
11
|
|
|
12
12
|
const GENERIC_READ = 0x80000000
|
|
13
13
|
const GENERIC_WRITE = 0x40000000
|
|
@@ -49,7 +49,7 @@ export const suite = {
|
|
|
49
49
|
|
|
50
50
|
t.section('CreateFile + WriteFile: write payload')
|
|
51
51
|
const hW = CreateFile(pathA, GENERIC_WRITE, 0, NULL,
|
|
52
|
-
|
|
52
|
+
FileCreation.CREATE_ALWAYS, 0x80 /* FILE_ATTRIBUTE_NORMAL */, NULL)
|
|
53
53
|
t.checkTrue(`write handle valid (${hW})`, hW !== -1) // 失败 = INVALID_HANDLE_VALUE(-1)
|
|
54
54
|
const payload = 'hello files'
|
|
55
55
|
const wData = toWide(payload)
|
|
@@ -60,17 +60,17 @@ export const suite = {
|
|
|
60
60
|
|
|
61
61
|
t.section('CreateFile(READ) + GetFileSize + SetFilePointer + ReadFile')
|
|
62
62
|
const hR = CreateFile(pathA, GENERIC_READ, 0, NULL,
|
|
63
|
-
|
|
63
|
+
FileCreation.OPEN_EXISTING, 0x80, NULL)
|
|
64
64
|
t.checkTrue(`read handle valid (${hR})`, hR !== -1)
|
|
65
65
|
t.check('GetFileSize', payload.length * 2, GetFileSize(hR, NULL))
|
|
66
|
-
t.check('SetFilePointer BEGIN → 0', 0, SetFilePointer(hR, 0, NULL,
|
|
66
|
+
t.check('SetFilePointer BEGIN → 0', 0, SetFilePointer(hR, 0, NULL, FileSeekFrom.BEGIN))
|
|
67
67
|
const rData = new PtrArrayBuffer(payload.length * 2)
|
|
68
68
|
const pRd = new PtrArrayBuffer(4)
|
|
69
69
|
t.checkTrue('ReadFile ok', ReadFile(hR, rData, payload.length * 2, pRd, NULL) !== 0)
|
|
70
70
|
t.check('read roundtrip', payload, fromWide(rData, payload.length))
|
|
71
71
|
// seek 到 END-4 验证负偏移 + 读尾 4 字节
|
|
72
72
|
t.check('SetFilePointer END-4', payload.length * 2 - 4,
|
|
73
|
-
SetFilePointer(hR, -4, NULL,
|
|
73
|
+
SetFilePointer(hR, -4, NULL, FileSeekFrom.END))
|
|
74
74
|
const rTail = new PtrArrayBuffer(8)
|
|
75
75
|
ReadFile(hR, rTail, 4, pRd, NULL)
|
|
76
76
|
t.check('seeked tail', payload.slice(-2), fromWide(rTail, 2))
|
|
@@ -78,13 +78,13 @@ export const suite = {
|
|
|
78
78
|
|
|
79
79
|
t.section('CopyFile + MoveFileEx + GetFileAttributes')
|
|
80
80
|
const hB = CreateFile(pathB, GENERIC_WRITE, 0, NULL,
|
|
81
|
-
|
|
81
|
+
FileCreation.CREATE_ALWAYS, 0x80, NULL)
|
|
82
82
|
const bData = toWide('bb')
|
|
83
83
|
WriteFile(hB, bData, 4, pWr, NULL)
|
|
84
84
|
CloseHandle(hB)
|
|
85
85
|
t.checkTrue('CopyFile ok', CopyFile(pathB, pathC, 0) !== 0)
|
|
86
86
|
const hC = CreateFile(pathC, GENERIC_READ, 0, NULL,
|
|
87
|
-
|
|
87
|
+
FileCreation.OPEN_EXISTING, 0x80, NULL)
|
|
88
88
|
t.check('copied size', 4, GetFileSize(hC, NULL))
|
|
89
89
|
t.checkTrue('CloseHandle (copy reader)', CloseHandle(hC) !== 0)
|
|
90
90
|
t.checkTrue('MoveFileEx replace ok',
|
|
@@ -95,7 +95,7 @@ export const suite = {
|
|
|
95
95
|
|
|
96
96
|
t.section('FindFirstFile/FindNextFile: enumerate *.txt incl. CJK name')
|
|
97
97
|
const hCn = CreateFile(pathCn, GENERIC_WRITE, 0, NULL,
|
|
98
|
-
|
|
98
|
+
FileCreation.CREATE_ALWAYS, 0x80, NULL)
|
|
99
99
|
CloseHandle(hCn) // 0 字节中文名文件
|
|
100
100
|
const fd = WIN32_FIND_DATAW.alloc()
|
|
101
101
|
const hFind = FindFirstFile(`${dir}\\a.txt`, fd.ptr)
|