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/test/test_ffi_bind.ts
CHANGED
|
@@ -1,22 +1,89 @@
|
|
|
1
1
|
import * as std from 'std'
|
|
2
2
|
import * as gui from 'gui'
|
|
3
|
+
import * as ffi from 'ffi'
|
|
4
|
+
import * as os from 'os'
|
|
3
5
|
import { Tester } from './test_helper.js'
|
|
4
|
-
import { bind, bindLib } from '../lib/ffi
|
|
6
|
+
import { bind, bindLib, closure, WCHAR, BYTE, type CodecMap } from '../lib/ffi/bind.js'
|
|
7
|
+
import { struct } from '../lib/ffi/struct.js'
|
|
8
|
+
import { NULL, type MaybePtr, Ptr, PtrArrayBuffer, readScalar, writeScalar, PTR_SIZE, normToken } from '../lib/ffi/ctype.js'
|
|
9
|
+
|
|
10
|
+
// 编译期断言工具(仅类型层,运行时无开销)
|
|
11
|
+
type Equal<A, B> = (<G>() => G extends A ? 1 : 2) extends (<G>() => G extends B ? 1 : 2) ? true : false
|
|
12
|
+
function expectType<T extends true>(_value?: T): void {}
|
|
13
|
+
// 从签名串推导参数元组 / 返回类型(纯类型层,不执行 bind)
|
|
14
|
+
type ParamsOf<S extends string> = Parameters<ReturnType<typeof bind<S, {}>>>
|
|
15
|
+
type RetOf<S extends string> = ReturnType<ReturnType<typeof bind<S, {}>>>
|
|
16
|
+
|
|
17
|
+
// 与 callPacked 相同的槽宽布局,供「ffiCall 直驱 closure」打包
|
|
18
|
+
function packArgs(kinds: string[], vals: (number | bigint)[]): ArrayBuffer {
|
|
19
|
+
const is64 = os.arch === 'x64'
|
|
20
|
+
const SZ: Record<string, number> = is64
|
|
21
|
+
? { u8: 8, i8: 8, u16: 8, i16: 8, u32: 8, i32: 8, u64: 8, i64: 8, f32: 8, f64: 8, ptr: 8 }
|
|
22
|
+
: { u8: 4, i8: 4, u16: 4, i16: 4, u32: 4, i32: 4, u64: 8, i64: 8, f32: 4, f64: 8, ptr: 4 }
|
|
23
|
+
const size = kinds.reduce((s, k) => s + SZ[k]!, 0)
|
|
24
|
+
const b = new ArrayBuffer(size)
|
|
25
|
+
const dv = new DataView(b)
|
|
26
|
+
let off = 0
|
|
27
|
+
for (let i = 0; i < kinds.length; i++) {
|
|
28
|
+
const k = kinds[i]!
|
|
29
|
+
const v = vals[i]!
|
|
30
|
+
switch (k) {
|
|
31
|
+
case 'f64': dv.setFloat64(off, Number(v), true); break
|
|
32
|
+
case 'f32': dv.setFloat32(off, Number(v), true); break
|
|
33
|
+
case 'u64': dv.setBigUint64(off, v as bigint, true); break
|
|
34
|
+
case 'i64': dv.setBigInt64(off, v as bigint, true); break
|
|
35
|
+
case 'ptr':
|
|
36
|
+
if (is64) dv.setBigUint64(off, BigInt(Number(v)), true)
|
|
37
|
+
else dv.setUint32(off, Number(v) >>> 0, true)
|
|
38
|
+
break
|
|
39
|
+
default: dv.setUint32(off, Number(v) >>> 0, true); break
|
|
40
|
+
}
|
|
41
|
+
off += SZ[k]!
|
|
42
|
+
}
|
|
43
|
+
return b
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ASCII 字符串 ↔ ArrayBuffer(供 msvcrt _strtoui64/_i64toa 类函数用)
|
|
47
|
+
function strToBuf(s: string): PtrArrayBuffer<string> {
|
|
48
|
+
const b = new PtrArrayBuffer(s.length + 1)
|
|
49
|
+
const u8 = new Uint8Array(b)
|
|
50
|
+
for (let i = 0; i < s.length; i++) u8[i] = s.charCodeAt(i)
|
|
51
|
+
return b
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function bufToString(buf: ArrayBuffer): string {
|
|
55
|
+
const u8 = new Uint8Array(buf)
|
|
56
|
+
let s = ''
|
|
57
|
+
for (let i = 0; i < u8.length && u8[i] !== 0; i++) s += String.fromCharCode(u8[i]!)
|
|
58
|
+
return s
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 读取 native 指针指向的 C 字符串(\0 结尾);空指针(0)→ 空串
|
|
62
|
+
function readCStr(p: number): string {
|
|
63
|
+
if (!p) return ''
|
|
64
|
+
let s = ''
|
|
65
|
+
for (let i = 0; ; i++) {
|
|
66
|
+
const b = ffi.readByte(p + i)
|
|
67
|
+
if (b === 0) break
|
|
68
|
+
s += String.fromCharCode(b)
|
|
69
|
+
}
|
|
70
|
+
return s
|
|
71
|
+
}
|
|
5
72
|
|
|
6
73
|
export const suite = {
|
|
7
74
|
name: 'ffi-bind',
|
|
8
75
|
run: (t: Tester) => {
|
|
9
76
|
t.section('bind single proc')
|
|
10
|
-
const getDC = bind('user32.dll', 'GetDC', 'ptr -> ptr')
|
|
77
|
+
const getDC = bind('user32.dll', 'GetDC', '<>ptr -> <>ptr')
|
|
11
78
|
const dc = getDC(0)
|
|
12
79
|
t.checkTrue('bind GetDC(NULL) returns screen DC', !!dc)
|
|
13
80
|
|
|
14
|
-
t.section('bindLib batch with
|
|
81
|
+
t.section('bindLib batch with <WCHAR>ptr auto-encode')
|
|
15
82
|
const user32 = bindLib('user32.dll', {
|
|
16
|
-
DrawTextW: 'ptr
|
|
17
|
-
ReleaseDC: 'ptr ptr -> i32',
|
|
83
|
+
DrawTextW: '<>ptr <WCHAR>ptr i32 <BYTE>ptr i32 -> i32',
|
|
84
|
+
ReleaseDC: '<>ptr <>ptr -> i32',
|
|
18
85
|
})
|
|
19
|
-
const rect = new
|
|
86
|
+
const rect = new PtrArrayBuffer(16)
|
|
20
87
|
const dv = new DataView(rect)
|
|
21
88
|
dv.setInt32(8, 500, true)
|
|
22
89
|
const lines = user32.DrawTextW(dc, 'hello ffi-bind', -1, rect, gui.DrawTextFlag.CALCRECT)
|
|
@@ -26,8 +93,8 @@ export const suite = {
|
|
|
26
93
|
if (dc) user32.ReleaseDC(0, dc)
|
|
27
94
|
|
|
28
95
|
t.section('signedness read-back: i32 vs u32')
|
|
29
|
-
const lstrcmpI = bind('kernel32.dll', 'lstrcmpW', '
|
|
30
|
-
const lstrcmpU = bind('kernel32.dll', 'lstrcmpW', '
|
|
96
|
+
const lstrcmpI = bind('kernel32.dll', 'lstrcmpW', '<WCHAR>ptr <WCHAR>ptr -> i32')
|
|
97
|
+
const lstrcmpU = bind('kernel32.dll', 'lstrcmpW', '<WCHAR>ptr <WCHAR>ptr -> u32')
|
|
31
98
|
const setLastError = bind('kernel32.dll', 'SetLastError', 'u32 -> void')
|
|
32
99
|
const getErrU = bind('kernel32.dll', 'GetLastError', ' -> u32')
|
|
33
100
|
const getErrI = bind('kernel32.dll', 'GetLastError', ' -> i32')
|
|
@@ -42,5 +109,489 @@ export const suite = {
|
|
|
42
109
|
std.printf(' GetLastError after SetLastError(0x80000000): u32=%s i32=%s\n', String(glU), String(glI))
|
|
43
110
|
t.check('GetLastError u32 keeps big value', 2147483648, glU)
|
|
44
111
|
t.check('GetLastError i32 reads as negative', -2147483648, glI)
|
|
112
|
+
|
|
113
|
+
t.section('f32/f64 floating args (ffi_types[] table regression)')
|
|
114
|
+
// ffi_types[] 曾把 FLOAT/DOUBLE 槽各错位一位(DOUBLE 槽指向 ffi_type_float),
|
|
115
|
+
// float/double 被按 4 字节处理。f64 用 msvcrt!sqrt/atan2(三平台必有)。
|
|
116
|
+
const sqrtF64 = bind('msvcrt.dll', 'sqrt', 'f64 -> f64')
|
|
117
|
+
const atan2F64 = bind('msvcrt.dll', 'atan2', 'f64 f64 -> f64')
|
|
118
|
+
const sq2 = sqrtF64(2.0)
|
|
119
|
+
std.printf(' sqrt(2.0) = %s (expect 1.4142135623730951)\n', String(sq2))
|
|
120
|
+
t.checkTrue('sqrt(2.0) close to 1.4142135623730951', Math.abs(sq2 - 1.4142135623730951) < 1e-12)
|
|
121
|
+
const pa = atan2F64(1.0, 1.0)
|
|
122
|
+
std.printf(' atan2(1,1) = %s (expect ~0.7853981634)\n', String(pa))
|
|
123
|
+
t.checkTrue('atan2(1,1) close to PI/4', Math.abs(pa - Math.PI / 4) < 1e-12)
|
|
124
|
+
// f32: gdi32!AngleArc 的 eStartAngle/eSweepAngle 是 REAL(float32)。
|
|
125
|
+
// 若 FLOAT 槽仍为 NULL,ffi_prep_cif 会返回 FFI_BAD_ARGTYPE → 抛 TypeError。
|
|
126
|
+
const getDCF = bind('user32.dll', 'GetDC', '<>ptr -> <>ptr')
|
|
127
|
+
const angleArc = bind('gdi32.dll', 'AngleArc', '<>ptr i32 i32 u32 f32 f32 -> i32')
|
|
128
|
+
const releaseDCF = bind('user32.dll', 'ReleaseDC', '<>ptr <>ptr -> i32')
|
|
129
|
+
const hdcF = getDCF(0)
|
|
130
|
+
// ptr 读回 0 保真(number):hdcF 为 0 表示无 DC,跳过——
|
|
131
|
+
// AngleArc(NULL,…) 必然返回 FALSE,不值得断言。
|
|
132
|
+
if (hdcF) {
|
|
133
|
+
const ok = angleArc(hdcF, 20, 20, 5, 0.0, 90.0)
|
|
134
|
+
std.printf(' AngleArc(0~90) on screen DC = %s (expect 1)\n', String(ok))
|
|
135
|
+
t.checkTrue('AngleArc with f32 angles succeeds', ok === 1)
|
|
136
|
+
releaseDCF(0, hdcF)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
t.section('kind strictness: <>ptr / <BYTE>ptr / <WCHAR>ptr')
|
|
140
|
+
const setRectB = bind('user32.dll', 'SetRect', '<BYTE>ptr i32 i32 i32 i32 -> i32')
|
|
141
|
+
const rb = new PtrArrayBuffer(16)
|
|
142
|
+
const okR = setRectB(rb, 1, 2, 3, 4)
|
|
143
|
+
t.checkTrue('<BYTE>ptr accepts PtrArrayBuffer', okR !== 0)
|
|
144
|
+
const rbv = new DataView(rb)
|
|
145
|
+
t.check('<BYTE>ptr writes through (left)', 1, rbv.getInt32(0, true))
|
|
146
|
+
t.check('<BYTE>ptr writes through (bottom)', 4, rbv.getInt32(12, true))
|
|
147
|
+
// 类型层已挡住裸 ArrayBuffer;动态路径同样 fail-loud(BYTE.encode 的 instanceof 检查)
|
|
148
|
+
let plainErr: unknown = null
|
|
149
|
+
try { setRectB(new ArrayBuffer(16) as never, 1, 2, 3, 4) } catch (e) { plainErr = e }
|
|
150
|
+
t.checkTrue('<BYTE>ptr rejects plain ArrayBuffer (fail-loud)', String(plainErr).includes('PtrArrayBuffer'))
|
|
151
|
+
|
|
152
|
+
let errPtr = ''
|
|
153
|
+
try {
|
|
154
|
+
(getDC as unknown as (v: unknown) => number)(rb)
|
|
155
|
+
} catch (e) {
|
|
156
|
+
errPtr = String(e)
|
|
157
|
+
}
|
|
158
|
+
t.checkTrue('<>ptr rejects ArrayBuffer, hint mentions <BYTE>ptr', errPtr.includes('<BYTE>ptr'))
|
|
159
|
+
|
|
160
|
+
// 编译期:token → 参数/返回类型。锁住「裸 ptr / 拼错 token 静默漏成 number」的回归。
|
|
161
|
+
// 根因:`never extends X` 恒真,故 ArgToken/RetToken 必须在原始 token 上把关、
|
|
162
|
+
// 且裸 'ptr' 需要显式 never 分支(删掉那行 'ptr' 就会漏成 number)。
|
|
163
|
+
expectType<Equal<ParamsOf<'ptr <BYTE>ptr -> i32'>[0], never>>()
|
|
164
|
+
expectType<Equal<ParamsOf<'i3z <BYTE>ptr -> i32'>[0], never>>()
|
|
165
|
+
expectType<Equal<ParamsOf<'void <BYTE>ptr -> i32'>[0], never>>()
|
|
166
|
+
// '<>ptr' 空品牌 → MaybePtr<''>(= NULL|number):与 number 双向可赋值(identity 不作要求)。
|
|
167
|
+
expectType<Equal<ParamsOf<'<>ptr -> i32'>[0], MaybePtr<''>>>()
|
|
168
|
+
expectType<[ParamsOf<'<>ptr -> i32'>[0]] extends [number] ? true : false>()
|
|
169
|
+
expectType<[number] extends [ParamsOf<'<>ptr -> i32'>[0]] ? true : false>()
|
|
170
|
+
expectType<Equal<ParamsOf<'i32 -> i32'>[0], number>>()
|
|
171
|
+
// 别名机制已移除:typedef 名塌 never(与 'i3z' 同路径,见上)
|
|
172
|
+
expectType<Equal<ParamsOf<'DWORD -> i32'>[0], never>>()
|
|
173
|
+
expectType<Equal<ParamsOf<'u64 -> i32'>[0], bigint>>()
|
|
174
|
+
expectType<Equal<ParamsOf<'<BYTE>ptr -> i32'>[0], PtrArrayBuffer<any> | MaybePtr<"BYTE">>>()
|
|
175
|
+
expectType<Equal<ParamsOf<'<WCHAR>ptr -> i32'>[0], string | MaybePtr<"WCHAR">>>()
|
|
176
|
+
// 别名机制已移除:'LPCWSTR' 塌 never(字符串 codec 只挂在 <WCHAR>ptr 品牌位上)
|
|
177
|
+
expectType<Equal<ParamsOf<'LPCWSTR -> i32'>[0], never>>()
|
|
178
|
+
// 返回位非法 token 塌 unknown(never 是 bottom 在返回位被全放行,unknown 逼收窄;
|
|
179
|
+
// 与参数位塌 never 拦调用的不对称是设计)
|
|
180
|
+
expectType<Equal<RetOf<'<>ptr -> ptr'>, unknown>>()
|
|
181
|
+
expectType<Equal<RetOf<'<>ptr -> i3z'>, unknown>>()
|
|
182
|
+
expectType<Equal<RetOf<'<>ptr -> <BYTE>ptr'>, MaybePtr<"BYTE">>>()
|
|
183
|
+
expectType<Equal<RetOf<'<>ptr -> void'>, void>>()
|
|
184
|
+
expectType<Equal<RetOf<'<>ptr -> i32'>, number>>()
|
|
185
|
+
expectType<Equal<RetOf<'<>ptr -> <RECT>ptr'>, MaybePtr<'RECT'>>>()
|
|
186
|
+
|
|
187
|
+
t.section('bigint 64-bit kinds: u64 / i64')
|
|
188
|
+
// 用 msvcrt 的 64 位字符串转换:win11(x64) kernel32 不导出 Interlocked*64
|
|
189
|
+
//(编译器 intrinsic),msvcrt.dll 三平台必有。
|
|
190
|
+
const strtoui64 = bind('msvcrt.dll', '_strtoui64', '<BYTE>ptr <BYTE>ptr i32 -> u64')
|
|
191
|
+
const u64Max = strtoui64(strToBuf('18446744073709551615'), NULL, 10)
|
|
192
|
+
t.check('u64 return 2^64-1', 18446744073709551615n, u64Max)
|
|
193
|
+
const u64Prec = strtoui64(strToBuf('9007199254740993'), NULL, 10)
|
|
194
|
+
t.check('u64 return 2^53+1', 9007199254740993n, u64Prec)
|
|
195
|
+
|
|
196
|
+
const i64toa = bind('msvcrt.dll', '_i64toa', 'i64 <BYTE>ptr i32 -> <>ptr')
|
|
197
|
+
const outI = new PtrArrayBuffer(64)
|
|
198
|
+
i64toa(-9223372036854775808n, outI, 10)
|
|
199
|
+
t.check('i64 arg -2^63 toa', '-9223372036854775808', bufToString(outI))
|
|
200
|
+
i64toa(9007199254740993n, outI, 10)
|
|
201
|
+
t.check('i64 arg 2^53+1 toa', '9007199254740993', bufToString(outI))
|
|
202
|
+
|
|
203
|
+
const ui64toa = bind('msvcrt.dll', '_ui64toa', 'u64 <BYTE>ptr i32 -> <>ptr')
|
|
204
|
+
const outU = new PtrArrayBuffer(64)
|
|
205
|
+
ui64toa(18446744073709551615n, outU, 10)
|
|
206
|
+
t.check('u64 arg 2^64-1 toa', '18446744073709551615', bufToString(outU))
|
|
207
|
+
|
|
208
|
+
let errBig = ''
|
|
209
|
+
try {
|
|
210
|
+
(i64toa as unknown as (v: unknown, b: unknown, rad: unknown) => unknown)(123, outI, 10)
|
|
211
|
+
} catch (e) {
|
|
212
|
+
errBig = String(e)
|
|
213
|
+
}
|
|
214
|
+
t.checkTrue('i64 rejects number', errBig.includes('bigint'))
|
|
215
|
+
|
|
216
|
+
t.section('struct layout <NAME>ptr (explicit layouts; param encode + branded ptr return)')
|
|
217
|
+
// 内建 <BYTE>ptr/<WCHAR>ptr 无需 layouts;用户 struct 由 ffi-struct 的
|
|
218
|
+
// struct() 定义、显式传入(import 谁传谁,未用布局可被 tree-shake)。
|
|
219
|
+
const LOGBRUSH = struct('LOGBRUSH', {
|
|
220
|
+
lbStyle: 'u32',
|
|
221
|
+
lbColor: 'u32',
|
|
222
|
+
lbHatch: '<>ptr',
|
|
223
|
+
})
|
|
224
|
+
const deleteObject = bind('gdi32.dll', 'DeleteObject', '<>ptr -> i32')
|
|
225
|
+
const createBrushIndirect = bind('gdi32.dll', 'CreateBrushIndirect', '<LOGBRUSH>ptr -> <>ptr', { LOGBRUSH })
|
|
226
|
+
const brush = createBrushIndirect({ lbStyle: 0, lbColor: 0x00ff0000, lbHatch: 0 })
|
|
227
|
+
t.checkTrue('CreateBrushIndirect(<LOGBRUSH>ptr) returns HBRUSH', !!brush)
|
|
228
|
+
if (brush) t.checkTrue('DeleteObject(HBRUSH) succeeds', deleteObject(brush) !== 0)
|
|
229
|
+
|
|
230
|
+
// bindLib 用户布局 encoder 直传 struct 对象(bindLib 的 L 泛型曾未接线:
|
|
231
|
+
// encoders?: EncoderMap 使 L 恒为 {},<LOGBRUSH>ptr 实参被类型层收窄成裸 number(丢失布局形))
|
|
232
|
+
const gdi32Lib = bindLib('gdi32.dll', {
|
|
233
|
+
CreateBrushIndirect: '<LOGBRUSH>ptr -> <>ptr',
|
|
234
|
+
DeleteObject: '<>ptr -> i32',
|
|
235
|
+
}, { LOGBRUSH })
|
|
236
|
+
const brushL = gdi32Lib.CreateBrushIndirect({ lbStyle: 0, lbColor: 0x0000ff00, lbHatch: 0 })
|
|
237
|
+
t.checkTrue('bindLib(<LOGBRUSH>ptr w/ encoder) returns HBRUSH', !!brushL)
|
|
238
|
+
if (brushL) t.checkTrue('bindLib DeleteObject succeeds', gdi32Lib.DeleteObject(brushL) !== 0)
|
|
239
|
+
|
|
240
|
+
// 嵌套 struct(LOGPEN 内含 POINT)验证 ShapeOf 递归 + 子结构写入
|
|
241
|
+
const POINT = struct('POINT', {
|
|
242
|
+
x: 'i32',
|
|
243
|
+
y: 'i32',
|
|
244
|
+
})
|
|
245
|
+
const LOGPEN = struct('LOGPEN', {
|
|
246
|
+
lopnStyle: 'u32',
|
|
247
|
+
lopnWidth: POINT.__struct,
|
|
248
|
+
lopnColor: 'u32',
|
|
249
|
+
})
|
|
250
|
+
const createPenIndirect = bind('gdi32.dll', 'CreatePenIndirect', '<LOGPEN>ptr -> <>ptr', { LOGPEN })
|
|
251
|
+
const pen = createPenIndirect({ lopnStyle: 0, lopnWidth: { x: 1, y: 1 }, lopnColor: 0x000000ff })
|
|
252
|
+
t.checkTrue('CreatePenIndirect(<LOGPEN>ptr nested) returns HPEN', !!pen)
|
|
253
|
+
if (pen) t.checkTrue('DeleteObject(HPEN) succeeds', deleteObject(pen) !== 0)
|
|
254
|
+
|
|
255
|
+
// 别名机制已移除:normToken 对 typedef 名 fail-loud(动态签名串的运行时防线)
|
|
256
|
+
try { normToken('LPCWSTR'); t.checkTrue('normToken 拒别名 LPCWSTR', false) }
|
|
257
|
+
catch (e) { t.checkTrue('normToken 拒别名 LPCWSTR', String(e).includes('LPCWSTR')) }
|
|
258
|
+
|
|
259
|
+
// 未知布局:延迟解析 —— bind 期不再抛;仅当真的收到「结构形」实参时才报错,
|
|
260
|
+
// 并列出内建 + 已传入的可选项。纯 number(NULL / 地址)透传不需要布局。
|
|
261
|
+
const deleteObjectLazy = bind('gdi32.dll', 'DeleteObject', '<NOPE>ptr -> i32')
|
|
262
|
+
t.checkTrue('unknown layout <NOPE>ptr bound lazily (no bind-time throw)', typeof deleteObjectLazy === 'function')
|
|
263
|
+
t.checkTrue('<NOPE>ptr accepts NULL without layout (passthrough)', deleteObjectLazy(NULL) === 0)
|
|
264
|
+
let errLayout = ''
|
|
265
|
+
const nopeBrush = bind('gdi32.dll', 'CreateBrushIndirect', '<NOPE>ptr -> <>ptr') as unknown as (v: unknown) => number
|
|
266
|
+
try {
|
|
267
|
+
nopeBrush({})
|
|
268
|
+
} catch (e) {
|
|
269
|
+
errLayout = String(e)
|
|
270
|
+
}
|
|
271
|
+
t.checkTrue('unknown layout <NOPE>ptr rejected on struct-shape call', errLayout.includes('unknown layout') && errLayout.includes('BYTE'))
|
|
272
|
+
|
|
273
|
+
// out 参数:命名 struct 的 encode() 新建零 buffer 返回 PtrArrayBuffer,.ptr 即 Ptr<'RECT'> 直接喂
|
|
274
|
+
// <RECT>ptr;读回走 RECT.decode(buf)(双态入参的 ArrayBuffer 分支)。
|
|
275
|
+
const RECT = struct('RECT', {
|
|
276
|
+
left: 'i32',
|
|
277
|
+
top: 'i32',
|
|
278
|
+
right: 'i32',
|
|
279
|
+
bottom: 'i32',
|
|
280
|
+
})
|
|
281
|
+
const getWindowRect = bind('user32.dll', 'GetWindowRect', '<>ptr <RECT>ptr -> i32', { RECT })
|
|
282
|
+
const getDesktopWindow = bind('user32.dll', 'GetDesktopWindow', ' -> <>ptr')
|
|
283
|
+
const desktop = getDesktopWindow()
|
|
284
|
+
const rectOut = RECT.alloc()
|
|
285
|
+
t.checkTrue('GetWindowRect(hwnd, RECT.alloc().ptr) succeeds', getWindowRect(desktop, rectOut.ptr) !== 0)
|
|
286
|
+
const rectV = RECT.decode(rectOut)
|
|
287
|
+
t.checkTrue('RECT.decode(encode()) decodes out-param', rectV.right > rectV.left && rectV.bottom > rectV.top)
|
|
288
|
+
|
|
289
|
+
// 成员 '<RECT>ptr':decode 得到 Ptr<'RECT'>,可直接喂 <RECT>ptr 形参(品牌在类型层流动)。
|
|
290
|
+
const RECTPTR = struct('RECTPTR', {r: '<RECT>ptr'})
|
|
291
|
+
const box = RECTPTR.decode(RECTPTR.encode({ r: rectOut.ptr }))
|
|
292
|
+
t.checkTrue('GetWindowRect(hwnd, member RECT*) succeeds', box.r !== 0 && getWindowRect(desktop, box.r) !== 0)
|
|
293
|
+
const rectThroughMember = RECT.decode(rectOut)
|
|
294
|
+
t.checkTrue('writes through the member pointer', rectThroughMember.right > rectThroughMember.left && rectThroughMember.bottom > rectThroughMember.top)
|
|
295
|
+
|
|
296
|
+
t.section('struct ptr return + branded passthrough')
|
|
297
|
+
// 返回位 <NAME>ptr → MaybePtr<NAME>(NULL|Ptr,0 保真,只读指针,品牌在类型层)。
|
|
298
|
+
// 该品牌指针可直接喂给另一函数的 <NAME>ptr 形参(裸地址透传,不重编码)。
|
|
299
|
+
// 链条:localtime(&t) 返回 struct tm* → asctime(tm*) 消费之。
|
|
300
|
+
const TM = struct('TM', {
|
|
301
|
+
tm_sec: 'i32',
|
|
302
|
+
tm_min: 'i32',
|
|
303
|
+
tm_hour: 'i32',
|
|
304
|
+
tm_mday: 'i32',
|
|
305
|
+
tm_mon: 'i32',
|
|
306
|
+
tm_year: 'i32',
|
|
307
|
+
tm_wday: 'i32',
|
|
308
|
+
tm_yday: 'i32',
|
|
309
|
+
tm_isdst: 'i32',
|
|
310
|
+
})
|
|
311
|
+
const localtime = bind('msvcrt.dll', 'localtime', '<BYTE>ptr -> <TM>ptr')
|
|
312
|
+
const asctime = bind('msvcrt.dll', 'asctime', '<TM>ptr -> <>ptr', { TM })
|
|
313
|
+
const tbuf = new PtrArrayBuffer(8) // time_t=0(32/64 位 time_t 都读起始字节)
|
|
314
|
+
const tm = localtime(tbuf)
|
|
315
|
+
t.checkTrue('localtime -> <TM>ptr returns branded pointer', tm !== 0)
|
|
316
|
+
const asc = tm !== 0 ? readCStr(asctime(tm)) : ''
|
|
317
|
+
std.printf(' asctime(localtime(0)) = %s', asc.replace(/\n$/, ''))
|
|
318
|
+
t.checkTrue('asctime(<TM>ptr) accepts branded pointer (passthrough)', asc.includes(':') && asc.length >= 20)
|
|
319
|
+
|
|
320
|
+
// 手动解 native 拥有的 <TM>ptr:def.decode 双态入参的指针分支(0 守卫在调用方)
|
|
321
|
+
const tmDecoded = tm !== 0 ? TM.decode(tm) : null
|
|
322
|
+
t.checkTrue('TM.decode(<TM>ptr) decodes native-owned struct', tmDecoded !== null && tmDecoded.tm_sec === 0)
|
|
323
|
+
|
|
324
|
+
// 编译期:裸 number 不是 Ptr<'TM'>,被类型层拒绝(此处永不执行)
|
|
325
|
+
if (false) {
|
|
326
|
+
// @ts-expect-error plain number is not assignable to Ptr<'TM'>
|
|
327
|
+
asctime(123)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Codec:decoders 显式传表 → 返回位自动解码为结构对象(替代 Ptr,空指针 0 保真)
|
|
331
|
+
const localtimeDec = bind('msvcrt.dll', 'localtime', '<BYTE>ptr -> <TM>ptr', undefined, { TM })
|
|
332
|
+
const tmObj = localtimeDec(tbuf)
|
|
333
|
+
t.checkTrue('localtime + decoders auto-decodes to struct object',
|
|
334
|
+
tmObj !== 0 && typeof tmObj.tm_sec === 'number')
|
|
335
|
+
// 类型层证明:解码结果非 0 分支上 tm_sec 字段可访问且为 number
|
|
336
|
+
// (返回若含 Ptr<'TM'> 分支——无 decode 的旧行为——此访问编译不过)
|
|
337
|
+
if (tmObj !== 0) expectType<Equal<typeof tmObj.tm_sec, number>>(true)
|
|
338
|
+
|
|
339
|
+
// Codec:返回位类型断言(decoders 显式 / 缺省回退 encoders)
|
|
340
|
+
// 断言「替换语义」两半:解码结果 = 结构对象 | 0(Ptr 品牌被替换掉);
|
|
341
|
+
// 无 decode 保留 0 | Ptr 品牌形态。
|
|
342
|
+
type RetOfDec<S extends string, D extends CodecMap> = ReturnType<ReturnType<typeof bind<S, {}, D>>>
|
|
343
|
+
type RetDecTM = RetOfDec<' -> <TM>ptr', { TM: typeof TM }>
|
|
344
|
+
type RetEncTM = ReturnType<ReturnType<typeof bind<' -> <TM>ptr', { TM: typeof TM }>>>
|
|
345
|
+
expectType<Equal<Extract<RetDecTM, Ptr<'TM'>>, never>>(true)
|
|
346
|
+
expectType<Equal<Extract<RetDecTM, { tm_sec: number }> extends never ? false : true, true>>(true)
|
|
347
|
+
expectType<Equal<Extract<RetEncTM, Ptr<'TM'>>, never>>(true)
|
|
348
|
+
expectType<Equal<Extract<RetEncTM, { tm_sec: number }> extends never ? false : true, true>>(true)
|
|
349
|
+
|
|
350
|
+
// 内建 WCHAR codec:无条件进两张表 → <WCHAR>ptr 返回直接 string(空 0 保真)
|
|
351
|
+
type RetW = ReturnType<ReturnType<typeof bind<' -> <WCHAR>ptr'>>>
|
|
352
|
+
expectType<Equal<RetW, string | NULL>>(true)
|
|
353
|
+
const getCommandLineW = bind('kernel32.dll', 'GetCommandLineW', ' -> <WCHAR>ptr')
|
|
354
|
+
const cl = getCommandLineW()
|
|
355
|
+
t.checkTrue('GetCommandLineW auto-decodes builtin <WCHAR>ptr to string',
|
|
356
|
+
typeof cl === 'string' && cl.length > 0)
|
|
357
|
+
|
|
358
|
+
// WCHAR alloc/decode 往返:PtrArrayBuffer 预分配 + 手填 UTF-16LE + 双态读回
|
|
359
|
+
const walloc = WCHAR.alloc(8)
|
|
360
|
+
const wdv = new DataView(walloc)
|
|
361
|
+
wdv.setUint16(0, 'h'.charCodeAt(0), true)
|
|
362
|
+
wdv.setUint16(2, 'i'.charCodeAt(0), true)
|
|
363
|
+
wdv.setUint16(4, 0, true)
|
|
364
|
+
t.check('WCHAR.alloc(8) = 16 bytes', 16, walloc.byteLength)
|
|
365
|
+
t.check('WCHAR.decode(ptr) roundtrip', 'hi', WCHAR.decode(walloc.ptr))
|
|
366
|
+
t.check('WCHAR.decode(ArrayBuffer) roundtrip', 'hi', WCHAR.decode(walloc))
|
|
367
|
+
t.check('BYTE.alloc(12) = 12 bytes', 12, BYTE.alloc(12).byteLength)
|
|
368
|
+
|
|
369
|
+
// out-only codec(只声明 decode)走入参位 → 编码期 fail-fast(不触 native 调用)
|
|
370
|
+
const asctimeOutOnly = bind('msvcrt.dll', 'asctime', '<TM>ptr -> <>ptr',
|
|
371
|
+
{ TM: { decode: (p: number) => p } })
|
|
372
|
+
let outOnlyErr = ''
|
|
373
|
+
try {
|
|
374
|
+
asctimeOutOnly({} as never) // 类型层 Ptr|null;运行时喂对象进 encoder 分支
|
|
375
|
+
} catch (e) {
|
|
376
|
+
outOnlyErr = String(e)
|
|
377
|
+
}
|
|
378
|
+
t.checkTrue('out-only codec rejected at encode time', outOnlyErr.includes('no encoder'))
|
|
379
|
+
|
|
380
|
+
t.section('closures: direct ABI drive via ffiCall')
|
|
381
|
+
const addClos = closure('i32 i32 -> i32', (a, b) => a + b)
|
|
382
|
+
const r1 = new ArrayBuffer(8)
|
|
383
|
+
ffi.ffiCall(addClos.ptr, packArgs(['i32', 'i32'], [5, 7]), r1, 0)
|
|
384
|
+
t.check('closure i32+i32 = 12', 12, new DataView(r1).getInt32(0, true))
|
|
385
|
+
addClos.dispose()
|
|
386
|
+
|
|
387
|
+
const ptrClos = closure('<>ptr i32 -> i32', (p, n) => p + n)
|
|
388
|
+
const r2 = new ArrayBuffer(8)
|
|
389
|
+
ffi.ffiCall(ptrClos.ptr, packArgs(['ptr', 'i32'], [0x1234, 100]), r2, 0)
|
|
390
|
+
t.check('closure ptr+i32 decode', 0x1234 + 100, new DataView(r2).getInt32(0, true))
|
|
391
|
+
ptrClos.dispose()
|
|
392
|
+
|
|
393
|
+
const f64Clos = closure('f64 f64 -> f64', (a, b) => a * b)
|
|
394
|
+
const r3 = new ArrayBuffer(8)
|
|
395
|
+
ffi.ffiCall(f64Clos.ptr, packArgs(['f64', 'f64'], [2.5, 4]), r3, 1)
|
|
396
|
+
t.check('closure f64 mul = 10', 10, new DataView(r3).getFloat64(0, true))
|
|
397
|
+
f64Clos.dispose()
|
|
398
|
+
|
|
399
|
+
const bigClos = closure('i64 u64 -> i32', (a, b) =>
|
|
400
|
+
(a === 9007199254740993n && b === 18446744073709551615n) ? 1 : 0)
|
|
401
|
+
const r4 = new ArrayBuffer(8)
|
|
402
|
+
ffi.ffiCall(bigClos.ptr, packArgs(['i64', 'u64'], [9007199254740993n, 18446744073709551615n]), r4, 0)
|
|
403
|
+
t.check('closure bigint args exact (2^53+1 / 2^64-1)', 1, new DataView(r4).getInt32(0, true))
|
|
404
|
+
bigClos.dispose()
|
|
405
|
+
|
|
406
|
+
// 64 位整数返回:ia32 走 EDX:EAX、x64 走 RAX。低/高 32 位都能被断言到,
|
|
407
|
+
// 因为 ffiCall 的整数路径(quickjs-ffi-call-ia32.S .Lint_ret)把两半都写进 out。
|
|
408
|
+
const u64Clos = closure('i32 -> u64', (n) => (n === 42) ? 0x0000000700000001n : 0n)
|
|
409
|
+
const r5 = new ArrayBuffer(8)
|
|
410
|
+
ffi.ffiCall(u64Clos.ptr, packArgs(['i32'], [42]), r5, 0)
|
|
411
|
+
t.check('closure u64 return exact (hi=7 lo=1)', 0x0000000700000001n,
|
|
412
|
+
new DataView(r5).getBigUint64(0, true))
|
|
413
|
+
u64Clos.dispose()
|
|
414
|
+
|
|
415
|
+
const i64Clos = closure('i32 -> i64', (n) => (n === 1) ? -0x7FFFFFFFFFFFFFFFn : 0n)
|
|
416
|
+
const r6 = new ArrayBuffer(8)
|
|
417
|
+
ffi.ffiCall(i64Clos.ptr, packArgs(['i32'], [1]), r6, 0)
|
|
418
|
+
t.check('closure i64 return exact (-2^63+1)', -0x7FFFFFFFFFFFFFFFn,
|
|
419
|
+
new DataView(r6).getBigInt64(0, true))
|
|
420
|
+
i64Clos.dispose()
|
|
421
|
+
|
|
422
|
+
t.section('closure arg capture limit(C 捕获窗 fail-fast + 边界 roundtrip)')
|
|
423
|
+
{
|
|
424
|
+
// 边界内:16 参(x64=4 寄存器+12 栈槽;ia32=16×4B=64B 恰好)创建并往返
|
|
425
|
+
const sig16 = Array(16).fill('u32').join(' ') + ' -> void'
|
|
426
|
+
let got: unknown[] = []
|
|
427
|
+
let c16: { ptr: number; dispose(): void } | null = null
|
|
428
|
+
try {
|
|
429
|
+
c16 = closure(sig16 as any, ((...a: unknown[]) => { got = a }) as any)
|
|
430
|
+
} catch { /* 失败落到下面的 check */ }
|
|
431
|
+
t.checkTrue('16×u32 创建成功', c16 !== null)
|
|
432
|
+
if (c16) {
|
|
433
|
+
const vals = Array.from({ length: 16 }, (_, i) => i + 1)
|
|
434
|
+
ffi.ffiCall(c16.ptr, packArgs(Array(16).fill('u32'), vals), new ArrayBuffer(8), 0)
|
|
435
|
+
t.check('16 参完整送达', 16, got.length)
|
|
436
|
+
t.check('第 16 参(栈尾槽)值正确', 16, got[15])
|
|
437
|
+
c16.dispose()
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// 超限:17×u32 → x64 17>16、ia32 68>64,双架构创建期同抛
|
|
441
|
+
const sig17 = Array(17).fill('u32').join(' ') + ' -> void'
|
|
442
|
+
let err17 = ''
|
|
443
|
+
try {
|
|
444
|
+
closure(sig17 as any, (() => { }) as any)
|
|
445
|
+
} catch (e) { err17 = String(e) }
|
|
446
|
+
t.checkTrue('17×u32 创建期抛错(fail-fast 而非静默垃圾)', err17.includes('capture window'))
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
t.section('closures: ! token 0 检查(dispatch 侧,与 callPacked 同语义)')
|
|
450
|
+
{
|
|
451
|
+
// 参数位:签名承诺 C 传非空,喂 0 → 跳过回调(fn 不被调用)+ 结果槽 0
|
|
452
|
+
let argCalled = false
|
|
453
|
+
const nnArg = closure('<>ptr! -> i32', (_p) => { argCalled = true; return 7 })
|
|
454
|
+
const rr1 = new ArrayBuffer(8)
|
|
455
|
+
ffi.ffiCall(nnArg.ptr, packArgs(['ptr'], [0]), rr1, 0)
|
|
456
|
+
t.checkTrue('closure <>ptr! 喂 0:回调被跳过(fn 未执行)', !argCalled)
|
|
457
|
+
t.check('closure <>ptr! 喂 0:结果槽保持 0', 0, new DataView(rr1).getInt32(0, true))
|
|
458
|
+
const rr2 = new ArrayBuffer(8)
|
|
459
|
+
ffi.ffiCall(nnArg.ptr, packArgs(['ptr'], [0x2000]), rr2, 0)
|
|
460
|
+
t.checkTrue('closure <>ptr! 非空:回调执行', argCalled)
|
|
461
|
+
t.check('closure <>ptr! 非空:返回值送达', 7, new DataView(rr2).getInt32(0, true))
|
|
462
|
+
nnArg.dispose()
|
|
463
|
+
|
|
464
|
+
// 返回位正例:fn 返回非零 → 检查通过、正常写槽
|
|
465
|
+
const nnRet = closure('i32 -> <>ptr!', (_n) => 0x3000)
|
|
466
|
+
const rr3 = new ArrayBuffer(8)
|
|
467
|
+
ffi.ffiCall(nnRet.ptr, packArgs(['i32'], [1]), rr3, 0)
|
|
468
|
+
t.check('closure 返回 ! 非零:正常写槽', 0x3000, readScalar(new DataView(rr3), 0, 'ptr'))
|
|
469
|
+
nnRet.dispose()
|
|
470
|
+
// 返回位负例:fn 返回 0 → printf 诊断 + 不写槽;C 侧 retbuf 初值恒零
|
|
471
|
+
// (quickjs-ffi-closure.c zero8 拷贝),终值 0 与「写了 0」不可分——
|
|
472
|
+
// 值层面只验分发不炸、槽保持 0(诊断走 stdout,见上方 printf)。
|
|
473
|
+
const nnRet0 = closure('i32 -> <>ptr!', (_n) => 0)
|
|
474
|
+
const rr4 = new ArrayBuffer(8)
|
|
475
|
+
ffi.ffiCall(nnRet0.ptr, packArgs(['i32'], [1]), rr4, 0)
|
|
476
|
+
t.check('closure 返回 ! 违约:分发不炸、槽 0', 0, readScalar(new DataView(rr4), 0, 'ptr'))
|
|
477
|
+
nnRet0.dispose()
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
t.section('closures: EnumWindows (stdcall, end-to-end)')
|
|
481
|
+
const enumWindows = bind('user32.dll', 'EnumWindows', '<>ptr <>ptr -> i32')
|
|
482
|
+
let wcount = 0
|
|
483
|
+
let wLp: number | null = null
|
|
484
|
+
const enumClos = closure('<>ptr <>ptr -> i32', (_hwnd, lParam) => { wcount++; wLp = lParam; return 1 })
|
|
485
|
+
const eok = enumWindows(enumClos.ptr, 0x5A5A)
|
|
486
|
+
t.checkTrue('EnumWindows succeeds', eok !== 0)
|
|
487
|
+
t.checkTrue('EnumWindows callback fired', wcount > 0)
|
|
488
|
+
t.check('EnumWindows lParam passthrough', 0x5A5A, wLp)
|
|
489
|
+
enumClos.dispose()
|
|
490
|
+
|
|
491
|
+
t.section('closures: qsort (cdecl, msvcrt)')
|
|
492
|
+
const qsort = bind('msvcrt.dll', 'qsort', '<BYTE>ptr <>ptr <>ptr <>ptr -> void')
|
|
493
|
+
const arrB = new PtrArrayBuffer(16)
|
|
494
|
+
const arr = new Uint32Array(arrB)
|
|
495
|
+
arr.set([5, 3, 8, 1])
|
|
496
|
+
const readI32 = (p: number): number =>
|
|
497
|
+
(ffi.readByte(p) | (ffi.readByte(p + 1) << 8) | (ffi.readByte(p + 2) << 16) | (ffi.readByte(p + 3) << 24))
|
|
498
|
+
const cmp = closure('<>ptr <>ptr -> i32', (a, b) => readI32(a as number) - readI32(b as number), { stdcall: false })
|
|
499
|
+
qsort(arrB, 4, 4, cmp.ptr)
|
|
500
|
+
t.check('qsort [0]', 1, arr[0])
|
|
501
|
+
t.check('qsort [1]', 3, arr[1])
|
|
502
|
+
t.check('qsort [2]', 5, arr[2])
|
|
503
|
+
t.check('qsort [3]', 8, arr[3])
|
|
504
|
+
cmp.dispose()
|
|
505
|
+
|
|
506
|
+
t.section('ptr 位有符号读写(对齐 C 版 JS_NewInt64)')
|
|
507
|
+
const pdv = new DataView(new ArrayBuffer(8))
|
|
508
|
+
writeScalar(pdv, 0, { k: 'ptr', v: -1 })
|
|
509
|
+
if (PTR_SIZE === 8) t.check('-1 写入位模式(x64 二补位)', 18446744073709551615n, pdv.getBigUint64(0, true))
|
|
510
|
+
else t.check('-1 写入位模式(ia32 二补位)', 0xFFFFFFFF, pdv.getUint32(0, true))
|
|
511
|
+
t.check('负值往返 write(-1) → read = -1', -1, readScalar(pdv, 0, 'ptr'))
|
|
512
|
+
writeScalar(pdv, 0, { k: 'ptr', v: 0 })
|
|
513
|
+
t.check('0 保真(原 0 归一为 null)', 0, readScalar(pdv, 0, 'ptr'))
|
|
514
|
+
writeScalar(pdv, 0, { k: 'ptr', v: 0x7FFFFFFF })
|
|
515
|
+
t.check('正哨兵往返', 0x7FFFFFFF, readScalar(pdv, 0, 'ptr'))
|
|
516
|
+
|
|
517
|
+
t.section('ptr! non-null token: 运行时 0 检查(写槽前 / 返回后)')
|
|
518
|
+
const dcScreen = bind('user32.dll', 'GetDC', '<>ptr -> <>ptr')(0)
|
|
519
|
+
// 参数位:禁 NULL 且传 0 → 写槽前当场 throw,错误带签名上下文与位号
|
|
520
|
+
try {
|
|
521
|
+
bind('user32.dll', 'GetDC', '<>ptr! -> <>ptr')(0)
|
|
522
|
+
t.checkTrue('GetDC(0) 参数位标 ! 后应 throw', false)
|
|
523
|
+
} catch (e) {
|
|
524
|
+
t.checkTrue('! 参数位 throw 带签名上下文', String(e).includes('<>ptr!'))
|
|
525
|
+
t.checkTrue('! 参数位 throw 带位号', String(e).includes('arg#1'))
|
|
526
|
+
}
|
|
527
|
+
// 正例:非零值照传、ABI 不受修饰影响
|
|
528
|
+
const drawText = bind('user32.dll', 'DrawTextW', '<>ptr! <WCHAR>ptr i32 <BYTE>ptr i32 -> i32')
|
|
529
|
+
const dtRect = new PtrArrayBuffer(16)
|
|
530
|
+
if (dcScreen) {
|
|
531
|
+
t.checkTrue('DrawTextW 非空 DC 通过 ! 参数位',
|
|
532
|
+
drawText(dcScreen, 'hi !', -1, dtRect, gui.DrawTextFlag.CALCRECT) > 0)
|
|
533
|
+
try {
|
|
534
|
+
drawText(0, 'hi !', -1, dtRect, gui.DrawTextFlag.CALCRECT)
|
|
535
|
+
t.checkTrue('DrawTextW NULL DC 标 ! 后应 throw', false)
|
|
536
|
+
} catch (e) { t.checkTrue('! 参数位(第 1 位)throw 指明 arg#1', String(e).includes('arg#1')) }
|
|
537
|
+
}
|
|
538
|
+
// 返回位:找不到必返 NULL → 标 ! 返回位当场 throw
|
|
539
|
+
try {
|
|
540
|
+
bind('user32.dll', 'FindWindowW', '<WCHAR>ptr <WCHAR>ptr -> <>ptr!')('No.Such.Class', 'No.Such.Title')
|
|
541
|
+
t.checkTrue('FindWindowW 返回 0 标 ! 后应 throw', false)
|
|
542
|
+
} catch (e) {
|
|
543
|
+
t.checkTrue('! 返回位 throw 带返回 token', String(e).includes('return <>ptr! got 0'))
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
t.section('token @Enum: 运行时归一(槽宽/ABI 只看底档)')
|
|
547
|
+
const glPlain = bind('kernel32.dll', 'GetLastError', ' -> u32')()
|
|
548
|
+
const glEnum = bind('kernel32.dll', 'GetLastError', ' -> u32@ErrorCode')()
|
|
549
|
+
const glUnknown = bind('kernel32.dll', 'GetLastError', ' -> u32@NopeEnum')()
|
|
550
|
+
t.check('返回位 @Enum 标注归一后取值一致', glPlain as number, glEnum as unknown)
|
|
551
|
+
t.check('返回位未知枚举名同样归一(不炸)', glPlain as number, glUnknown as unknown)
|
|
552
|
+
|
|
553
|
+
// 编译期:修饰对精确类型的影响(Equal 不成立即 tsc 报错)
|
|
554
|
+
expectType<Equal<ParamsOf<'<X>ptr i32 -> i32'>[0], MaybePtr<'X'>>>()
|
|
555
|
+
expectType<Equal<ParamsOf<'<X>ptr! i32 -> i32'>[0], Ptr<'X'>>>()
|
|
556
|
+
expectType<Equal<RetOf<'<>ptr -> <X>ptr'>, MaybePtr<'X'>>>()
|
|
557
|
+
expectType<Equal<RetOf<'<>ptr -> <X>ptr!'>, Ptr<'X'>>>()
|
|
558
|
+
expectType<Equal<RetOf<'i32 -> i32'>, number>>()
|
|
559
|
+
// @Enum 未知枚举名:参数位塌 never(fail-loud),返回位塌 unknown(never 会全放行)
|
|
560
|
+
expectType<Equal<ParamsOf<'i32@NopeEnum -> i32'>[0], never>>()
|
|
561
|
+
expectType<Equal<RetOf<'i32 -> i32@NopeEnum'>, unknown>>()
|
|
562
|
+
// 返回位已知枚举名:钉住 pattern 命中后 = EnumMap 值域(分支失配落 unknown 时此条转红)
|
|
563
|
+
expectType<Equal<RetOf<'i32 -> u32@ErrorCode'>, EnumMap['ErrorCode']>>()
|
|
564
|
+
|
|
565
|
+
// struct 字段 '!':EncodeIn 必填(省略 = 编译错)+ decode 0 断言(后置兜底)
|
|
566
|
+
t.section("struct 字段 '!': encode 必填 + decode 0 断言")
|
|
567
|
+
const S2 = struct('S2', { p: '<X>ptr', q: '<X>ptr!' })
|
|
568
|
+
S2.alloc() // 出参槽分配的无参语义在 alloc,不在 encode
|
|
569
|
+
try {
|
|
570
|
+
S2.decode(new PtrArrayBuffer(16)) // 全 0 槽(alloc 等价)→ '!' 字段读出 0 → throw
|
|
571
|
+
t.checkTrue('decode 对 ! 字段 0 断言 throw', false)
|
|
572
|
+
} catch (e) {
|
|
573
|
+
t.checkTrue('decode ! 字段 0 断言指明字段名', String(e).includes('"q"'))
|
|
574
|
+
}
|
|
575
|
+
const s2buf = new PtrArrayBuffer(16)
|
|
576
|
+
new DataView(s2buf).setUint32(S2.offsetOf('q'), 0x1234, true) // 填非零 q(偏移动态,双架构)
|
|
577
|
+
const d2 = S2.decode(s2buf)
|
|
578
|
+
expectType<Equal<typeof d2.p, MaybePtr<'X'>>>()
|
|
579
|
+
expectType<Equal<typeof d2.q, Ptr<'X'>>>()
|
|
580
|
+
S2.encode(d2)
|
|
581
|
+
// @ts-expect-error EncodeIn:'!' 字段 q 必填 —— 省略即编译错,不静默写 0
|
|
582
|
+
S2.encode({ p: NULL })
|
|
583
|
+
|
|
584
|
+
// 编译期:@Enum 值域 —— 枚举成员与域内字面量放行,宽 number / 异种枚举拒绝
|
|
585
|
+
if (false) {
|
|
586
|
+
const sw = bind('user32.dll', 'ShowWindow', '<X>ptr i32@ShowWindowCmd -> i32')
|
|
587
|
+
const sp = bind('user32.dll', 'SetWindowPos', '<X>ptr <>ptr@SetWindowPosHwnd i32 i32 i32 i32 u32 -> i32')
|
|
588
|
+
sw(NULL, gui.ShowWindowCmd.SHOW) // 枚举成员
|
|
589
|
+
sw(NULL, 5) // 域内字面量(5 = SW_SHOW)
|
|
590
|
+
sp(NULL, gui.SetWindowPosHwnd.TOP, 0, 0, 0, 0, gui.SetWindowPosFlag.SWP_NOSIZE)
|
|
591
|
+
// @ts-expect-error 域外字面量被拦(11152 不是任何 ShowWindow 命令)
|
|
592
|
+
sw(NULL, 0x2B90)
|
|
593
|
+
// @ts-expect-error 跨枚举误用:HWND_TOP/TOPMOST 不是 ShowWindow 命令
|
|
594
|
+
sw(NULL, gui.SetWindowPosHwnd.TOP)
|
|
595
|
+
}
|
|
45
596
|
},
|
|
46
597
|
}
|