119/149
Projets exportés par la V7 qui se recompilent tels quels dans l'IDE VB6 — le legacy n'a pas été soumis au même banc
88 s12 s
Ouvrir un binaire de 9,35 Mo et écrire le projet complet
2290
Procédures rendues avec un corps vide alors que la source ne l'est pas
1 1290
Marqueurs d'erreur de structure laissés dans le code rendu
75 0140
Signatures rendues As Unknow, faute de type

Un 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.

legacy 6.4 VBReFormer V7 une piste vide vaut zéro
Procédures exportées1 799 vs 1 976
Procédures retrouvées sous leur nom1 263 vs 1 252
Corps vide alors que la source ne l’est pas229 vs 0
Marqueurs d’erreur dans le code rendu1 129 vs 0
Signatures rendues « As Unknow »75 014 vs 0
Les trois dernières lignes sont les plus lourdes de conséquence : un corps vide, un marqueur d'erreur et une signature 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.

legacy 6.4 VBReFormer V7
Procédures écrites dans l’export (238 dans la source)136 vs 227
Lignes de code, tous fichiers (11 878 dans la source)7 464 vs 8 119
Fichiers exportés26 vs 27

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.

Ouvrir — format et localisation des 227 méthodes0,05 s
Décompiler les 227 corps (2,8 ms par corps)0,64 s
TypeLib, arbre et le reste du chemin produit0,26 s
De l’octet au projet complet0,95 s
Décompiler un corps coûte 2,8 ms. Le reste du temps est le format, la localisation des méthodes, les TypeLib et la construction de l'arbre.

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

${x.t}2 vs 22

Les deux cas favorables au legacy sont les deux corps que la V7 tronque — le même défaut de blocs isolés.

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.

Source VB6 d'origine
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
    …(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...)
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)

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

Témoin 9,35 Mo — 232 procédures touchées sur 1 976 (12 %)0 vs 1 541
SETUP1.EXE — 87 procédures touchées sur 227 (38 %)0 vs 127
C'est ce défaut qui explique presque entièrement l'autre chiffre défavorable du comparatif : sur les 51 procédures de SETUP1 appariées par leur nom — les gestionnaires d'événements, ceux qui portent le plus de 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'export136227238 dans la source
Procédures retrouvées sous leur nom5151é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 pas10
Lignes de corps, sur les 51 appariées1 524888avantage legacy — conséquence des blocs isolés
Lignes de code, tous fichiers7 4648 11911 878 dans la source
Fichiers exportés2627V7 : 26 + EXPORT-NOTES.txt
Blocs absents du source émis0127sur 87 procédures — avantage legacy
Reference= et options de compilation dans le .vbpouinonla 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 s12 slegacy : 71 s d'analyse + ~15 s d'écriture
Procédures écrites dans l'export1 7991 9761 718 dans la source ; le binaire porte plus de méthodes (accesseurs générés)
Procédures retrouvées sous leur nom1 2631 252avantage legacy de 11 procédures
Corps vide alors que la source ne l'est pas2290l'écart le plus large du comparatif
Lignes de corps, sur les procédures appariées13 01812 23520 973 dans la source ; 1 129 des lignes legacy sont des marqueurs d'erreur
Lignes de code, tous fichiers54 37442 40945 165 dans la source ; le legacy gonfle par 75 014 As Unknow
Fichiers de ressources28818 .frx + 10 .ctx contre 8 .frx, aucun .ctx
Blocs absents du source émis01 541sur 232 procédures (12 %) — avantage legacy
Procédures dont la structuration aboutit1 976 / 1 976aucune procédure non structurée
Fonctions du produit legacy V7
Décompilation P-codenon — module exporté videoui
Renommage global de symbolesnonoui
Références croisées, graphe d'appels, graphe de flotnonoui
Liste des symboles, signets, navigation avant/arrièreGoto seuloui
Éditeur hexadécimalpatcheur « adresse + octets »vue complète
API Browser / Object BrowserExplorateur de librairiesles deux
Notes d'export : ce qui n'a pas pu être récupéré est écritnonoui
Recherche de texte, DLL Finder, recherche de programmes, vue des ressources, export d'image, optionsouinon
Ouvre les binaires réels sans planter (build 7.1)nonoui

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 :

Source VB6 d'origine
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 — depuis le 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

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 recompilenonnon
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 / 1616 / 16
Paramètres déclarés As Unknow (ce type n'existe pas en VB6) 1 4420
Modules de 0 octet30
Noms masqués par l'édition Free99

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 .frx ni .ctx n'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.

Méthode. Les deux produits ont été exécutés sur les mêmes fichiers, leurs exports appariés par composant, et chaque procédure comparée à la source VB6 d'origine (…/PDWizard/Setup1/*.FRM,*.BAS et les sources de l'ancien produit). Les temps V7 sont des médianes de trois passes après chauffe. Aucune ligne de moteur n'a été modifiée entre la mesure et la publication.

Un chiffre de cette page qui ne serait pas reproductible est un défaut de la page. Le protocole complet et les scripts d'appariement sont publiés avec le produit.