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.
24 procedures, three columns opened side by side
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.
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
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 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 | |
| Body lines, across the 51 matched | 1,524 | 888 | legacy advantage — a consequence of the isolated blocks |
| Lines of code, all files | 7,464 | 8,119 | 11,878 in the source |
| Files exported | 26 | 27 | V7: 26 + EXPORT-NOTES.txt |
| Blocks absent from the emitted source | 0 | 127 | across 87 procedures — legacy advantage |
Reference= and compilation options in the .vbp | yes | no | V7 omits them and documents it — 7 export notes |
| 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) |
| Procedures recovered under their name | 1,263 | 1,252 | legacy advantage of 11 procedures |
| Empty body where the source is not empty | 229 | 0 | the widest gap in the comparison |
| Body lines, across the matched procedures | 13,018 | 12,235 | 20,973 in the source; 1,129 of the legacy lines are error markers |
| Lines of code, all files | 54,374 | 42,409 | 45,165 in the source; the legacy inflates by 75,014 As Unknow |
| Resource files | 28 | 8 | 18 .frx + 10 .ctx against 8 .frx, no .ctx |
| Blocks absent from the emitted source | 0 | 1,541 | across 232 procedures (12%) — legacy advantage |
| 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 | no |
| 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
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 recompiles | no | no |
| 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 / 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 | — |
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
.frxor.ctxwas 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.