As Unknow, faute de typeUn décompilateur ne se juge pas sur une capture d'écran. Il se juge sur ce qu'il rend quand on possède la source et qu'on peut la poser à côté.
C'est le cas ici, deux fois. SETUP1.EXE est le Setup Toolkit livré avec Visual Basic 6 : Microsoft en publie les sources, et elles sont installées sur toute machine qui a VB6. Le second témoin est un binaire de 9,35 Mo dont nous possédons le code — c'est l'ancien VBReFormer lui-même, compilé depuis ses propres sources.
Les deux produits ont ouvert ces deux fichiers et exporté le projet. Les trois versions de chaque procédure — legacy, V7 et la source d'origine — ont ensuite été appariées par composant et comparées.
Ce que « legacy » désigne exactement
La colonne legacy est celle de VBReFormer 6.4. Le build 7.1 du produit
historique lève une Run-time error '91' fatale sur tout binaire contenant un
formulaire — dont nos deux témoins ; il ne peut donc pas figurer dans une comparaison de
sorties, puisqu'il n'en produit aucune.
La 6.4 utilisée est l'édition Free, qui masque certains noms de membres
([PROPERTY NOT VISIBLE IN FREE EDITION] : 358 occurrences sur le témoin, 99 sur
SETUP1). Une Professional en règle rendrait ces noms — ce qui rapprocherait le legacy sur le
seul critère des noms de membres, et ne changerait rien aux corps vides ni aux
marqueurs d'erreur de structure, qui ne relèvent pas du bridage.
Ce que chaque produit écrit vraiment — témoin de 9,35 Mo
Chaque ligne est mise à l'échelle de son propre maximum : les grandeurs n'ont pas d'unité commune. La valeur exacte est écrite à droite.
As Unknow sont trois façons de rendre un fichier
que l'IDE refusera de recompiler.SETUP1.EXE — 238 procédures dans la source d'origine
Même échelle pour les deux barres de chaque ligne.
Le temps
Deux mesures indépendantes, sur deux binaires de tailles très différentes.
Sur le témoin de 9,35 Mo, de l'ouverture au projet écrit sur disque : ≈ 88 s pour le legacy — dont 71 s d'analyse et une quinzaine d'écriture — contre 12 s pour la V7. Un facteur sept.
Sur SETUP1.EXE, la V7 a été instrumentée phase par phase, parce qu'un chiffre global ne dit pas ce qu'on attend. Médiane de trois passes après chauffe :
V7 sur SETUP1.EXE — où passe la seconde
227 méthodes localisées, 227 corps rendus, 6 262 lignes de source produites.
La lecture à la main
Parce qu'un compteur ne dit pas si le code est juste.
Trente procédures ont été tirées au sort dans les sources du témoin (graine fixe, corps de 3 à
40 lignes) et douze dans celles de SETUP1, puis appariées par composant — et non
par nom de fichier : cAppFile.cls ressort en ProjectContext.cls chez
les deux produits, et un appariement naïf perd 41 fichiers sur 165.
Sur les trente du témoin, dix-sept sont absentes des deux (procédures de modules standard : leur nom n'est pas dans le binaire), une n'est rendue que par la V7, douze sont comparables. Avec les douze de SETUP1 : 24 procédures lues ligne à ligne contre leur source.
24 procédures, trois colonnes ouvertes côte à côte
Le cas typique — frmGroup.LoadProgManGroups
Une boucle qui interroge le Gestionnaire de programmes par DDE, découpe la réponse et remplit une liste. Trente-trois lignes dans la 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 …(sept fois le même bloc) 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)
Les noms de variables locales ne sont dans aucun des deux rendus — ils ne sont pas dans le
binaire, et aucun décompilateur ne peut les inventer. Ce qui sépare les deux colonnes est
ailleurs : la V7 retrouve l'objet réellement lu (lblDDE.Caption),
la constante strPROGMANLINKTOPIC repliée en "ProgMan|Progman", et
l'appel d'API avec ses quatre arguments. Le legacy rend une chaîne vide à la place de l'objet,
laisse de l'assembleur non lifté, et referme sa boucle sur un marqueur d'erreur.
Et le point où le legacy est plus juste
Il en existe. Dans cVirtualMemory.ReadDouble, la V7 insère un argument au mauvais
rang d'un appel à Replace — alors que la procédure jumelle
ReadSingle, dans la même classe, sort correctement. Sur les 56 appels à
Replace de l'export du témoin, c'est le seul cas relevé. Il est ouvert comme
défaut.
Les deux endroits où le legacy gagne
Un comparatif qui ne perd nulle part est un comparatif qu'on n'a pas fait.
Les blocs isolés — notre défaut ouvert le plus large
Quand une procédure contient des blocs qu'aucune arête ne relie à l'entrée — les cibles de
On Error GoTo, GoSub, Resume — la V7 les voit, les sert au
graphe de flot, et ne les écrit pas dans le source.
Le legacy, lui, n'abandonne rien : son émission linéaire écrit tout, mal structuré mais présent.
Sur le cas extrême, frmPath.cmdOK_Click de SETUP1 : 33 lignes dans la source,
10 blocs sur 11 isolés, 2 lignes émises par la V7 contre
45 par le legacy.
Blocs présents dans le binaire, absents du source émis
On Error GoTo — le legacy écrit
1 524 lignes de corps contre 888. Tous fichiers confondus, la V7 en écrit pourtant plus :
8 119 contre 7 464.Les ressources, et six fonctions
Sur le témoin, le legacy écrit 28 fichiers de ressources (18 .frx
et 10 .ctx) contre 8 pour la V7, et aucun .ctx.
Six fonctions du produit historique n'ont pas encore d'équivalent sur le bureau V7 : recherche de texte dans le projet, recherche des bibliothèques importées, recherche de programmes VB sur le disque, vue des ressources du binaire, export de l'image d'un formulaire, écran d'options.
Le tableau complet
Toutes les dimensions mesurées, sans sélection.
| SETUP1.EXE — 286 Ko, natif, 16 composants | legacy 6.4 | V7 | Lecture |
|---|---|---|---|
| Procédures écrites dans l'export | 136 | 227 | 238 dans la source |
| Procédures retrouvées sous leur nom | 51 | 51 | égalité exacte : ce sont les gestionnaires d'événements ; les noms des procédures de module ne sont pas dans le binaire |
| Corps vide alors que la source ne l'est pas | 1 | 0 | |
| Lignes de corps, sur les 51 appariées | 1 524 | 888 | avantage legacy — conséquence des blocs isolés |
| Lignes de code, tous fichiers | 7 464 | 8 119 | 11 878 dans la source |
| Fichiers exportés | 26 | 27 | V7 : 26 + EXPORT-NOTES.txt |
| Blocs absents du source émis | 0 | 127 | sur 87 procédures — avantage legacy |
Reference= et options de compilation dans le .vbp | oui | non | la V7 les omet et le documente — 7 notes d'export |
| Témoin — 9,35 Mo, natif, 165 composants | legacy 6.4 | V7 | Lecture |
|---|---|---|---|
| Ouverture → projet écrit | ≈ 88 s | 12 s | legacy : 71 s d'analyse + ~15 s d'écriture |
| Procédures écrites dans l'export | 1 799 | 1 976 | 1 718 dans la source ; le binaire porte plus de méthodes (accesseurs générés) |
| Procédures retrouvées sous leur nom | 1 263 | 1 252 | avantage legacy de 11 procédures |
| Corps vide alors que la source ne l'est pas | 229 | 0 | l'écart le plus large du comparatif |
| Lignes de corps, sur les procédures appariées | 13 018 | 12 235 | 20 973 dans la source ; 1 129 des lignes legacy sont des marqueurs d'erreur |
| Lignes de code, tous fichiers | 54 374 | 42 409 | 45 165 dans la source ; le legacy gonfle par 75 014 As Unknow |
| Fichiers de ressources | 28 | 8 | 18 .frx + 10 .ctx contre 8 .frx, aucun .ctx |
| Blocs absents du source émis | 0 | 1 541 | sur 232 procédures (12 %) — avantage legacy |
| Procédures dont la structuration aboutit | — | 1 976 / 1 976 | aucune procédure non structurée |
| Fonctions du produit | legacy | V7 |
|---|---|---|
| Décompilation P-code | non — module exporté vide | oui |
| Renommage global de symboles | non | oui |
| Références croisées, graphe d'appels, graphe de flot | non | oui |
| Liste des symboles, signets, navigation avant/arrière | Goto seul | oui |
| Éditeur hexadécimal | patcheur « adresse + octets » | vue complète |
| API Browser / Object Browser | Explorateur de librairies | les deux |
| Notes d'export : ce qui n'a pas pu être récupéré est écrit | non | oui |
| Recherche de texte, DLL Finder, recherche de programmes, vue des ressources, export d'image, options | oui | non |
| Ouvre les binaires réels sans planter (build 7.1) | non | oui |
Le P-code
Le legacy identifie le P-code — il écrit CompilationType=-1 'P-Code dans
le .vbp — puis exporte un module de 0 octet. La V7 en écrit
75 lignes, et les trois premières fonctions sont identiques à la source à la nomenclature près :
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
Et ce n'est pas à notre avantage sur un point
Ces fonctions sont Public dans la source et sortent Private proc_N :
sur ce module-là, la V7 ne récupère pas les noms publics. Le legacy ne rend rien du tout, donc
la comparaison lui est défavorable — mais notre rendu n'est pas juste pour autant.
Le test qui tranche
Un export lisible ne vaut rien s'il ne se recharge pas. Alors : est-ce qu'il recompile ?
C'est le seul critère qui mesure ce qu'un client veut vraiment, et c'était jusqu'ici la seule ligne de ce document marquée « non mesurée des deux côtés ». Elle l'est maintenant.
Sur l'ensemble du banc — 149 binaires exportés puis soumis à
VB6.EXE /make — la V7 en fait recompiler 119, soit 80 %.
Le legacy n'a pas pu être soumis au même banc : il n'a pas d'export en ligne de commande, et
il faudrait ouvrir les 149 binaires un par un dans sa fenêtre.
Sur SETUP1.EXE, en revanche, le tête-à-tête a été fait. Le projet a été exporté par chacun des deux produits, puis les deux exports passés au même compilateur.
SETUP1.EXE, passé à VB6.EXE /make |
legacy 6.4 | V7 |
|---|---|---|
| Le projet recompile | non | non |
| Où VB6 s'arrête | 1re ligne de code | ligne 119 |
| Ce que VB6 dit | Invalid character |
Argument not optional |
Fichiers portant Attribute VB_Name (VB6 l'exige pour
ouvrir un fichier) |
0 / 16 | 16 / 16 |
Paramètres déclarés As Unknow (ce type n'existe pas en
VB6) |
1 442 | 0 |
| Modules de 0 octet | 3 | 0 |
| Noms masqués par l'édition Free | 99 | — |
Aucun des deux ne recompile, et c'est la première chose à dire. Mais l'endroit où chacun s'arrête n'est pas du même ordre.
VB6 refuse l'export du legacy avant d'avoir lu une ligne de code : les fichiers n'ont
pas d'en-tête Attribute VB_Name, et la première procédure déclare soixante et un
paramètres d'un type qui n'existe pas. Le fichier n'est pas compilable parce qu'il n'est pas
lisible.
L'export de la V7 est lu en entier : le formulaire, ses contrôles, puis cent dix-huit lignes de code. Il s'arrête sur un appel auquel il manque un argument. C'est un défaut de décompilation, et il est réparable en une ligne.
La précaution qui va avec
Le legacy mesuré ici est la 6.4 Free, qui masque 99 noms de membres sur
SETUP1. Ce bridage aurait de toute façon empêché la compilation — mais il n'est pas la
cause de l'arrêt en première ligne : celle-ci tient aux en-têtes absents et au type
As Unknow, que l'édition Professional écrit de la même façon.
Et 119 / 149 reste un chiffre de la V7 seule. Le tête-à-tête ci-dessus porte sur un binaire, pas sur le banc.
Ce que nous n'avons pas mesuré
La partie d'un comparatif qui dit s'il faut le croire.
- Le legacy Professional 7.1 sur les deux témoins. Il plante avant d'afficher quoi que ce soit.
- La justesse des ressources. Seules les tailles ont été comparées ; aucun
.frxni.ctxn'a été rouvert. - Le legacy sur l'ensemble du banc de recompilation. Il y a été comparé sur SETUP1 (voir plus haut), pas sur les 149 binaires : il n'a pas d'export en ligne de commande. 119 / 149 reste donc un chiffre de la V7, pas un écart.
- Le témoin de 9,35 Mo passé au compilateur. L'export legacy du témoin n'a pas pu être obtenu : le produit attendait derrière sa fenêtre d'avertissement sur le droit d'auteur.
- Les fonctions du legacy que nous n'avons fait qu'ouvrir (Object Browser, Explorateur de librairies, DLL Finder) : leur présence est constatée, leur qualité non.
- L'ergonomie, la latence d'interface, la stabilité en usage prolongé.
- Les binaires obfusqués ou protégés, absents du matériel de comparaison.
Sur les deux binaires réels, la V7 rend un code substantiellement plus fidèle que le legacy, à parité de noms retrouvés — et elle le fait sept fois plus vite. Elle perd en revanche 1 541 blocs de code que le legacy, lui, écrit tous. C'est le défaut que nous traitons en premier.