119/149
Projects exported by V7 that recompile as-is in the VB6 IDE — the legacy was not put through the same bench
88 s12 s
Opening a 9.35 MB binary and writing the complete project
2290
Procedures rendered with an empty body where the source is not empty
1,1290
Structure-error markers left inside the rendered code
75,0140
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
Procedures recovered under their name1,263 vs 1,252
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.

24 procedures, three columns opened side by side

${x.t}2 vs 22

The two cases favouring the legacy are the two bodies V7 truncates — the same isolated-block defect.

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.

And the point where the legacy is more correct

There is one. In cVirtualMemory.ReadDouble, V7 inserts an argument at the wrong position in a call to Replace — while the twin procedure ReadSingle, in the same class, comes out correctly. Across the 56 calls to Replace in the witness export, it is the only case found. It is open as a defect.


The two places the legacy wins

A comparison that loses nowhere is a comparison that was never run.

Isolated blocks — our widest open defect

When a procedure contains blocks that no edge connects to the entry — the targets of On Error GoTo, GoSub, Resume — V7 sees them, feeds them to the control-flow graph, and does not write them into the source.

The legacy abandons nothing: its linear emission writes everything, badly structured but present. In the extreme case, frmPath.cmdOK_Click from SETUP1: 33 lines in the source, 10 of 11 blocks isolated, 2 lines emitted by V7 against 45 by the legacy.

Blocks present in the binary, absent from the emitted source

9.35 MB witness — 232 procedures affected out of 1,976 (12%)0 vs 1,541
SETUP1.EXE — 87 procedures affected out of 227 (38%)0 vs 127
This defect explains almost entirely the other figure that goes against us: across the 51 SETUP1 procedures matched by name — the event handlers, the ones carrying the most On Error GoTo — the legacy writes 1,524 body lines against 888. Across all files, V7 still writes more: 8,119 against 7,464.

Resources, and six features

On the witness, the legacy writes 28 resource files (18 .frx and 10 .ctx) against 8 for V7, and no .ctx at all.

Six features of the historical product have no equivalent yet on the V7 desktop: project-wide text search, imported-library search, VB program search on disk, binary resource view, form image export, options screen.


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
Body lines, across the 51 matched1,524888legacy advantage — a consequence of the isolated blocks
Lines of code, all files7,4648,11911,878 in the source
Files exported2627V7: 26 + EXPORT-NOTES.txt
Blocks absent from the emitted source0127across 87 procedures — legacy advantage
Reference= and compilation options in the .vbpyesnoV7 omits them and documents it — 7 export notes
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)
Procedures recovered under their name1,2631,252legacy advantage of 11 procedures
Empty body where the source is not empty2290the widest gap in the comparison
Body lines, across the matched procedures13,01812,23520,973 in the source; 1,129 of the legacy lines are error markers
Lines of code, all files54,37442,40945,165 in the source; the legacy inflates by 75,014 As Unknow
Resource files28818 .frx + 10 .ctx against 8 .frx, no .ctx
Blocks absent from the emitted source01,541across 232 procedures (12%) — legacy advantage
Procedures whose structuring succeeds1,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, optionsyesno
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

And on one point this is not to our credit

These functions are Public in the source and come out Private proc_N: on that module, V7 does not recover the public names. The legacy renders nothing at all, so the comparison goes against it — but our rendering is not correct either.

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 — 149 binaries exported and then passed to VB6.EXE /make — V7 gets 119 of them to recompile, or 80%. The legacy could not be put through the same bench: it has no command-line export, and it would mean opening all 149 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 recompilesnono
Where VB6 stops 1st line of code line 119
What VB6 says Invalid character Argument not optional
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

Neither of them recompiles, and that is the first thing to say. But where each one stops is not of the same order.

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 one hundred and eighteen lines of code. It stops on a call missing an argument. That is a decompilation defect, and it is a one-line fix.

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 119 / 149 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 149 binaries: it has no command-line export. 119 / 149 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 correctness of resources. Only sizes were compared; no .frx or .ctx was reopened.
  • 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. It does, on the other hand, lose 1,541 blocks of code that the legacy writes every one of. That is the defect we are working on first.

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.

Any figure on this page that is not reproducible is a defect of this page. The full protocol and the matching scripts ship with the product.