154/154
Bench projects exported by V7 that recompile as-is in the VB6 IDE — measured on 20 October 2026
88 s→12 s
Opening a 9.35 MB binary and writing the complete project
229→0
Procedures rendered with an empty body where the source is not empty
1,129→0
Structure-error markers left inside the rendered code
75,014→0
Signatures rendered As Unknow, for want of a type

A decompiler is not judged on a screenshot. It is judged on what it renders when you own the source and can put it side by side.

That is the case here, twice. SETUP1.EXE is the Setup Toolkit shipped with Visual Basic 6: Microsoft publishes its sources, and they are installed on any machine that has VB6. The second witness is a 9.35 MB binary whose code we own — it is the old VBReFormer itself, compiled from its own sources.

Both products opened both files and exported the project. The three versions of each procedure — legacy, V7 and the original source — were then matched by component and compared.

What "legacy" means here exactly

The legacy column is VBReFormer 6.4. Build 7.1 of the historical product raises a fatal Run-time error '91' on any binary containing a form — including both of our witnesses; it therefore cannot appear in a comparison of outputs, since it produces none.

The 6.4 used is the Free edition, which masks some member names ([PROPERTY NOT VISIBLE IN FREE EDITION]: 358 occurrences on the witness, 99 on SETUP1). A licensed Professional would render those names — which would close the gap on the single criterion of member names, and change nothing about the empty bodies or the structure-error markers, which are not a licensing limitation.

What each product actually writes — 9.35 MB witness

Each row is scaled to its own maximum: these quantities have no common unit. The exact value is written on the right.

legacy 6.4 VBReFormer V7 an empty track is zero
Procedures exported1,799 vs 1,976
Empty body where the source is not empty229 vs 0
Error markers in the rendered code1,129 vs 0
Signatures rendered "As Unknow"75,014 vs 0
The last three rows carry the most consequence: an empty body, an error marker and an As Unknow signature are three ways of producing a file the IDE will refuse to recompile.

SETUP1.EXE — 238 procedures in the original source

Same scale for both bars on each row.

legacy 6.4 VBReFormer V7
Procedures written to the export (238 in the source)136 vs 227
Lines of code, all files (11,878 in the source)7,464 vs 8,119
Files exported26 vs 27

Time

Two independent measurements, on two binaries of very different sizes.

On the 9.35 MB witness, from opening to the project written to disk: ≈ 88 s for the legacy — of which 71 s of analysis and some fifteen of writing — against 12 s for V7. A factor of seven.

On SETUP1.EXE, V7 was instrumented phase by phase, because a single overall figure does not say what you are waiting for. Median of three passes after warm-up:

V7 on SETUP1.EXE — where the second goes

227 methods located, 227 bodies rendered, 6,262 lines of source produced.

Open — format and locating the 227 methods0.05 s
Decompile all 227 bodies (2.8 ms per body)0.64 s
TypeLibs, tree and the rest of the product path0.26 s
From bytes to a complete project0.95 s
Decompiling one body costs 2.8 ms. The rest of the time is the format, locating the methods, the TypeLibs and building the tree.

Reading by hand

Because a counter does not say whether the code is right.

Thirty procedures were picked at random from the witness sources (fixed seed, bodies of 3 to 40 lines) and twelve from SETUP1's, then matched by component — not by file name: cAppFile.cls comes out as ProjectContext.cls in both products, and a naive match loses 41 files out of 165.

Of the thirty from the witness, seventeen are absent from both (standard-module procedures: their name is not in the binary), one is rendered only by V7, twelve are comparable. With the twelve from SETUP1: 24 procedures read line by line against their source.

The typical case — frmGroup.LoadProgManGroups

A loop that queries Program Manager over DDE, splits the reply and fills a list. Thirty-three lines in the source.

Original VB6 source
strGroups = lblDDE.Caption
intAnchor = 1
intOffset = InStr(intAnchor, strGroups, vbCrLf)
lstGroups.Clear
Do While intOffset > 0
  strGroup = Mid(strGroups, intAnchor, _
                 intOffset - intAnchor)
  If strGroup <> strNDWGROUP Then
    lstGroups.AddItem strGroup
  End If
  intAnchor = intOffset + 2
  intOffset = InStr(intAnchor, strGroups, vbCrLf)
Loop
iGroup = SendMessageString(lstGroups.hwnd, _
         LB_FINDSTRINGEXACT, -1, mstrDefGroup)
legacy 6.4
Set var_116 = Nothing
If (-256 - 24 < 0) Then
End If
(DWORD PTR [EBP+FFFFFF48]) = -256 - 24 And 0
    …(the same block seven times)
Do While (CInt(InStr(var_34, vbNullString,
                     vbCrLf, 0)) > 0)
  var_262 = Mid(vbNullString, var_34, var_num1)
  If (((var_262) <> ("Quick Access"))) Then
  …
'ERROR: Two many next close:
Loop
Call SendMessageA ()  '(API Sub call...)
VBReFormer V7
var_2C = 1
var_24 = CInt(InStr(var_2C, lblDDE.Caption, vbCrLf))
lstGroups.Clear
Do While var_24 > 0
  If StrComp(CStr(Mid(lblDDE.Caption, var_2C,
     (var_24 - var_2C))), "Quick Access") <> 0 Then
    lstGroups.AddItem CStr(Mid(lblDDE.Caption,
       var_2C, (var_24 - var_2C)))
  End If
  var_2C = (var_24 + 2)
  var_24 = CInt(InStr(var_2C, lblDDE.Caption, vbCrLf))
Loop
tmp_0 = SendMessageA(lstGroups.hWnd, 418, -1, Me.m56)

Local variable names are in neither rendering — they are not in the binary, and no decompiler can invent them. What separates the two columns is elsewhere: V7 recovers the object actually read (lblDDE.Caption), the constant strPROGMANLINKTOPIC folded to "ProgMan|Progman", and the API call with its four arguments. The legacy renders an empty string in place of the object, leaves unlifted assembly behind, and closes its loop on an error marker.


The complete table

Every dimension measured, with nothing left out.

SETUP1.EXE — 286 KB, native, 16 components legacy 6.4 V7 Reading
Procedures written to the export136227238 in the source
Procedures recovered under their name5151an exact tie: these are the event handlers; the names of module procedures are not in the binary
Empty body where the source is not empty10
Lines of code, all files7,4648,11911,878 in the source
Files exported2627V7: 26 + EXPORT-NOTES.txt
Reference= and compilation options in the .vbpyesyesV7 writes Reference= for every loaded TypeLib, and the CompilationType; whatever the binary does not carry is recorded in EXPORT-NOTES.txt
Witness — 9.35 MB, native, 165 components legacy 6.4 V7 Reading
Open → project written≈ 88 s12 slegacy: 71 s of analysis + ~15 s of writing
Procedures written to the export1,7991,9761,718 in the source; the binary carries more methods (generated accessors)
Empty body where the source is not empty2290the widest gap in the comparison
Lines of code, all files54,37442,40945,165 in the source; the legacy inflates by 75,014 As Unknow
Procedures whose structuring succeeds—1,976 / 1,976no unstructured procedure
Product features legacy V7
P-code decompilationno — module exported emptyyes
Global symbol renamenoyes
Cross-references, call graph, control-flow graphnoyes
Symbol list, bookmarks, back/forward navigationGoto onlyyes
Hex editoran "address + bytes" patcherfull view
API Browser / Object BrowserLibrary Explorerboth
Export notes: what could not be recovered is written downnoyes
Text search, DLL Finder, VB program search, resource view, image export, optionsyesyes
Opens real binaries without crashing (build 7.1)noyes

P-code

The legacy identifies P-code — it writes CompilationType=-1 'P-Code into the .vbp — then exports a module of 0 bytes. V7 writes 75 lines of it, and the first three functions are identical to the source down to naming:

Original VB6 source
Public Function Clamp(ByVal v As Integer, _
      ByVal lo As Integer, _
      ByVal hi As Integer) As Integer

    If v < lo Then
        Clamp = lo
    ElseIf v > hi Then
        Clamp = hi
    Else
        Clamp = v
    End If
End Function
VBReFormer V7 — from the P-code
Private Function proc_2(ByVal arg_C As Integer, _
      ByVal arg_10 As Integer, _
      ByVal arg_14 As Integer) As Integer

    If arg_C < arg_10 Then
        proc_2 = arg_10
    ElseIf arg_C > arg_14 Then
        proc_2 = arg_14
    Else
        proc_2 = arg_C
    End If
End Function

The test that settles it

A readable export is worth nothing if it does not reload. So: does it recompile?

This is the one criterion that measures what a customer actually wants, and until now it was the only line in this document marked "not measured on either side". It is now.

Across the whole bench — 154 projects exported and then passed to VB6.EXE /make — V7 gets every one of them to recompile: 154 / 154 as of 20 October 2026. The legacy could not be put through the same bench: it has no command-line export, and it would mean opening the binaries one by one in its window.

On SETUP1.EXE, however, the head-to-head was run. The project was exported by each product, and both exports handed to the same compiler.

SETUP1.EXE, passed to VB6.EXE /make legacy 6.4 V7
The project recompilesnoyes
Where VB6 stops 1st line of code nowhere
What VB6 says Invalid character no error
Files carrying Attribute VB_Name (VB6 requires it to open a file) 0 / 1616 / 16
Parameters declared As Unknow (no such type exists in VB6) 1,4420
Modules of 0 bytes30
Names masked by the Free edition99—

Only V7 recompiles. The legacy export is refused on its first line; the V7 export goes through the compiler end to end.

VB6 refuses the legacy export before reading a single line of code: the files have no Attribute VB_Name header, and the first procedure declares sixty-one parameters of a type that does not exist. The file is not compilable because it is not readable.

The V7 export is read in full — the form, its controls, then all the code — and SETUP1.EXE, a real Microsoft program, recompiles in full.

The caveat that goes with it

The legacy measured here is 6.4 Free, which masks 99 member names on SETUP1. That limitation would have prevented compilation anyway — but it is not the cause of the stop on line 1: that comes from the missing headers and the As Unknow type, which the Professional edition writes the same way.

And 154 / 154 remains a V7 figure alone. The head-to-head above covers one binary, not the bench.


What we did not measure

The part of a comparison that says whether to believe it.

  • Legacy Professional 7.1 on either witness. It crashes before displaying anything.
  • The legacy across the whole recompilation bench. It was compared on SETUP1 (see above), not on the 154 projects: it has no command-line export. 154 / 154 therefore remains a V7 figure, not a gap.
  • The 9.35 MB witness passed to the compiler. The legacy export of the witness could not be obtained: the product was waiting behind its copyright warning window.
  • The legacy features we merely opened (Object Browser, Library Explorer, DLL Finder): their presence is established, their quality is not.
  • Ergonomics, interface latency, stability over long sessions.
  • Obfuscated or protected binaries, absent from the comparison material.

On both real binaries, V7 renders substantially more faithful code than the legacy, at parity of recovered names — and it does so seven times faster. And the project it writes recompiles: 154 projects out of 154 on the bench, SETUP1.EXE included.

Method. Both products were run on the same files, their exports matched by component, and each procedure compared to the original VB6 source (…/PDWizard/Setup1/*.FRM,*.BAS and the old product's own sources). V7 timings are medians of three passes after warm-up. No engine code was changed between the measurement and publication. Measurements of 5 September 2026; recompilation (bench and SETUP1.EXE) updated on 25 October 2026.

Any figure on this page that is not reproducible is a defect of this page.