PDFlibPas 3.538.0 statički povezuje vanjski JBIG2 enkoder u programe napisane u Free Pascalu i Lazarusu. Projekt dodaje jedinicu PDFlibJBIG2EncC, istu jedinicu koju Delphi i C++Builder već koriste, pa enkoder završava unutar izvršne datoteke i uz nju više ne treba isporučiti ništa dodatno. Time se poništava prethodni zaključak o toj značajci, prema kojem je Free Pascal do vanjskog enkodera mogao doći samo kroz DLL
Zašto je DLL izgledao kao jedina mogućnost?
DLL je izgledao kao jedina mogućnost zato što su tri puta povezivanja otkazala na tri nepovezana načina, a nijedna opcija kompajlera nije dopirala do ijednog od njih. Interni linker izravno odbija asocijativne COMDAT sekcije. Vanjsko povezivanje kroz priloženi binutils ruši se tijekom uklanjanja neiskorištenih sekcija, što Free Pascal bezuvjetno prosljeđuje za 64-bitni Windows cilj. Noviji binutils uopće ne može obraditi skriptu povezivanja Free Pascala. Ponovna izgradnja C++ strane drugim lancem alata samo zamjenjuje jedno odbijanje drugim, jer instanciranje predložaka i inline funkcija po konstrukciji emitira slabe vanjske simbole, a Free Pascal ih prijavljuje kao Unsupported COFF symbol type 105. Nijedan od tih dokaza nije bio pogrešan, a raniji prikaz pozadinskih JBIG2 enkodera i linkera Free Pascala prolazi kroz svaku slijepu ulicu u obliku koji se i danas može ponoviti. Pogrešna je bila pretpostavka o tome gdje popravak može živjeti. Svaki pokušaj prolazio je kroz kompajler ili linker, a nijedan ne može promijeniti ono što objektna datoteka već sadrži. Objektna datoteka cijelo je vrijeme bila problem. ObjConv čita COFF i zapisuje COFF, a svaka konstrukcija na kojoj se Free Pascal spotiče ima mehanički ekvivalent koji prihvaća
Pogreška koja nikad ne imenuje svoj uzrok
Interni linker Free Pascala implementira pick-any COMDAT samo napola, a upravo je ta poluimplementacija ovdje najteža za dijagnosticiranje. Duple definicije doista spaja, kako format nalaže. No TExeOutput.RemoveUnreferencedSections pri označavanju korištenih sekcija kroz exesymbol preusmjerava na pobjedničku definiciju, dok TCoffexeoutput.DoRelocationFixup izravno čita objreloc.symbol.objsection. Kada korištena sekcija referencira simbol koji njegov vlastiti objekt definira u kopiji koja je izgubila spajanje, ta dva prolaza gledaju različite sekcije i povezivanje se zaustavlja s Internal error 200603061
Usporedite to s dvama ograničenjima s obje strane te pogreške. Unsupported COFF symbol type 105 govori da je riječ o slabom vanjskom simbolu. Associative or exact match COMDAT sections are not yet supported govori da je riječ o asocijativnom COMDAT-u i čak imenuje sporni simbol. Interna pogreška 200603061 ne govori baš ništa: nema imena simbola, imena sekcije, imena datoteke ni faze. To je usto uobičajen slučaj, a ne rubna situacija, jer MSVC svaki string literal te svaku inline instancijaciju ili instancijaciju predloška stavlja u pick-any COMDAT, a u 186 objekata ovog skupa enkodera linker je napravio 2656 spajanja. Izgradnja s /Gy- drži obične funkcije izvan COMDAT sekcija po funkciji, ali string literale i instancijacije predložaka ostavlja točno ondje gdje su bile
Zašto popunjavanje CRT simbola uvijek izgleda kao da je zadnji dodani uzrok kvara?
Jer linker do prolaza za popravak dolazi tek nakon što se svaki simbol razriješi. Dok nešto još nedostaje, izvođenje rano završava s Undefined symbol i COMDAT problem uopće ne dobije priliku pokazati se. Popunite posljednji stub C runtimea i linker prijeđe u sljedeću fazu, ravno u internu pogrešku 200603061. Simptom na terenu zato sustavno vara: kada jedno po jedno dodajete Pascalova tijela za referencirane C simbole, uvijek izgleda kao da je zadnji dodatak pokvario izgradnju ili da je prijeđen neki prag od otprilike stotinu stubova. Nije tako. Posve je nevažno koji je simbol dodan zadnji i koliko ih je ukupno dodano, jer je kvar bio skriven već u prvom objektu i postao je dohvatljiv tek nakon uspješnog razrješavanja. Kada linker promijeni prigovor nakon što popravite nešto nepovezano, pitajte se jeste li samo prešli u novu fazu, a ne prouzročili regresiju
Popravak je jedan prolaz kroz ObjConv, a ne zastavica kompajlera
Cijeli se popravak svodi na jednu naredbu naknadne obrade pokrenutu nad svakim prevedenim objektom: ObjConv -fcoff64 -xw -xc -xn -np:__imp_:pdflibimp_. Tri su opcije dodane upravo za ovaj rad. -xw pretvara simbole IMAGE_SYM_CLASS_WEAK_EXTERNAL u obične vanjske simbole. -xn normalizira simbole IMAGE_SYM_CLASS_NULL, poput _fltused, koje Free Pascal prijavljuje kao Unsupported COFF symbol type 0. -xc obavlja glavni posao: svaku COMDAT sekciju spušta na običnu sekciju, a simbole koje ona definira čini statičnima. Time se kvar uklanja tako što se uklanja odluka: bez COMDAT sekcija nema spajanja, nema pobjedničke kopije na koju bi se jedan prolaz preusmjerio, a drugi je promašio, i asocijativne unwind sekcije .pdata i .xdata nestaju zajedno s time. Cijena postoji, ali je mala: kopije koje su se legitimno mogle spojiti sada svaka preživljava
Preimenovanje prefiksa -np:__imp_:pdflibimp_ rješava zaseban sudar. MSVC poziva uvezene Win32 API-je preko indirekcijskih ćelija imena __imp_*, Free Pascal taj prefiks čuva za vlastiti mehanizam importa, a izravno definiranje jednog od tih imena izaziva istu internu pogrešku 200603061. Preimenovanjem ćelija Pascalova ih strana može objaviti kao obične varijable i popuniti tijekom izvođenja. Sami objekti prevedeni su s uključenom zastavicom za statičko povezivanje, /GS- /Gs999999 /Gy- /Zl /GR- /EHs-c-, i s isključenim slikovnim kodecima, pa mrtvi putovi za rad s datotekama i kodecima trebaju mnogo manje stubova samo za povezivanje. Smještaju se u Lib\thirdparty\Win64f, dok put za Delphi i C++Builder i dalje povezuje vlastiti skup Win64x bez promjene, što je ispravan ishod popravka prenosivosti ograničenog na jedan lanac alata
Što Pascalova strana još mora izvesti
Free Pascal import C objekta razrješava prema imenu simbola i traži da to ime bude izričito navedeno, pa svaka Pascalova rutina koja zamjenjuje C ulaznu točku nosi izričitu klauzulu public name. Delphi ime rutine uzima kao ime simbola i ne treba nikakvu klauzulu, zbog čega jedna jedinica služi obama kompajlerima, s klauzulama pod {$IFDEF FPC}. Zamka je u tome što deklaracija external 'msvcrt.dll' ne zadovoljava ništa: ona stvara import, nikada definiciju na koju se povezani objekt može vezati. Prosljeđujuće tijelo mora postojati
// Vanjska deklaracija stvara samo import. Nijedan povezani objekt ne može
// se na nju vezati.
function crt_memcmp(Buf1, Buf2: Pointer; Count: NativeUInt): Integer; cdecl;
external 'msvcrt.dll' name 'memcmp';
// Pascalovo tijelo objavljeno pod točnim imenom C simbola ono je na što se
// skup objekata stvarno veže.
function jbig2_memcmp(Buf1, Buf2: Pointer; Count: NativeUInt): Integer; cdecl;
public name 'memcmp';
begin
Result := crt_memcmp(Buf1, Buf2, Count);
end;
Varijadične ulazne točke ruše taj obrazac jer Pascalov omotač ne može proslijediti vlastite varargs argumente drugom varargs pozivu. Izlaz je prestati biti omotač: izvesti golu rutinu pod C imenom i skočiti na stvarnu implementaciju tako da registri s argumentima i stog ostanu točno onakvi kakvima ih je pozivatelj pripremio. Sloj JPEG 2000 već na taj način obrađuje snprintf i vsnprintf, skačući na imena msvcrt-a s prefiksom podvlake jer obična imena izvozi samo UCRT. Jedno povezano ograničenje proizlazi iz iste interne pogreške: preimenovane importne ćelije popunjavaju se iz odjeljka initialization preko GetModuleHandleA i GetProcAddress, a ne iz statičkih inicijalizatora, jer uzimanje adrese uvezene rutine u inicijalizatoru navodi kompajler da emitira popravak koji ne može obraditi i ponovno završi s pogreškom 200603061
function crt_sprintf(Buffer: PAnsiChar; Fmt: PAnsiChar): Integer; cdecl; varargs;
external 'msvcrt.dll' name 'sprintf';
var
SPrintfTarget: Pointer = @crt_sprintf;
// Varargs se ne mogu proslijediti iz Pascalova omotača, pa izvezeni
// simbol skače na kraj, s okvirom koji je pozivatelj već pripremio.
procedure jbig2_sprintf; assembler; nostackframe;
public name 'sprintf';
asm
jmp qword ptr [rip + SPrintfTarget]
end;
Što Free Pascal projekt sada radi drukčije
Ništa osim imena jedinice u uses klauzuli, a datoteka za isporuku više ne postoji. Pozadinski modul registrira se iz vlastitog odjeljka initialization kroz RegisterJBIG2EncoderBackend, a pozivatelji ga traže kao i prije: bitom opcija PDF_JBIG2_OPTION_EXTERNAL_ENCODER, čija je vrijednost 4, ili argumentom UseExternalEncoder proširenih ulaznih točaka. Zahtjev i dalje ostaje preferencija, a ne jamstvo, jer se izgradnja iz koje je jedinica izostavljena nečujno vraća na izvorni Pascalov MMR enkoder i proizvodi veće datoteke umjesto pogreške
uses
Classes, SysUtils,
PDFlibrary,
PDFlibJBIG2EncC; // Delphi, C++Builder i od 3.538.0 Free Pascal
var
Pdf: TPDFlib;
Scan: TStream;
ImageId: Integer;
begin
Pdf := TPDFlib.Create(nil);
try
Pdf.NewDocument;
Pdf.NewPage;
Scan := TFileStream.Create('scan-page-1.tif', fmOpenRead);
try
// Interpolate, SymbolExtract, UseExternalEncoder, SkipBlackDots,
// BlackDotSize, LossyLevel
ImageId := Pdf.AddImageJBIG2FromStreamEx(Scan, 0, 1, 1, 0, 0, 0);
finally
Scan.Free;
end;
if ImageId = 0 then
raise Exception.Create('JBIG2 encoding failed');
Pdf.SaveToFile('archive.pdf');
finally
Pdf.Free;
end;
end;
Vrijedi jasno navesti dva ograničenja. Postoji samo skup Win64 objekata, pa na svakom drugom cilju Free Pascala ulazna točka za vanjsko kodiranje prijavljuje neuspjeh, a posao preuzima izvorni Pascalov enkoder. Regresija koja sve ovo potvrđuje pritom je usporedba prikaza, a ne provjera veličine: oba su enkodera bez gubitaka nad istim izvorom, pa se njihov izlaz prikazuje i uspoređuje bajt po bajt, a Lazarusov paket prolazi 26 od 26 testova, uključujući ovaj. Usporedba veličina sažetih tokova ne bi dokazala ništa jer se invertirna stranica sažima na približno istu veličinu kao i ispravna
Šira se pouka proteže i izvan JBIG2-a. DLL je pravi oblik kada je granica doista dinamična, što je slučaj kojem služe površine integracije DLL-a, ActiveX-a i dylib-a; pogrešan je oblik kada je samo zaobilazno rješenje za COFF čitač, jer svakoj instalaciji dodaje datoteku, svakoj isporuci put pretraživanja i način kvara zbog nepodudaranja verzija koji statičko povezivanje ne može imati. Važan je i uzvodni dio, jer način proizvodnje dvobojne slike više određuje konačnu veličinu od samog enkodera, a monokromatsko iscrtavanje po regijama u Delphiju pokriva tu polovicu cjevovoda. Pokrivenost lanaca alata, skupovi objekata po kompajleru i podržani ciljevi navedeni su na stranici proizvoda losLab PDF Developer Library