PDFlibPas 3.538.0 statično poveže zunanji enkoder JBIG2 s programi v Free Pascalu in Lazarusu. Projekt doda enoto PDFlibJBIG2EncC, isto enoto, ki jo že uporabljata Delphi in C++Builder, zato enkoder konča v izvršni datoteki in poleg nje ni treba dostaviti ničesar dodatnega. S tem se obrne prejšnji sklep o tej funkciji, po katerem je Free Pascal do zunanjega enkoderja lahko prišel samo prek DLL-a
Zakaj je bil DLL videti kot edina možnost?
DLL je bil videti kot edina možnost, ker so tri poti povezovanja odpovedale na tri nepovezane načine, nobena zastavica prevajalnika pa ni dosegla nobene od njih. Notranji povezovalnik asociativne sekcije COMDAT zavrne neposredno. Zunanje povezovanje prek priloženega binutils se sesuje med odstranjevanjem neuporabljenih sekcij, ki ga Free Pascal na 64-bitnem cilju Windows posreduje brezpogojno. Novejši binutils skripte povezovanja Free Pascala sploh ne more obdelati. Ponovna izgradnja strani C++ z drugo verigo orodij eno zavrnitev zamenja z drugo, saj instanciranje predlog in vstavljenih funkcij po naravi oddaja šibke zunanje simbole, Free Pascal pa jih prijavi kot Unsupported COFF symbol type 105. Noben od teh dokazov ni bil napačen, prejšnji opis zaledij enkoderja JBIG2 in povezovalnika Free Pascala v prejšnjem članku pa še danes reproducibilno preide vsako slepo ulico. Napačna je bila predpostavka o tem, kje je mogoče uvesti popravek. Vsak poskus je šel skozi prevajalnik ali povezovalnik, vendar noben ne more spremeniti tega, kar objektna datoteka že vsebuje. Objektna datoteka je bila ves čas problem. ObjConv bere COFF in zapisuje COFF, vsaka konstrukcija, ob kateri se Free Pascal spotakne, pa ima mehanski ekvivalent, ki ga sprejme
Napaka, ki nikoli ne poimenuje svojega vzroka
Notranji povezovalnik Free Pascala implementira pick-any COMDAT samo napol, prav ta napol izvedena implementacija pa je tukaj najtežja za diagnosticiranje. Podvojene definicije res združi, kot predvideva format. Toda TExeOutput.RemoveUnreferencedSections pri označevanju uporabljenih sekcij prek exesymbol preusmeri na zmagovalno definicijo, medtem ko TCoffexeoutput.DoRelocationFixup neposredno bere objreloc.symbol.objsection. Ko uporabljena sekcija referencira simbol, ki ga njegova lastna objektna datoteka definira v kopiji, izgubljeni pri združevanju, oba prehoda gledata različni sekciji in povezovanje se ustavi z Internal error 200603061
Primerjajte to z omejitvama na obeh straneh te napake. Unsupported COFF symbol type 105 pove, da gre za šibek zunanji simbol. Associative or exact match COMDAT sections are not yet supported pove, da gre za asociativni COMDAT, in celo poimenuje sporni simbol. Interna napaka 200603061 ne pove ničesar: ne imena simbola, ne imena sekcije, ne imena datoteke in ne faze. Poleg tega gre za običajen primer, ne za robno situacijo, saj MSVC vsak nizovni literal ter vsako instanciranje vstavljenih funkcij ali predlogov postavi v pick-any COMDAT, povezovalnik pa je v 186 objektih tega nabora enkoderja izvedel 2656 združitev. Gradnja z /Gy- ohrani običajne funkcije zunaj COMDAT sekcij po funkcijah, vendar nizovne literale in instanciranja predlogov pusti natanko tam, kjer so bila
Zakaj je pri dodajanju CRT-simbolov vedno videti, kot da je zadnji povzročil napako?
Ker povezovalnik do prehoda za popravke pride šele, ko so razrešeni vsi simboli. Dokler nekaj še manjka, se izvajanje konča zgodaj z Undefined symbol in težava s COMDAT sploh ne dobi priložnosti, da bi se pokazala. Dodajte zadnji izsek za C-jevo izvajalno knjižnico in povezovalnik preide v naslednjo fazo, naravnost v interno napako 200603061. Simptom na terenu je zato sistematično zavajajoč: ko eno za drugim dodajate Pascalova telesa za referencirane C-simbole, je vedno videti, kot da je zadnji dodatek pokvaril gradnjo ali da je bil presežen prag okoli stotih izsekov. Nič od tega ne drži. Kateri simbol je bil dodan zadnji in koliko jih je bilo dodanih skupaj, je nepomembno, saj je bila napaka skrita že v prvem objektu in je postala dosegljiva šele po uspešni razrešitvi. Ko povezovalnik spremeni svoje sporočilo, potem ko popravite nekaj nepovezanega, se vprašajte, ali ste napredovali v novo fazo, ne pa povzročili regresije
Popravek je en prehod skozi ObjConv, ne zastavica prevajalnika
Celoten popravek je en sam ukaz naknadne obdelave, ki se izvede nad vsakim prevedenim objektom: ObjConv -fcoff64 -xw -xc -xn -np:__imp_:pdflibimp_. Tri od teh možnosti so bile dodane prav za to delo. -xw simbole IMAGE_SYM_CLASS_WEAK_EXTERNAL razreši v običajne zunanje simbole. -xn normalizira simbole IMAGE_SYM_CLASS_NULL, kot je _fltused, ki ga Free Pascal prijavi kot Unsupported COFF symbol type 0. -xc opravi glavnino dela: vsako sekcijo COMDAT zniža na navadno sekcijo in simbole, ki jih definira, naredi statične. Napako odpravi tako, da odstrani odločitev: brez sekcij COMDAT ni združevanja, ni zmagovalne kopije, na katero bi en prehod preusmeril, drug pa je ne bi našel, asociativni sekciji za odvijanje .pdata in .xdata pa izgineta skupaj z njim. Cena je resnična, vendar majhna, saj kopije, ki bi se lahko zakonito združile, zdaj vse preživijo
Preimenovanje predpone -np:__imp_:pdflibimp_ reši ločen trk. MSVC uvožene API-je Win32 kliče prek posrednih celic z imeni __imp_*, Free Pascal pa to predpono rezervira za lastni mehanizem uvozov, zato neposredna definicija enega od teh imen sproži isto interno napako 200603061. S preimenovanjem celic jih lahko Pascalova stran objavi kot običajne spremenljivke in jih zapolni med izvajanjem. Objekti so prevedeni z vključeno zastavico za statično povezovanje, /GS- /Gs999999 /Gy- /Zl /GR- /EHs-c-, in z izklopljenimi slikovnimi kodeki, zato mrtve poti za V/I datotek in kodeke potrebujejo precej manj izsekov samo za povezovanje. Končajo v Lib\thirdparty\Win64f, medtem ko pot za Delphi in C++Builder še naprej povezuje lasten nabor Win64x brez sprememb, kar je pravi rezultat popravka prenosljivosti, omejenega na eno verigo orodij
Kaj mora Pascalova stran še izvoziti
Free Pascal uvoz C-jevega objekta razreši po imenu simbola in zahteva, da je to ime izpisano, zato ima vsaka Pascalova rutina, ki nadomešča vstopno točko C, izrecno klavzulo public name. Delphi ime rutine vzame kot ime simbola in klavzule sploh ne potrebuje, zato ena enota služi obema prevajalnikoma, klavzule pa so pod {$IFDEF FPC}. Past je v tem, da deklaracija external 'msvcrt.dll' ne zadovolji ničesar: ustvari uvoz, nikoli pa definicije, na katero bi se lahko vezal povezani objekt. Telo za posredovanje mora obstajati
// Zunanja deklaracija ustvari samo uvoz. Nanj se ne more vezati noben
// povezani objekt.
function crt_memcmp(Buf1, Buf2: Pointer; Count: NativeUInt): Integer; cdecl;
external 'msvcrt.dll' name 'memcmp';
// Pascalovo telo, objavljeno pod natanko enakim imenom simbola C, je tisto,
// na kar se nabor objektov dejansko veže.
function jbig2_memcmp(Buf1, Buf2: Pointer; Count: NativeUInt): Integer; cdecl;
public name 'memcmp';
begin
Result := crt_memcmp(Buf1, Buf2, Count);
end;
Vstopne točke z variadnimi argumenti ta vzorec porušijo, saj Pascalov ovoj ne more posredovati lastnih argumentov varargs drugemu klicu varargs. Rešitev je, da ovoj preneha biti ovoj: pod imenom C izvozi golo rutino in s skokom na koncu preide v pravo implementacijo, pri čemer registra z argumenti in sklad ostaneta natanko takšna, kot ju je pripravil klicatelj. Plast JPEG 2000 tako že obravnava snprintf in vsnprintf, pri čemer skače na imena msvcrt s podčrtajem, ker običajna imena izvaža samo UCRT. Iz iste interne napake izhaja še ena povezana omejitev: preimenovane uvozne celice se zapolnijo iz odseka initialization prek GetModuleHandleA in GetProcAddress, ne pa iz statičnih inicializatorjev, saj jemanje naslova uvožene rutine v inicializatorju prevajalnik pripravi do oddaje popravka, ki ga ne more obdelati, in znova konča z napako 200603061
function crt_sprintf(Buffer: PAnsiChar; Fmt: PAnsiChar): Integer; cdecl; varargs;
external 'msvcrt.dll' name 'sprintf';
var
SPrintfTarget: Pointer = @crt_sprintf;
// Varargs ni mogoče posredovati iz Pascalovega ovoja, zato izvoženi
// simbol skoči na koncu z okvirjem, ki ga je pripravil klicatelj.
procedure jbig2_sprintf; assembler; nostackframe;
public name 'sprintf';
asm
jmp qword ptr [rip + SPrintfTarget]
end;
Kaj projekt v Free Pascalu zdaj počne drugače
Ničesar razen imena enote v stavku uses, poleg tega ni več datoteke, ki bi jo bilo treba namestiti. Zaledje se registrira iz lastnega odseka initialization prek RegisterJBIG2EncoderBackend, klicatelji pa ga zahtevajo natanko tako kot prej: prek bita možnosti PDF_JBIG2_OPTION_EXTERNAL_ENCODER, ki ima vrednost 4, ali prek argumenta UseExternalEncoder razširjenih vstopnih točk. Zahteva ostaja preferenca, ne zagotovilo, saj se gradnja, ki izpusti enoto, tiho vrne k izvornemu Pascalovemu enkoderju MMR in namesto napake ustvari večje datoteke
uses
Classes, SysUtils,
PDFlibrary,
PDFlibJBIG2EncC; // Delphi, C++Builder in od 3.538.0 tudi 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;
Jasno je treba povedati še dve omejitvi. Obstaja samo nabor objektov Win64, zato na vsakem drugem cilju Free Pascala vstopna točka zunanjega kodiranja sporoči napako, delo pa prevzame izvorni Pascalov enkoder. Regresija, ki odloča o vsem tem, je primerjava izrisa, ne preverjanje velikosti: oba enkoderja sta za isti vir brezizgubna, zato se njun izhod izriše in primerja bajt za bajtom, zbirka Lazarus pa je opravila vseh 26 od 26 testov, vključno s tem. Primerjava velikosti stisnjenih tokov ne bi dokazala ničesar, saj se obrnjena stran stisne približno enako kot pravilna
Širši nauk presega JBIG2. DLL je prava oblika, ko je meja resnično dinamična, kar je primer, za katerega obstajajo integracijske površine DLL, ActiveX in dylib; napačna pa je, ko je le obvoz za bralnik COFF, saj vsakemu namestitvenemu programu doda datoteko, vsaki namestitvi iskalno pot in način odpovedi zaradi neusklajenih različic, ki ga statično povezovanje nima. Pomembno je tudi, kaj pride prej, saj način izdelave dvobarvne slike na končno velikost vpliva bolj kot enkoder, drugo polovico cevovoda pa pokriva enobarvno izrisovanje po območjih v Delphiju. Pokritost verig orodij, nabori objektov po prevajalnikih in podprti cilji so navedeni na strani izdelka losLab PDF Developer Library