quickwin 1.3.0 → 2.1.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 +18 -24
- package/README.md +17 -23
- package/examples/PdfCanvas.js +8 -37
- package/examples/PdfCanvas.tsx +11 -41
- package/examples/exec_server.js +20155 -1420
- 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 +1424 -1180
- package/examples/pdf_viewer.tsx +16 -67
- package/examples/setres.js +22 -47
- package/examples/setres.ts +23 -57
- package/examples/test_gallery.js +1418 -1075
- package/examples/test_react_lazy.js +1391 -1059
- package/examples/test_react_listbox.js +1390 -1058
- package/examples/test_root_window.js +1384 -1052
- package/examples/test_tab.js +1391 -1059
- package/lib/abort.js +70 -0
- package/lib/abort.ts +69 -0
- package/lib/certs.js +219 -0
- package/lib/certs.ts +229 -0
- package/lib/exec-log.js +15 -0
- package/lib/exec-log.ts +30 -0
- package/lib/fetch.js +88 -7
- package/lib/fetch.ts +73 -7
- package/lib/ffi/bind.js +441 -0
- package/lib/ffi/bind.ts +539 -0
- package/lib/ffi/ctype.js +194 -0
- package/lib/ffi/ctype.ts +369 -0
- package/lib/ffi/index.js +6 -0
- package/lib/ffi/index.ts +6 -0
- package/lib/ffi/struct.js +490 -0
- package/lib/ffi/struct.ts +753 -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 +50 -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 +5 -3
- package/lib/react-qw/index.ts +8 -3
- 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 +20 -23
- package/lib/text-measure.ts +22 -26
- package/lib/websocket.js +44 -4
- package/lib/websocket.ts +40 -4
- package/lib/windows/advapi32.js +53 -0
- package/lib/windows/advapi32.ts +64 -0
- package/lib/windows/comctl32.js +18 -0
- package/lib/windows/comctl32.ts +19 -0
- package/lib/windows/comdlg32.js +8 -0
- package/lib/windows/comdlg32.ts +10 -0
- package/lib/windows/gdi32.js +392 -0
- package/lib/windows/gdi32.ts +420 -0
- package/lib/windows/kernel32.js +262 -0
- package/lib/windows/kernel32.ts +291 -0
- package/lib/windows/msimg32.js +38 -0
- package/lib/windows/msimg32.ts +45 -0
- package/lib/windows/ntdll.js +9 -0
- package/lib/windows/ntdll.ts +11 -0
- package/lib/windows/ole32.js +7 -0
- package/lib/windows/ole32.ts +9 -0
- package/lib/windows/psapi.js +32 -0
- package/lib/windows/psapi.ts +37 -0
- package/lib/windows/shell32.js +13 -0
- package/lib/windows/shell32.ts +16 -0
- package/lib/windows/structs.js +519 -0
- package/lib/windows/structs.ts +566 -0
- package/lib/windows/user32.js +572 -0
- package/lib/windows/user32.ts +607 -0
- package/lib/windows/version.js +29 -0
- package/lib/windows/version.ts +35 -0
- package/lib/windows/winspool.js +71 -0
- package/lib/windows/winspool.ts +80 -0
- package/package.json +1 -1
- package/quickwin.d.ts +31 -40
- package/quickwin_const.d.ts +137 -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 +19 -4
- package/test/run.ts +20 -5
- package/test/test_basic_fetch.js +29 -0
- package/test/test_basic_fetch.ts +26 -0
- package/test/test_ffi.js +27 -45
- package/test/test_ffi.ts +35 -54
- package/test/test_ffi_abi.js +290 -0
- package/test/test_ffi_abi.ts +274 -0
- package/test/test_ffi_array.js +116 -0
- package/test/test_ffi_array.ts +141 -0
- package/test/test_ffi_bind.js +620 -8
- package/test/test_ffi_bind.ts +630 -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_files.js +102 -0
- package/test/test_files.ts +121 -0
- package/test/test_gdi_draw.js +681 -0
- package/test/test_gdi_draw.ts +725 -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 +74 -11
- package/test/test_net_fetch.ts +73 -10
- package/test/test_net_websocket.js +1 -1
- package/test/test_net_websocket.ts +1 -1
- package/test/test_printer.js +137 -0
- package/test/test_printer.ts +148 -0
- package/test/test_proc.js +141 -0
- package/test/test_proc.ts +163 -0
- package/test/test_react_complex.js +1401 -1069
- package/test/test_react_counter.js +1391 -1059
- package/test/test_react_flex.js +1411 -1072
- package/test/test_react_hidden.js +1394 -1062
- package/test/test_react_listview_link.js +1391 -1059
- package/test/test_react_notify.js +18638 -0
- package/test/test_react_render.js +1391 -1059
- package/test/test_registry.js +74 -0
- package/test/test_registry.ts +95 -0
- package/test/test_sysinfo.js +122 -0
- package/test/test_sysinfo.ts +141 -0
- package/test/test_wolfssl_read.js +45 -43
- package/test/test_wolfssl_read.ts +42 -40
- 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
|
@@ -17,7 +17,8 @@ export const suite = {
|
|
|
17
17
|
else { t.fail++; std.printf(' FAIL: %s\n', name) }
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
// ── wolfSSL_read return contract:
|
|
20
|
+
// ── wolfSSL_read return contract: raw int, data lands in the
|
|
21
|
+
// caller-owned buffer (thin wrapper — errors stay numbers) ──
|
|
21
22
|
t.section('wolfSSL_read contract (HTTPS /large/65536)')
|
|
22
23
|
await new Promise<void>((resolve) => {
|
|
23
24
|
const s = sock.socket()
|
|
@@ -34,10 +35,9 @@ export const suite = {
|
|
|
34
35
|
let sentRequest = false
|
|
35
36
|
let total = 0
|
|
36
37
|
const EXPECTED = 65536
|
|
37
|
-
let
|
|
38
|
-
let
|
|
39
|
-
let
|
|
40
|
-
let nullErrOk = true
|
|
38
|
+
let sawNonNumber = false
|
|
39
|
+
let sawEof = false
|
|
40
|
+
let errOk = true
|
|
41
41
|
let done = false
|
|
42
42
|
|
|
43
43
|
const finish = () => {
|
|
@@ -47,10 +47,10 @@ export const suite = {
|
|
|
47
47
|
assert('connected', connected)
|
|
48
48
|
assert('handshake ok', handshakeDone)
|
|
49
49
|
assert('request sent', sentRequest)
|
|
50
|
-
assert('received
|
|
51
|
-
assert('wolfSSL_read
|
|
52
|
-
assert('
|
|
53
|
-
assert('
|
|
50
|
+
assert('received full body', total >= EXPECTED)
|
|
51
|
+
assert('wolfSSL_read always returned number', !sawNonNumber)
|
|
52
|
+
assert('no-data/EOF observed via int (0 or ZERO_RETURN)', sawEof)
|
|
53
|
+
assert('n<=0 get_error in {WANT_READ,WANT_WRITE,ZERO_RETURN}', errOk)
|
|
54
54
|
if (ssl) { wolfssl.wolfSSL_free(ssl); ssl = null }
|
|
55
55
|
if (ctx) { wolfssl.wolfSSL_CTX_free(ctx); ctx = null }
|
|
56
56
|
sock.closesocket(s)
|
|
@@ -72,37 +72,39 @@ export const suite = {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
const onReadable = () => {
|
|
75
|
-
// Drain until
|
|
75
|
+
// Drain until EOF (n===0 / ZERO_RETURN) or WANT_* hands back
|
|
76
|
+
// to the event loop.
|
|
76
77
|
for (let guard = 0; guard < 10000; guard++) {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
std.printf(' null get_error=%d\n', err)
|
|
84
|
-
}
|
|
85
|
-
if (err === ZERO_RETURN || err === 1 || err === 5) {
|
|
86
|
-
// EOF / fatal — done draining
|
|
87
|
-
break
|
|
88
|
-
}
|
|
89
|
-
// WANT_*: wait for next FD_READ/WRITE
|
|
78
|
+
const buf = new ArrayBuffer(8192)
|
|
79
|
+
const n = wolfssl.wolfSSL_read(ssl!, buf)
|
|
80
|
+
if (typeof n !== 'number') {
|
|
81
|
+
sawNonNumber = true
|
|
82
|
+
std.printf(' FAIL got non-number %s (want int)\n', String(n))
|
|
83
|
+
finish()
|
|
90
84
|
return
|
|
91
85
|
}
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
86
|
+
if (n > 0) {
|
|
87
|
+
total += n
|
|
88
|
+
// Keep draining after the body is complete: the point of
|
|
89
|
+
// this suite is the no-data/EOF contract, so a full body
|
|
90
|
+
// must not short-circuit the read that would observe it.
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
const err = wolfssl.wolfSSL_get_error(ssl!, n)
|
|
94
|
+
if (err !== WANT_READ && err !== WANT_WRITE && err !== ZERO_RETURN) {
|
|
95
|
+
errOk = false
|
|
96
|
+
std.printf(' n=%d get_error=%d outside {WANT_READ,WANT_WRITE,ZERO_RETURN}\n', n, err)
|
|
96
97
|
finish()
|
|
97
98
|
return
|
|
98
99
|
}
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (total >= EXPECTED) break
|
|
100
|
+
if (n === 0 || err === ZERO_RETURN) {
|
|
101
|
+
sawEof = true
|
|
102
|
+
break
|
|
103
103
|
}
|
|
104
|
+
// WANT_*: wait for next FD_READ/WRITE
|
|
105
|
+
return
|
|
104
106
|
}
|
|
105
|
-
if (
|
|
107
|
+
if (sawEof) finish()
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
sock.set_on_event(s, (event: { lNetworkEvents: number; iErrorCode: number[] }) => {
|
|
@@ -161,17 +163,17 @@ export const suite = {
|
|
|
161
163
|
|
|
162
164
|
if (event.lNetworkEvents & sock.FdEvent.FD_CLOSE) {
|
|
163
165
|
// drain remaining then finish
|
|
164
|
-
if (ssl && sentRequest && !
|
|
166
|
+
if (ssl && sentRequest && !sawNonNumber) {
|
|
165
167
|
onReadable()
|
|
166
168
|
}
|
|
167
|
-
if (!
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
169
|
+
if (!sawNonNumber) {
|
|
170
|
+
// Server close without an observed EOF read is an edge —
|
|
171
|
+
// don't fail if the body already completed.
|
|
172
|
+
if (!sawEof && total < EXPECTED && total > 0) {
|
|
173
|
+
sawEof = true
|
|
172
174
|
}
|
|
173
|
-
if (total >= EXPECTED ||
|
|
174
|
-
else assert('data before close', false); if (!done) finish()
|
|
175
|
+
if (total >= EXPECTED || sawEof) finish()
|
|
176
|
+
else { assert('data before close', false); if (!done) finish() }
|
|
175
177
|
}
|
|
176
178
|
}
|
|
177
179
|
})
|
package/test/test_worker_net.js
CHANGED
|
@@ -27,25 +27,43 @@ export const suite = {
|
|
|
27
27
|
catch (e) {
|
|
28
28
|
t.checkTrue('worker creation failed: ' + e, false);
|
|
29
29
|
}
|
|
30
|
+
// Retry across jsdelivr's official CDN hosts: a dropped connection
|
|
31
|
+
// on a slow CI network must not be reported as a TLS regression.
|
|
30
32
|
t.section('npm-package');
|
|
31
33
|
let worker3;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
34
|
+
const NPM_HOSTS = ['cdn.jsdelivr.net', 'fastly.jsdelivr.net', 'gcore.jsdelivr.net'];
|
|
35
|
+
let workerOk = false;
|
|
36
|
+
for (const host of NPM_HOSTS) {
|
|
37
|
+
for (let attempt = 0; attempt < 3 && !workerOk; attempt++) {
|
|
38
|
+
try {
|
|
39
|
+
const { data: r3 } = await new Promise((resolve, reject) => {
|
|
40
|
+
worker3 = new os.Worker('https://' + host + '/npm/quickwin/test/worker_module.js');
|
|
41
|
+
const timer = os.setTimeout(() => reject(new Error('timeout')), 15000);
|
|
42
|
+
worker3.onmessage = (e) => {
|
|
43
|
+
os.clearTimeout(timer);
|
|
44
|
+
resolve({ worker: worker3, data: e.data });
|
|
45
|
+
};
|
|
46
|
+
worker3.postMessage({ type: 'start' });
|
|
47
|
+
});
|
|
48
|
+
t.check('npm worker message type', 'result', r3.type);
|
|
49
|
+
t.check('npm worker imported value', 42, r3.value);
|
|
50
|
+
worker3.postMessage({ type: 'done' });
|
|
51
|
+
worker3.onmessage = null;
|
|
52
|
+
workerOk = true;
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
if (worker3) {
|
|
56
|
+
worker3.onmessage = null;
|
|
57
|
+
worker3 = null;
|
|
58
|
+
}
|
|
59
|
+
if (attempt === 2 && host === NPM_HOSTS[NPM_HOSTS.length - 1])
|
|
60
|
+
t.checkTrue('npm worker failed: ' + e, false);
|
|
61
|
+
else
|
|
62
|
+
os.sleep(500);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (workerOk)
|
|
66
|
+
break;
|
|
49
67
|
}
|
|
50
68
|
}
|
|
51
69
|
};
|
package/test/test_worker_net.ts
CHANGED
|
@@ -31,24 +31,36 @@ export const suite = {
|
|
|
31
31
|
t.checkTrue('worker creation failed: ' + e, false)
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// Retry across jsdelivr's official CDN hosts: a dropped connection
|
|
35
|
+
// on a slow CI network must not be reported as a TLS regression.
|
|
34
36
|
t.section('npm-package')
|
|
35
37
|
let worker3: any
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
const NPM_HOSTS = ['cdn.jsdelivr.net', 'fastly.jsdelivr.net', 'gcore.jsdelivr.net']
|
|
39
|
+
let workerOk = false
|
|
40
|
+
for (const host of NPM_HOSTS) {
|
|
41
|
+
for (let attempt = 0; attempt < 3 && !workerOk; attempt++) {
|
|
42
|
+
try {
|
|
43
|
+
const { data: r3 } = await new Promise<any>((resolve, reject) => {
|
|
44
|
+
worker3 = new os.Worker('https://' + host + '/npm/quickwin/test/worker_module.js')
|
|
45
|
+
const timer = os.setTimeout(() => reject(new Error('timeout')), 15000)
|
|
46
|
+
worker3.onmessage = (e: any) => {
|
|
47
|
+
os.clearTimeout(timer)
|
|
48
|
+
resolve({ worker: worker3, data: e.data })
|
|
49
|
+
}
|
|
50
|
+
worker3.postMessage({ type: 'start' })
|
|
51
|
+
})
|
|
52
|
+
t.check('npm worker message type', 'result', r3.type)
|
|
53
|
+
t.check('npm worker imported value', 42, r3.value)
|
|
54
|
+
worker3.postMessage({ type: 'done' })
|
|
55
|
+
worker3.onmessage = null
|
|
56
|
+
workerOk = true
|
|
57
|
+
} catch (e) {
|
|
58
|
+
if (worker3) { worker3.onmessage = null; worker3 = null }
|
|
59
|
+
if (attempt === 2 && host === NPM_HOSTS[NPM_HOSTS.length - 1]) t.checkTrue('npm worker failed: ' + e, false)
|
|
60
|
+
else os.sleep(500)
|
|
43
61
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
t.check('npm worker message type', 'result', r3.type)
|
|
47
|
-
t.check('npm worker imported value', 42, r3.value)
|
|
48
|
-
worker3.postMessage({ type: 'done' })
|
|
49
|
-
worker3.onmessage = null
|
|
50
|
-
} catch (e) {
|
|
51
|
-
t.checkTrue('npm worker failed: ' + e, false)
|
|
62
|
+
}
|
|
63
|
+
if (workerOk) break
|
|
52
64
|
}
|
|
53
65
|
}
|
|
54
66
|
}
|
package/tsconfig.json
CHANGED
package/examples/exec_server.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { createServer } from '../lib/http-server.js'
|
|
2
|
-
import * as os from 'os'
|
|
3
|
-
|
|
4
|
-
// WORKER_DATA_URL 由 esbuild --define 在 build.ts 中注入(base64 data: URL)
|
|
5
|
-
declare const WORKER_DATA_URL: string
|
|
6
|
-
|
|
7
|
-
const EXEC_TIMEOUT_MS = 60_000
|
|
8
|
-
|
|
9
|
-
interface ExecResult {
|
|
10
|
-
out: Uint8Array
|
|
11
|
-
code: number
|
|
12
|
-
error: string | null
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function runInWorker(cmd: string): Promise<ExecResult> {
|
|
16
|
-
return new Promise((resolve, reject) => {
|
|
17
|
-
const worker = new os.Worker(WORKER_DATA_URL)
|
|
18
|
-
const id = 1
|
|
19
|
-
let settled = false
|
|
20
|
-
let timer: ReturnType<typeof os.setTimeout>
|
|
21
|
-
|
|
22
|
-
const finish = (fn: () => void) => {
|
|
23
|
-
if (settled) return
|
|
24
|
-
settled = true
|
|
25
|
-
os.clearTimeout(timer)
|
|
26
|
-
worker.onmessage = null
|
|
27
|
-
fn()
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
timer = os.setTimeout(() => {
|
|
31
|
-
finish(() => reject(new Error('exec timeout')))
|
|
32
|
-
}, EXEC_TIMEOUT_MS)
|
|
33
|
-
|
|
34
|
-
worker.onmessage = (e) => {
|
|
35
|
-
const msg = e.data as { type: string; id: number; out?: Uint8Array; code?: number; error?: string | null }
|
|
36
|
-
if (msg.type !== 'result' || msg.id !== id) return
|
|
37
|
-
finish(() => resolve({ out: msg.out ?? new Uint8Array(0), code: msg.code ?? -1, error: msg.error ?? null }))
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
worker.postMessage({ type: 'run', id, cmd })
|
|
42
|
-
} catch (ex) {
|
|
43
|
-
finish(() => reject(ex instanceof Error ? ex : new Error(String(ex))))
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const server = createServer(async (req) => {
|
|
49
|
-
const path = new URL(req.url).pathname
|
|
50
|
-
if (req.method === 'GET' && (path === '/' || path === '/health')) {
|
|
51
|
-
return new Response('ok', { headers: { 'Content-Type': 'text/plain' } })
|
|
52
|
-
}
|
|
53
|
-
if (req.method === 'POST' && path === '/exec') {
|
|
54
|
-
const body = await req.json() as { cmd: string }
|
|
55
|
-
try {
|
|
56
|
-
const { out, code, error } = await runInWorker(body.cmd)
|
|
57
|
-
if (error) {
|
|
58
|
-
return new Response(JSON.stringify({ error }), {
|
|
59
|
-
status: 500,
|
|
60
|
-
headers: { 'Content-Type': 'application/json' }
|
|
61
|
-
})
|
|
62
|
-
}
|
|
63
|
-
return new Response(out, {
|
|
64
|
-
headers: {
|
|
65
|
-
'Content-Type': 'application/octet-stream',
|
|
66
|
-
'X-Exit-Code': String(code),
|
|
67
|
-
}
|
|
68
|
-
})
|
|
69
|
-
} catch (ex) {
|
|
70
|
-
return new Response(JSON.stringify({ error: String(ex) }), {
|
|
71
|
-
status: 504,
|
|
72
|
-
headers: { 'Content-Type': 'application/json' }
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return new Response('Not Found', { status: 404 })
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
const rc = server.listen(8080, '0.0.0.0')
|
|
80
|
-
console.log('listen rc=' + rc + ' addr=' + JSON.stringify(server.address()))
|
package/lib/ffi-bind.js
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import * as ffi from 'ffi';
|
|
2
|
-
import * as win from 'win';
|
|
3
|
-
import './text-codec.js';
|
|
4
|
-
const KIND_TO_FFI = {
|
|
5
|
-
void: ffi.FFI_TYPE_VOID,
|
|
6
|
-
u8: ffi.FFI_TYPE_UINT8,
|
|
7
|
-
i8: ffi.FFI_TYPE_SINT8,
|
|
8
|
-
u16: ffi.FFI_TYPE_UINT16,
|
|
9
|
-
i16: ffi.FFI_TYPE_SINT16,
|
|
10
|
-
u32: ffi.FFI_TYPE_UINT32,
|
|
11
|
-
i32: ffi.FFI_TYPE_SINT32,
|
|
12
|
-
u64: ffi.FFI_TYPE_UINT64,
|
|
13
|
-
i64: ffi.FFI_TYPE_SINT64,
|
|
14
|
-
f32: ffi.FFI_TYPE_FLOAT,
|
|
15
|
-
f64: ffi.FFI_TYPE_DOUBLE,
|
|
16
|
-
ptr: ffi.FFI_TYPE_POINTER,
|
|
17
|
-
wstr: ffi.FFI_TYPE_POINTER,
|
|
18
|
-
};
|
|
19
|
-
// 与类型层 CTypeOf 同步的运行时别名表
|
|
20
|
-
const C_ALIAS = {
|
|
21
|
-
int: 'i32', long: 'i32', short: 'i16', char: 'i8', float: 'f32', double: 'f64',
|
|
22
|
-
DWORD: 'u32', UINT: 'u32', ULONG: 'u32', LONG: 'i32', BOOL: 'i32', HRESULT: 'i32',
|
|
23
|
-
SHORT: 'i16', USHORT: 'u16', BYTE: 'u8',
|
|
24
|
-
LONG_PTR: 'ptr', ULONG_PTR: 'ptr', INT_PTR: 'ptr', UINT_PTR: 'ptr', DWORD_PTR: 'ptr',
|
|
25
|
-
SIZE_T: 'ptr', WPARAM: 'ptr', LPARAM: 'ptr',
|
|
26
|
-
HANDLE: 'ptr', HWND: 'ptr', HDC: 'ptr', HMODULE: 'ptr', HFONT: 'ptr', HBRUSH: 'ptr',
|
|
27
|
-
HICON: 'ptr', HBITMAP: 'ptr', LPVOID: 'ptr', LPCVOID: 'ptr', LPCWSTR: 'ptr',
|
|
28
|
-
PCWSTR: 'ptr', LPWSTR: 'ptr',
|
|
29
|
-
};
|
|
30
|
-
export function normKind(t) {
|
|
31
|
-
const k = C_ALIAS[t] ?? t;
|
|
32
|
-
if (!(k in KIND_TO_FFI))
|
|
33
|
-
throw new Error(`ffi-bind: invalid kind "${t}"`);
|
|
34
|
-
return k;
|
|
35
|
-
}
|
|
36
|
-
const _dllCache = new Map();
|
|
37
|
-
function loadDll(dll) {
|
|
38
|
-
const cached = _dllCache.get(dll);
|
|
39
|
-
if (cached !== undefined)
|
|
40
|
-
return cached;
|
|
41
|
-
const loaded = win.LoadLibrary(dll);
|
|
42
|
-
if (!loaded)
|
|
43
|
-
throw new Error(`ffi-bind: LoadLibrary("${dll}") failed`);
|
|
44
|
-
_dllCache.set(dll, loaded);
|
|
45
|
-
return loaded;
|
|
46
|
-
}
|
|
47
|
-
function parseSig(sig) {
|
|
48
|
-
const parts = sig.split(' -> ');
|
|
49
|
-
const ret = parts[1];
|
|
50
|
-
if (ret === undefined || parts.length > 2) {
|
|
51
|
-
throw new Error(`ffi-bind: invalid signature "${sig}" (expected "arg1 arg2 -> ret")`);
|
|
52
|
-
}
|
|
53
|
-
const retK = normKind(ret);
|
|
54
|
-
if (retK === 'wstr')
|
|
55
|
-
throw new Error('ffi-bind: "wstr" cannot be a return type');
|
|
56
|
-
const tokens = (parts[0] ?? '') === '' ? [] : parts[0].split(' ');
|
|
57
|
-
const args = tokens.map((t) => normKind(t));
|
|
58
|
-
return { args, ret: retK };
|
|
59
|
-
}
|
|
60
|
-
function makeFn(proc, sig) {
|
|
61
|
-
const { args, ret } = parseSig(sig);
|
|
62
|
-
const argTypes = args.map((k) => KIND_TO_FFI[k]);
|
|
63
|
-
const retType = KIND_TO_FFI[ret];
|
|
64
|
-
return (...a) => {
|
|
65
|
-
const vals = args.map((k, i) => k === 'wstr' ? new TextEncoder('utf-16le').encode(String(a[i]) + '\0').buffer : a[i]);
|
|
66
|
-
return ffi.ffiCall(proc, argTypes, vals, retType);
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
export function bind(dll, name, sig) {
|
|
70
|
-
const proc = win.GetProcAddress(loadDll(dll), name);
|
|
71
|
-
if (!proc)
|
|
72
|
-
throw new Error(`ffi-bind: proc "${name}" not found in ${dll}`);
|
|
73
|
-
return makeFn(proc, sig);
|
|
74
|
-
}
|
|
75
|
-
export function bindLib(dll, map) {
|
|
76
|
-
const out = {};
|
|
77
|
-
const h = loadDll(dll);
|
|
78
|
-
for (const [name, sig] of Object.entries(map)) {
|
|
79
|
-
const proc = win.GetProcAddress(h, name);
|
|
80
|
-
if (!proc)
|
|
81
|
-
throw new Error(`ffi-bind: proc "${name}" not found in ${dll}`);
|
|
82
|
-
out[name] = makeFn(proc, sig);
|
|
83
|
-
}
|
|
84
|
-
return out;
|
|
85
|
-
}
|
package/lib/ffi-bind.ts
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import * as ffi from 'ffi'
|
|
2
|
-
import * as win from 'win'
|
|
3
|
-
import './text-codec.js'
|
|
4
|
-
|
|
5
|
-
// 声明式 FFI 绑定:把字符串签名('ptr wstr i32 ptr i32 -> i32')解析成
|
|
6
|
-
// 一个可调用函数(bind)或一个 dll 的签名表(bindLib)。
|
|
7
|
-
// kind:void u8 i8 u16 i16 u32 i32 u64 i64 f32 f64 ptr wstr
|
|
8
|
-
// 可用 C/Windows typedef 别名:int long short char float double
|
|
9
|
-
// + DWORD UINT LONG BOOL HRESULT ... + *_PTR WPARAM LPARAM SIZE_T
|
|
10
|
-
// + HANDLE HWND HDC ... (+ LPVOID/LPCWSTR 等指针 typedef)
|
|
11
|
-
// 别名在 token 层归一化到上面规范 kind(见 CTypeOf / C_ALIAS,两者须同步)
|
|
12
|
-
// ptr 收 ArrayBuffer | number | null,槽宽=指针宽(ia32 4B / x64 8B)
|
|
13
|
-
// wstr 收 string,自动 utf-16le+'\0' 编码后走 ptr 槽(仅参数,不能作返回)
|
|
14
|
-
|
|
15
|
-
type Kind = 'void' | 'u8' | 'i8' | 'u16' | 'i16' | 'u32' | 'i32' | 'u64' | 'i64' | 'f32' | 'f64' | 'ptr' | 'wstr'
|
|
16
|
-
|
|
17
|
-
type ArgTypeOf = {
|
|
18
|
-
u8: number; i8: number; u16: number; i16: number
|
|
19
|
-
u32: number; i32: number
|
|
20
|
-
u64: number; i64: number
|
|
21
|
-
f32: number; f64: number
|
|
22
|
-
ptr: ArrayBuffer | number | null
|
|
23
|
-
wstr: string
|
|
24
|
-
}
|
|
25
|
-
type RetTypeOf = {
|
|
26
|
-
u8: number; i8: number; u16: number; i16: number
|
|
27
|
-
u32: number; i32: number
|
|
28
|
-
u64: number; i64: number
|
|
29
|
-
f32: number; f64: number
|
|
30
|
-
ptr: number | null
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// C / Windows typedef → 规范 kind。Windows x86/x64 均 LLP64:int/long 恒 32 位,
|
|
34
|
-
// long long 恒 64 位;LONG_PTR/WPARAM/SIZE_T 等指针宽随 arch 走 'ptr' 槽。
|
|
35
|
-
// 运行时见 C_ALIAS(两表内容必须保持一致)。
|
|
36
|
-
type CTypeOf = {
|
|
37
|
-
int: 'i32'
|
|
38
|
-
long: 'i32'
|
|
39
|
-
short: 'i16'
|
|
40
|
-
char: 'i8'
|
|
41
|
-
float: 'f32'
|
|
42
|
-
double: 'f64'
|
|
43
|
-
DWORD: 'u32'
|
|
44
|
-
UINT: 'u32'
|
|
45
|
-
ULONG: 'u32'
|
|
46
|
-
LONG: 'i32'
|
|
47
|
-
BOOL: 'i32'
|
|
48
|
-
HRESULT: 'i32'
|
|
49
|
-
SHORT: 'i16'
|
|
50
|
-
USHORT: 'u16'
|
|
51
|
-
BYTE: 'u8'
|
|
52
|
-
LONG_PTR: 'ptr'
|
|
53
|
-
ULONG_PTR: 'ptr'
|
|
54
|
-
INT_PTR: 'ptr'
|
|
55
|
-
UINT_PTR: 'ptr'
|
|
56
|
-
DWORD_PTR: 'ptr'
|
|
57
|
-
SIZE_T: 'ptr'
|
|
58
|
-
WPARAM: 'ptr'
|
|
59
|
-
LPARAM: 'ptr'
|
|
60
|
-
HANDLE: 'ptr'
|
|
61
|
-
HWND: 'ptr'
|
|
62
|
-
HDC: 'ptr'
|
|
63
|
-
HMODULE: 'ptr'
|
|
64
|
-
HFONT: 'ptr'
|
|
65
|
-
HBRUSH: 'ptr'
|
|
66
|
-
HICON: 'ptr'
|
|
67
|
-
HBITMAP: 'ptr'
|
|
68
|
-
LPVOID: 'ptr'
|
|
69
|
-
LPCVOID: 'ptr'
|
|
70
|
-
LPCWSTR: 'ptr'
|
|
71
|
-
PCWSTR: 'ptr'
|
|
72
|
-
LPWSTR: 'ptr'
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export type Norm<T extends string> = T extends keyof CTypeOf ? CTypeOf[T] : T
|
|
76
|
-
|
|
77
|
-
type ArgsOf<T extends string> =
|
|
78
|
-
T extends '' ? []
|
|
79
|
-
: T extends `${infer H} ${infer R}` ? [ArgTypeOf[Norm<H> & keyof ArgTypeOf], ...ArgsOf<R>]
|
|
80
|
-
: [ArgTypeOf[Norm<T> & keyof ArgTypeOf]]
|
|
81
|
-
|
|
82
|
-
type _Args<S extends string> = S extends `${infer A} -> ${string}` ? ArgsOf<A> : never
|
|
83
|
-
type _Ret<S extends string> = S extends `${string} -> ${infer R}` ? RetTypeOf[Norm<R> & keyof RetTypeOf] : never
|
|
84
|
-
|
|
85
|
-
const KIND_TO_FFI: Record<Kind, ffi.FfiType> = {
|
|
86
|
-
void: ffi.FFI_TYPE_VOID,
|
|
87
|
-
u8: ffi.FFI_TYPE_UINT8,
|
|
88
|
-
i8: ffi.FFI_TYPE_SINT8,
|
|
89
|
-
u16: ffi.FFI_TYPE_UINT16,
|
|
90
|
-
i16: ffi.FFI_TYPE_SINT16,
|
|
91
|
-
u32: ffi.FFI_TYPE_UINT32,
|
|
92
|
-
i32: ffi.FFI_TYPE_SINT32,
|
|
93
|
-
u64: ffi.FFI_TYPE_UINT64,
|
|
94
|
-
i64: ffi.FFI_TYPE_SINT64,
|
|
95
|
-
f32: ffi.FFI_TYPE_FLOAT,
|
|
96
|
-
f64: ffi.FFI_TYPE_DOUBLE,
|
|
97
|
-
ptr: ffi.FFI_TYPE_POINTER,
|
|
98
|
-
wstr: ffi.FFI_TYPE_POINTER,
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// 与类型层 CTypeOf 同步的运行时别名表
|
|
102
|
-
const C_ALIAS: Record<string, Kind> = {
|
|
103
|
-
int: 'i32', long: 'i32', short: 'i16', char: 'i8', float: 'f32', double: 'f64',
|
|
104
|
-
DWORD: 'u32', UINT: 'u32', ULONG: 'u32', LONG: 'i32', BOOL: 'i32', HRESULT: 'i32',
|
|
105
|
-
SHORT: 'i16', USHORT: 'u16', BYTE: 'u8',
|
|
106
|
-
LONG_PTR: 'ptr', ULONG_PTR: 'ptr', INT_PTR: 'ptr', UINT_PTR: 'ptr', DWORD_PTR: 'ptr',
|
|
107
|
-
SIZE_T: 'ptr', WPARAM: 'ptr', LPARAM: 'ptr',
|
|
108
|
-
HANDLE: 'ptr', HWND: 'ptr', HDC: 'ptr', HMODULE: 'ptr', HFONT: 'ptr', HBRUSH: 'ptr',
|
|
109
|
-
HICON: 'ptr', HBITMAP: 'ptr', LPVOID: 'ptr', LPCVOID: 'ptr', LPCWSTR: 'ptr',
|
|
110
|
-
PCWSTR: 'ptr', LPWSTR: 'ptr',
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function normKind(t: string): Kind {
|
|
114
|
-
const k = C_ALIAS[t] ?? t
|
|
115
|
-
if (!(k in KIND_TO_FFI)) throw new Error(`ffi-bind: invalid kind "${t}"`)
|
|
116
|
-
return k as Kind
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const _dllCache: Map<string, win.HMODULE> = new Map()
|
|
120
|
-
|
|
121
|
-
function loadDll(dll: string): win.HMODULE {
|
|
122
|
-
const cached = _dllCache.get(dll)
|
|
123
|
-
if (cached !== undefined) return cached
|
|
124
|
-
const loaded = win.LoadLibrary(dll)
|
|
125
|
-
if (!loaded) throw new Error(`ffi-bind: LoadLibrary("${dll}") failed`)
|
|
126
|
-
_dllCache.set(dll, loaded)
|
|
127
|
-
return loaded
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function parseSig(sig: string): { args: Kind[]; ret: Kind } {
|
|
131
|
-
const parts = sig.split(' -> ')
|
|
132
|
-
const ret = parts[1]
|
|
133
|
-
if (ret === undefined || parts.length > 2) {
|
|
134
|
-
throw new Error(`ffi-bind: invalid signature "${sig}" (expected "arg1 arg2 -> ret")`)
|
|
135
|
-
}
|
|
136
|
-
const retK = normKind(ret)
|
|
137
|
-
if (retK === 'wstr') throw new Error('ffi-bind: "wstr" cannot be a return type')
|
|
138
|
-
const tokens = (parts[0] ?? '') === '' ? [] : (parts[0] as string).split(' ')
|
|
139
|
-
const args = tokens.map((t) => normKind(t))
|
|
140
|
-
return { args, ret: retK }
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function makeFn(proc: number, sig: string): (...a: unknown[]) => unknown {
|
|
144
|
-
const { args, ret } = parseSig(sig)
|
|
145
|
-
const argTypes = args.map((k) => KIND_TO_FFI[k])
|
|
146
|
-
const retType = KIND_TO_FFI[ret]
|
|
147
|
-
return (...a: unknown[]) => {
|
|
148
|
-
const vals = args.map((k, i) =>
|
|
149
|
-
k === 'wstr' ? new TextEncoder('utf-16le').encode(String(a[i]) + '\0').buffer : a[i])
|
|
150
|
-
return ffi.ffiCall(proc, argTypes as any, vals as any, retType)
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function bind<const S extends string>(dll: string, name: string, sig: S): (...args: _Args<S>) => _Ret<S> {
|
|
155
|
-
const proc = win.GetProcAddress(loadDll(dll), name)
|
|
156
|
-
if (!proc) throw new Error(`ffi-bind: proc "${name}" not found in ${dll}`)
|
|
157
|
-
return makeFn(proc, sig) as unknown as (...args: _Args<S>) => _Ret<S>
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export function bindLib<const M extends Record<string, string>>(dll: string, map: M):
|
|
161
|
-
{ [K in keyof M]: (...args: _Args<M[K]>) => _Ret<M[K]> } {
|
|
162
|
-
const out: Record<string, (...a: unknown[]) => unknown> = {}
|
|
163
|
-
const h = loadDll(dll)
|
|
164
|
-
for (const [name, sig] of Object.entries(map)) {
|
|
165
|
-
const proc = win.GetProcAddress(h, name)
|
|
166
|
-
if (!proc) throw new Error(`ffi-bind: proc "${name}" not found in ${dll}`)
|
|
167
|
-
out[name] = makeFn(proc, sig)
|
|
168
|
-
}
|
|
169
|
-
return out as { [K in keyof M]: (...args: _Args<M[K]>) => _Ret<M[K]> }
|
|
170
|
-
}
|