Tehnički članak

Matrica izgradnje Delphi kompajlera: HotXLS od XE5

HotXLS isporučuje jednu bazu koda Object Pascala za svako izdanje Delphija i C++Buildera od XE5 nadalje, a build-All-Lib-TRIAL.cmd skripta je koja to dokazuje: 43 izgradbena kraka, s 12 verzija Delphija na Win32 i Win64 te 10 C++Builder Win32 i 9 Win64 izgradnji paketa. Od v2.363 do v2.374 ta skripta nikad nije završila do kraja, a krak XE5 cijelo je vrijeme bio pokvaren

Ništa u kvaru nije bilo suptilno nakon što je viđen. Pet različitih konstrukcija koje aktualni kompajler prihvaća bez komentara tvrde su pogreške na RAD Studio XE5, koje matrica označava kao 12.0. Izdanje v2.375.0 popravilo je svih pet i matrica je ponovno pozelenjela s 43 od 43. Slijedi svako odbijanje, zašto je stari kompajler za dva od njih razumno u pravu iz razloga tipa i neugodniji dio: probna skripta napisana za dijagnosticiranje nereda u prvom je pokretanju prijavila lažni prolaz

Zašto je krak XE5 zarđao a da nitko nije primijetio?

Krak XE5 zarđao je jer je svakodnevni razvoj pokretao samo četveroskriptni skup 37.0, a zelena lokalna izgradnja ne govori ništa o kompajleru koji niste pozvali. Puna matrica zasebna je, spora skripta koju trial instalacijski program poziva prije nego što Inno Setup prikupi datoteke, pa se pokreće u trenutku pakiranja, a ne u trenutku commita. Dvanaest izdanja stalo je u tu prazninu

Aritmetiku kraka vrijedi raščlaniti jer se ondje nalazi iluzija pokrivenosti. DELPHI_TRIAL_VERSIONS nabraja od 12.0 do 37.0, a svaka se od tih 12 verzija gradi dvaput, za Win32 i Win64. CB_TRIAL_WIN32_VERSIONS navodi 10 verzija, a CB_TRIAL_WIN64_VERSIONS samo 9 jer XE5 ima projekt paketa C++Buildera, ali ne isporučuje početni objekt za paket Win64 c0pkg64.o. Dvanaest plus dvanaest plus deset plus devet jest 43. Pokrenuti četiri od njih i nazvati bazu koda prenosivom kategorijska je pogreška, a upravo je ta pogreška ovo omogućila

HotXLS je istim oblikom problema pogođen iz suprotnog smjera. Nova jedinica dostupna kroz klauzulu uses, ali odsutna s popisa datoteka .cbproj, savršeno se prevodi pod Delphijem jer dcc neizravno povlači nenavedene jedinice u paket i u najgorem slučaju emitira napomenu W1033. C++Builder emitira .obj samo za jedinice imenovane u <DelphiCompile>, pa isti kod umire u fazi ilink s nerazriješenim vanjskim simbolom. Jedan lanac alata skriva ono što drugi uhvati. To je cijeli argument za pokretanje matrice, a ne vjerovanje reprezentativnom kompajleru

Tvrda pretvaranja tipa koja stari Win32 kompajleri odbijaju

Dva od pet odbijanja ista su pogreška u različitoj odjeći: tvrdo pretvaranje tipa primijenjeno na izraz s pomičnim zarezom umjesto na varijablu. Na Win32 stariji kompajleri računaju aritmetiku kroz x87 stog, pa se zbrajanje koje uključuje Double izvršava s viškom preciznosti od 80 bitova, a njegov statički tip postaje 10-bajtni Extended. Pretvaranje 10 bajtova u 8-bajtni TDateTime nije dopušteno pretvaranje tipa, a kompajler to kaže s E2089 Invalid typecast

Izluđujući detalj jest da je oblik s varijablom dobar. TDateTime(Serial) prevodi se u svakoj verziji matrice jer je Serial već širok 8 bajtova i pretvaranje čuva veličinu. Dodajte mu bilo što i izraz se ispod vas proširi. Popravak nije šire pretvaranje ni uvjetna definicija, nego prestanak pretvaranja: implicitna dodjela realnog u realni ispravno konvertira na svakom kompajleru koji HotXLS podržava i govori ono što kod doista znači

// Odbijeno na XE5 (Win32): svako se zbrajanje računa kao 10-bajtni
// Extended, a pretvaranje 10 u 8 bajtova podiže E2089
if Dates1904 then
  Value := TDateTime(Serial + XLSDate1904Offset)
else if Serial < 60 then
  Value := TDateTime(Serial + 1)
else
  Value := TDateTime(Serial);        // ovo je prihvaćeno: nema zbrajanja

// Sigurno za verzije: neka dodjela realnog u realni odradi pretvorbu
if Dates1904 then
  Value := Serial + XLSDate1904Offset
else if Serial < 60 then
  Value := Serial + 1
else
  Value := Serial;

// Ista klasa odbijanja u pakiraču vrijednosti ćelije: tvrdo pretvaranje
// Doublea iz cijelog broja. Dijeli umjesto toga, operator već daje realni broj
if (Scaled = intVal) and (Double(intVal) / 100 = AValue) then   // E2089
  ;
if (Scaled = intVal) and (intVal / 100 = AValue) then           // prenosivo
  ;

Grana Serial < 60 fikcija je prijestupne godine 1900, a ne pomak za jedan: serijski broj 60 Excelov je nepostojeći datum 1900-02-29, pa serijski brojevi ispod njega trebaju dodatni dan prije nego što ih DecodeDate vidi. Posao na prenosivosti nikad ne bi smio tiho promijeniti takvu logiku, zbog čega sigurni popravak ovdje uklanja pretvaranje i ostavlja aritmetiku netaknutom

Što se pokvari kada je nil proceduralni argument?

Goli nil predan ondje gdje se očekuje proceduralni tip ne uspijeva se vezati tijekom razrješavanja overloadova na starijim kompajlerima. Pozivno mjesto u HotXLS-u je ResolveIndexedColor, koji je overloadan i prima povratni poziv TXLSTryResolveSystemColor koji većini pozivatelja ne treba. Noviji kompajleri razrješavaju nil prema proceduralnom parametru i biraju ispravan overload. XE5 to ne radi, a dijagnostika pokazuje na skup overloadova, a ne na argument, što je način da izgubite dvadeset minuta

Prenosivi odgovor jest dati nul-povratnom pozivu tip. Varijabla proceduralnog tipa na razini jedinice jezik inicijalizira nulom, pa je već nil bez inicijalizatora, a nosi informacije o tipu koje stari razrješivač želi. Ondje gdje bi varijabla na razini jedinice bila pretjerana, tipizirana lokalna varijabla kojoj je dodijeljen nil radi istu stvar

var
  // Proceduralni literal nil ne veže se pri razrješavanju overloadova
  // starijih kompajlera; tipizirana varijabla inicijalizirana nulom veže se
  NilSystemColorResolver: TXLSTryResolveSystemColor;

// Mjesto poziva

FWorkbook.ResolveIndexedColor(AIndexedColor, xicsBiffIcv, ARole,
  NilSystemColorResolver, Resolution);

// Isti popravak s tipiziranom lokalnom varijablom u XLSX radnoj knjizi
function TXLSXWorkbook.ResolveIndexedColor(AIndex: Int64;
  ASpace: TXLSIndexedColorSpace;
  out AResolution: TXLSIndexedColorResolution): Boolean;
var
  NoResolver: TXLSTryResolveSystemColor;
begin
  NoResolver := nil;
  Result := ResolveIndexedColor(AIndex, ASpace, xicrGeneral, NoResolver,
    AResolution);
end;

Primijetite da je to stvarna razlika na razini jezika, a ne pogreška kompajlera koju vrijedi zaobići definicijama. Varijabla inicijalizirana nulom ispravna je u svakoj verziji matrice i košta jedan redak, pa ovdje uopće nema uvodnog prevođenja. Za {$IF CompilerVersion} posegnite samo kada se platforma doista razlikuje između izdanja, što se u ovom paketu događa točno jednom

Zaštićene VCL metode mijenjaju dostupnost između izdanja

TPicture.LoadFromStream je public u aktualnom VCL-u, a protected u starijim verzijama koje HotXLS podržava, pa se izravan poziv sada prevodi, a tada ne. HotXLS ga koristi za provjeru da se korisni teret slike pozadine radnog lista doista dekodira, što je provjera potpisa koja se događa prije nego što se HTML izvoz obveže na ugrađivanje bajtova. Primjenjuje se klasičan odgovor Pascala: deklarirati potomka u istoj jedinici samo radi proširenja vidljivosti i kroz njega pretipizirati na mjestu poziva

type
  // TPicture.LoadFromStream je protected u starijim VCL verzijama
  // koje biblioteka podržava; potomak iz iste jedinice izlaže ga
  TXlsxPictureAccess = class(TPicture);

// Mjesto poziva

Stream.WriteBuffer(AData[1], Length(AData));
Stream.Position := 0;
TXlsxPictureAccess(Picture).LoadFromStream(Stream);
Result := (Picture.Graphic <> nil) and not Picture.Graphic.Empty and
  (Picture.Graphic.Width > 0) and (Picture.Graphic.Height > 0);

Trik s pristupnom klasom ovdje je siguran jer potomak ne dodaje polja i nikad se ne instancira; pretvaranje mijenja samo ono što će vam kompajler dopustiti imenovati. I dalje vrijedi komentar na deklaraciji jer će čitatelj koji gradi samo u aktualnom IDE-u inače vidjeti besmislen tip. Obrada slike pozadine ponovno se pojavljuje u putu iscrtavanja prilagođene VCL mreže, gdje isti dekodirani korisni teret hrani list na zaslonu

Tip tokena GdiplusStartup promijenio se dvaput

Jedino odbijanje u paketu koje doista traži uvodno prevođenje jest vrsta parametra var funkcije GdiplusStartup, koja se među generacijama VCL-a promijenila na način koji ne ostavlja nijedan jedinstveni zapis valjan svugdje. Ispitivanje po verzijama utvrdilo je stvarno ponašanje: krakovi od 12.0 do 20.0 prihvaćaju samo Cardinal, krakovi 21.0 i 22.0 samo THandle ili ULONG_PTR, a 23.0 i 37.0 prihvaćaju oba. U nazivima izdanja to je Cardinal od XE5 do 10.3 Rio, a THandle od 10.4 Sydney nadalje. Budući da se dva prihvatljiva raspona od 12.0 do 22.0 ne preklapaju, nijedna bezuvjetna deklaracija ne radi: zaštita se veže na CompilerVersion >= 34, što je Sydney, a poziv je potpuno kvalificiran kao Winapi.GDIPAPI.GdiplusStartup tako da redoslijed razrješavanja jedinica ne može zamijeniti drugu deklaraciju u nekoj verziji usred raspona

function TXLSPageImageExporter.EncodeTiff(Stream: TStream): Integer;
var
  StartupInput: TGdiplusStartupInput;
  // Vrsta var-parametra GdiplusStartup u GDIPAPI-ju slijedi generaciju
  // VCL-a: Cardinal do Rija, THandle od Sydneya nadalje
  {$IF CompilerVersion >= 34}
  StartupToken: THandle;
  {$ELSE}
  StartupToken: Cardinal;
  {$IFEND}
  TiffEncoder: TGUID;
begin
  FillChar(StartupInput, SizeOf(StartupInput), 0);
  StartupInput.GdiplusVersion := 1;
  CheckStatus(Winapi.GDIPAPI.GdiplusStartup(StartupToken, @StartupInput,
    nil), 'startup');
  if GetEncoderClsid('image/tiff', TiffEncoder) < 0 then
    raise EInvalidGraphic.Create('GDI+ TIFF encoder is unavailable');
  // Kodiraj TIFF izlaz
end;

Ovo je TIFF grana izvoznika slike stranice, pa je radijus udara pogreške cijela površina rasterskog izvoza, uključujući putove opisane u članku o izvozu raspona ćelija kao jedne slike. Primijetite i što zaštita ne tvrdi: ULONG_PTR i THandle iste su širine na obje platforme, pa se izbor odnosi na to koji identifikator deklaracija imenuje, a ne na ispravnost 32-bitnog u odnosu na 64-bitni kod

Zašto je prvo pokretanje probe prijavilo ništa?

Proba verzije pri prvom je pokretanju prijavila ništa jer su se dodjele res=$(...) radile unutar podljuske, odakle se ne prenose roditelju. dcc32 pri uspjehu izlazi s nulom, pa je izlazni kod bio ispravan signal za hvatanje, a skripta ga je hvatala u varijabli koja je prestala postojati redak kasnije. Svaki se krak vratio prazan, a izlaz je izgledao kao proba koja ništa nije prevela, što je upravo i bila

Drugi je neuspjeh bio gori jer je proizveo pogrešan odgovor, a ne izostanak odgovora. Proba je krak klasificirala brojanjem redaka koji se podudaraju s Error, a Delphi ne prefiksira svaku fatalnu pogrešku tom riječju. F1026 File not found fatalan je i ne podudara se, pa je proba koja uopće nije mogla razriješiti jedinicu dobila ocjenu čistog prolaza. XE5 ne isporučuje Winapi.GDIPOPS.dcu, prva je proba upravo to pogodila i lažno pozelenjela. Pravilo koje je iz toga proizašlo usko je i vrijedi ga jasno reći: probu kompajlera prosuđujte po proizvedenom artefaktu ili po vlastitom sažetom retku kompajlera, nikad grepom izlaza za ključnom riječi. Grepanje stderr-a za Error heuristika je koja zakazuje u smjeru koji si ne možete priuštiti, tiho prijavljujući uspjeh

Koliko doista košta podrška desetljeću kompajlera?

Iskren račun jest da su promjene koda ovdje trivijalne, a procesne promjene nisu. Četiri od pet odbijanja popravljena su pisanjem običnijeg Pascala, a ne dodavanjem verzijske mehanike: uklonite pretvaranje, dijelite umjesto pretvaranja, dajte nilu tip i deklarirajte pristupnu klasu. Samo je GdiplusStartup zaslužio {$IF}. Baza koda koja se proteže od XE5 do aktualnog izdanja ne postaje šikara uvjetnih definicija osim ako dopustite da se tvrda pretvaranja i idiomi najnovijeg kompajlera na početku nakupe

Stvarni su trošak vrijeme izgradnje i disciplina. Četrdeset i tri kraka spora su skripta, upravo zato je odlutala u vrijeme pakiranja, a zatim u nikad. Obranjiva sredina jest zadržati brzu četveroskriptni petlju za iteraciju i pokretati punu matricu po rasporedu koji se ne može preskočiti, jer način kvara nije pokvarena izgradnja koju primijetite, nego podržani IDE koji je tiho prestao biti podržan prije dvanaest izdanja

Ta je obveza druga strana isporuke izvorne komponente uopće. HotXLS čita i zapisuje XLS, XLSX i ODS samo kroz Object Pascal, bez instalacije Excela i bez COM ovisnosti, što omogućuje automatizaciju radnih knjiga bez Officea na zaključanom poslužitelju. Ista značajka znači da je kompajler cijeli ugovor platforme, pa je svaka verzija u matrici obećanje koje treba ponovno provjeriti, a ne pretpostaviti

Matrica izgradnje među kompajlerima i ovdje opisan kod siguran za verzije isporučuju se kao dio HotXLS Delphi Spreadsheet Component, koji podržava Delphi i C++Builder od XE5 do aktualnog izdanja s unaprijed izgrađenim binarnim bibliotekama za svaki podržani IDE