quickwin 1.2.1 → 1.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.
Files changed (50) hide show
  1. package/README.en.md +2 -1
  2. package/README.md +2 -1
  3. package/examples/PdfCanvas.js +3 -3
  4. package/examples/PdfCanvas.tsx +3 -3
  5. package/examples/pdf_preview2.js +34 -19
  6. package/examples/pdf_preview2.ts +34 -20
  7. package/examples/pdf_viewer.js +959 -219
  8. package/examples/pdf_viewer.tsx +29 -19
  9. package/examples/test_gallery.js +925 -198
  10. package/examples/test_react_lazy.js +908 -181
  11. package/examples/test_react_listbox.js +908 -181
  12. package/examples/test_root_window.js +908 -181
  13. package/examples/test_tab.js +908 -181
  14. package/lib/ffi-bind.js +85 -0
  15. package/lib/ffi-bind.ts +170 -0
  16. package/lib/ffi-struct.js +224 -0
  17. package/lib/ffi-struct.ts +239 -0
  18. package/lib/react-qw/components/DateTimePicker.js +21 -15
  19. package/lib/react-qw/components/Link.js +4 -6
  20. package/lib/react-qw/components/ListView.js +84 -40
  21. package/lib/react-qw/components/PathPicker.js +40 -24
  22. package/lib/react-qw/components/Tab.js +21 -10
  23. package/lib/react-qw/components/Tooltip.js +27 -15
  24. package/lib/react-qw/components/TreeView.js +23 -33
  25. package/lib/react-qw/nmhdr.js +26 -0
  26. package/lib/react-qw/nmhdr.ts +32 -0
  27. package/lib/text-measure.js +14 -26
  28. package/lib/text-measure.ts +14 -26
  29. package/lib/win-common-structs.js +39 -0
  30. package/lib/win-common-structs.ts +41 -0
  31. package/package.json +1 -1
  32. package/quickwin.d.ts +6 -2
  33. package/qwin-nowasm-x86.exe +0 -0
  34. package/qwin-nowasm.exe +0 -0
  35. package/qwin-x86.exe +0 -0
  36. package/qwin.exe +0 -0
  37. package/test/run.js +2 -0
  38. package/test/run.ts +2 -0
  39. package/test/test_ffi.js +12 -8
  40. package/test/test_ffi.ts +12 -12
  41. package/test/test_ffi_bind.js +42 -0
  42. package/test/test_ffi_bind.ts +46 -0
  43. package/test/test_ffi_struct.js +201 -0
  44. package/test/test_ffi_struct.ts +223 -0
  45. package/test/test_react_complex.js +911 -184
  46. package/test/test_react_counter.js +908 -181
  47. package/test/test_react_flex.js +908 -181
  48. package/test/test_react_hidden.js +910 -183
  49. package/test/test_react_listview_link.js +908 -181
  50. package/test/test_react_render.js +908 -181
@@ -0,0 +1,46 @@
1
+ import * as std from 'std'
2
+ import * as gui from 'gui'
3
+ import { Tester } from './test_helper.js'
4
+ import { bind, bindLib } from '../lib/ffi-bind.js'
5
+
6
+ export const suite = {
7
+ name: 'ffi-bind',
8
+ run: (t: Tester) => {
9
+ t.section('bind single proc')
10
+ const getDC = bind('user32.dll', 'GetDC', 'ptr -> ptr')
11
+ const dc = getDC(0)
12
+ t.checkTrue('bind GetDC(NULL) returns screen DC', !!dc)
13
+
14
+ t.section('bindLib batch with wstr auto-encode')
15
+ const user32 = bindLib('user32.dll', {
16
+ DrawTextW: 'ptr wstr i32 ptr i32 -> i32',
17
+ ReleaseDC: 'ptr ptr -> i32',
18
+ })
19
+ const rect = new ArrayBuffer(16)
20
+ const dv = new DataView(rect)
21
+ dv.setInt32(8, 500, true)
22
+ const lines = user32.DrawTextW(dc, 'hello ffi-bind', -1, rect, gui.DrawTextFlag.CALCRECT)
23
+ t.checkTrue('DrawTextW returns line count > 0', lines > 0)
24
+ t.checkTrue('measured width > 0', dv.getInt32(8, true) > 0)
25
+ t.checkTrue('measured height > 0', dv.getInt32(12, true) > 0)
26
+ if (dc) user32.ReleaseDC(0, dc)
27
+
28
+ t.section('signedness read-back: i32 vs u32')
29
+ const lstrcmpI = bind('kernel32.dll', 'lstrcmpW', 'wstr wstr -> i32')
30
+ const lstrcmpU = bind('kernel32.dll', 'lstrcmpW', 'wstr wstr -> u32')
31
+ const setLastError = bind('kernel32.dll', 'SetLastError', 'u32 -> void')
32
+ const getErrU = bind('kernel32.dll', 'GetLastError', ' -> u32')
33
+ const getErrI = bind('kernel32.dll', 'GetLastError', ' -> i32')
34
+ const ri = lstrcmpI('a', 'b')
35
+ const ru = lstrcmpU('a', 'b')
36
+ std.printf(' lstrcmpW("a","b"): i32=%s u32=%s\n', String(ri), String(ru))
37
+ t.checkTrue('i32 read-back keeps negative sign', ri < 0)
38
+ t.check('u32 read-back is 0xFFFFFFFF', 4294967295, ru)
39
+ setLastError(0x80000000)
40
+ const glU = getErrU()
41
+ const glI = getErrI()
42
+ std.printf(' GetLastError after SetLastError(0x80000000): u32=%s i32=%s\n', String(glU), String(glI))
43
+ t.check('GetLastError u32 keeps big value', 2147483648, glU)
44
+ t.check('GetLastError i32 reads as negative', -2147483648, glI)
45
+ },
46
+ }
@@ -0,0 +1,201 @@
1
+ import * as os from 'os';
2
+ import { struct } from '../lib/ffi-struct.js';
3
+ import { bind } from '../lib/ffi-bind.js';
4
+ const RECT = struct({ left: 'i32', top: 'i32', right: 'i32', bottom: 'i32' });
5
+ const TVITEM = struct({
6
+ mask: 'u32',
7
+ hItem: 'ptr',
8
+ state: 'u32',
9
+ stateMask: 'u32',
10
+ pszText: 'ptr',
11
+ cchTextMax: 'i32',
12
+ iImage: 'i32',
13
+ iSelectedImage: 'i32',
14
+ cChildren: 'i32',
15
+ lParam: 'ptr',
16
+ });
17
+ const TVINSERTSTRUCT = struct({ hParent: 'ptr', hInsertAfter: 'ptr', item: TVITEM });
18
+ export const suite = {
19
+ name: 'ffi-struct',
20
+ run: (t) => {
21
+ const is64 = os.arch === 'x64';
22
+ t.section('RECT layout & roundtrip');
23
+ t.check('RECT.size == 16', 16, RECT.size);
24
+ const rbuf = RECT.write({ left: 10, top: 20, right: 30, bottom: 40 });
25
+ const r = RECT.read(rbuf);
26
+ t.check('left', 10, r.left);
27
+ t.check('top', 20, r.top);
28
+ t.check('right', 30, r.right);
29
+ t.check('bottom', 40, r.bottom);
30
+ t.check('offsetOf left', 0, RECT.offsetOf('left'));
31
+ t.check('offsetOf bottom', 12, RECT.offsetOf('bottom'));
32
+ t.section('GetWindowRect fills RECT buffer');
33
+ const getWindowRect = bind('user32.dll', 'GetWindowRect', 'ptr ptr -> int');
34
+ const getDesktopWindow = bind('user32.dll', 'GetDesktopWindow', ' -> ptr');
35
+ const hwnd = getDesktopWindow();
36
+ const wrect = new ArrayBuffer(16);
37
+ const ok = getWindowRect(hwnd, wrect);
38
+ t.checkTrue('GetWindowRect succeeds', ok !== 0);
39
+ const wr = RECT.read(wrect);
40
+ t.checkTrue('screen RECT non-empty', wr.right > 0 && wr.bottom > 0);
41
+ t.section('ptr layout matches arch');
42
+ t.check(`TVITEM.size (${os.arch})`, is64 ? 56 : 40, TVITEM.size);
43
+ t.check('TVITEM.offsetOf pszText', is64 ? 24 : 16, TVITEM.offsetOf('pszText'));
44
+ t.check('TVINSERTSTRUCT.size', is64 ? 72 : 48, TVINSERTSTRUCT.size);
45
+ t.check('TVINSERTSTRUCT.offsetOf item', is64 ? 16 : 8, TVINSERTSTRUCT.offsetOf('item'));
46
+ t.section('nested struct roundtrip');
47
+ const ins = TVINSERTSTRUCT.write({
48
+ hParent: 0x11111111,
49
+ hInsertAfter: 0x22222222,
50
+ item: {
51
+ mask: 1, hItem: 0x33333333, state: 0, stateMask: 0,
52
+ pszText: 0x44444444, cchTextMax: 100,
53
+ iImage: 0, iSelectedImage: 0, cChildren: 5, lParam: 0x55555555,
54
+ },
55
+ });
56
+ const got = TVINSERTSTRUCT.read(ins);
57
+ t.check('nested hParent', 0x11111111, got.hParent);
58
+ t.check('nested hInsertAfter', 0x22222222, got.hInsertAfter);
59
+ t.check('nested cChildren', 5, got.item.cChildren);
60
+ t.check('nested ptr pszText', 0x44444444, got.item.pszText);
61
+ t.section('C typedef aliases (int/DWORD/LPARAM/LONG_PTR/short)');
62
+ const M = struct({ n: 'int', d: 'DWORD', w: 'LPARAM', s: 'short', q: 'LONG_PTR' });
63
+ t.check(`aliased size (${os.arch})`, is64 ? 32 : 20, M.size);
64
+ const mb = M.write({ n: -5, d: 0xFFFFFFFF, w: 0xAABBCCDD, s: -7, q: 0x11223344 });
65
+ const m = M.read(mb);
66
+ t.check('int -5', -5, m.n);
67
+ t.check('DWORD', 0xFFFFFFFF, m.d);
68
+ t.check('LPARAM', 0xAABBCCDD, m.w);
69
+ t.check('short -7', -7, m.s);
70
+ t.check('LONG_PTR', 0x11223344, m.q);
71
+ t.section('wstr[8] roundtrip & truncation');
72
+ const W = struct({ name: 'wstr[8]' });
73
+ t.check('wstr[8].size == 16', 16, W.size);
74
+ const wb = W.write({ name: 'hello' });
75
+ t.check('read back "hello"', 'hello', W.read(wb).name);
76
+ const lb = W.write({ name: 'a very long string over' });
77
+ t.check('truncated to 7 chars', 'a very ', W.read(lb).name);
78
+ t.section('numeric arrays roundtrip');
79
+ const A = struct({ v: 'u16[4]', k: 'i32[2]' });
80
+ t.check('size == 16', 16, A.size);
81
+ const ab = A.write({ v: [1, 2, 3, 4], k: [-1, 300000] });
82
+ const ad = A.read(ab);
83
+ t.check('u16[0]', 1, ad.v[0]);
84
+ t.check('u16[3]', 4, ad.v[3]);
85
+ t.check('i32[0] -1', -1, ad.k[0]);
86
+ t.check('i32[1]', 300000, ad.k[1]);
87
+ t.section('f32/f64 layout (MSVC: f@0, double@8)');
88
+ const FL = struct({ f: 'float', d: 'double' });
89
+ t.check('size == 16 (f@0, double aligned @8)', 16, FL.size);
90
+ const fb = FL.write({ f: 1.5, d: -2.25 });
91
+ const fr = FL.read(fb);
92
+ t.check('float', 1.5, fr.f);
93
+ t.check('double', -2.25, fr.d);
94
+ t.section('comctl dialog structs track 32/64-bit ABI');
95
+ const TCITEMW = struct({
96
+ mask: 'u32', dwState: 'u32', dwStateMask: 'u32',
97
+ pszText: 'ptr', cchTextMax: 'i32', iImage: 'i32', lParam: 'ptr',
98
+ });
99
+ t.check('TCITEMW.size', is64 ? 40 : 28, TCITEMW.size);
100
+ t.check('TCITEMW.offsetOf pszText', is64 ? 16 : 12, TCITEMW.offsetOf('pszText'));
101
+ t.check('TCITEMW.offsetOf cchTextMax', is64 ? 24 : 16, TCITEMW.offsetOf('cchTextMax'));
102
+ const TTTOOLINFOW = struct({
103
+ cbSize: 'u32', uFlags: 'u32', hwnd: 'ptr', uId: 'ptr',
104
+ rect: 'i32[4]', hinst: 'ptr', lpszText: 'ptr', lParam: 'ptr',
105
+ lpReserved: 'ptr', // WinXP+ 追加
106
+ });
107
+ t.check('TTTOOLINFOW.size', is64 ? 72 : 48, TTTOOLINFOW.size);
108
+ t.check('TTTOOLINFOW.offsetOf lpszText', is64 ? 48 : 36, TTTOOLINFOW.offsetOf('lpszText'));
109
+ const OPENFILENAMEW = struct({
110
+ lStructSize: 'u32', hwndOwner: 'ptr', hInstance: 'ptr', lpstrFilter: 'ptr',
111
+ lpstrCustomFilter: 'ptr', nMaxCustFilter: 'u32', nFilterIndex: 'u32',
112
+ lpstrFile: 'ptr', nMaxFile: 'u32', lpstrFileTitle: 'ptr', nMaxFileTitle: 'u32',
113
+ lpstrInitialDir: 'ptr', lpstrTitle: 'ptr', Flags: 'u32',
114
+ nFileOffset: 'u16', nFileExtension: 'u16',
115
+ lpstrDefExt: 'ptr', lCustData: 'ptr', lpfnHook: 'ptr', lpTemplateName: 'ptr',
116
+ pvReserved: 'ptr', dwReserved: 'u32', FlagsEx: 'u32', // Win2000+ 追加
117
+ });
118
+ t.check('OPENFILENAMEW.size', is64 ? 152 : 88, OPENFILENAMEW.size);
119
+ t.check('OPENFILENAMEW.offsetOf lpstrFile', is64 ? 48 : 28, OPENFILENAMEW.offsetOf('lpstrFile'));
120
+ t.check('OPENFILENAMEW.offsetOf Flags', is64 ? 96 : 52, OPENFILENAMEW.offsetOf('Flags'));
121
+ t.check('OPENFILENAMEW.offsetOf lpstrTitle', is64 ? 88 : 48, OPENFILENAMEW.offsetOf('lpstrTitle'));
122
+ const BROWSEINFOW = struct({
123
+ hwndOwner: 'ptr', pidlRoot: 'ptr', pszDisplayName: 'ptr', lpszTitle: 'ptr',
124
+ ulFlags: 'u32', lpfn: 'ptr', lParam: 'ptr', iImage: 'i32',
125
+ });
126
+ t.check('BROWSEINFOW.size', is64 ? 64 : 32, BROWSEINFOW.size);
127
+ t.check('BROWSEINFOW.offsetOf lpszTitle', is64 ? 24 : 12, BROWSEINFOW.offsetOf('lpszTitle'));
128
+ t.check('BROWSEINFOW.offsetOf ulFlags', is64 ? 32 : 16, BROWSEINFOW.offsetOf('ulFlags'));
129
+ t.section('NMHDR(嵌套时 MSVC 尾 padding 传染)');
130
+ const NMHDR = struct({ hwndFrom: 'ptr', idFrom: 'ptr', code: 'i32' });
131
+ t.check('NMHDR.size', is64 ? 24 : 12, NMHDR.size);
132
+ t.check('NMHDR.offsetOf code', is64 ? 16 : 8, NMHDR.offsetOf('code'));
133
+ t.section('NMCUSTOMDRAW / NMLVCUSTOMDRAW (ListView custom draw)');
134
+ const NMCUSTOMDRAW = struct({
135
+ hdr: NMHDR,
136
+ dwDrawStage: 'u32', hdc: 'ptr', rc: 'i32[4]',
137
+ dwItemSpec: 'ptr', uItemState: 'u32', lItemlParam: 'ptr',
138
+ });
139
+ t.check('NMCUSTOMDRAW.size', is64 ? 80 : 48, NMCUSTOMDRAW.size);
140
+ t.check('NMCUSTOMDRAW.offsetOf dwDrawStage', is64 ? 24 : 12, NMCUSTOMDRAW.offsetOf('dwDrawStage'));
141
+ t.check('NMCUSTOMDRAW.offsetOf hdc', is64 ? 32 : 16, NMCUSTOMDRAW.offsetOf('hdc'));
142
+ t.check('NMCUSTOMDRAW.offsetOf dwItemSpec', is64 ? 56 : 36, NMCUSTOMDRAW.offsetOf('dwItemSpec'));
143
+ const NMLVCUSTOMDRAW = struct({
144
+ hdr: NMHDR,
145
+ dwDrawStage: 'u32', hdc: 'ptr', rc: 'i32[4]',
146
+ dwItemSpec: 'ptr', uItemState: 'u32', lItemlParam: 'ptr',
147
+ clrText: 'u32', clrTextBk: 'u32', iSubItem: 'i32', dwItemType: 'u32',
148
+ });
149
+ t.check('NMLVCUSTOMDRAW.size', is64 ? 96 : 64, NMLVCUSTOMDRAW.size);
150
+ t.check('NMLVCUSTOMDRAW.offsetOf clrText', is64 ? 80 : 48, NMLVCUSTOMDRAW.offsetOf('clrText'));
151
+ t.check('NMLVCUSTOMDRAW.offsetOf clrTextBk', is64 ? 84 : 52, NMLVCUSTOMDRAW.offsetOf('clrTextBk'));
152
+ t.check('NMLVCUSTOMDRAW.offsetOf iSubItem', is64 ? 88 : 56, NMLVCUSTOMDRAW.offsetOf('iSubItem'));
153
+ t.section('NMLISTVIEW / NMITEMACTIVATE');
154
+ const NMLISTVIEW = struct({
155
+ hdr: NMHDR,
156
+ iItem: 'i32', iSubItem: 'i32', uNewState: 'u32', uOldState: 'u32', uChanged: 'u32',
157
+ ptAction: 'i32[2]', lParam: 'ptr',
158
+ });
159
+ t.check('NMLISTVIEW.size', is64 ? 64 : 44, NMLISTVIEW.size);
160
+ t.check('NMLISTVIEW.offsetOf iItem', is64 ? 24 : 12, NMLISTVIEW.offsetOf('iItem'));
161
+ t.check('NMLISTVIEW.offsetOf iSubItem', is64 ? 28 : 16, NMLISTVIEW.offsetOf('iSubItem'));
162
+ t.check('NMLISTVIEW.offsetOf uNewState', is64 ? 32 : 20, NMLISTVIEW.offsetOf('uNewState'));
163
+ t.check('NMLISTVIEW.offsetOf uOldState', is64 ? 36 : 24, NMLISTVIEW.offsetOf('uOldState'));
164
+ t.section('LVITEMW / LVCOLUMNW');
165
+ const LVITEMW = struct({
166
+ mask: 'u32', iItem: 'i32', iSubItem: 'i32',
167
+ state: 'u32', stateMask: 'u32',
168
+ pszText: 'ptr', cchTextMax: 'i32', iImage: 'i32', lParam: 'ptr',
169
+ iIndent: 'i32', iGroupId: 'i32', cColumns: 'u32',
170
+ puColumns: 'ptr', piColFmt: 'ptr', iGroup: 'i32',
171
+ });
172
+ t.check('LVITEMW.size', is64 ? 88 : 60, LVITEMW.size);
173
+ t.check('LVITEMW.offsetOf iItem', 4, LVITEMW.offsetOf('iItem'));
174
+ t.check('LVITEMW.offsetOf pszText', is64 ? 24 : 20, LVITEMW.offsetOf('pszText'));
175
+ t.check('LVITEMW.offsetOf iImage', is64 ? 36 : 28, LVITEMW.offsetOf('iImage'));
176
+ t.check('LVITEMW.offsetOf lParam', is64 ? 40 : 32, LVITEMW.offsetOf('lParam'));
177
+ const LVCOLUMNW = struct({
178
+ mask: 'u32', fmt: 'i32', cx: 'i32',
179
+ pszText: 'ptr', cchTextMax: 'i32', iSubItem: 'i32', iImage: 'i32', iOrder: 'i32',
180
+ cxMin: 'i32', cxDefault: 'i32', cxIdeal: 'i32',
181
+ });
182
+ t.check('LVCOLUMNW.size', is64 ? 56 : 44, LVCOLUMNW.size);
183
+ t.check('LVCOLUMNW.offsetOf pszText', is64 ? 16 : 12, LVCOLUMNW.offsetOf('pszText'));
184
+ t.check('LVCOLUMNW.offsetOf iSubItem', is64 ? 28 : 20, LVCOLUMNW.offsetOf('iSubItem'));
185
+ t.check('LVCOLUMNW.offsetOf iOrder', is64 ? 36 : 28, LVCOLUMNW.offsetOf('iOrder'));
186
+ t.section('NMDATETIMECHANGE');
187
+ const SYSTEMTIME = struct({
188
+ wYear: 'u16', wMonth: 'u16', wDayOfWeek: 'u16', wDay: 'u16',
189
+ wHour: 'u16', wMinute: 'u16', wSecond: 'u16', wMilliseconds: 'u16',
190
+ });
191
+ const NMDATETIMECHANGE = struct({ hdr: NMHDR, dwFlags: 'u32', st: SYSTEMTIME });
192
+ t.check('NMDATETIMECHANGE.size', is64 ? 48 : 32, NMDATETIMECHANGE.size);
193
+ t.check('NMDATETIMECHANGE.offsetOf dwFlags', is64 ? 24 : 12, NMDATETIMECHANGE.offsetOf('dwFlags'));
194
+ t.check('NMDATETIMECHANGE.offsetOf st', is64 ? 28 : 16, NMDATETIMECHANGE.offsetOf('st'));
195
+ t.section('NMLINK szUrl offset');
196
+ const LITEM = struct({ mask: 'u32', iLink: 'i32', state: 'u32', stateMask: 'u32', szID: 'wstr[48]' });
197
+ const NMLINK = struct({ hdr: NMHDR, item: LITEM, szUrl: 'wstr[2084]' });
198
+ t.check('LITEM.offsetOf szID', 16, LITEM.offsetOf('szID'));
199
+ t.check('NMLINK.offsetOf szUrl', is64 ? 136 : 124, NMLINK.offsetOf('szUrl'));
200
+ },
201
+ };
@@ -0,0 +1,223 @@
1
+ import * as os from 'os'
2
+ import { Tester } from './test_helper.js'
3
+ import { struct } from '../lib/ffi-struct.js'
4
+ import { bind } from '../lib/ffi-bind.js'
5
+
6
+ const RECT = struct({ left: 'i32', top: 'i32', right: 'i32', bottom: 'i32' })
7
+
8
+ const TVITEM = struct({
9
+ mask: 'u32',
10
+ hItem: 'ptr',
11
+ state: 'u32',
12
+ stateMask: 'u32',
13
+ pszText: 'ptr',
14
+ cchTextMax: 'i32',
15
+ iImage: 'i32',
16
+ iSelectedImage: 'i32',
17
+ cChildren: 'i32',
18
+ lParam: 'ptr',
19
+ })
20
+ const TVINSERTSTRUCT = struct({ hParent: 'ptr', hInsertAfter: 'ptr', item: TVITEM })
21
+
22
+ export const suite = {
23
+ name: 'ffi-struct',
24
+ run: (t: Tester) => {
25
+ const is64 = os.arch === 'x64'
26
+
27
+ t.section('RECT layout & roundtrip')
28
+ t.check('RECT.size == 16', 16, RECT.size)
29
+ const rbuf = RECT.write({ left: 10, top: 20, right: 30, bottom: 40 })
30
+ const r = RECT.read(rbuf)
31
+ t.check('left', 10, r.left)
32
+ t.check('top', 20, r.top)
33
+ t.check('right', 30, r.right)
34
+ t.check('bottom', 40, r.bottom)
35
+ t.check('offsetOf left', 0, RECT.offsetOf('left'))
36
+ t.check('offsetOf bottom', 12, RECT.offsetOf('bottom'))
37
+
38
+ t.section('GetWindowRect fills RECT buffer')
39
+ const getWindowRect = bind('user32.dll', 'GetWindowRect', 'ptr ptr -> int')
40
+ const getDesktopWindow = bind('user32.dll', 'GetDesktopWindow', ' -> ptr')
41
+ const hwnd = getDesktopWindow()
42
+ const wrect = new ArrayBuffer(16)
43
+ const ok = getWindowRect(hwnd, wrect)
44
+ t.checkTrue('GetWindowRect succeeds', ok !== 0)
45
+ const wr = RECT.read(wrect)
46
+ t.checkTrue('screen RECT non-empty', wr.right > 0 && wr.bottom > 0)
47
+
48
+ t.section('ptr layout matches arch')
49
+ t.check(`TVITEM.size (${os.arch})`, is64 ? 56 : 40, TVITEM.size)
50
+ t.check('TVITEM.offsetOf pszText', is64 ? 24 : 16, TVITEM.offsetOf('pszText'))
51
+ t.check('TVINSERTSTRUCT.size', is64 ? 72 : 48, TVINSERTSTRUCT.size)
52
+ t.check('TVINSERTSTRUCT.offsetOf item', is64 ? 16 : 8, TVINSERTSTRUCT.offsetOf('item'))
53
+
54
+ t.section('nested struct roundtrip')
55
+ const ins = TVINSERTSTRUCT.write({
56
+ hParent: 0x11111111,
57
+ hInsertAfter: 0x22222222,
58
+ item: {
59
+ mask: 1, hItem: 0x33333333, state: 0, stateMask: 0,
60
+ pszText: 0x44444444, cchTextMax: 100,
61
+ iImage: 0, iSelectedImage: 0, cChildren: 5, lParam: 0x55555555,
62
+ },
63
+ })
64
+ const got = TVINSERTSTRUCT.read(ins)
65
+ t.check('nested hParent', 0x11111111, got.hParent)
66
+ t.check('nested hInsertAfter', 0x22222222, got.hInsertAfter)
67
+ t.check('nested cChildren', 5, got.item.cChildren)
68
+ t.check('nested ptr pszText', 0x44444444, got.item.pszText)
69
+
70
+ t.section('C typedef aliases (int/DWORD/LPARAM/LONG_PTR/short)')
71
+ const M = struct({ n: 'int', d: 'DWORD', w: 'LPARAM', s: 'short', q: 'LONG_PTR' })
72
+ t.check(`aliased size (${os.arch})`, is64 ? 32 : 20, M.size)
73
+ const mb = M.write({ n: -5, d: 0xFFFFFFFF, w: 0xAABBCCDD, s: -7, q: 0x11223344 })
74
+ const m = M.read(mb)
75
+ t.check('int -5', -5, m.n)
76
+ t.check('DWORD', 0xFFFFFFFF, m.d)
77
+ t.check('LPARAM', 0xAABBCCDD, m.w)
78
+ t.check('short -7', -7, m.s)
79
+ t.check('LONG_PTR', 0x11223344, m.q)
80
+
81
+ t.section('wstr[8] roundtrip & truncation')
82
+ const W = struct({ name: 'wstr[8]' })
83
+ t.check('wstr[8].size == 16', 16, W.size)
84
+ const wb = W.write({ name: 'hello' })
85
+ t.check('read back "hello"', 'hello', W.read(wb).name)
86
+ const lb = W.write({ name: 'a very long string over' })
87
+ t.check('truncated to 7 chars', 'a very ', W.read(lb).name)
88
+
89
+ t.section('numeric arrays roundtrip')
90
+ const A = struct({ v: 'u16[4]', k: 'i32[2]' })
91
+ t.check('size == 16', 16, A.size)
92
+ const ab = A.write({ v: [1, 2, 3, 4], k: [-1, 300000] })
93
+ const ad = A.read(ab)
94
+ t.check('u16[0]', 1, ad.v[0])
95
+ t.check('u16[3]', 4, ad.v[3])
96
+ t.check('i32[0] -1', -1, ad.k[0])
97
+ t.check('i32[1]', 300000, ad.k[1])
98
+
99
+ t.section('f32/f64 layout (MSVC: f@0, double@8)')
100
+ const FL = struct({ f: 'float', d: 'double' })
101
+ t.check('size == 16 (f@0, double aligned @8)', 16, FL.size)
102
+ const fb = FL.write({ f: 1.5, d: -2.25 })
103
+ const fr = FL.read(fb)
104
+ t.check('float', 1.5, fr.f)
105
+ t.check('double', -2.25, fr.d)
106
+
107
+ t.section('comctl dialog structs track 32/64-bit ABI')
108
+ const TCITEMW = struct({
109
+ mask: 'u32', dwState: 'u32', dwStateMask: 'u32',
110
+ pszText: 'ptr', cchTextMax: 'i32', iImage: 'i32', lParam: 'ptr',
111
+ })
112
+ t.check('TCITEMW.size', is64 ? 40 : 28, TCITEMW.size)
113
+ t.check('TCITEMW.offsetOf pszText', is64 ? 16 : 12, TCITEMW.offsetOf('pszText'))
114
+ t.check('TCITEMW.offsetOf cchTextMax', is64 ? 24 : 16, TCITEMW.offsetOf('cchTextMax'))
115
+
116
+ const TTTOOLINFOW = struct({
117
+ cbSize: 'u32', uFlags: 'u32', hwnd: 'ptr', uId: 'ptr',
118
+ rect: 'i32[4]', hinst: 'ptr', lpszText: 'ptr', lParam: 'ptr',
119
+ lpReserved: 'ptr', // WinXP+ 追加
120
+ })
121
+ t.check('TTTOOLINFOW.size', is64 ? 72 : 48, TTTOOLINFOW.size)
122
+ t.check('TTTOOLINFOW.offsetOf lpszText', is64 ? 48 : 36, TTTOOLINFOW.offsetOf('lpszText'))
123
+
124
+ const OPENFILENAMEW = struct({
125
+ lStructSize: 'u32', hwndOwner: 'ptr', hInstance: 'ptr', lpstrFilter: 'ptr',
126
+ lpstrCustomFilter: 'ptr', nMaxCustFilter: 'u32', nFilterIndex: 'u32',
127
+ lpstrFile: 'ptr', nMaxFile: 'u32', lpstrFileTitle: 'ptr', nMaxFileTitle: 'u32',
128
+ lpstrInitialDir: 'ptr', lpstrTitle: 'ptr', Flags: 'u32',
129
+ nFileOffset: 'u16', nFileExtension: 'u16',
130
+ lpstrDefExt: 'ptr', lCustData: 'ptr', lpfnHook: 'ptr', lpTemplateName: 'ptr',
131
+ pvReserved: 'ptr', dwReserved: 'u32', FlagsEx: 'u32', // Win2000+ 追加
132
+ })
133
+ t.check('OPENFILENAMEW.size', is64 ? 152 : 88, OPENFILENAMEW.size)
134
+ t.check('OPENFILENAMEW.offsetOf lpstrFile', is64 ? 48 : 28, OPENFILENAMEW.offsetOf('lpstrFile'))
135
+ t.check('OPENFILENAMEW.offsetOf Flags', is64 ? 96 : 52, OPENFILENAMEW.offsetOf('Flags'))
136
+ t.check('OPENFILENAMEW.offsetOf lpstrTitle', is64 ? 88 : 48, OPENFILENAMEW.offsetOf('lpstrTitle'))
137
+
138
+ const BROWSEINFOW = struct({
139
+ hwndOwner: 'ptr', pidlRoot: 'ptr', pszDisplayName: 'ptr', lpszTitle: 'ptr',
140
+ ulFlags: 'u32', lpfn: 'ptr', lParam: 'ptr', iImage: 'i32',
141
+ })
142
+ t.check('BROWSEINFOW.size', is64 ? 64 : 32, BROWSEINFOW.size)
143
+ t.check('BROWSEINFOW.offsetOf lpszTitle', is64 ? 24 : 12, BROWSEINFOW.offsetOf('lpszTitle'))
144
+ t.check('BROWSEINFOW.offsetOf ulFlags', is64 ? 32 : 16, BROWSEINFOW.offsetOf('ulFlags'))
145
+
146
+ t.section('NMHDR(嵌套时 MSVC 尾 padding 传染)')
147
+ const NMHDR = struct({ hwndFrom: 'ptr', idFrom: 'ptr', code: 'i32' })
148
+ t.check('NMHDR.size', is64 ? 24 : 12, NMHDR.size)
149
+ t.check('NMHDR.offsetOf code', is64 ? 16 : 8, NMHDR.offsetOf('code'))
150
+
151
+ t.section('NMCUSTOMDRAW / NMLVCUSTOMDRAW (ListView custom draw)')
152
+ const NMCUSTOMDRAW = struct({
153
+ hdr: NMHDR,
154
+ dwDrawStage: 'u32', hdc: 'ptr', rc: 'i32[4]',
155
+ dwItemSpec: 'ptr', uItemState: 'u32', lItemlParam: 'ptr',
156
+ })
157
+ t.check('NMCUSTOMDRAW.size', is64 ? 80 : 48, NMCUSTOMDRAW.size)
158
+ t.check('NMCUSTOMDRAW.offsetOf dwDrawStage', is64 ? 24 : 12, NMCUSTOMDRAW.offsetOf('dwDrawStage'))
159
+ t.check('NMCUSTOMDRAW.offsetOf hdc', is64 ? 32 : 16, NMCUSTOMDRAW.offsetOf('hdc'))
160
+ t.check('NMCUSTOMDRAW.offsetOf dwItemSpec', is64 ? 56 : 36, NMCUSTOMDRAW.offsetOf('dwItemSpec'))
161
+ const NMLVCUSTOMDRAW = struct({
162
+ hdr: NMHDR,
163
+ dwDrawStage: 'u32', hdc: 'ptr', rc: 'i32[4]',
164
+ dwItemSpec: 'ptr', uItemState: 'u32', lItemlParam: 'ptr',
165
+ clrText: 'u32', clrTextBk: 'u32', iSubItem: 'i32', dwItemType: 'u32',
166
+ })
167
+ t.check('NMLVCUSTOMDRAW.size', is64 ? 96 : 64, NMLVCUSTOMDRAW.size)
168
+ t.check('NMLVCUSTOMDRAW.offsetOf clrText', is64 ? 80 : 48, NMLVCUSTOMDRAW.offsetOf('clrText'))
169
+ t.check('NMLVCUSTOMDRAW.offsetOf clrTextBk', is64 ? 84 : 52, NMLVCUSTOMDRAW.offsetOf('clrTextBk'))
170
+ t.check('NMLVCUSTOMDRAW.offsetOf iSubItem', is64 ? 88 : 56, NMLVCUSTOMDRAW.offsetOf('iSubItem'))
171
+
172
+ t.section('NMLISTVIEW / NMITEMACTIVATE')
173
+ const NMLISTVIEW = struct({
174
+ hdr: NMHDR,
175
+ iItem: 'i32', iSubItem: 'i32', uNewState: 'u32', uOldState: 'u32', uChanged: 'u32',
176
+ ptAction: 'i32[2]', lParam: 'ptr',
177
+ })
178
+ t.check('NMLISTVIEW.size', is64 ? 64 : 44, NMLISTVIEW.size)
179
+ t.check('NMLISTVIEW.offsetOf iItem', is64 ? 24 : 12, NMLISTVIEW.offsetOf('iItem'))
180
+ t.check('NMLISTVIEW.offsetOf iSubItem', is64 ? 28 : 16, NMLISTVIEW.offsetOf('iSubItem'))
181
+ t.check('NMLISTVIEW.offsetOf uNewState', is64 ? 32 : 20, NMLISTVIEW.offsetOf('uNewState'))
182
+ t.check('NMLISTVIEW.offsetOf uOldState', is64 ? 36 : 24, NMLISTVIEW.offsetOf('uOldState'))
183
+
184
+ t.section('LVITEMW / LVCOLUMNW')
185
+ const LVITEMW = struct({
186
+ mask: 'u32', iItem: 'i32', iSubItem: 'i32',
187
+ state: 'u32', stateMask: 'u32',
188
+ pszText: 'ptr', cchTextMax: 'i32', iImage: 'i32', lParam: 'ptr',
189
+ iIndent: 'i32', iGroupId: 'i32', cColumns: 'u32',
190
+ puColumns: 'ptr', piColFmt: 'ptr', iGroup: 'i32',
191
+ })
192
+ t.check('LVITEMW.size', is64 ? 88 : 60, LVITEMW.size)
193
+ t.check('LVITEMW.offsetOf iItem', 4, LVITEMW.offsetOf('iItem'))
194
+ t.check('LVITEMW.offsetOf pszText', is64 ? 24 : 20, LVITEMW.offsetOf('pszText'))
195
+ t.check('LVITEMW.offsetOf iImage', is64 ? 36 : 28, LVITEMW.offsetOf('iImage'))
196
+ t.check('LVITEMW.offsetOf lParam', is64 ? 40 : 32, LVITEMW.offsetOf('lParam'))
197
+ const LVCOLUMNW = struct({
198
+ mask: 'u32', fmt: 'i32', cx: 'i32',
199
+ pszText: 'ptr', cchTextMax: 'i32', iSubItem: 'i32', iImage: 'i32', iOrder: 'i32',
200
+ cxMin: 'i32', cxDefault: 'i32', cxIdeal: 'i32',
201
+ })
202
+ t.check('LVCOLUMNW.size', is64 ? 56 : 44, LVCOLUMNW.size)
203
+ t.check('LVCOLUMNW.offsetOf pszText', is64 ? 16 : 12, LVCOLUMNW.offsetOf('pszText'))
204
+ t.check('LVCOLUMNW.offsetOf iSubItem', is64 ? 28 : 20, LVCOLUMNW.offsetOf('iSubItem'))
205
+ t.check('LVCOLUMNW.offsetOf iOrder', is64 ? 36 : 28, LVCOLUMNW.offsetOf('iOrder'))
206
+
207
+ t.section('NMDATETIMECHANGE')
208
+ const SYSTEMTIME = struct({
209
+ wYear: 'u16', wMonth: 'u16', wDayOfWeek: 'u16', wDay: 'u16',
210
+ wHour: 'u16', wMinute: 'u16', wSecond: 'u16', wMilliseconds: 'u16',
211
+ })
212
+ const NMDATETIMECHANGE = struct({ hdr: NMHDR, dwFlags: 'u32', st: SYSTEMTIME })
213
+ t.check('NMDATETIMECHANGE.size', is64 ? 48 : 32, NMDATETIMECHANGE.size)
214
+ t.check('NMDATETIMECHANGE.offsetOf dwFlags', is64 ? 24 : 12, NMDATETIMECHANGE.offsetOf('dwFlags'))
215
+ t.check('NMDATETIMECHANGE.offsetOf st', is64 ? 28 : 16, NMDATETIMECHANGE.offsetOf('st'))
216
+
217
+ t.section('NMLINK szUrl offset')
218
+ const LITEM = struct({ mask: 'u32', iLink: 'i32', state: 'u32', stateMask: 'u32', szID: 'wstr[48]' })
219
+ const NMLINK = struct({ hdr: NMHDR, item: LITEM, szUrl: 'wstr[2084]' })
220
+ t.check('LITEM.offsetOf szID', 16, LITEM.offsetOf('szID'))
221
+ t.check('NMLINK.offsetOf szUrl', is64 ? 136 : 124, NMLINK.offsetOf('szUrl'))
222
+ },
223
+ }