quickwin 3.0.3 → 3.0.4
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 +10 -11
- package/README.md +10 -11
- package/lib/ffi/bind.d.ts +7 -1
- package/lib/ffi/bind.js +198 -128
- package/lib/ffi/ctype.d.ts +4 -1
- package/lib/ffi/ctype.js +18 -14
- package/package.json +1 -1
- 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/README.en.md
CHANGED
|
@@ -6,22 +6,18 @@ QuickJS runtime for Windows, with native GUI, networking, WASM, FFI and more.
|
|
|
6
6
|
|
|
7
7
|
Supports Windows XP+ (32/64-bit) with a minimal binary size (~1.5MB). The 32-bit build is CI-tested in a QEMU Windows XP VM.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## Quick Start
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Scaffold a desktop app with [quickwin-starter](https://github.com/quickwinlabs/quickwin-starter):
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
# Option 2: download directly from GitHub Release
|
|
19
|
-
iwr https://github.com/quickwinlabs/quickwin/releases/latest/download/qwin.exe -OutFile qwin.exe
|
|
20
|
-
iwr https://github.com/quickwinlabs/quickwin/releases/latest/download/main.js -OutFile main.js
|
|
21
|
-
.\qwin.exe main.js
|
|
14
|
+
npm create -y quickwin@latest myapp
|
|
15
|
+
cd myapp
|
|
16
|
+
npm install
|
|
17
|
+
npm run build # produces dist\app.exe
|
|
22
18
|
```
|
|
23
19
|
|
|
24
|
-
The
|
|
20
|
+
The default template is React (JSX + system tray); `-- --template=minimal` starts from a bare skeleton.
|
|
25
21
|
|
|
26
22
|
## Features
|
|
27
23
|
|
|
@@ -36,7 +32,10 @@ The npm package ships 4 executables: `qwin.exe` (64-bit) / `qwin-x86.exe` (32-bi
|
|
|
36
32
|
|
|
37
33
|
## CLI
|
|
38
34
|
|
|
35
|
+
The runtime itself: install with `npm install quickwin`, or download `qwin.exe` and `main.js` from [Releases](https://github.com/quickwinlabs/quickwin/releases). The npm package ships 4 executables: `qwin.exe` (64-bit) / `qwin-x86.exe` (32-bit, XP-compatible) plus their no-WASM variants `qwin-nowasm.exe` / `qwin-nowasm-x86.exe`.
|
|
36
|
+
|
|
39
37
|
```bash
|
|
38
|
+
npx quickwin node_modules/quickwin/main.js # run the bundled demo to see it working
|
|
40
39
|
quickwin script.js # run a script
|
|
41
40
|
quickwin -o CON script.js # run with console (AllocConsole)
|
|
42
41
|
quickwin -o LOG script.js # run with auto-generated log file
|
package/README.md
CHANGED
|
@@ -6,22 +6,18 @@ Windows 平台上的 QuickJS 运行时,支持原生 GUI、网络、WASM、FFI
|
|
|
6
6
|
|
|
7
7
|
支持 Windows XP+(32/64 位),保持最小二进制体积(约 1.5MB)。32 位构建已在 QEMU Windows XP 虚拟机中通过 CI 测试。
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## 快速开始
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
用脚手架新建一个桌面应用([quickwin-starter](https://github.com/quickwinlabs/quickwin-starter)):
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
# 方式二:直接从 GitHub Release 下载
|
|
19
|
-
iwr https://github.com/quickwinlabs/quickwin/releases/latest/download/qwin.exe -OutFile qwin.exe
|
|
20
|
-
iwr https://github.com/quickwinlabs/quickwin/releases/latest/download/main.js -OutFile main.js
|
|
21
|
-
.\qwin.exe main.js
|
|
14
|
+
npm create -y quickwin@latest myapp
|
|
15
|
+
cd myapp
|
|
16
|
+
npm install
|
|
17
|
+
npm run build # 产出 dist\app.exe
|
|
22
18
|
```
|
|
23
19
|
|
|
24
|
-
|
|
20
|
+
默认 React 模板(JSX + 系统托盘),`-- --template=minimal` 可从更朴素的骨架起步。
|
|
25
21
|
|
|
26
22
|
## 特性
|
|
27
23
|
|
|
@@ -36,7 +32,10 @@ npm 包自带 4 个可执行文件:`qwin.exe`(64 位)/ `qwin-x86.exe`(32
|
|
|
36
32
|
|
|
37
33
|
## CLI
|
|
38
34
|
|
|
35
|
+
运行时本体:`npm install quickwin` 安装,或从 [Releases](https://github.com/quickwinlabs/quickwin/releases) 下载 `qwin.exe` 与 `main.js`。npm 包自带 4 个可执行文件:`qwin.exe`(64 位)/ `qwin-x86.exe`(32 位,兼容 XP)及其无 WASM 版本 `qwin-nowasm.exe` / `qwin-nowasm-x86.exe`。
|
|
36
|
+
|
|
39
37
|
```bash
|
|
38
|
+
npx quickwin node_modules/quickwin/main.js # 跑包自带的示例
|
|
40
39
|
quickwin script.js # 运行脚本
|
|
41
40
|
quickwin -o CON script.js # 带控制台运行(AllocConsole)
|
|
42
41
|
quickwin -o LOG script.js # 自动生成日志文件运行
|
package/lib/ffi/bind.d.ts
CHANGED
|
@@ -44,7 +44,13 @@ export declare const BYTE: Required<Pick<Codec<'BYTE', PtrArrayBuffer<any>, numb
|
|
|
44
44
|
* encoders 喂入参位(encode 值域 → 形参类型),decoders 喂返回位(decode 值域
|
|
45
45
|
* → 返回 V | null,替代 Ptr | null)。decoders 缺省时回退到 encoders——双向 codec
|
|
46
46
|
* 只需传一次(与运行时 `decoders ?? encoders` 同构:都只在实参缺省时生效,
|
|
47
|
-
* 显式传空对象不 fallback)。内建 WCHAR/BYTE 无条件参与两张表。
|
|
47
|
+
* 显式传空对象不 fallback)。内建 WCHAR/BYTE 无条件参与两张表。
|
|
48
|
+
*
|
|
49
|
+
* codec 契约(参数帧复用的前提,调用方须遵守):
|
|
50
|
+
* 1. encode/decode 内不得回调任何已 bind 的函数。参数帧按签名只分配一次并逐调复用,
|
|
51
|
+
* 回调重入发生在本层 ffiCall 之前,会把本层已写好的槽抹掉(静默错参)。
|
|
52
|
+
* 2. encode(v, buf, off) 必须写满槽宽:少写会残留上一调的字节。
|
|
53
|
+
* 输入校验/转码若需要调 Win32 API,请在调用 bind 出的函数**之前**做。 */
|
|
48
54
|
export declare function bind<const S extends string, const LE extends CodecMap = {}, const LD extends CodecMap = LE>(dll: string, name: string, sig: S, encoders?: LE, decoders?: LD): BindFn<S, BindEncoders<LE>, BindDecoders<LD>>;
|
|
49
55
|
/** 批量绑定(map = 函数名 → 签名串)。encoders/decoders 语义同 bind()。 */
|
|
50
56
|
export declare function bindLib<const M extends Record<string, string>, const LE extends CodecMap = {}, const LD extends CodecMap = LE>(dll: string, map: M, encoders?: LE, decoders?: LD): { [K in keyof M]: BindFn<M[K], BindEncoders<LE>, BindDecoders<LD>>; };
|
package/lib/ffi/bind.js
CHANGED
|
@@ -72,7 +72,8 @@ function makeFn(proc, sig, encoders, decoders) {
|
|
|
72
72
|
throw new Error(`ffi-bind: ${sig}: by-value position <${name}> requires a struct ` +
|
|
73
73
|
`layout with size >= 1 (got ${codec.size}; non-struct codecs expose no size)`);
|
|
74
74
|
}
|
|
75
|
-
|
|
75
|
+
const plan = buildPlan(ps, encoders, decoders);
|
|
76
|
+
return (...a) => callPlan(proc, plan, a);
|
|
76
77
|
}
|
|
77
78
|
// 每个参数在 argFrame 里占的字节数。与 src/quickjs-ffi-type.h 的 qwin_ffi_arg_size[]
|
|
78
79
|
// 保持一致(ia32:≤4B 类型进 4 字节、更大的进 8 字节;x64:恒 8 字节槽)。
|
|
@@ -84,149 +85,212 @@ const IA32_ARG_SIZE = {
|
|
|
84
85
|
};
|
|
85
86
|
// x64 桩无条件双读 slots[0..3],故 argFrame 至少 4 个槽(32 字节)
|
|
86
87
|
const X64_MIN_SLOTS = 4;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
88
|
+
// os.arch 是编译期的进程指针宽度(见 worker.ts 同款说明),全程恒定:提到模块级,
|
|
89
|
+
// 别在每调路径上读属性。
|
|
90
|
+
const IS64 = os.arch === 'x64';
|
|
91
|
+
// 指针槽按二补位位模式写(读回的负值再回传时保真):setBigUint64/setUint32 内部即
|
|
92
|
+
// ToBigUint64/ToUint32,负值自然取模成全 1;QuickJS 只收 BigInt,故 x64 仍需 BigInt(v)。
|
|
93
|
+
// 宽度分支在模块加载时就定死,call 期零判断。
|
|
94
|
+
const writePtr = PTR_SIZE === 8
|
|
95
|
+
? (dv, off, v) => { dv.setBigUint64(off, BigInt(v), true); }
|
|
96
|
+
: (dv, off, v) => { dv.setUint32(off, v, true); };
|
|
97
|
+
// 返回槽读法:与 ctype.readScalar 的对应分支逐条等价,但按类型定型到闭包——readScalar
|
|
98
|
+
// 每次调用要走 token 字符串 switch,实测 ~0.5μs/call,是 0 参调用里最大的一笔。
|
|
99
|
+
// 指针位有符号读(对齐 C 版 JS_NewInt64):-1 哨兵等负值保真。
|
|
100
|
+
const RET_READER = {
|
|
101
|
+
u8: dv => dv.getUint8(0),
|
|
102
|
+
i8: dv => dv.getInt8(0),
|
|
103
|
+
u16: dv => dv.getUint16(0, true),
|
|
104
|
+
i16: dv => dv.getInt16(0, true),
|
|
105
|
+
u32: dv => dv.getUint32(0, true),
|
|
106
|
+
i32: dv => dv.getInt32(0, true),
|
|
107
|
+
u64: dv => dv.getBigUint64(0, true),
|
|
108
|
+
i64: dv => dv.getBigInt64(0, true),
|
|
109
|
+
f32: dv => dv.getFloat32(0, true),
|
|
110
|
+
f64: dv => dv.getFloat64(0, true),
|
|
111
|
+
ptr: PTR_SIZE === 8 ? dv => Number(dv.getBigInt64(0, true)) : dv => dv.getInt32(0, true),
|
|
112
|
+
};
|
|
113
|
+
/** 返回槽:所有 bind 出来的函数共用一个 8 字节缓冲。不变量:两个桩都在 call **之后**
|
|
114
|
+
* 才写 out,而 JS 在 ffiCall 返回后立刻由 RET_READER 读出——中间不得插入任何用户代码
|
|
115
|
+
* (decode 必须在 raw 取出之后)。故 native 期间的回调重入即使写了同一块缓冲,外层读到的
|
|
116
|
+
* 仍是本层自己的返回值(每一层自己的桩写都是该层读到前的最后一次写)。 */
|
|
117
|
+
const RET_DV = new DataView(new ArrayBuffer(8));
|
|
118
|
+
// 标量/指针标量槽写器按 token 定型(bind 期分派完,call 期只剩一次 DataView 写)。
|
|
119
|
+
// 32 位槽不显式 `>>> 0` / `Number()`:setUint32 内部即 ToUint32/ToNumber,逐位等价。
|
|
120
|
+
function mkScalarWriter(tok, off) {
|
|
121
|
+
switch (tok) {
|
|
122
|
+
// 所有 ≤4 字节槽一次 setUint32:小整数按 int 提升写(i8 = -1 必须给 0xFFFFFFFF),
|
|
123
|
+
// u32/i32 槽宽本就是 4 字节——ToUint32 取模让两种情形写法一致,合并成一档。
|
|
124
|
+
case 'u8':
|
|
125
|
+
case 'i8':
|
|
126
|
+
case 'u16':
|
|
127
|
+
case 'i16':
|
|
128
|
+
case 'u32':
|
|
129
|
+
case 'i32':
|
|
130
|
+
return (dv, v) => { dv.setUint32(off, v, true); };
|
|
131
|
+
case 'u64':
|
|
132
|
+
return (dv, v) => { dv.setBigUint64(off, v, true); };
|
|
133
|
+
case 'i64':
|
|
134
|
+
return (dv, v) => { dv.setBigInt64(off, v, true); };
|
|
135
|
+
case 'f32':
|
|
136
|
+
return (dv, v) => { dv.setFloat32(off, v, true); };
|
|
137
|
+
case 'f64':
|
|
138
|
+
return (dv, v) => { dv.setFloat64(off, v, true); };
|
|
139
|
+
// 指针标量槽只收 number(NULL 品牌零也是 number):类型层已排除 null/undefined,
|
|
140
|
+
// 这里不再运行时校验;带布局的 <N>ptr 走 mkPtrWriter,那边仍 fail-loud。
|
|
141
|
+
case 'ptr':
|
|
142
|
+
return (dv, v) => { writePtr(dv, off, v); };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// 指针位写器:number(裸地址 / NULL 品牌零 / 品牌指针)直通不重编码,其余交 codec 编码。
|
|
146
|
+
// 布局存在性保持「懒校验」——未注册布局的 <NOPE>ptr 只在真正调用时 throw,bind 期不炸。
|
|
147
|
+
function mkPtrWriter(name, codec, nonnull, sig, at, off, encoders) {
|
|
148
|
+
const encode = codec?.encode;
|
|
149
|
+
return (dv, arg) => {
|
|
150
|
+
if (typeof arg === 'number') {
|
|
151
|
+
if (nonnull && arg === 0)
|
|
152
|
+
throw new Error(`ffi-bind: ${sig}: ${at} <${name}>ptr! got 0 ` +
|
|
153
|
+
`(null pointer forbidden here; the token marks this position non-null)`);
|
|
154
|
+
writePtr(dv, off, arg);
|
|
155
|
+
return; // number(裸地址 / NULL / 品牌指针)直通,不进下面的 codec 分支
|
|
156
|
+
}
|
|
157
|
+
if (arg === null || arg === undefined) {
|
|
158
|
+
// JS null/undefined 不是指针词汇(空位只写 NULL)——fail-loud,不静默当 0。
|
|
159
|
+
throw new Error(`ffi-bind: <${name}>ptr got ${String(arg)}; ` +
|
|
160
|
+
`pass the imported NULL constant for an empty pointer`);
|
|
161
|
+
}
|
|
162
|
+
if (!codec) {
|
|
163
|
+
// 未知布局落到这:只收 NULL / 有效地址 / codec 值,不静默当 0。
|
|
164
|
+
throw new Error(name === ''
|
|
165
|
+
? `ffi-bind: <>ptr expects number (NULL | Ptr), got ${typeof arg}; use <BYTE>ptr with a PtrArrayBuffer (new PtrArrayBuffer(n)), or ffi.bufferPtr(buf) for an address`
|
|
166
|
+
: `ffi-bind: unknown layout "<${name}>ptr" (available: ${Object.keys(encoders).join(', ') || 'none'})`);
|
|
113
167
|
}
|
|
114
|
-
|
|
115
|
-
|
|
168
|
+
if (!encode)
|
|
169
|
+
throw new Error(`ffi-bind: <${name}>ptr has no encoder (out-only codec)`);
|
|
170
|
+
const buf = encode(arg);
|
|
171
|
+
// 类型层已保证 encode 返回 PtrArrayBuffer;这里再 fail-loud 兜一层
|
|
172
|
+
// (动态传入的无类型 codec 仍可能违反),然后直接取 .ptr——不再重算。
|
|
173
|
+
const p = buf.ptr;
|
|
174
|
+
if (typeof p !== 'number')
|
|
175
|
+
throw new Error('ffi-bind: codec.encode must return a PtrArrayBuffer, got plain ArrayBuffer');
|
|
176
|
+
if (nonnull && p === 0)
|
|
177
|
+
throw new Error(`ffi-bind: ${sig}: ${at} <${name}>ptr! encode produced 0 ` +
|
|
178
|
+
`(null pointer forbidden here)`);
|
|
179
|
+
writePtr(dv, off, p);
|
|
180
|
+
return buf;
|
|
116
181
|
};
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
const
|
|
182
|
+
}
|
|
183
|
+
// 按值位写器:x64 尺寸 ∈{1,2,4,8} 按「同尺寸整数」落槽(字节在槽低位,callee 右对齐读;
|
|
184
|
+
// 帧首分配即零填充,槽高位无歧义——复用帧同样成立,写器每调写入的区间恒定),
|
|
185
|
+
// 其余尺寸按引用传 caller 分配的对齐临时副本;ia32 结构字节原样压栈(任意尺寸)。
|
|
186
|
+
// 布局与尺寸已由 makeFn 在 bind 期校验。
|
|
187
|
+
function mkValWriter(name, codec, size, off) {
|
|
188
|
+
const encode = codec.encode;
|
|
189
|
+
const badArg = (arg) => {
|
|
190
|
+
throw new Error(`ffi-bind: <${name}> value position got ${String(arg)}; ` +
|
|
191
|
+
`pass a struct object (use <${name}>ptr to pass a pointer)`);
|
|
192
|
+
};
|
|
193
|
+
if (!IS64 || (size <= 8 && (size & (size - 1)) === 0))
|
|
194
|
+
// 帧由 dv 反查(SlotWriter 不再单传 frame):若将来 argFrame 走池化子视图,
|
|
195
|
+
// 这里要像 struct.ts 的字节访问那样补上 dv.byteOffset。
|
|
196
|
+
return (dv, arg) => {
|
|
197
|
+
if (arg === null || arg === undefined)
|
|
198
|
+
badArg(arg);
|
|
199
|
+
encode(arg, dv.buffer, off);
|
|
200
|
+
};
|
|
201
|
+
return (dv, arg) => {
|
|
202
|
+
if (arg === null || arg === undefined)
|
|
203
|
+
badArg(arg);
|
|
204
|
+
// 16 字节对齐自算(不依赖分配器的对齐保证);返回值让调用方 held 到最后
|
|
205
|
+
const buf = new ArrayBuffer(size + 15);
|
|
206
|
+
const base = ffi.bufferPtr(buf);
|
|
207
|
+
const pad = (16 - (base % 16)) % 16;
|
|
208
|
+
encode(arg, buf, pad);
|
|
209
|
+
writePtr(dv, off, base + pad);
|
|
210
|
+
return buf;
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
// bind 期算一次:槽偏移/写器/返回读法全部固化,call 期只剩搬运。
|
|
214
|
+
function buildPlan(ps, encoders, decoders) {
|
|
215
|
+
const { argTokens, retToken, sig } = ps;
|
|
216
|
+
const slots = [];
|
|
125
217
|
let off = 0;
|
|
126
218
|
for (let i = 0; i < argTokens.length; i++) {
|
|
127
219
|
const argToken = argTokens[i];
|
|
128
|
-
const
|
|
129
|
-
const arg = args[i];
|
|
220
|
+
const at = 'arg#' + String(i + 1);
|
|
130
221
|
if (isCPtrToken(argToken)) {
|
|
222
|
+
// '!' 留在 token 尾缀上(运行时要执法的语义跟着 token 走,无独立 flag)
|
|
131
223
|
const name = ptrName(argToken); // '' = <>ptr 裸地址档
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (nonnull && arg === 0)
|
|
136
|
-
throw new Error(`ffi-bind: ${sig}: arg#${i + 1} <${name}>ptr! got 0 ` +
|
|
137
|
-
`(null pointer forbidden here; the token marks this position non-null)`);
|
|
138
|
-
writeSlot(dv, off, { k: 'ptr', v: arg });
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
if (arg === null || arg === undefined) {
|
|
142
|
-
// JS null/undefined 不是指针词汇(空位只写 NULL)——fail-loud,不静默当 0。
|
|
143
|
-
throw new Error(`ffi-bind: <${name}>ptr got ${String(arg)}; ` +
|
|
144
|
-
`pass the imported NULL constant for an empty pointer`);
|
|
145
|
-
}
|
|
146
|
-
const codec = encoders[name];
|
|
147
|
-
if (!codec) {
|
|
148
|
-
// 未知布局落到这:只收 NULL / 有效地址 / codec 值,不静默当 0。
|
|
149
|
-
throw new Error(name === ''
|
|
150
|
-
? `ffi-bind: <>ptr expects number (NULL | Ptr), got ${typeof arg}; use <BYTE>ptr with a PtrArrayBuffer (new PtrArrayBuffer(n)), or ffi.bufferPtr(buf) for an address`
|
|
151
|
-
: `ffi-bind: unknown layout "<${name}>ptr" (available: ${Object.keys(encoders).join(', ') || 'none'})`);
|
|
152
|
-
}
|
|
153
|
-
if (!codec.encode) {
|
|
154
|
-
// codec 存在但只声明了 decode/alloc(只读出),入参位无序列化可用。
|
|
155
|
-
throw new Error(`ffi-bind: <${name}>ptr has no encoder (out-only codec)`);
|
|
156
|
-
}
|
|
157
|
-
const buf = codec.encode(arg);
|
|
158
|
-
held.push(buf);
|
|
159
|
-
// 类型层已保证 encode 返回 PtrArrayBuffer;这里再 fail-loud 兜一层
|
|
160
|
-
// (动态传入的无类型 codec 仍可能违反),然后直接取 .ptr——不再重算。
|
|
161
|
-
const p = buf.ptr;
|
|
162
|
-
if (typeof p !== 'number')
|
|
163
|
-
throw new Error('ffi-bind: codec.encode must return a PtrArrayBuffer, got plain ArrayBuffer');
|
|
164
|
-
if (nonnull && p === 0)
|
|
165
|
-
throw new Error(`ffi-bind: ${sig}: arg#${i + 1} <${name}>ptr! encode produced 0 ` +
|
|
166
|
-
`(null pointer forbidden here)`);
|
|
167
|
-
writeSlot(dv, off, { k: 'ptr', v: p });
|
|
168
|
-
}
|
|
224
|
+
slots.push(mkPtrWriter(name, encoders[name], argToken.endsWith('!'), sig, at, off, encoders));
|
|
225
|
+
off += IS64 ? 8 : PTR_SIZE;
|
|
226
|
+
continue;
|
|
169
227
|
}
|
|
170
|
-
|
|
171
|
-
// 按值位:对象直传(值域由注册布局推导),无地址词汇 —— NULL / number
|
|
172
|
-
// 进这里是必然错位(引擎按值语义,指针位请写 <N>ptr),fail-loud。
|
|
228
|
+
if (isCValToken(argToken)) {
|
|
173
229
|
const name = valName(argToken);
|
|
230
|
+
// makeFn 已校验:布局存在、是结构(有 size)且 size >= 1
|
|
174
231
|
const codec = valCodec(name, encoders);
|
|
175
|
-
if (!codec)
|
|
176
|
-
throw new Error(`ffi-bind: ${sig}: arg#${i + 1} <${name}> by-value position ` +
|
|
177
|
-
`has no registered layout`);
|
|
178
|
-
if (arg === null || arg === undefined)
|
|
179
|
-
throw new Error(`ffi-bind: <${name}> value position got ${String(arg)}; ` +
|
|
180
|
-
`pass a struct object (use <${name}>ptr to pass a pointer)`);
|
|
181
232
|
const size = codec.size;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
codec.encode(arg, argFrame, off);
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
// x64 其余尺寸(>8 或非 2 的幂)按引用:槽放指针,指向 caller 分配的
|
|
190
|
-
// 16 字节对齐临时副本(MS x64 官方规则:单参数永不跨多个寄存器;对齐
|
|
191
|
-
// 偏移自算,不依赖分配器的对齐保证)。held 保持引用直到调用返回。
|
|
192
|
-
const buf = new ArrayBuffer(size + 15);
|
|
193
|
-
const base = ffi.bufferPtr(buf);
|
|
194
|
-
const pad = (16 - (base % 16)) % 16;
|
|
195
|
-
codec.encode(arg, buf, pad);
|
|
196
|
-
held.push(buf);
|
|
197
|
-
writeSlot(dv, off, { k: 'ptr', v: base + pad });
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
-
writeSlot(dv, off, { k: argToken, v: arg });
|
|
233
|
+
slots.push(mkValWriter(name, codec, size, off));
|
|
234
|
+
// ia32 = ALIGN(size, 4):结构字节原样压栈(libffi ffi.c 的 ia32 分支同规则)
|
|
235
|
+
off += IS64 ? 8 : (size % 4 === 0 ? size : size + 4 - (size % 4));
|
|
236
|
+
continue;
|
|
202
237
|
}
|
|
203
|
-
|
|
238
|
+
slots.push(mkScalarWriter(argToken, off));
|
|
239
|
+
off += IS64 ? 8 : IA32_ARG_SIZE[argToken];
|
|
204
240
|
}
|
|
205
|
-
|
|
241
|
+
// x64 头 4 槽恒被桩读取,帧不足 4 槽时补零到 32 字节
|
|
242
|
+
const total = IS64 && argTokens.length < X64_MIN_SLOTS ? X64_MIN_SLOTS * 8 : off;
|
|
243
|
+
const ret = cTokenToType(retToken); // 按值形返回已在 parseSig 拒掉
|
|
206
244
|
const retIsFp = retToken === 'f32' || retToken === 'f64';
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (isCPtrToken(retToken) && raw !== 0) {
|
|
245
|
+
// 返回位 '!' = 「C 保证非零」的作者担保,标错由这里当场 throw 兜底(显式崩溃而非静默流窜)
|
|
246
|
+
const zeroMsg = retToken.endsWith('!')
|
|
247
|
+
? `ffi-bind: ${sig}: return ${retToken} got 0 (the token promises C never returns null here)`
|
|
248
|
+
: null;
|
|
249
|
+
// 返回位自动解码:品牌指针且该键声明了 decode → 交还 JS 值;空指针 0 / 裸 <>ptr /
|
|
250
|
+
// 无 decode 的键原样返回。
|
|
251
|
+
let decode = null;
|
|
252
|
+
if (isCPtrToken(retToken)) {
|
|
216
253
|
const name = ptrName(retToken);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
return codec.decode(raw);
|
|
221
|
-
}
|
|
254
|
+
const dec = name !== '' ? decoders[name]?.decode : undefined;
|
|
255
|
+
if (dec)
|
|
256
|
+
decode = (raw) => dec(raw);
|
|
222
257
|
}
|
|
223
|
-
|
|
258
|
+
const read = ret === 'void'
|
|
259
|
+
? () => undefined
|
|
260
|
+
: () => {
|
|
261
|
+
// 返槽读法按类型定型(RET_READER),不再走 readScalar 的 token 字符串 switch
|
|
262
|
+
const raw = RET_READER[ret](RET_DV);
|
|
263
|
+
if (zeroMsg !== null && raw === 0)
|
|
264
|
+
throw new Error(zeroMsg);
|
|
265
|
+
if (decode !== null && raw !== 0)
|
|
266
|
+
return decode(raw);
|
|
267
|
+
return raw;
|
|
268
|
+
};
|
|
269
|
+
return { total, slots, retIsFp, read, dv: null };
|
|
224
270
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
271
|
+
function callPlan(proc, plan, args) {
|
|
272
|
+
const slots = plan.slots;
|
|
273
|
+
const n = slots.length;
|
|
274
|
+
// 参数帧复用(见 CallPlan.dv):首调分配,其后原样重用——写入区间恒定,无需清零。
|
|
275
|
+
let dv = plan.dv;
|
|
276
|
+
if (dv === null) {
|
|
277
|
+
dv = new DataView(new ArrayBuffer(plan.total));
|
|
278
|
+
plan.dv = dv;
|
|
279
|
+
}
|
|
280
|
+
// codec 编码缓冲要活到 read 之后(decode 可能顺着回传的指针读调用方缓冲):靠这个
|
|
281
|
+
// 局部变量存活,返回即不可达、交 GC 回收。原先恒分配空数组,无编码参数时这次省掉。
|
|
282
|
+
let held = null;
|
|
283
|
+
for (let i = 0; i < n; i++) {
|
|
284
|
+
const h = slots[i](dv, args[i]);
|
|
285
|
+
if (h !== undefined) {
|
|
286
|
+
if (held === null)
|
|
287
|
+
held = [h];
|
|
288
|
+
else
|
|
289
|
+
held.push(h);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
ffi.ffiCall(proc, dv.buffer, RET_DV.buffer, plan.retIsFp ? 1 : 0);
|
|
293
|
+
return plan.read();
|
|
230
294
|
}
|
|
231
295
|
// 内建 codec:类型校验放在这里,与「用户布局 codec」同一分派点。
|
|
232
296
|
// BYTE/WCHAR 拒绝裸 number(无句柄可 pin),提示改用 <>ptr。
|
|
@@ -295,7 +359,13 @@ const builtinCodecs = { WCHAR, BYTE };
|
|
|
295
359
|
* encoders 喂入参位(encode 值域 → 形参类型),decoders 喂返回位(decode 值域
|
|
296
360
|
* → 返回 V | null,替代 Ptr | null)。decoders 缺省时回退到 encoders——双向 codec
|
|
297
361
|
* 只需传一次(与运行时 `decoders ?? encoders` 同构:都只在实参缺省时生效,
|
|
298
|
-
* 显式传空对象不 fallback)。内建 WCHAR/BYTE 无条件参与两张表。
|
|
362
|
+
* 显式传空对象不 fallback)。内建 WCHAR/BYTE 无条件参与两张表。
|
|
363
|
+
*
|
|
364
|
+
* codec 契约(参数帧复用的前提,调用方须遵守):
|
|
365
|
+
* 1. encode/decode 内不得回调任何已 bind 的函数。参数帧按签名只分配一次并逐调复用,
|
|
366
|
+
* 回调重入发生在本层 ffiCall 之前,会把本层已写好的槽抹掉(静默错参)。
|
|
367
|
+
* 2. encode(v, buf, off) 必须写满槽宽:少写会残留上一调的字节。
|
|
368
|
+
* 输入校验/转码若需要调 Win32 API,请在调用 bind 出的函数**之前**做。 */
|
|
299
369
|
export function bind(dll, name, sig, encoders, decoders) {
|
|
300
370
|
const proc = ffi.GetProcAddress(loadDll(dll), name);
|
|
301
371
|
if (!proc)
|
package/lib/ffi/ctype.d.ts
CHANGED
|
@@ -99,7 +99,10 @@ type Entry = {
|
|
|
99
99
|
};
|
|
100
100
|
}[C_BasicType_No_Void];
|
|
101
101
|
/** 内存写:按 sizeof 精确写,供 struct 字段编码用。小整数只占自己的字节,
|
|
102
|
-
* 否则 `{a:u8,b:u8}` 这类紧凑布局会被 setUint32 越界覆盖相邻字段。
|
|
102
|
+
* 否则 `{a:u8,b:u8}` 这类紧凑布局会被 setUint32 越界覆盖相邻字段。
|
|
103
|
+
* 32 位槽不显式 `>>> 0` / `Number()`:setUint32/setUint8… 内部即 ToUint32/ToNumber,
|
|
104
|
+
* 逐位等价;只有给这些槽塞 BigInt 会从「静默截断」变成 TypeError(类型层本就禁它)。
|
|
105
|
+
* 指针槽的取模见下(setBigUint64 内部即 ToBigUint64)。 */
|
|
103
106
|
export declare function writeScalar(dv: DataView, off: number, { k, v }: Entry): void;
|
|
104
107
|
/** 槽写:供 bind 的参数槽/返回槽用。小整数扩展到 4 字节 —— 可变参数按 int 提升读,
|
|
105
108
|
* `i8 = -1` 必须给 `0xFFFFFFFF` 而非 `0x000000FF`。u32/i32/u64/i64/f32/f64/ptr
|
package/lib/ffi/ctype.js
CHANGED
|
@@ -121,26 +121,29 @@ export function readScalar(dv, off, k) {
|
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
123
|
/** 内存写:按 sizeof 精确写,供 struct 字段编码用。小整数只占自己的字节,
|
|
124
|
-
* 否则 `{a:u8,b:u8}` 这类紧凑布局会被 setUint32 越界覆盖相邻字段。
|
|
124
|
+
* 否则 `{a:u8,b:u8}` 这类紧凑布局会被 setUint32 越界覆盖相邻字段。
|
|
125
|
+
* 32 位槽不显式 `>>> 0` / `Number()`:setUint32/setUint8… 内部即 ToUint32/ToNumber,
|
|
126
|
+
* 逐位等价;只有给这些槽塞 BigInt 会从「静默截断」变成 TypeError(类型层本就禁它)。
|
|
127
|
+
* 指针槽的取模见下(setBigUint64 内部即 ToBigUint64)。 */
|
|
125
128
|
export function writeScalar(dv, off, { k, v }) {
|
|
126
129
|
switch (k) {
|
|
127
130
|
case 'u8':
|
|
128
|
-
dv.setUint8(off,
|
|
131
|
+
dv.setUint8(off, v);
|
|
129
132
|
break;
|
|
130
133
|
case 'i8':
|
|
131
|
-
dv.setInt8(off,
|
|
134
|
+
dv.setInt8(off, v);
|
|
132
135
|
break;
|
|
133
136
|
case 'u16':
|
|
134
|
-
dv.setUint16(off,
|
|
137
|
+
dv.setUint16(off, v, true);
|
|
135
138
|
break;
|
|
136
139
|
case 'i16':
|
|
137
|
-
dv.setInt16(off,
|
|
140
|
+
dv.setInt16(off, v, true);
|
|
138
141
|
break;
|
|
139
142
|
case 'u32':
|
|
140
|
-
dv.setUint32(off,
|
|
143
|
+
dv.setUint32(off, v, true);
|
|
141
144
|
break;
|
|
142
145
|
case 'i32':
|
|
143
|
-
dv.setUint32(off,
|
|
146
|
+
dv.setUint32(off, v, true);
|
|
144
147
|
break;
|
|
145
148
|
case 'u64':
|
|
146
149
|
dv.setBigUint64(off, v, true);
|
|
@@ -149,22 +152,23 @@ export function writeScalar(dv, off, { k, v }) {
|
|
|
149
152
|
dv.setBigInt64(off, v, true);
|
|
150
153
|
break;
|
|
151
154
|
case 'f32':
|
|
152
|
-
dv.setFloat32(off,
|
|
155
|
+
dv.setFloat32(off, v, true);
|
|
153
156
|
break;
|
|
154
157
|
case 'f64':
|
|
155
|
-
dv.setFloat64(off,
|
|
158
|
+
dv.setFloat64(off, v, true);
|
|
156
159
|
break;
|
|
157
160
|
case 'ptr': {
|
|
158
161
|
// JS null/undefined 不是指针词汇(空位写 NULL)——fail-loud,不做 null→0 转换。
|
|
159
162
|
if (v == null) {
|
|
160
163
|
throw new Error('ffi: got null/undefined for a ptr slot; pass the imported NULL constant for empty');
|
|
161
164
|
}
|
|
162
|
-
// 读回的负值(LRESULT/句柄)再作参数回传时按二补位位模式写:
|
|
163
|
-
//
|
|
165
|
+
// 读回的负值(LRESULT/句柄)再作参数回传时按二补位位模式写:setBigUint64 /
|
|
166
|
+
// setUint32 内部即 ToBigUint64 / ToUint32,-1n 与负 number 都自然取模成全 1。
|
|
167
|
+
// QuickJS 的 setBigUint64 只收 BigInt(传 number 会 TypeError),故仍需 BigInt(v)。
|
|
164
168
|
if (PTR_SIZE === 8)
|
|
165
|
-
dv.setBigUint64(off, BigInt
|
|
169
|
+
dv.setBigUint64(off, BigInt(v), true);
|
|
166
170
|
else
|
|
167
|
-
dv.setUint32(off, v
|
|
171
|
+
dv.setUint32(off, v, true);
|
|
168
172
|
break;
|
|
169
173
|
}
|
|
170
174
|
}
|
|
@@ -175,7 +179,7 @@ export function writeScalar(dv, off, { k, v }) {
|
|
|
175
179
|
export function writeSlot(dv, off, e) {
|
|
176
180
|
const { k, v } = e;
|
|
177
181
|
if (k === 'u8' || k === 'i8' || k === 'u16' || k === 'i16') {
|
|
178
|
-
dv.setUint32(off,
|
|
182
|
+
dv.setUint32(off, v, true);
|
|
179
183
|
return;
|
|
180
184
|
}
|
|
181
185
|
writeScalar(dv, off, e);
|
package/package.json
CHANGED
package/qwin-nowasm-x86.exe
CHANGED
|
Binary file
|
package/qwin-nowasm.exe
CHANGED
|
Binary file
|
package/qwin-x86.exe
CHANGED
|
Binary file
|
package/qwin.exe
CHANGED
|
Binary file
|