# VB6 技能包

<!-- autos.skill.namespace: autos.skills.vb6 -->
<!-- autos.skill.description: Visual Basic 6.0 编程开发；可编写 aardio 控件。 -->
<!-- autos.skill.version: 0.2.15 -->
<!-- autos.skill.minAutos: 4.15 -->
<!-- autos.skill.disabled: false -->

## 定位与首选路线

以 **独立 VB6 EXE 开发/既有工程维护**为主，ActiveX DLL/OCX 与 aardio 组合为辅。复用已有 VB6 知识，不把 VB.NET、VBA7（LongPtr/PtrSafe）或 aardio 语法套入 VB6。纯 VB6 应用不能依赖 aardio 宿主才能运行。

0.2.2 修复备用 IDE 的 DAO350 缺失/未注册、跨会话编译器定位，并补齐原 mini 安装器的一项 IDE 配置；保留 Direct2D 薄画板与 ASCII/ANSI 编辑规则。0.2.6 增加纯 ASCII `hello` 工程模板，0.2.7 修复旧失败备用目录，0.2.8 将 setup 自动并入父库：`import autos.skills.vb6` 后即可使用 `vb.ensureCompiler()`，而 `build()` 也会自动做最小检查、修复并在静默编译器崩溃后重试一次。0.2.11 补齐原 mini 包的 MSSTDFMT.DLL 与固定用户级 COM 注册，修复 Win11 上空窗体可编译而 TextBox/Label 导致 VB6.EXE 访问冲突的遗漏；旧激活日志保留原值升级。验收范围见 SOURCES.md。

**自绘是重点：游戏、动画和简单按钮优先 Direct2D + DirectWrite 薄 Canvas，AI 直接写规则/几何/绘图，不造控件框架。** 先读 `knowledge/direct2d.md`；统一使用 `vb.createProject(dir,{kind="canvas"或"pulse";name})`，内部按需复用 d2d 子库，原子库入口保持可用。随技能有开发 TLB，EXE 不带绘图引擎。复杂输入/列表等密集控件界面可用网页，不强迫自绘重造。

图片/透明贴图/渐变按需用 `assets=true`（`canvas/pulse` 选项），直接生成已验证的 WIC 文件/内存解码与原生笔刷适配；canvas 同时带内嵌 PNG 示范。先读 `knowledge/direct2d-assets.md`，CPU 像素保留、设备位图与笔刷按 Generation 整组重建；默认模板不增加依赖。原三份 IDL/TLB 保留，可选功能用新可重建的小 `assetsvb.idl/tlb`，不要为简单贴图先造 DXGI/Effects 引擎。

- 直接 `import autos.skills.vb6`；父库会自动载入普通 `autos.skills.vb6.setup` 子库，并提供 `vb.ensureCompiler()`。它优先复用永久的 `%LocalAppData%\aardio\vb6\`，快速检查核心文件及固定 DAO350/StdDataFormat 注册；缺文件、缺注册、旧的半安装目录会自动做最小修复。其他 VB6 目录也会做同样的 DAO 快速检查，缺失时尽量原地补齐。`build()` 会再次调用这个检查，遇到无日志失败或编译器崩溃会自动修复并只重试一次。`selftest` 只在陌生环境或自动修复仍失败时使用，不要把它写成每次任务的固定前置步骤。
- 维持既有 CRLF、Attribute、引用、FRX/CTX 配对与偏移；二进制资源不交给文本编辑器。编辑前备份，修改后编译与聚焦运行验证。
- **GUI 生命周期（0.2.12）**：`Form_Load` 不代表窗体已显示，不能直接/间接 `SetFocus`。把业务更新与焦点/模态 UI 分开；隐藏 `/smoke` 调共享业务过程，不直接调用混有焦点操作的 Click 事件。确需焦点时等显示后，在带一次性守卫的 `Form_Activate` 或外部真实按钮测试中验证；不用 `DoEvents`、固定延时或全局 `Resume Next` 掩盖。隐藏报告不是完整 UI 验收，见 `knowledge/independent.md` / `maintenance.md`。
- VB6 String 本来就是 Unicode BSTR，不是 ANSI-only；源文件编码、ANSI 控件/API、路径/输入与 BSTR 分层理解。需要不能直接写入默认 ANSI 的 Unicode 常量时可用 ChrW$ 构造。
- **VB6 DLL/OCX 一律不用直接内存加载**：嵌入资源后先 `io.appData` 释放，再 `com.lite(path)` 磁盘免注册；已有外部组件直接磁盘加载。用户组件缓存用自己的厂商/应用目录，**`aardio/std` 仅供官方标准库**。路线与生命周期见 `knowledge/interop.md`。
- **发布文档面向产品，而不是开发工具清单**：不默认罗列调用过的接口、开发 TLB 或未确认的系统依赖猜测。TLB 来源只在 TLB 旁的 `SOURCE.md` 简短记录；构建细节留开发文档。使用指南只写产品用法和必要的运行要求，不把开发依赖说明带进 EXE/DLL/OCX 的发布文档。
- 本技能的检查是文件/资源链接清单，不是完整 VB6 解析器、依赖验证或 IDE 调试器。项目类型 Exe/OleDll/Control；ActiveX EXE、复杂第三方组件、真实数据库、manifest 部署、真实 PMv2/IME/完整输入控件仍未验收。

## 入口（短别名 + 可追溯）

```aardio
import autos.skills.vb6;
var vb = autos.skills.vb6;

// setup is loaded by the parent library; build also performs readiness repair.
var project = vb.createProject("/MyVb6Project",{kind="hello";name="MyVb6App"});
if(!project.ok) return project;
return vb.build(project.projectPath);
```

### 直接按用途选模板

**不必先枚举模板目录、读全部源码或重写模板。** `exe/dll/ocx` 分别是标准窗体 EXE、ActiveX DLL、ActiveX 控件；`hello/gui/task/canvas/pulse` 也是 EXE，只是用途不同。

| kind | 用途 |
| --- | --- |
| `exe` | 空白 MainForm，适合从零做窗体工具；`/smoke` 自动报告后退出 |
| `task` | 无窗体 Sub Main 任务，读取 `Command$`，演示写 `task-result.txt`；不是控制台子系统 |
| `hello` | Hello World 按钮与标签；用户只要 Hello World 时直接用它 |
| `gui` | 已有整数乘二示例，含输入错误处理；保留默认行为 |
| `dll` | Engine COM 类：方法、属性、ByRef、数组、事件；不是 C ABI DLL |
| `ocx` | CounterPanel 可视控件：原生按钮、属性、事件 |
| `canvas` | Direct2D 薄画板，适合原创游戏/动画 |
| `pulse` | 完整可玩的 Pulse Grid 谜题示例 |
| `selftest` | 编译器诊断专用，不是写普通应用的起点 |

所有类型统一：`vb.createProject(dir,{kind="ocx";name="MyControl"})` → `vb.build(project.projectPath)`。如需程序化清单，直接 `vb.listTemplates()`；它不创建工程、不初始化编译器。生成后只改业务代码，DLL/OCX 的 aardio 宿主例见 `examples/README.md`。

需要单独查看环境时才调用：

```aardio
var ready = vb.ensureCompiler();
if(!ready.ok) return ready;
```

- `getCompiler(path?)`：显式 EXE 路径优先，随后是永久备用目录、记忆路径、OLB 同目录、约定目录和缓存；返回路径或 `null,error`。它只定位文件，实际检查与修复由自动载入的 `ensureCompiler()` 完成。
- `listTemplates()`：返回上表九种类型的 `{kind;title;description;projectType;extension}` 数组，无环境初始化或文件写入。
- `createProject(targetDirectory,{kind;name})`：支持上表九种类型，省略 kind 仍为 `gui`；项目名 ASCII 标识符、最长 40。普通模板来自 `.res/templates/`，自绘复用 `.res/d2d/`；由库直接写为当前默认 ANSI、CRLF，无 BOM。生成后是可直接在 VB6 IDE 打开的普通源码工程，不依赖 aardio 才能运行。拒绝已有目录；失败时查看返回的目录，不自动删除用户资料。
- `inspectProject(vbp)`：返回普通表 `{ok;files;references;resources;fields;errors;warnings;artifactName}`。仅读取默认 ANSI 文件；清点源文件、FRX/CTX 链接是否存在及偏移是否越界；保留引用原文但不激活/验证 32 位依赖。不能把 `ok` 解读为可编译。
- `build(vbp,{compilerPath;timeoutMs=30000;buildRoot?})`：`VB6.EXE /MAKE /OUT /OUTDIR` + 数组 argv；开始前自动调用 `ensureCompiler()`，缺失 DAO/注册或旧备用目录会先修复。每次新建 attempt/out/log，私有 `process.desktop` 与独立 `process.job.limitKill()`（挂起→纳入 Job→恢复），等待退出再清理。静默失败或 `0xC0000005` 会自动调用一次 `repairCompiler()` 并重试；返回 `autoRepair` 与 `initialFailure` 供诊断。非空编译日志的普通源码错误不会盲目重试。
- 构建成功条件：`built.ok`（正常退出码 0、本次新目录中的非空产物、进程树与桌面清理成功）。不凭日志字样或旧文件存在判成功；不自动把产物覆盖到发布目录/兼容性基准。
- 极少数清理失败会返回 `cleanupPending`（活 Job/桌面/进程对象），必须重试 `job.dispose`，确认残余进程退出并 free 后才 close 桌面；不要将其当普通成功结果或遗忘句柄。

## 编译器预检与失败分层

正常任务不需要先写几十句预检代码：`vb.build()` 已经会自动调用 `ensureCompiler()`。初始化只做快速的文件与固定注册检查，不强制每次运行 `selftest`。

- `ensureCompiler()` 成功：受管备用目录的核心文件和固定 DAO350/StdDataFormat 用户注册已核对或修复；普通外部 VB6 目录仍只做 DAO 快速检查，不是完整 IDE 依赖验证。默认 `%LocalAppData%\aardio\vb6\` 是长期可用的备用目录，不是一次性临时目录。
- `build.status="compileFailed"` 且 `failureKind="compilerCrash"` 或 `"compilerNoLog"`：库会自动执行一次最小 `repairCompiler()` 并重试。若仍失败，再用一次 `kind="selftest"` 区分编译器环境与用户工程，不要反复重写窗体。
- `failureKind="compilerNoLog"`：同时检查返回的 `attemptDir/logPath`，必要时改用短的**物理** `buildRoot`。
- **无窗体 selftest 不验证 GUI 依赖**。若空窗体/按钮成功，TextBox/Label 编译以 `0xC0000005` 崩溃，先检查 MSSTDFMT.DLL 及 `MSSTDFMT.StdDataFormat` COM 类注册；不要重写窗体或盲目注册全部 OCX。0.2.11 的受管备用目录已自动修复此项。
- 带日志的普通源码错误按日志定位；selftest 成功不能反证所有环境依赖完整。

`execute_code` 中的 `/Project` 是 GuestVAP；工具的 `path` 参数是 HostPath。通常不需要手填 `VB6.EXE` 路径；让 `ensureCompiler()` 和 `build()` 自动选择。若明确传入 `compilerPath`，仍应使用当前机器返回的实际路径。`buildRoot` 可用虚拟路径，库会转换为物理路径；给其他工具读文件时使用返回表中的物理 `attemptDir/logPath/artifact`。


## 查询官方 VB6 文档（0.2.14）

按需导入文档子库（不初始化编译器）：

```aardio
import autos.skills.vb6.docs;
var docs = autos.skills.vb6.docs;
return docs.lookup("AppActivate Statement"); // {title;url;text}；失败 null,error
```

VB6 独有主题（参数必须英文）先 `docs.resolve("TextBox Control")` 本地标题定位，再 `docs.fetch(url)`；明确标题可 `lookup(title)` 一步获取 Markdown。目录快照随包，定位可离线；正文/搜索/示例需联网。语言级检索用 `docs.search("Format function VBA")`，不要加 `VB6.0` 后缀；实测 Learn search 未命中旧 VB6 页面，不表示页面不存在。`docs.samples("Shell function VBA","vb")` 返回上游示例，但 **vb 标签不保证 VB6 兼容**，需排除 VBA7/VB.NET/Office 宿主专属语法。含糊标题先看候选，不盲信第一条。缓存/返回值/查询习惯见 `knowledge/docs.md`；`loadIndex(true)` 才在线刷新，结束可 `docs.close()`。

## 源码编辑

### 1. 创建或保存 VB 源代码文件

首选：使用 save_files 工具，并指定参数 `charset="ansi",newline="crlf"`，由 Autos 框架自动转换默认 UTF-8 编码为 ANSI，并且规范化换行为 CRLF 后写入文件。你不必自己调用 string.fromto / string.crlf 。

在执行 aardio 代码时，你也可以这样创建 ANSI 文件：

```aardio 
var formText = /**
VERSION 5.00
Begin VB.Form MainForm
   Caption         =   "VB6 文本框示例"
   ...
Attribute VB_Exposed = False
Option Explicit

Private Sub cmdSayHello_Click()
    txtMessage.Text = "你好"
End Sub
**/

var res = textkit.ansi.save({path="/TextDemo/MainForm.frm";content=formText}); //原样写入，不转换换行
```

- 块注释会自动规范化换行到 CRLF 
- textkit.ansi 会自动执行 UTF-8 → ANSI 转换

一块注释 + 一行调用解决所有问题，你不必自己调用 string.fromto / string.crlf 。

### 2. 编辑 ASCII / UTF-8 兼容的 VB 源代码

ASCII 是 ANSI/UTF-8 共用字节，已知为纯 ASCII 的 `.vbp/.bas/.cls/.frm/.ctl`，直接使用 `save_files/read_text_file/patch_text_file` 等 UTF-8 工具，保存为无 BOM、CRLF；不要对英文源码再搭不必要的 ANSI 中转。若不需要中文，注释也写英文，避免中文或智能引号把文件变成非 ASCII。。**旧源码如果仅在注释中包含少量非 ASCII 字符则应先转换为纯英文**。仍保留 CRLF、Attribute 与二进制资源配对。

纯 ASCII 的 VB 源代码也可用于显示中文界面，方法：

1. ChrW$ 拼接方式: `txtMessage.Text = ChrW$(&H4F60) & ChrW$(&H597D)` 显示`你好`
2. 如果是用 VB6 编写 aardio 控件，中文交互可由 aardio 传入 Unicode BSTR，不必写进 VB6 源码。
3. 另一种方式是将界面语言写入独立的语言包文件，这不仅可以轻松支持多语言界面，也可以大幅提升你编辑 VB 源码的效率，不必再浪费时间频繁地做编码转换。

### 3. 使用 ANSI 手术刀 `textkit.ansi` 编辑含非 ASCII 的 ANSI 源码

 `textkit.ansi` 的巧妙之处在于你始终处理的是 UTF-8 文本，仅在读写 ANSI 文件时由  `textkit.ansi` 自动进行 UTF-8 ↔  ANSI 双向转换。你不必自己调用 string.fromto 转换编码。

示例：

```aardio
import textkit.ansi;
var view = textkit.ansi.read(path="/MyVb6Project/MainForm.frm";lineNumbers=false);
if(!view.ok) return view;

var edit = textkit.ansi.edit(
    path="/MyVb6Project/MainForm.frm";
    operation="replace";
    oldText=`Caption         =   "旧标题"`; 
    newText=`Caption         =   "新标题"` // 多行文本可用块注释（天然 CRLF）表示
);
return edit; // 先检查 ok，再看 written / backupPath
```

`read/patch/edit/save` 都是 `{path;...}` → 结果表，固定 codepage=0；参数、视图、补丁与诊断均 UTF-8。局部改动用 patch/edit，新建或明确全文覆盖才用 save。SEARCH 必须复制唯一的原文（去展示行号），失败不模糊重试。

textkit.ansi 换行规则: read 读取、patch/edit 匹配时都会统一换行为 LF，patch/edit 写入新片段的换行适配为命中位置附近的局部 EOL ，save 原样写入。

默认备份是 **原始 ANSI 字节**，恢复用 `io.copy(edit.backupPath,edit.path)`，不能用 UTF-8 文本工具读取备份再写回。dryRun 不写文件；read 的 LF 展示不是原文件全文备份；save 的父目录必须已存在，不自动改换行/加 BOM。NUL、FRX/CTX 等二进制不支持。本库遵循系统默认 ANSI 转换，不猜测/修复乱码，也不承诺异常原始字节整份转码后完全不变。

> 次选方法：可用 `fsys.codepage.fromto(path,0,65001)` 临时将 VB 文件转换为 UTF-8 ，然后使用 UTF-8 工具处理（ edit_text_file, patch_text_file ），然后用 `fsys.codepage.fromto(path,65001,0)` 就地转回 ANSI 。

## 按需知识路由

资源位于 `io.joinpath(io.splitpath(io.libpath("autos.skills.vb6")).dir,".res")`，只读当前问题需要的文件：

| 任务 | 入口 |
| --- | --- |
| 自绘游戏/动画、轻量 Canvas（重点） | `knowledge/direct2d.md` → `d2d/README.md`；普通子库 `autos.skills.vb6.d2d`，无需再 load_skill |
| 图片/内嵌资源/透明贴图/渐变 | `knowledge/direct2d-assets.md`；`createProject(...,{kind="canvas"或"pulse";assets=true})` |
| 独立 EXE、模板运行 | `knowledge/independent.md` |
| 编译器缺失、轻量备用部署/定制安装包 | `knowledge/setup.md`；普通子库 `autos.skills.vb6.setup`（无需再次加载技能） |
| 旧工程/资源维护、UI生命周期、错误清理 | `knowledge/maintenance.md` |
| 报错定位、罕见IDE启动安装弹窗 | `knowledge/diagnostics.md` |
| ADO/数据库任务（本地验证与接库边界） | `knowledge/ado.md` |
| DLL/OCX 免注册、BSTR/ByRef/数组/事件/宿主回调、锁与升级 | `knowledge/interop.md`；`com.lite` / `com.typeAttrs` / `com.tlbDoc` |
| 组合源码示例、独立EXE文件协作 | `examples/README.md`（先生成ANSI工程/构建，COM另进程加载） |
| ANSI 精确编辑 | `textkit.ansi` 库参考；上面的片段 |
| 官方 VB6 文档、语言参考、代码示例查询 | `knowledge/docs.md`；普通子库 `autos.skills.vb6.docs`（标题索引 + Microsoft Learn MCP，不是第二技能） |
| 来源与验收边界 | `SOURCES.md` |

默认流程：明确需求/工程路径 → `import autos.skills.vb6` → 从上表选模板直接 `createProject()`（不必先读模板源码），已有工程则按 ASCII/ANSI 最小补丁 → 直接 `build()`（自动检查、修复和必要的一次重试）→ 运行/冒烟或独立进程 COM 验证 → 只有仍失败时才用 `selftest` 和 `knowledge/diagnostics.md` 深入定位。


## 其他知识点

### 探查类型库

aardio 标准库自带 `com.tlbDoc` / `com.typeAttrs` 可直接用用于探查 ocx/dll/tlb/comObject 。
也可以运行 `import dotNet.oleview` 引入 oleviewdotnet 组件（因 GPL 与体积较大，交付代码通常不要包含 dotNet.oleview，仅用于开发时探查备用 ） 。

### 重新编译 IDL

```aardio 
import vc6; 
var exitCode = vc.exec('MIDL.EXE',"/client", "none", "/server","none" ,"/I","/typelib", "example.idl","/tlb", "example.tlb")
```

### VB EXE 调用 aardio ActiveX EXE

可用 aardioProjectBuilder 先创建 ActiveX 工程，工程内 `/typelib/typelib.odl` 是类型库源代码，`/.build/default.init.aardio` 负责自动调用 vc6 扩展库的 midl.exe 编译 tlb 。