Når et Delphi-bibliotek får en build-konfiguration uden det visuelle framework, er erstatningsklasserne, hvor fejlene bor. Ikke platformen, ikke compileren: stand-ins'erne. PDFlibPas har et grafiklag, der leverer bitmap-, canvas-, font-, metafil- og printer-ækvivalenter til byg uden VCL, og porteringen til Free Pascal bragte alle de fejltilstande, en stand-in kan have, op i lyset. De sorterer pænt efter diagnoseomkostning, og rækkefølgen er det modsatte af, hvad intuitionen antyder
En stand-in, der kaster, er billig at finde; exceptionen navngiver metoden. En stand-in, der returnerer tomme data, er dyr, for fejlen viser sig adskillige lag fra sin årsag. En stand-in, der returnerer succes, er det værste af alt, for returkoden er gyldig, fejlkoden er nul, ingen exception kastes, og det eneste bevis på, at noget gik galt, ligger i de bytes, der kom ud
Form tre: en gyldig image-identifikator over et tomt XObject
Vektor-metafilkonverteren var en tom procedurekrop i non-VCL-konfigurationen. Alt over den fortsatte med at virke. EMF-import-entry points og canvas-capture-entry pointet kørede til ende og returnerede en lovlig image-identifikator, som kalderen derefter placerede på en side. Det, der landede i filen, var et form XObject med en indholdslængde på nul. Siden renderede hvid
Ingenting meldte et problem, og det inkluderer bibliotekets eget demoprogram for denne funktion, som tegnede en blank side og ikke bemærkede det. Der var ingen fejlande returværdi at tjekke, for kaldsekvensen lykkedes genuint alle sammen; det eneste, der var galt, var størrelsen af den producerede strøm. At diagnosticere denne klasse af defekter betyder at stille et andet spørgsmål: ikke "fejlede kaldet", men "er artefaktet plausibelt". Et form XObject med længden nul, et billede på nul pixels, en side på nul indholdsbytes — det er de assertions, der fanger det
Rettelsen har to halvdele, og den anden halvdel er let at glemme. For det første: få den tomme implementation til at kaste, så fejlen overhovedet har en kanal. For det andet: omdan den exception til et null-resultat i image-fabrikken og tilføj null-tjek de to steder, der indtager en image-identifikator, for ellers bliver "ren fejl" til en access violation, idet sidetræet derefererer ingenting. En stub, der kaster, er kun en forbedring, hvis kalderne var forberedt på en fejl, de aldrig før havde kunnet modtage
Form to: tomme data, tre lag fra crashet
Metafil-canvas stand-in'en udfyldte ikke sine fysiske dimensioner. Den værdi divideres ind i en sidegeometriberegning, så beregningen gav nul, så bounding-box-beregningen dividerede med nul. En bar exception-handler slugte det, image-fabrikken returnerede et null-resultat, og access violationen skete til sidst i sidetræet, da null'et blev brugt. Tre lag mellem årsag og symptom med en exception-handler i midten, der udviser beviset
Samme unit havde to instanser mere af mønstret. Fontklassen havde tomme Assign- og konstruktor-krope, hvilket betyder mere, end det ser ud, for canvasens font-egenskab er skrivebeskyttet: at assigne ind i den er den eneste måde at levere en font på, så en tom implementation gør fontvalget i stilhed virkningsløst, og teksten kommer ud i, hvad standarden nu var. Og en pixels-per-inch-værdi på nul fik enhver kalder, der dimensionerer en canvas ud fra font-metrics, til at producere en nul-gange-nul canvas, hvilket giver en blank side og en succes-retur
// Formen, der skal kigges efter i en stand-in-unit: en metode, der
// hverken kaster eller gør noget. Begge disse kompilerer, og begge
// producerer "succes" uden output
procedure TMetafileCanvasStandIn.Create(...);
begin
// intet inherited-kald, ingen feltinitialisering
end;
function TBitmapStandIn.LoadFromStream(Stream: TStream): Boolean;
begin
Result := True; // og bitmapmen er stadig tom
end;
Den brede struktur, der kun beholder første tegn
Denne er slet ikke et stand-in-problem, men den hører hjemme i samme katalog, for symptomet er lige så langt fra årsagen. Printerens enumerate-struktur var erklæret med alle sine tolv strengmedlemmer typet som pointere til enkeltbyte-tegn, mens funktionen, der fylder den ud, er wide-character-varianten af enumeration-API'et
Pointerstørrelser er identiske, så strukturens layout er korrekt, og intet crasher. Det, der sker i stedet, er, at læsning af en UTF-16-streng som en enkeltbyte-streng stopper ved den første nul-byte, som for ethvert ASCII-printernavn er den høje halvdel af det andet tegn. Hvert printernavn kom tilbage som præcis ét tegn. Nedefter fejlede navnevalidering, printeroprettelse og udskrivning for hver rigtig printer på maskinen, og intet af de symptomer peger på en strukturdeklaration
// Forkert: rigtig størrelse, forkert elementtype. Ingen compilefejl,
// intet crash, hver streng afkortet til ét tegn
type
TPrinterInfo2Wrong = record
pServerName: PAnsiChar;
pPrinterName: PAnsiChar;
// ... ti flere
end;
// Rigtigt: en *W-struktur har brede medlemmer hele vejen igennem
type
TPrinterInfo2W = record
pServerName: PWideChar;
pPrinterName: PWideChar;
// ... ti flere
end;
Reglen, der kommer ud af det, er mekanisk og værd at anvende uden at tænke: for enhver Win32-struktur, hvis navn slutter på W, verificér at hvert strengmedlem er den brede variant, felt for felt. At blande ANSI- og den brede verden giver hverken en compilerdiagnose eller et crash, kun stille afkortning, og det samme gælder i modsat retning for ANSI-varianterne
En bar exception-handler er den egentlige modstander
Undersøgelsen af hver eneste af disse blev forsinket af samme konstruktion: en handler, der fanger alt og omdanner det til en falsk returværdi. Det er en rimelig ting at skrive rundt om en billeddecoder, da et korrupt billede ikke bør vælte et dokumentjob. Det er også en anordning til at slette den ene oplysning, du behøver
Det praktiske svar er at gøre handleren midlertidigt højlydt. At dumpe exceptionklassen, beskeden og backtracen indefra i den bare handler, under en debug-betingelse, omdanner en uforklarlig null-retur til en navngiven exception med en placering. I to af de tre tilfælde ovenfor afsluttede det eneste trin undersøgelsen, for exceptionen var en division med nul eller en access violation i en stand-in-metode, hvis navn sagde alt
Tjekliste for at tage en stand-in-vej i brug
Fire punkter, i den rækkefølge, de betaler sig. Før du kalder ind i en erstatningsklasse, læs de metoder, du er ved at bruge, og bekræft, at hver har en reel krop; en tom krop er ikke en implementeringsdetalje, den er en manglende funktion. Foretræk stand-ins, der kaster, frem for stand-ins, der returnerer neutrale værdier, og par det med null-tjek de steder, hvor en fabrik nu lovligt kan returnere ingenting. Verificér en funktion ved at inspicere artefaktet, ikke returkoden, da hele fejltilstanden her er en ren returkode over et tomt artefakt; en opdeling på byte-niveau af, hvad et dokument reelt indeholder, er den hurtigste måde at se det på, og artiklen om filstørrelsesaudit dækker det værktøj. Og når en funktion ikke har nogen levedygtig erstatningsimplementation, så send de berørte eksempler til vejen, der virker, og sig hvorfor i en kommentar i stedet for at efterlade en demonstration, der i stilhed producerer tomt output
Det bredere punkt gælder langt ud over ét bibliotek. Enhver kodebase med en betinget anden implementation, et mock-lag, en headless-tilstand, en platform-shim, er udsat for form tre. Grunden til, at den skjuler sig så godt, er, at enhver kvalitetsport, et team normalt støtter sig til — returkoder, fejlkoder, exceptions, exit-statusser — er en statuskanal, og form tre holder dem alle rene. Kun outputtet forråder den. Det er også begrundelsen bag at tjekke artefakter frem for statusser ved håndtering af upålideligt input, beskrevet i artiklen om parsing af upålidelige PDF-filer, og bag at sammenligne renderet output på tværs af motorer frem for at stole på én, beskrevet i multi-engine-rendering
PDFlibPas er et indbygget Object Pascal PDF-bibliotek til Delphi, C++Builder og Free Pascal, og dets non-VCL-konfiguration er det, der gør headless- og cross-toolchain-byg mulige; aktuel konfigurationsdækning er opført på produktsiden losLab PDF Developer Library