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
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import * as std from 'std';
|
|
2
|
+
import * as win from 'win';
|
|
3
|
+
import { bind } from '../lib/ffi/bind.js';
|
|
4
|
+
import { NULL, PtrArrayBuffer } from '../lib/ffi/ctype.js';
|
|
5
|
+
// ABI 边界回归:覆盖 REMOVE_LIBFFI_PLAN.md §4.2 的维度,全部用真实 Win32/CRT
|
|
6
|
+
// 函数(不新增测试专用导出)。缺函数的平台记 SKIP 而非 FAIL,避免环境差异误报。
|
|
7
|
+
//
|
|
8
|
+
// 维度 → 用例:
|
|
9
|
+
// 参数个数 0/1/3/4/5/6/8/12:GetTickCount/fabs/memcpy/GetLocaleInfoA/
|
|
10
|
+
// SetRect/CompareStringW/WideCharToMultiByte/SetDIBitsToDevice
|
|
11
|
+
// 整浮混合:ldexp(f64,i32)、frexp(f64,ptr)、modf(f64,ptr)
|
|
12
|
+
// 前 4 全整数(RCX-R9):GetLocaleInfoA 的 4 个参数
|
|
13
|
+
// 负数返回 i32/i16/i8:lstrcmpW;i64:InterlockedIncrement64
|
|
14
|
+
// 窄整型负值传参:abs(i8/i16/u8/u16)
|
|
15
|
+
// 大 64 位传参:InterlockedExchange64
|
|
16
|
+
// 指针 NULL:GetDC(NULL)、IsWindow(NULL);null/undefined 不是指针合法值,应拒绝
|
|
17
|
+
function readAscii(buf, len) {
|
|
18
|
+
const dv = new DataView(buf);
|
|
19
|
+
let s = '';
|
|
20
|
+
for (let i = 0; i < len; i++) {
|
|
21
|
+
const c = dv.getUint8(i);
|
|
22
|
+
if (c === 0)
|
|
23
|
+
break;
|
|
24
|
+
s += String.fromCharCode(c);
|
|
25
|
+
}
|
|
26
|
+
return s;
|
|
27
|
+
}
|
|
28
|
+
function has(dll, name) {
|
|
29
|
+
return !!dll && !!win.GetProcAddress(dll, name);
|
|
30
|
+
}
|
|
31
|
+
export const suite = {
|
|
32
|
+
name: 'ffi-abi',
|
|
33
|
+
run: (t) => {
|
|
34
|
+
const k32 = win.LoadLibrary('kernel32.dll');
|
|
35
|
+
const usr = win.LoadLibrary('user32.dll');
|
|
36
|
+
const gdi = win.LoadLibrary('gdi32.dll');
|
|
37
|
+
const crt = win.LoadLibrary('msvcrt.dll');
|
|
38
|
+
t.section('param count 0 ( -> u32)');
|
|
39
|
+
if (has(k32, 'GetTickCount')) {
|
|
40
|
+
const getTickCount = bind('kernel32.dll', 'GetTickCount', ' -> u32');
|
|
41
|
+
const tc = getTickCount();
|
|
42
|
+
t.checkTrue('GetTickCount() > 0', tc > 0);
|
|
43
|
+
}
|
|
44
|
+
else
|
|
45
|
+
t.skipCase('GetTickCount missing');
|
|
46
|
+
t.section('param count 1 (f64 -> f64)');
|
|
47
|
+
if (has(crt, 'fabs')) {
|
|
48
|
+
const fabs = bind('msvcrt.dll', 'fabs', 'f64 -> f64');
|
|
49
|
+
t.check('fabs(-3.5)', 3.5, fabs(-3.5));
|
|
50
|
+
}
|
|
51
|
+
else
|
|
52
|
+
t.skipCase('fabs missing');
|
|
53
|
+
t.section('param count 3 (<BYTE>ptr <BYTE>ptr u32 -> ptr)');
|
|
54
|
+
if (has(crt, 'memcpy')) {
|
|
55
|
+
const memcpy = bind('msvcrt.dll', 'memcpy', '<BYTE>ptr <BYTE>ptr u32 -> <>ptr');
|
|
56
|
+
const srcB = new PtrArrayBuffer(8);
|
|
57
|
+
const src = new Uint8Array(srcB);
|
|
58
|
+
src.set([1, 2, 3, 4, 5, 6, 7, 8]);
|
|
59
|
+
const dstB = new PtrArrayBuffer(8);
|
|
60
|
+
const dst = new Uint8Array(dstB);
|
|
61
|
+
const r = memcpy(dstB, srcB, 8);
|
|
62
|
+
const rd = Array.from(dst).join(',');
|
|
63
|
+
t.check('memcpy copied bytes', '1,2,3,4,5,6,7,8', rd);
|
|
64
|
+
t.checkTrue('memcpy returns dest ptr', typeof r === 'number' && r !== 0);
|
|
65
|
+
}
|
|
66
|
+
else
|
|
67
|
+
t.skipCase('memcpy missing');
|
|
68
|
+
t.section('param count 4 + all-int-regs (u32 u32 <BYTE>ptr i32 -> i32)');
|
|
69
|
+
if (has(k32, 'GetLocaleInfoA')) {
|
|
70
|
+
const getLocaleInfoA = bind('kernel32.dll', 'GetLocaleInfoA', 'u32 u32 <BYTE>ptr i32 -> i32');
|
|
71
|
+
const buf = new PtrArrayBuffer(64);
|
|
72
|
+
const n = getLocaleInfoA(0x0409, 0x00001001 /* LOCALE_SENGLANGUAGE */, buf, 64);
|
|
73
|
+
if (n > 0) {
|
|
74
|
+
const s = readAscii(buf, n);
|
|
75
|
+
std.printf(' GetLocaleInfoA(0x0409, LOCALE_SENGLANGUAGE) = "%s" (%d)\n', s, n);
|
|
76
|
+
t.check('en-US language name', 'English', s);
|
|
77
|
+
}
|
|
78
|
+
else
|
|
79
|
+
t.skipCase('en-US locale unavailable');
|
|
80
|
+
}
|
|
81
|
+
else
|
|
82
|
+
t.skipCase('GetLocaleInfoA missing');
|
|
83
|
+
t.section('param count 5 (<BYTE>ptr i32 i32 i32 i32 -> i32)');
|
|
84
|
+
if (has(usr, 'SetRect')) {
|
|
85
|
+
const setRect = bind('user32.dll', 'SetRect', '<BYTE>ptr i32 i32 i32 i32 -> i32');
|
|
86
|
+
const rc = new PtrArrayBuffer(16);
|
|
87
|
+
const ok = setRect(rc, 1, 2, 30, 40);
|
|
88
|
+
const dv = new DataView(rc);
|
|
89
|
+
t.checkTrue('SetRect returns nonzero', ok !== 0);
|
|
90
|
+
t.check('SetRect left', 1, dv.getInt32(0, true));
|
|
91
|
+
t.check('SetRect top', 2, dv.getInt32(4, true));
|
|
92
|
+
t.check('SetRect right', 30, dv.getInt32(8, true));
|
|
93
|
+
t.check('SetRect bottom', 40, dv.getInt32(12, true));
|
|
94
|
+
}
|
|
95
|
+
else
|
|
96
|
+
t.skipCase('SetRect missing');
|
|
97
|
+
t.section('param count 6 (u32 u32 <WCHAR>ptr i32 <WCHAR>ptr i32 -> i32)');
|
|
98
|
+
if (has(k32, 'CompareStringW')) {
|
|
99
|
+
const compareStringW = bind('kernel32.dll', 'CompareStringW', 'u32 u32 <WCHAR>ptr i32 <WCHAR>ptr i32 -> i32');
|
|
100
|
+
const r = compareStringW(0x0400 /* LOCALE_USER_DEFAULT */, 0, 'abc', -1, 'abd', -1);
|
|
101
|
+
t.check('CompareStringW("abc","abd") = CSTR_LESS_THAN', 1, r);
|
|
102
|
+
}
|
|
103
|
+
else
|
|
104
|
+
t.skipCase('CompareStringW missing');
|
|
105
|
+
t.section('param count 8 (u32 u32 <WCHAR>ptr i32 <BYTE>ptr i32 <>ptr <>ptr -> i32)');
|
|
106
|
+
if (has(k32, 'WideCharToMultiByte')) {
|
|
107
|
+
const wideCharToMultiByte = bind('kernel32.dll', 'WideCharToMultiByte', 'u32 u32 <WCHAR>ptr i32 <BYTE>ptr i32 <>ptr <>ptr -> i32');
|
|
108
|
+
const out = new PtrArrayBuffer(16);
|
|
109
|
+
// 显式给长度(cchWideChar=2)避免 -1 空终止语义下返回值是否含 '\0' 的版本差异。
|
|
110
|
+
const n = wideCharToMultiByte(0 /* CP_ACP */, 0, 'AB', 2, out, 16, NULL, NULL);
|
|
111
|
+
t.check('WideCharToMultiByte char count', 2, n);
|
|
112
|
+
t.check('converted byte 0 = A', 65, new DataView(out).getUint8(0));
|
|
113
|
+
t.check('converted byte 1 = B', 66, new DataView(out).getUint8(1));
|
|
114
|
+
}
|
|
115
|
+
else
|
|
116
|
+
t.skipCase('WideCharToMultiByte missing');
|
|
117
|
+
t.section('param count 12 (SetDIBitsToDevice on memory DC)');
|
|
118
|
+
if (has(gdi, 'SetDIBitsToDevice') && has(gdi, 'CreateCompatibleDC') && has(gdi, 'DeleteDC')) {
|
|
119
|
+
const createCompatibleDC = bind('gdi32.dll', 'CreateCompatibleDC', '<>ptr -> <>ptr');
|
|
120
|
+
const deleteDC = bind('gdi32.dll', 'DeleteDC', '<>ptr -> i32');
|
|
121
|
+
const setDIBitsToDevice = bind('gdi32.dll', 'SetDIBitsToDevice', '<>ptr i32 i32 u32 u32 i32 i32 u32 u32 <BYTE>ptr <BYTE>ptr u32 -> i32');
|
|
122
|
+
const hdc = createCompatibleDC(0);
|
|
123
|
+
if (hdc) {
|
|
124
|
+
const bmi = new PtrArrayBuffer(40);
|
|
125
|
+
const bdv = new DataView(bmi);
|
|
126
|
+
bdv.setUint32(0, 40, true); // biSize
|
|
127
|
+
bdv.setInt32(4, 1, true); // biWidth
|
|
128
|
+
bdv.setInt32(8, 1, true); // biHeight (bottom-up)
|
|
129
|
+
bdv.setUint16(12, 1, true); // biPlanes
|
|
130
|
+
bdv.setUint16(14, 32, true); // biBitCount
|
|
131
|
+
bdv.setUint32(16, 0, true); // biCompression = BI_RGB
|
|
132
|
+
const bits = new PtrArrayBuffer(4);
|
|
133
|
+
const lines = setDIBitsToDevice(hdc, 0, 0, 1, 1, 0, 0, 0, 1, bits, bmi, 0 /* DIB_RGB_COLORS */);
|
|
134
|
+
t.check('SetDIBitsToDevice scanlines copied', 1, lines);
|
|
135
|
+
deleteDC(hdc);
|
|
136
|
+
}
|
|
137
|
+
else
|
|
138
|
+
t.skipCase('CreateCompatibleDC returned NULL');
|
|
139
|
+
}
|
|
140
|
+
else
|
|
141
|
+
t.skipCase('SetDIBitsToDevice/CreateCompatibleDC missing');
|
|
142
|
+
t.section('int/float mix: f64 i32 -> f64');
|
|
143
|
+
if (has(crt, 'ldexp')) {
|
|
144
|
+
const ldexp = bind('msvcrt.dll', 'ldexp', 'f64 i32 -> f64');
|
|
145
|
+
t.check('ldexp(1.5, 3)', 12.0, ldexp(1.5, 3));
|
|
146
|
+
}
|
|
147
|
+
else
|
|
148
|
+
t.skipCase('ldexp missing');
|
|
149
|
+
t.section('int/float mix: f64 <BYTE>ptr -> f64');
|
|
150
|
+
if (has(crt, 'frexp')) {
|
|
151
|
+
const frexp = bind('msvcrt.dll', 'frexp', 'f64 <BYTE>ptr -> f64');
|
|
152
|
+
const e = new PtrArrayBuffer(4);
|
|
153
|
+
const m = frexp(12.0, e);
|
|
154
|
+
t.check('frexp(12).mantissa', 0.75, m);
|
|
155
|
+
t.check('frexp(12).exponent', 4, new DataView(e).getInt32(0, true));
|
|
156
|
+
}
|
|
157
|
+
else
|
|
158
|
+
t.skipCase('frexp missing');
|
|
159
|
+
if (has(crt, 'modf')) {
|
|
160
|
+
const modf = bind('msvcrt.dll', 'modf', 'f64 <BYTE>ptr -> f64');
|
|
161
|
+
const ip = new PtrArrayBuffer(8);
|
|
162
|
+
const frac = modf(3.75, ip);
|
|
163
|
+
t.check('modf(3.75).frac', 0.75, frac);
|
|
164
|
+
t.check('modf(3.75).int', 3.0, new DataView(ip).getFloat64(0, true));
|
|
165
|
+
}
|
|
166
|
+
else
|
|
167
|
+
t.skipCase('modf missing');
|
|
168
|
+
t.section('narrow signed args (msvcrt!abs)');
|
|
169
|
+
if (has(crt, 'abs')) {
|
|
170
|
+
const absI8 = bind('msvcrt.dll', 'abs', 'i8 -> i32');
|
|
171
|
+
const absU8 = bind('msvcrt.dll', 'abs', 'u8 -> i32');
|
|
172
|
+
const absI16 = bind('msvcrt.dll', 'abs', 'i16 -> i32');
|
|
173
|
+
const absU16 = bind('msvcrt.dll', 'abs', 'u16 -> i32');
|
|
174
|
+
t.check('abs(i8 -128)', 128, absI8(-128));
|
|
175
|
+
t.check('abs(i8 -5)', 5, absI8(-5));
|
|
176
|
+
t.check('abs(u8 200)', 200, absU8(200));
|
|
177
|
+
t.check('abs(i16 -300)', 300, absI16(-300));
|
|
178
|
+
t.check('abs(u16 60000)', 60000, absU16(60000));
|
|
179
|
+
}
|
|
180
|
+
else
|
|
181
|
+
t.skipCase('abs missing');
|
|
182
|
+
t.section('negative return i32/i16/i8 (lstrcmpW)');
|
|
183
|
+
if (has(k32, 'lstrcmpW')) {
|
|
184
|
+
const cmpI32 = bind('kernel32.dll', 'lstrcmpW', '<WCHAR>ptr <WCHAR>ptr -> i32');
|
|
185
|
+
const cmpI16 = bind('kernel32.dll', 'lstrcmpW', '<WCHAR>ptr <WCHAR>ptr -> i16');
|
|
186
|
+
const cmpI8 = bind('kernel32.dll', 'lstrcmpW', '<WCHAR>ptr <WCHAR>ptr -> i8');
|
|
187
|
+
t.checkTrue('lstrcmpW i32 < 0', cmpI32('a', 'b') < 0);
|
|
188
|
+
t.checkTrue('lstrcmpW i16 < 0', cmpI16('a', 'b') < 0);
|
|
189
|
+
t.checkTrue('lstrcmpW i8 < 0', cmpI8('a', 'b') < 0);
|
|
190
|
+
}
|
|
191
|
+
else
|
|
192
|
+
t.skipCase('lstrcmpW missing');
|
|
193
|
+
t.section('negative return i64 (InterlockedIncrement64)');
|
|
194
|
+
if (has(k32, 'InterlockedExchange64') && has(k32, 'InterlockedIncrement64')) {
|
|
195
|
+
const exchange64 = bind('kernel32.dll', 'InterlockedExchange64', '<BYTE>ptr i64 -> i64');
|
|
196
|
+
const increment64 = bind('kernel32.dll', 'InterlockedIncrement64', '<BYTE>ptr -> i64');
|
|
197
|
+
const cell = new PtrArrayBuffer(8);
|
|
198
|
+
exchange64(cell, -2n);
|
|
199
|
+
t.check('InterlockedIncrement64(-2)', -1n, increment64(cell));
|
|
200
|
+
}
|
|
201
|
+
else
|
|
202
|
+
t.skipCase('Interlocked*64 missing');
|
|
203
|
+
t.section('64-bit arg transport (InterlockedExchange64)');
|
|
204
|
+
if (has(k32, 'InterlockedExchange64')) {
|
|
205
|
+
const exchange64 = bind('kernel32.dll', 'InterlockedExchange64', '<BYTE>ptr i64 -> i64');
|
|
206
|
+
const cell = new PtrArrayBuffer(8);
|
|
207
|
+
const prev0 = exchange64(cell, 4294967297n); // 2^32+1:超过 32 位
|
|
208
|
+
const prev1 = exchange64(cell, -1n); // 64 位全 1
|
|
209
|
+
const prev2 = exchange64(cell, 0n);
|
|
210
|
+
t.check('exchange empty cell returns 0', 0n, prev0);
|
|
211
|
+
t.check('exchange returns 2^32+1', 4294967297n, prev1);
|
|
212
|
+
t.check('exchange returns -1', -1n, prev2);
|
|
213
|
+
}
|
|
214
|
+
else
|
|
215
|
+
t.skipCase('InterlockedExchange64 missing');
|
|
216
|
+
t.section('ptr NULL/undefined');
|
|
217
|
+
if (has(usr, 'GetDC') && has(usr, 'IsWindow')) {
|
|
218
|
+
const getDC = bind('user32.dll', 'GetDC', '<>ptr -> <>ptr');
|
|
219
|
+
const isWindow = bind('user32.dll', 'IsWindow', '<>ptr -> i32');
|
|
220
|
+
t.checkTrue('GetDC(NULL) non-null', !!getDC(NULL));
|
|
221
|
+
// undefined / null 不是指针合法值(指针只收 NULL 或有效地址),必须拒绝而非静默当 0。
|
|
222
|
+
let errUndef = '';
|
|
223
|
+
try {
|
|
224
|
+
getDC(undefined);
|
|
225
|
+
}
|
|
226
|
+
catch (e) {
|
|
227
|
+
errUndef = String(e);
|
|
228
|
+
}
|
|
229
|
+
t.checkTrue('GetDC(undefined) rejected', errUndef.includes('undefined'));
|
|
230
|
+
t.check('IsWindow(NULL) = FALSE', 0, isWindow(NULL));
|
|
231
|
+
}
|
|
232
|
+
else
|
|
233
|
+
t.skipCase('GetDC/IsWindow missing');
|
|
234
|
+
},
|
|
235
|
+
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import * as std from 'std'
|
|
2
|
+
import * as win from 'win'
|
|
3
|
+
import { bind } from '../lib/ffi/bind.js'
|
|
4
|
+
import { NULL, PtrArrayBuffer } from '../lib/ffi/ctype.js'
|
|
5
|
+
import { Tester } from './test_helper.js'
|
|
6
|
+
|
|
7
|
+
// ABI 边界回归:覆盖 REMOVE_LIBFFI_PLAN.md §4.2 的维度,全部用真实 Win32/CRT
|
|
8
|
+
// 函数(不新增测试专用导出)。缺函数的平台记 SKIP 而非 FAIL,避免环境差异误报。
|
|
9
|
+
//
|
|
10
|
+
// 维度 → 用例:
|
|
11
|
+
// 参数个数 0/1/3/4/5/6/8/12:GetTickCount/fabs/memcpy/GetLocaleInfoA/
|
|
12
|
+
// SetRect/CompareStringW/WideCharToMultiByte/SetDIBitsToDevice
|
|
13
|
+
// 整浮混合:ldexp(f64,i32)、frexp(f64,ptr)、modf(f64,ptr)
|
|
14
|
+
// 前 4 全整数(RCX-R9):GetLocaleInfoA 的 4 个参数
|
|
15
|
+
// 负数返回 i32/i16/i8:lstrcmpW;i64:InterlockedIncrement64
|
|
16
|
+
// 窄整型负值传参:abs(i8/i16/u8/u16)
|
|
17
|
+
// 大 64 位传参:InterlockedExchange64
|
|
18
|
+
// 指针 NULL:GetDC(NULL)、IsWindow(NULL);null/undefined 不是指针合法值,应拒绝
|
|
19
|
+
|
|
20
|
+
function readAscii(buf: ArrayBuffer, len: number): string {
|
|
21
|
+
const dv = new DataView(buf)
|
|
22
|
+
let s = ''
|
|
23
|
+
for (let i = 0; i < len; i++) {
|
|
24
|
+
const c = dv.getUint8(i)
|
|
25
|
+
if (c === 0) break
|
|
26
|
+
s += String.fromCharCode(c)
|
|
27
|
+
}
|
|
28
|
+
return s
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function has(dll: win.HMODULE | null, name: string): boolean {
|
|
32
|
+
return !!dll && !!win.GetProcAddress(dll, name)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const suite = {
|
|
36
|
+
name: 'ffi-abi',
|
|
37
|
+
run: (t: Tester) => {
|
|
38
|
+
const k32 = win.LoadLibrary('kernel32.dll')
|
|
39
|
+
const usr = win.LoadLibrary('user32.dll')
|
|
40
|
+
const gdi = win.LoadLibrary('gdi32.dll')
|
|
41
|
+
const crt = win.LoadLibrary('msvcrt.dll')
|
|
42
|
+
|
|
43
|
+
t.section('param count 0 ( -> u32)')
|
|
44
|
+
if (has(k32, 'GetTickCount')) {
|
|
45
|
+
const getTickCount = bind('kernel32.dll', 'GetTickCount', ' -> u32')
|
|
46
|
+
const tc = getTickCount()
|
|
47
|
+
t.checkTrue('GetTickCount() > 0', tc > 0)
|
|
48
|
+
} else t.skipCase('GetTickCount missing')
|
|
49
|
+
|
|
50
|
+
t.section('param count 1 (f64 -> f64)')
|
|
51
|
+
if (has(crt, 'fabs')) {
|
|
52
|
+
const fabs = bind('msvcrt.dll', 'fabs', 'f64 -> f64')
|
|
53
|
+
t.check('fabs(-3.5)', 3.5, fabs(-3.5))
|
|
54
|
+
} else t.skipCase('fabs missing')
|
|
55
|
+
|
|
56
|
+
t.section('param count 3 (<BYTE>ptr <BYTE>ptr u32 -> ptr)')
|
|
57
|
+
if (has(crt, 'memcpy')) {
|
|
58
|
+
const memcpy = bind('msvcrt.dll', 'memcpy', '<BYTE>ptr <BYTE>ptr u32 -> <>ptr')
|
|
59
|
+
const srcB = new PtrArrayBuffer(8)
|
|
60
|
+
const src = new Uint8Array(srcB)
|
|
61
|
+
src.set([1, 2, 3, 4, 5, 6, 7, 8])
|
|
62
|
+
const dstB = new PtrArrayBuffer(8)
|
|
63
|
+
const dst = new Uint8Array(dstB)
|
|
64
|
+
const r = memcpy(dstB, srcB, 8)
|
|
65
|
+
const rd = Array.from(dst).join(',')
|
|
66
|
+
t.check('memcpy copied bytes', '1,2,3,4,5,6,7,8', rd)
|
|
67
|
+
t.checkTrue('memcpy returns dest ptr', typeof r === 'number' && r !== 0)
|
|
68
|
+
} else t.skipCase('memcpy missing')
|
|
69
|
+
|
|
70
|
+
t.section('param count 4 + all-int-regs (u32 u32 <BYTE>ptr i32 -> i32)')
|
|
71
|
+
if (has(k32, 'GetLocaleInfoA')) {
|
|
72
|
+
const getLocaleInfoA = bind('kernel32.dll', 'GetLocaleInfoA', 'u32 u32 <BYTE>ptr i32 -> i32')
|
|
73
|
+
const buf = new PtrArrayBuffer(64)
|
|
74
|
+
const n = getLocaleInfoA(0x0409, 0x00001001 /* LOCALE_SENGLANGUAGE */, buf, 64)
|
|
75
|
+
if (n > 0) {
|
|
76
|
+
const s = readAscii(buf, n)
|
|
77
|
+
std.printf(' GetLocaleInfoA(0x0409, LOCALE_SENGLANGUAGE) = "%s" (%d)\n', s, n)
|
|
78
|
+
t.check('en-US language name', 'English', s)
|
|
79
|
+
} else t.skipCase('en-US locale unavailable')
|
|
80
|
+
} else t.skipCase('GetLocaleInfoA missing')
|
|
81
|
+
|
|
82
|
+
t.section('param count 5 (<BYTE>ptr i32 i32 i32 i32 -> i32)')
|
|
83
|
+
if (has(usr, 'SetRect')) {
|
|
84
|
+
const setRect = bind('user32.dll', 'SetRect', '<BYTE>ptr i32 i32 i32 i32 -> i32')
|
|
85
|
+
const rc = new PtrArrayBuffer(16)
|
|
86
|
+
const ok = setRect(rc, 1, 2, 30, 40)
|
|
87
|
+
const dv = new DataView(rc)
|
|
88
|
+
t.checkTrue('SetRect returns nonzero', ok !== 0)
|
|
89
|
+
t.check('SetRect left', 1, dv.getInt32(0, true))
|
|
90
|
+
t.check('SetRect top', 2, dv.getInt32(4, true))
|
|
91
|
+
t.check('SetRect right', 30, dv.getInt32(8, true))
|
|
92
|
+
t.check('SetRect bottom', 40, dv.getInt32(12, true))
|
|
93
|
+
} else t.skipCase('SetRect missing')
|
|
94
|
+
|
|
95
|
+
t.section('param count 6 (u32 u32 <WCHAR>ptr i32 <WCHAR>ptr i32 -> i32)')
|
|
96
|
+
if (has(k32, 'CompareStringW')) {
|
|
97
|
+
const compareStringW = bind('kernel32.dll', 'CompareStringW', 'u32 u32 <WCHAR>ptr i32 <WCHAR>ptr i32 -> i32')
|
|
98
|
+
const r = compareStringW(0x0400 /* LOCALE_USER_DEFAULT */, 0, 'abc', -1, 'abd', -1)
|
|
99
|
+
t.check('CompareStringW("abc","abd") = CSTR_LESS_THAN', 1, r)
|
|
100
|
+
} else t.skipCase('CompareStringW missing')
|
|
101
|
+
|
|
102
|
+
t.section('param count 8 (u32 u32 <WCHAR>ptr i32 <BYTE>ptr i32 <>ptr <>ptr -> i32)')
|
|
103
|
+
if (has(k32, 'WideCharToMultiByte')) {
|
|
104
|
+
const wideCharToMultiByte = bind('kernel32.dll', 'WideCharToMultiByte', 'u32 u32 <WCHAR>ptr i32 <BYTE>ptr i32 <>ptr <>ptr -> i32')
|
|
105
|
+
const out = new PtrArrayBuffer(16)
|
|
106
|
+
// 显式给长度(cchWideChar=2)避免 -1 空终止语义下返回值是否含 '\0' 的版本差异。
|
|
107
|
+
const n = wideCharToMultiByte(0 /* CP_ACP */, 0, 'AB', 2, out, 16, NULL, NULL)
|
|
108
|
+
t.check('WideCharToMultiByte char count', 2, n)
|
|
109
|
+
t.check('converted byte 0 = A', 65, new DataView(out).getUint8(0))
|
|
110
|
+
t.check('converted byte 1 = B', 66, new DataView(out).getUint8(1))
|
|
111
|
+
} else t.skipCase('WideCharToMultiByte missing')
|
|
112
|
+
|
|
113
|
+
t.section('param count 12 (SetDIBitsToDevice on memory DC)')
|
|
114
|
+
if (has(gdi, 'SetDIBitsToDevice') && has(gdi, 'CreateCompatibleDC') && has(gdi, 'DeleteDC')) {
|
|
115
|
+
const createCompatibleDC = bind('gdi32.dll', 'CreateCompatibleDC', '<>ptr -> <>ptr')
|
|
116
|
+
const deleteDC = bind('gdi32.dll', 'DeleteDC', '<>ptr -> i32')
|
|
117
|
+
const setDIBitsToDevice = bind('gdi32.dll', 'SetDIBitsToDevice',
|
|
118
|
+
'<>ptr i32 i32 u32 u32 i32 i32 u32 u32 <BYTE>ptr <BYTE>ptr u32 -> i32')
|
|
119
|
+
const hdc = createCompatibleDC(0)
|
|
120
|
+
if (hdc) {
|
|
121
|
+
const bmi = new PtrArrayBuffer(40)
|
|
122
|
+
const bdv = new DataView(bmi)
|
|
123
|
+
bdv.setUint32(0, 40, true) // biSize
|
|
124
|
+
bdv.setInt32(4, 1, true) // biWidth
|
|
125
|
+
bdv.setInt32(8, 1, true) // biHeight (bottom-up)
|
|
126
|
+
bdv.setUint16(12, 1, true) // biPlanes
|
|
127
|
+
bdv.setUint16(14, 32, true) // biBitCount
|
|
128
|
+
bdv.setUint32(16, 0, true) // biCompression = BI_RGB
|
|
129
|
+
const bits = new PtrArrayBuffer(4)
|
|
130
|
+
const lines = setDIBitsToDevice(hdc, 0, 0, 1, 1, 0, 0, 0, 1, bits, bmi, 0 /* DIB_RGB_COLORS */)
|
|
131
|
+
t.check('SetDIBitsToDevice scanlines copied', 1, lines)
|
|
132
|
+
deleteDC(hdc)
|
|
133
|
+
} else t.skipCase('CreateCompatibleDC returned NULL')
|
|
134
|
+
} else t.skipCase('SetDIBitsToDevice/CreateCompatibleDC missing')
|
|
135
|
+
|
|
136
|
+
t.section('int/float mix: f64 i32 -> f64')
|
|
137
|
+
if (has(crt, 'ldexp')) {
|
|
138
|
+
const ldexp = bind('msvcrt.dll', 'ldexp', 'f64 i32 -> f64')
|
|
139
|
+
t.check('ldexp(1.5, 3)', 12.0, ldexp(1.5, 3))
|
|
140
|
+
} else t.skipCase('ldexp missing')
|
|
141
|
+
|
|
142
|
+
t.section('int/float mix: f64 <BYTE>ptr -> f64')
|
|
143
|
+
if (has(crt, 'frexp')) {
|
|
144
|
+
const frexp = bind('msvcrt.dll', 'frexp', 'f64 <BYTE>ptr -> f64')
|
|
145
|
+
const e = new PtrArrayBuffer(4)
|
|
146
|
+
const m = frexp(12.0, e)
|
|
147
|
+
t.check('frexp(12).mantissa', 0.75, m)
|
|
148
|
+
t.check('frexp(12).exponent', 4, new DataView(e).getInt32(0, true))
|
|
149
|
+
} else t.skipCase('frexp missing')
|
|
150
|
+
if (has(crt, 'modf')) {
|
|
151
|
+
const modf = bind('msvcrt.dll', 'modf', 'f64 <BYTE>ptr -> f64')
|
|
152
|
+
const ip = new PtrArrayBuffer(8)
|
|
153
|
+
const frac = modf(3.75, ip)
|
|
154
|
+
t.check('modf(3.75).frac', 0.75, frac)
|
|
155
|
+
t.check('modf(3.75).int', 3.0, new DataView(ip).getFloat64(0, true))
|
|
156
|
+
} else t.skipCase('modf missing')
|
|
157
|
+
|
|
158
|
+
t.section('narrow signed args (msvcrt!abs)')
|
|
159
|
+
if (has(crt, 'abs')) {
|
|
160
|
+
const absI8 = bind('msvcrt.dll', 'abs', 'i8 -> i32')
|
|
161
|
+
const absU8 = bind('msvcrt.dll', 'abs', 'u8 -> i32')
|
|
162
|
+
const absI16 = bind('msvcrt.dll', 'abs', 'i16 -> i32')
|
|
163
|
+
const absU16 = bind('msvcrt.dll', 'abs', 'u16 -> i32')
|
|
164
|
+
t.check('abs(i8 -128)', 128, absI8(-128))
|
|
165
|
+
t.check('abs(i8 -5)', 5, absI8(-5))
|
|
166
|
+
t.check('abs(u8 200)', 200, absU8(200))
|
|
167
|
+
t.check('abs(i16 -300)', 300, absI16(-300))
|
|
168
|
+
t.check('abs(u16 60000)', 60000, absU16(60000))
|
|
169
|
+
} else t.skipCase('abs missing')
|
|
170
|
+
|
|
171
|
+
t.section('negative return i32/i16/i8 (lstrcmpW)')
|
|
172
|
+
if (has(k32, 'lstrcmpW')) {
|
|
173
|
+
const cmpI32 = bind('kernel32.dll', 'lstrcmpW', '<WCHAR>ptr <WCHAR>ptr -> i32')
|
|
174
|
+
const cmpI16 = bind('kernel32.dll', 'lstrcmpW', '<WCHAR>ptr <WCHAR>ptr -> i16')
|
|
175
|
+
const cmpI8 = bind('kernel32.dll', 'lstrcmpW', '<WCHAR>ptr <WCHAR>ptr -> i8')
|
|
176
|
+
t.checkTrue('lstrcmpW i32 < 0', cmpI32('a', 'b') < 0)
|
|
177
|
+
t.checkTrue('lstrcmpW i16 < 0', cmpI16('a', 'b') < 0)
|
|
178
|
+
t.checkTrue('lstrcmpW i8 < 0', cmpI8('a', 'b') < 0)
|
|
179
|
+
} else t.skipCase('lstrcmpW missing')
|
|
180
|
+
|
|
181
|
+
t.section('negative return i64 (InterlockedIncrement64)')
|
|
182
|
+
if (has(k32, 'InterlockedExchange64') && has(k32, 'InterlockedIncrement64')) {
|
|
183
|
+
const exchange64 = bind('kernel32.dll', 'InterlockedExchange64', '<BYTE>ptr i64 -> i64')
|
|
184
|
+
const increment64 = bind('kernel32.dll', 'InterlockedIncrement64', '<BYTE>ptr -> i64')
|
|
185
|
+
const cell = new PtrArrayBuffer(8)
|
|
186
|
+
exchange64(cell, -2n)
|
|
187
|
+
t.check('InterlockedIncrement64(-2)', -1n, increment64(cell))
|
|
188
|
+
} else t.skipCase('Interlocked*64 missing')
|
|
189
|
+
|
|
190
|
+
t.section('64-bit arg transport (InterlockedExchange64)')
|
|
191
|
+
if (has(k32, 'InterlockedExchange64')) {
|
|
192
|
+
const exchange64 = bind('kernel32.dll', 'InterlockedExchange64', '<BYTE>ptr i64 -> i64')
|
|
193
|
+
const cell = new PtrArrayBuffer(8)
|
|
194
|
+
const prev0 = exchange64(cell, 4294967297n) // 2^32+1:超过 32 位
|
|
195
|
+
const prev1 = exchange64(cell, -1n) // 64 位全 1
|
|
196
|
+
const prev2 = exchange64(cell, 0n)
|
|
197
|
+
t.check('exchange empty cell returns 0', 0n, prev0)
|
|
198
|
+
t.check('exchange returns 2^32+1', 4294967297n, prev1)
|
|
199
|
+
t.check('exchange returns -1', -1n, prev2)
|
|
200
|
+
} else t.skipCase('InterlockedExchange64 missing')
|
|
201
|
+
|
|
202
|
+
t.section('ptr NULL/undefined')
|
|
203
|
+
if (has(usr, 'GetDC') && has(usr, 'IsWindow')) {
|
|
204
|
+
const getDC = bind('user32.dll', 'GetDC', '<>ptr -> <>ptr')
|
|
205
|
+
const isWindow = bind('user32.dll', 'IsWindow', '<>ptr -> i32')
|
|
206
|
+
t.checkTrue('GetDC(NULL) non-null', !!getDC(NULL))
|
|
207
|
+
// undefined / null 不是指针合法值(指针只收 NULL 或有效地址),必须拒绝而非静默当 0。
|
|
208
|
+
let errUndef = ''
|
|
209
|
+
try {
|
|
210
|
+
(getDC as unknown as (v: unknown) => number)(undefined)
|
|
211
|
+
} catch (e) {
|
|
212
|
+
errUndef = String(e)
|
|
213
|
+
}
|
|
214
|
+
t.checkTrue('GetDC(undefined) rejected', errUndef.includes('undefined'))
|
|
215
|
+
t.check('IsWindow(NULL) = FALSE', 0, isWindow(NULL))
|
|
216
|
+
} else t.skipCase('GetDC/IsWindow missing')
|
|
217
|
+
},
|
|
218
|
+
}
|