Skip to content

2026-07-12 修复 cHttpClient 编译 DLL 后 SendPost/SendGet 崩溃

问题现象

cHttpClient 在 VB6 IDE 中以项目引用(联调)方式运行完全正常,但编译为 VBMAN.dll 后,外部项目(csv3)调用 SendPost/SendGet第一次成功,第二次崩溃(进程直接终止,无 VB6 错误对话框)。

排查过程

1. 排除二进制兼容性

注册表中发现 VBMAN 类型库有 46 个残留版本(2.0 ~ e0.2),二进制兼容被反复破坏。清理注册表并重新注册后,崩溃依然存在——排除了接口版本不匹配的可能。

2. 排除自定义 Dictionary 类本身

csv3 的 Form_LoadDim a As New Dictionary + For Each x In a 跨 DLL 边界使用 Dictionary 类完全正常,证明 Dictionary 类的暴露和基本功能没有问题。

3. 逐行日志定位

Fetch 函数中插入文件日志逐步缩小范围:

  • 崩溃发生在 For Each x In RequestHeaders.Keys() 这一行
  • 第一次调用正常通过,第二次执行到 For Each 时进程崩溃
  • 改为 hdrKeys = .Keys() + For idx = 0 To UBound(hdrKeys) 数组下标迭代后,多次调用均不再崩溃

4. 对比 Scripting.Dictionary 找到根因

Scripting.Dictionary自定义 Dictionary
Keys() 返回类型As VariantAs Variant()
Items() 返回类型As VariantAs Variant()

As Variant 在 COM 类型库中是 VARIANT,SAFEARRAY 包装在 VARIANT 内部,VB6 运行时对其跨 DLL 边界的生命周期管理完善。

As Variant() 在 COM 类型库中是 SAFEARRAY(VARIANT)*,VB6 把它当原始 SAFEARRAY 指针处理。当 For Each 枚举一个函数返回的临时 SAFEARRAY 时,VB6 编译后的运行时对该临时数组的引用计数管理存在缺陷——数组可能在枚举过程中被提前释放,导致访问已释放内存而崩溃。第一次碰巧内存尚未被覆盖所以正常,第二次则内存已被重用而崩溃。

根因

Dictionary.cls 的 Keys()Items() 返回类型声明为 As Variant() 而非 As Variant,与 Scripting.Dictionary 的 COM 签名不一致。

VB6 编译后跨 DLL 边界调用时,For Each 枚举 As Variant() 函数返回的临时 SAFEARRAY 会触发 VB6 运行时的生命周期管理 bug,导致二次调用崩溃。

IDE 模式下 VB6 运行时内存管理更保守(不释放临时数组),所以联调不会触发。

修复方案

Dictionary.cls 中将 Keys()Items() 的返回类型从 As Variant() 改为 As Variant,与 Scripting.Dictionary 签名完全一致:

vb
' 修复前
Public Function Keys() As Variant()
Public Function Items() As Variant()

' 修复后
Public Function Keys() As Variant
Public Function Items() As Variant

cHttpClient.cls 通过 git checkout 恢复原版,无需任何改动。

修改文件

文件改动
src/Collection/Dictionary.clsKeys() 返回类型 Variant()VariantItems() 返回类型 Variant()Variant
src/HttpClient/cHttpClient.cls无改动(git checkout 恢复)

经验总结

  1. VB6 COM 签名必须与被封装的对象一致:封装 Scripting.Dictionary 时,公共方法的返回类型要严格匹配原版签名,尤其是涉及数组的返回值。As VariantAs Variant() 在 VB6 源码层面语义相同,但在 COM 类型库中的 marshalling 行为完全不同。

  2. IDE 正常 ≠ 编译正常:VB6 IDE 的运行时对临时对象的内存管理比编译后更宽容。涉及跨 DLL 边界的 COM 调用,必须用编译后的 DLL 做实际测试。

  3. "第一次正常第二次崩溃"是典型的临时对象生命周期 bug 的特征:如果遇到这种模式,优先检查函数返回的临时数组/对象是否在跨 DLL 边界时被错误释放。

VB6及其LOGO版权为微软公司所有