As Unknow, for want of a typeA 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.
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.
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.
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.
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)
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...)
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 export | 136 | 227 | 238 in the source |
| Procedures recovered under their name | 51 | 51 | an exact tie: these are the event handlers; the names of module procedures are not in the binary |
| Empty body where the source is not empty | 1 | 0 | |
| Lines of code, all files | 7,464 | 8,119 | 11,878 in the source |
| Files exported | 26 | 27 | V7: 26 + EXPORT-NOTES.txt |
Reference= and compilation options in the .vbp | yes | yes | V7 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 s | 12 s | legacy: 71 s of analysis + ~15 s of writing |
| Procedures written to the export | 1,799 | 1,976 | 1,718 in the source; the binary carries more methods (generated accessors) |
| Empty body where the source is not empty | 229 | 0 | the widest gap in the comparison |
| Lines of code, all files | 54,374 | 42,409 | 45,165 in the source; the legacy inflates by 75,014 As Unknow |
| Procedures whose structuring succeeds | — | 1,976 / 1,976 | no unstructured procedure |
| Product features | legacy | V7 |
|---|---|---|
| P-code decompilation | no — module exported empty | yes |
| Global symbol rename | no | yes |
| Cross-references, call graph, control-flow graph | no | yes |
| Symbol list, bookmarks, back/forward navigation | Goto only | yes |
| Hex editor | an "address + bytes" patcher | full view |
| API Browser / Object Browser | Library Explorer | both |
| Export notes: what could not be recovered is written down | no | yes |
| Text search, DLL Finder, VB program search, resource view, image export, options | yes | yes |
| Opens real binaries without crashing (build 7.1) | no | yes |
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:
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
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 recompiles | no | yes |
| 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 / 16 | 16 / 16 |
Parameters declared As Unknow (no such type exists in
VB6) |
1,442 | 0 |
| Modules of 0 bytes | 3 | 0 |
| Names masked by the Free edition | 99 | — |
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.