quickwin 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.en.md +8 -8
  2. package/README.md +6 -7
  3. package/examples/{exec_server.js → execd.js} +618 -228
  4. package/examples/execd.tsx +798 -0
  5. package/examples/execd_worker.js +122 -0
  6. package/examples/execd_worker.ts +127 -0
  7. package/examples/pdf_viewer.js +100 -111
  8. package/examples/test_gallery.js +100 -111
  9. package/examples/test_react_lazy.js +96 -109
  10. package/examples/test_react_listbox.js +96 -109
  11. package/examples/test_root_window.js +96 -109
  12. package/examples/test_tab.js +96 -109
  13. package/lib/ffi/ctype.ts +3 -1
  14. package/lib/ffi/struct.js +23 -7
  15. package/lib/ffi/struct.ts +50 -23
  16. package/lib/http-server.js +1 -1
  17. package/lib/http-server.ts +1 -1
  18. package/lib/windows/gdi32.js +1 -1
  19. package/lib/windows/gdi32.ts +1 -1
  20. package/lib/windows/structs.js +176 -92
  21. package/lib/windows/structs.ts +216 -92
  22. package/package.json +1 -1
  23. package/quickwin_const.d.ts +26 -0
  24. package/qwin-nowasm-x86.exe +0 -0
  25. package/qwin-nowasm.exe +0 -0
  26. package/qwin-x86.exe +0 -0
  27. package/qwin.exe +0 -0
  28. package/test/test_ffi_bind.js +5 -3
  29. package/test/test_ffi_bind.ts +6 -4
  30. package/test/test_ffi_struct.js +58 -24
  31. package/test/test_ffi_struct.ts +55 -25
  32. package/test/test_gdi_draw.js +1 -1
  33. package/test/test_gdi_draw.ts +1 -1
  34. package/test/test_react_complex.js +96 -109
  35. package/test/test_react_counter.js +96 -109
  36. package/test/test_react_flex.js +96 -109
  37. package/test/test_react_hidden.js +96 -109
  38. package/test/test_react_listview_link.js +96 -109
  39. package/test/test_react_notify.js +96 -109
  40. package/test/test_react_render.js +96 -109
  41. package/examples/exec_server.tsx +0 -489
  42. package/examples/exec_server_worker.js +0 -395
  43. package/examples/exec_server_worker.ts +0 -417
  44. package/examples/setres.js +0 -79
  45. package/examples/setres.ts +0 -86
package/lib/ffi/ctype.ts CHANGED
@@ -106,7 +106,9 @@ type First =
106
106
  // 对象值的唯一判别键是 '#'(多字段/聚合元素无法用糖表达,故保留)
107
107
  // '#' 必填(无「裸嵌套 map 默认 struct」):漏写 '#' 时 union 不会静默翻转成 struct,
108
108
  // 类型层直接报错、运行时 throw。顶层 struct()/union() 例外 —— kind 由函数名表明,
109
- // 参数是平铺字段表(不含 '#'),'#'/`#pack` 由构造器写入 __struct。
109
+ // 参数是平铺字段表(本身不含 '#';DECL 常量直传时 '#' 须与函数名 kind 一致 —— 参数
110
+ // 类型交叉 `{ '#'?: 'struct' }` / `{ '#'?: 'union' }` 编译期锁),'#'/`#pack` 由构造器
111
+ // 写入声明(buildDecl)。
110
112
  // ============================================================
111
113
 
112
114
  // token 值面:规范 token(C_Number 档)∪ 指针形(含 '!' 非空尾缀)∪ '@枚举标注形'
package/lib/ffi/struct.js CHANGED
@@ -350,7 +350,7 @@ function doEncode(dv, base, fields, v) {
350
350
  function createStruct(t) {
351
351
  const { fields, size, maxEffectiveAlign } = computeStructLayout(t);
352
352
  return {
353
- __struct: t,
353
+ fields,
354
354
  size,
355
355
  structAlign: maxEffectiveAlign,
356
356
  decode: (p) => {
@@ -389,10 +389,26 @@ function createStruct(t) {
389
389
  },
390
390
  };
391
391
  }
392
- /** 平铺字段表 → '#' 声明:'#'/`#pack` 由构造器写入(顶层 kind 由函数名表明,字段表本身
393
- * 不收指令键)。member 里混进的 '#' 前缀键在此剔除 —— 类型层已禁(fresh literal excess
394
- * check),这里运行时兜底,避免用户键覆盖构造器写入的 '#'/`#pack`。 */
392
+ /** 平铺字段表 → '#' 声明:'#'/`#pack` 由构造器写入(顶层 kind 由函数名表明)。
393
+ * member 里混进的其他 '#' 前缀键在此剔除 —— fresh literal 由 excess check 拒,这里
394
+ * 运行时兜底,避免用户键覆盖构造器写入的 '#'/`#pack`。
395
+ * 两处守卫(DECL 常量直传形态,`struct('X', X_DECL)`):
396
+ * - member 自带 '#' 时须与函数名 kind 一致,否则 throw。TS 侧 overload 参数收
397
+ * `M & { '#'?: 'struct' }`(union 镜像),'#' 与函数名 kind 不符在编译期就交叉成
398
+ * never 报错;此 throw 是 JS / `as any` / 动态构造 DECL 调用方的运行时兜底 ——
399
+ * union DECL 误传 struct() 会静默翻转成 struct 布局,是最难查的一类错;
400
+ * - '#pack' 透传(剥键即丢,pack 布局静默变自然对齐):opts.pack 未给时取 member 的,
401
+ * 两者都给且不等则 throw —— 跨参数的相等约束类型层表达不了,只能运行时校。 */
395
402
  function buildDecl(kind, member, pack) {
403
+ const declKind = member['#'];
404
+ if (declKind !== undefined && declKind !== kind)
405
+ throw new Error(`ffi-struct: ${kind}() received a '${String(declKind)}' declaration ` +
406
+ `(kind mismatch — use ${declKind}() for it)`);
407
+ const declPack = member['#pack'];
408
+ if (pack === undefined && declPack !== undefined)
409
+ pack = declPack;
410
+ else if (pack !== undefined && declPack !== undefined && pack !== declPack)
411
+ throw new Error(`ffi-struct: #pack conflict: opts.pack=${pack} vs declaration ${String(declPack)}`);
396
412
  const decl = { '#': kind };
397
413
  if (pack !== undefined)
398
414
  decl['#pack'] = pack;
@@ -418,10 +434,10 @@ function buildDecl(kind, member, pack) {
418
434
  * - 裸地址形:count 必填(地址无长度信息),逐字节拷读(def.decode 的 number 形
419
435
  * 同机制);长度不可校 → 信任 C 的 count。
420
436
  * - count = 0 → [](读 0 个真空安全,与 encode 禁空的 PAB(0) 理由刻意不对称)。
421
- * 实现:encode 走纯公开 API(alloc + encode offset 循环);decode 在本文件内复用
422
- * computeStructLayout/doDecode。 */
437
+ * 实现:encode 走纯公开 API(alloc + encode offset 循环);decode 直接读 def.fields
438
+ * (createStruct 暴露的布局 IR,与 def.decode 同一份,无需重算)。 */
423
439
  export function structArray(def) {
424
- const { fields, size } = computeStructLayout(def.__struct);
440
+ const { fields, size } = def;
425
441
  const readN = (dv, n) => {
426
442
  const out = [];
427
443
  for (let i = 0; i < n; i++)
package/lib/ffi/struct.ts CHANGED
@@ -204,7 +204,10 @@ export type Fields = Field[]
204
204
  // N = 布局品牌(struct(name, …) 传入):decode 入参与 alloc().ptr 同品牌。默认 '' ——
205
205
  // `Ptr<''>` 归一裸 number(与旧 never 守卫结果相同),品牌化 def 未来收窄时签名不用再动。
206
206
  export type StructDef<T extends { '#': 'struct' | 'union' }, N extends string = ''> = {
207
- readonly __struct: T
207
+ // 布局 IR 直接暴露(原 __struct 成员读已删:成员读是可观察副作用,esbuild 无法把
208
+ // 「PURE 调用 + X.__struct 实参」与 X 的定义一起摇掉 —— DECL 常量(对象字面量)走
209
+ // 标识符引用则整链可摇。T 只活在 decode/encode 的类型签名里,运行时不挂成员)。
210
+ readonly fields: Fields
208
211
  readonly size: number
209
212
  readonly structAlign: number
210
213
  decode(buf: ArrayBuffer | Ptr<N>): ShapeOfC<T>
@@ -593,7 +596,7 @@ function doEncode(dv: DataView, base: number, fields: Fields, v: Record<string,
593
596
  function createStruct(t: C_Struct | C_Union): any {
594
597
  const { fields, size, maxEffectiveAlign } = computeStructLayout(t)
595
598
  return {
596
- __struct: t,
599
+ fields,
597
600
  size,
598
601
  structAlign: maxEffectiveAlign,
599
602
  decode: (p: ArrayBuffer | number) => {
@@ -638,10 +641,25 @@ function createStruct(t: C_Struct | C_Union): any {
638
641
  /** 聚合对齐上限(pack 压 maxAlign,语义同 MSVC #pragma pack)。 */
639
642
  export type AggOpts = { pack?: number }
640
643
 
641
- /** 平铺字段表 → '#' 声明:'#'/`#pack` 由构造器写入(顶层 kind 由函数名表明,字段表本身
642
- * 不收指令键)。member 里混进的 '#' 前缀键在此剔除 —— 类型层已禁(fresh literal excess
643
- * check),这里运行时兜底,避免用户键覆盖构造器写入的 '#'/`#pack`。 */
644
+ /** 平铺字段表 → '#' 声明:'#'/`#pack` 由构造器写入(顶层 kind 由函数名表明)。
645
+ * member 里混进的其他 '#' 前缀键在此剔除 —— fresh literal 由 excess check 拒,这里
646
+ * 运行时兜底,避免用户键覆盖构造器写入的 '#'/`#pack`。
647
+ * 两处守卫(DECL 常量直传形态,`struct('X', X_DECL)`):
648
+ * - member 自带 '#' 时须与函数名 kind 一致,否则 throw。TS 侧 overload 参数收
649
+ * `M & { '#'?: 'struct' }`(union 镜像),'#' 与函数名 kind 不符在编译期就交叉成
650
+ * never 报错;此 throw 是 JS / `as any` / 动态构造 DECL 调用方的运行时兜底 ——
651
+ * union DECL 误传 struct() 会静默翻转成 struct 布局,是最难查的一类错;
652
+ * - '#pack' 透传(剥键即丢,pack 布局静默变自然对齐):opts.pack 未给时取 member 的,
653
+ * 两者都给且不等则 throw —— 跨参数的相等约束类型层表达不了,只能运行时校。 */
644
654
  function buildDecl(kind: 'struct' | 'union', member: Record<string, unknown>, pack?: number): C_Struct | C_Union {
655
+ const declKind = member['#']
656
+ if (declKind !== undefined && declKind !== kind)
657
+ throw new Error(`ffi-struct: ${kind}() received a '${String(declKind)}' declaration ` +
658
+ `(kind mismatch — use ${declKind}() for it)`)
659
+ const declPack = member['#pack']
660
+ if (pack === undefined && declPack !== undefined) pack = declPack as number
661
+ else if (pack !== undefined && declPack !== undefined && pack !== declPack)
662
+ throw new Error(`ffi-struct: #pack conflict: opts.pack=${pack} vs declaration ${String(declPack)}`)
645
663
  const decl: Record<string, unknown> = { '#': kind }
646
664
  if (pack !== undefined) decl['#pack'] = pack
647
665
  for (const [k, v] of Object.entries(member))
@@ -649,11 +667,19 @@ function buildDecl(kind: 'struct' | 'union', member: Record<string, unknown>, pa
649
667
  return decl as unknown as C_Struct | C_Union
650
668
  }
651
669
 
670
+ /** def 的类型经方法签名索引提取(StructDef 不再挂 __struct 成员 —— T 只活在签名里):
671
+ * encode 重载末条首参 = EncodeIn<T>、decode 返回 = ShapeOfC<T>、alloc 返回 = PAB<N>、
672
+ * decode 首参剥 ArrayBuffer = Ptr<N>(裸地址形)。 */
673
+ type EncInOf<D extends StructDef<any, any>> = Parameters<D['encode']>[0]
674
+ type DecOf<D extends StructDef<any, any>> = ReturnType<D['decode']>
675
+ type PABOf<D extends StructDef<any, any>> = ReturnType<D['alloc']>
676
+ type AddrOf<D extends StructDef<any, any>> = Exclude<Parameters<D['decode']>[0], ArrayBuffer>
677
+
652
678
  /** decode 双态重载:有 byteLength 的 buffer 形 count 可缺省(自算);裸地址无长度
653
679
  * 信息,count 必填(类型层拦,缺省调用编译红)。 */
654
- type StructArrayDecode<T extends { '#': 'struct' | 'union' }, N extends string> = {
655
- (p: ArrayBuffer, count?: number): ShapeOfC<T>[]
656
- (p: Ptr<N>, count: number): ShapeOfC<T>[]
680
+ type StructArrayDecode<D extends StructDef<any, any>> = {
681
+ (p: ArrayBuffer, count?: number): DecOf<D>[]
682
+ (p: AddrOf<D>, count: number): DecOf<D>[]
657
683
  }
658
684
 
659
685
  /** 结构/联合体的数组编解码对工厂:返回 `{ encode, decode }`。
@@ -673,25 +699,23 @@ type StructArrayDecode<T extends { '#': 'struct' | 'union' }, N extends string>
673
699
  * - 裸地址形:count 必填(地址无长度信息),逐字节拷读(def.decode 的 number 形
674
700
  * 同机制);长度不可校 → 信任 C 的 count。
675
701
  * - count = 0 → [](读 0 个真空安全,与 encode 禁空的 PAB(0) 理由刻意不对称)。
676
- * 实现:encode 走纯公开 API(alloc + encode offset 循环);decode 在本文件内复用
677
- * computeStructLayout/doDecode。 */
678
- export function structArray<T extends { '#': 'struct' | 'union' }, N extends string = ''>(
679
- def: StructDef<T, N>,
680
- ) {
681
- const { fields, size } = computeStructLayout(def.__struct)
702
+ * 实现:encode 走纯公开 API(alloc + encode offset 循环);decode 直接读 def.fields
703
+ * (createStruct 暴露的布局 IR,与 def.decode 同一份,无需重算)。 */
704
+ export function structArray<D extends StructDef<any, any>>(def: D) {
705
+ const { fields, size } = def
682
706
  const readN = (dv: DataView, n: number): unknown[] => {
683
707
  const out: unknown[] = []
684
708
  for (let i = 0; i < n; i++) out.push(doDecode(dv, i * size, fields))
685
709
  return out
686
710
  }
687
711
  return {
688
- encode: (els: readonly EncodeIn<T>[]): PtrArrayBuffer<N> => {
712
+ encode: (els: readonly EncInOf<D>[]): PABOf<D> => {
689
713
  if (!Array.isArray(els) || els.length === 0)
690
714
  throw new Error(`ffi-struct: structArray(def).encode(els) requires a ` +
691
715
  `non-empty element array (pass NULL for an empty pointer position)`)
692
716
  const out = def.alloc(els.length)
693
717
  for (let i = 0; i < els.length; i++) def.encode(els[i], out, i * def.size)
694
- return out
718
+ return out as PABOf<D>
695
719
  },
696
720
  decode: ((p: ArrayBuffer | number, count?: number): unknown[] => {
697
721
  // fail-loud 校验矩阵(见 JSDoc):显式 count 信调用方但拦越界;缺省只对
@@ -725,16 +749,18 @@ export function structArray<T extends { '#': 'struct' | 'union' }, N extends str
725
749
  }
726
750
  if (n === 0) return []
727
751
  return readN(new DataView(p), n)
728
- }) as StructArrayDecode<T, N>,
752
+ }) as StructArrayDecode<D>,
729
753
  }
730
754
  }
731
755
 
732
756
  /** 平铺字段表定义结构体(token/`X[n]`/`unit:width`/`'#'` 声明均可,见 ctype.ts §4)。
733
- * 给 name 则 alloc().ptr 与 decode 入参带 Ptr<name> 品牌。 */
757
+ * 给 name 则 alloc().ptr 与 decode 入参带 Ptr<name> 品牌。DECL 常量可直传(
758
+ * `struct('X', X_DECL)`)—— 参数交叉 `{ '#'?: 'struct' }` 锁 kind:传 union/array
759
+ * DECL 时 '#' 字面类型交叉成 never,编译期即报错。 */
734
760
  type StructOf<M, N extends string = ''> = StructDef<{ '#': 'struct', '#pack'?: number } & M, N>
735
761
  type UnionOf<M, N extends string = ''> = StructDef<{ '#': 'union', '#pack'?: number } & M, N>
736
- export function struct<const M extends SimpleMember>(member: M, opts?: AggOpts): StructOf<M>
737
- export function struct<const N extends string, const M extends SimpleMember>(name: N, member: M, opts?: AggOpts): StructOf<M, N>
762
+ export function struct<const M extends SimpleMember>(member: M & { '#'?: 'struct' }, opts?: AggOpts): StructOf<M>
763
+ export function struct<const N extends string, const M extends SimpleMember>(name: N, member: M & { '#'?: 'struct' }, opts?: AggOpts): StructOf<M, N>
738
764
  export function struct(a: string | SimpleMember, b?: SimpleMember | AggOpts, c?: AggOpts): any {
739
765
  const isNamed = typeof a === 'string'
740
766
  const raw = (isNamed ? b : a) as Record<string, unknown>
@@ -742,9 +768,10 @@ export function struct(a: string | SimpleMember, b?: SimpleMember | AggOpts, c?:
742
768
  return createStruct(buildDecl('struct', raw, opts?.pack))
743
769
  }
744
770
 
745
- /** 平铺字段表定义联合体;布局/读写与 struct 相同,仅成员偏移重叠。 */
746
- export function union<const M extends SimpleMember>(member: M, opts?: AggOpts): UnionOf<M>
747
- export function union<const N extends string, const M extends SimpleMember>(name: N, member: M, opts?: AggOpts): UnionOf<M, N>
771
+ /** 平铺字段表定义联合体;布局/读写与 struct 相同,仅成员偏移重叠。DECL 常量可直传,
772
+ * 参数交叉 `{ '#'?: 'union' }` 锁 kind(struct 侧镜像,理由见上)。 */
773
+ export function union<const M extends SimpleMember>(member: M & { '#'?: 'union' }, opts?: AggOpts): UnionOf<M>
774
+ export function union<const N extends string, const M extends SimpleMember>(name: N, member: M & { '#'?: 'union' }, opts?: AggOpts): UnionOf<M, N>
748
775
  export function union(a: string | SimpleMember, b?: SimpleMember | AggOpts, c?: AggOpts): any {
749
776
  const isNamed = typeof a === 'string'
750
777
  const raw = (isNamed ? b : a) as Record<string, unknown>
@@ -342,7 +342,7 @@ async function writeStreamingBody(c, body, chunked, trailer) {
342
342
  }
343
343
  if (c.closed) {
344
344
  // 客户端断开(FD_CLOSE / send 失败):取消响应体,让 producer 感知
345
- //(exec_server 借此 kill 子进程),而不是干等它自然结束。
345
+ //(execd 借此 kill 子进程),而不是干等它自然结束。
346
346
  try {
347
347
  await reader.cancel();
348
348
  }
@@ -357,7 +357,7 @@ async function writeStreamingBody(c: Conn, body: ReadableStream<Uint8Array> | nu
357
357
  }
358
358
  if (c.closed) {
359
359
  // 客户端断开(FD_CLOSE / send 失败):取消响应体,让 producer 感知
360
- //(exec_server 借此 kill 子进程),而不是干等它自然结束。
360
+ //(execd 借此 kill 子进程),而不是干等它自然结束。
361
361
  try { await reader.cancel() } catch (e) {
362
362
  console.error('[http-server] body cancel error:', e)
363
363
  }
@@ -12,7 +12,7 @@ import { LOGFONTW, POINT, RECT } from './structs.js';
12
12
  const b = (name, sig, encoders) => bind('gdi32.dll', name, sig, encoders);
13
13
  // 点数组编解码对(Polyline/Polygon/PlgBlt 共享):注册后 <POINT>ptr 位收元素数组
14
14
  // (值域由 encoder 推导),C 侧 NULL 位传 NULL(number 直通),[]/单对象 fail-loud
15
- const POINTArr = structArray(POINT);
15
+ const POINTArr = /*@__PURE__*/ structArray(POINT);
16
16
  /** 创建实心画刷;配对 DeleteObject
17
17
  * @returns 失败 → 0
18
18
  * @param args_0 crColor 画刷颜色 0x00BBGGRR */
@@ -15,7 +15,7 @@ const b = <const S extends string, const LE extends CodecMap = {}>(name: string,
15
15
 
16
16
  // 点数组编解码对(Polyline/Polygon/PlgBlt 共享):注册后 <POINT>ptr 位收元素数组
17
17
  // (值域由 encoder 推导),C 侧 NULL 位传 NULL(number 直通),[]/单对象 fail-loud
18
- const POINTArr = structArray(POINT)
18
+ const POINTArr = /*@__PURE__*/ structArray(POINT)
19
19
 
20
20
  /** 创建实心画刷;配对 DeleteObject
21
21
  * @returns 失败 → 0