HotXLS, XE5'ten ileriye her Delphi ve C++Builder release'ine tek bir Object Pascal codebase sunar ve bunu kanıtlayan script build-All-Lib-TRIAL.cmd'dir: Win32 ve Win64 üzerinde 12 Delphi sürümünü, ayrıca 10 C++Builder Win32 ve 9 Win64 package build'ini kapsayan 43 build leg. v2.363 ile v2.374 arasında bu script tamamlanana kadar hiç çalıştırılmadı ve XE5 leg'i bütün süre boyunca bozuktu
Hata görüldüğünde hiçbir şeyi gizemli değildi. Güncel compiler'ın yorumsuz kabul ettiği beş ayrı construct, build matrix'in 12.0 olarak adlandırdığı RAD Studio XE5'te hard error'dur. v2.375.0 beşinin tamamını düzeltti ve matrix 43'te 43 ile yeniden yeşile döndü. Aşağıda her reddin ne olduğunu, eskı compiler'ın type grounds üzerinde reddettiği iki konuda neden aslında haklı olduğunu ve daha utandırıcı kısmı bulacaksınız: karmaşayı teşhis etmek için yazılan probe script'i ilk çalıştırmasında false pass raporladı
XE5 leg'i kimse fark etmeden neden çürüdü?
XE5 leg'i çürüdü, çünkü günlük geliştirme yalnızca 37.0 için dört script'lik seti çalıştırıyordu ve yeşil bir local build çağırmadığınız compiler hakkında hiçbir şey söylemez. Full matrix ayrı ve yavaş bir script'tir; trial installer dosyaları Inno Setup toplamadan önce onu çağırır. Yani commit zamanında değil packaging zamanında çalışır. On iki release bu boşluğa sığdı
Leg aritmetiğini açmak gerekir, çünkü coverage illüzyonu burada yaşar. DELPHI_TRIAL_VERSIONS 12.0 ile 37.0 arasındaki 12 sürümü listeler ve bu 12 sürümün her biri Win32 ile Win64 olmak üzere iki kez build edilir. CB_TRIAL_WIN32_VERSIONS 10 sürüm listeler, CB_TRIAL_WIN64_VERSIONS ise yalnızca 9 listeler; çünkü XE5'te C++Builder package project vardır ama Win64 package startup object'i c0pkg64.o yoktur. On iki artı on iki artı on artı dokuz 43 eder. Dördünü çalıştırıp codebase'e portable demek kategori hatasıdır ve bunun olmasına izin veren spesifik hata budur
HotXLS aynı problemin ters yönünden de darbe aldı. uses cümlesiyle erişilebilir ama .cbproj file list'inde olmayan yeni bir unit Delphi altında kusursuz derlenir; çünkü dcc listelenmemiş unit'leri package içine örtük olarak çeker ve en fazla W1033 hint'i verir. C++Builder yalnızca <DelphiCompile> içinde adı bulunan unit'ler için bir .obj üretir; aynı kod ilink aşamasında unresolved external ile ölür. Bir toolchain diğerinin yakaladığını gizler. Representative compiler'a güvenmek yerine matrix çalıştırmanın bütün savı budur
Eski Win32 compiler'larının reddettiği hard type cast'ler
Beş reddin ikisi farklı kıyafet giyen aynı bug'dır: değişkene değil floating-point expression'a uygulanan hard type cast. Win32'de eski compiler'lar arithmetic'i x87 stack üzerinden değerlendirir; bu nedenle Double içeren bir addition 80 bitlik excess precision ile taşınır ve static type 10 baytlık Extended olur. 10 baytı 8 baytlık TDateTime'a cast etmek geçerli bir typecast değildir ve compiler E2089 Invalid typecast der
Çileden çıkaran ayrıntı variable form'un iyi olmasıdır. TDateTime(Serial) matrix'teki her sürümde derlenir, çünkü Serial zaten 8 bayttır ve cast size-preserving'dir. Ona herhangi bir şey ekleyin, expression altınızda genişler. Düzeltme daha geniş bir cast veya conditional define değildir; cast etmeyi bırakmaktır. Implicit real-to-real assignment her desteklenen compiler'da doğru dönüşümü yapar ve kodun gerçekte ne demek istediğini söyler
// XE5 (Win32)'de reddedilir: her addition 10 baytlık Extended olarak
// değerlendirilir ve 10'dan 8'e narrowing cast E2089 üretir
if Dates1904 then
Value := TDateTime(Serial + XLSDate1904Offset)
else if Serial < 60 then
Value := TDateTime(Serial + 1)
else
Value := TDateTime(Serial); // bu kabul edilir: addition yok
// Sürüm güvenli: dönüşümü real-to-real assignment'a bırak
if Dates1904 then
Value := Serial + XLSDate1904Offset
else if Serial < 60 then
Value := Serial + 1
else
Value := Serial;
// Cell value packer'da aynı sınıf ret: integer üzerinde hard Double cast
// Divide et; operator zaten real döndürüyor
if (Scaled = intVal) and (Double(intVal) / 100 = AValue) then // E2089
;
if (Scaled = intVal) and (intVal / 100 = AValue) then // portable
;
Serial < 60 branch'i 1900 leap-year fiction'dır, off-by-one değildir: serial 60, Excel'in var olmayan 1900-02-29 tarihidir; bu yüzden altındaki serial'lar DecodeDate görmeden önce ek günü ister. Portability çalışması bu tür mantığı sessizce değiştirmemelidir; burada güvenli edit'in cast'i kaldırıp arithmetic'i olduğu gibi bırakmasının nedeni tam olarak budur
nil procedural argument olduğunda ne bozulur?
Procedural type beklenen yere çıplak bir nil geçirmek, eski compiler'larda overload resolution sırasında bind edilemez. HotXLS'teki call site ResolveIndexedColor'dır; overload edilmiş bu çağrı, çoğu çağıranın ihtiyaç duymadığı bir TXLSTryResolveSystemColor callback'i alır. Yeni compiler'lar nil'i procedural parameter'a çözer ve doğru overload'u seçer. XE5 bunu yapamaz ve diagnostic argument yerine overload set'i gösterir; yirmi dakikayı kaybettiğiniz yer burasıdır
Portable cevap null callback'e type vermektir. Procedural type'ın unit-level variable'ı dil tarafından zero-initialize edilir; initializer olmadan zaten nil'dir ve eski resolver'ın istediği type bilgisini taşır. Unit-level variable gereksizse typed local'a nil atamak da aynı işi yapar
var
// nil procedural literal eski compiler'ların overload resolution'ında
// bind olmaz; typed ve zero-initialized variable olur
NilSystemColorResolver: TXLSTryResolveSystemColor;
// ...
FWorkbook.ResolveIndexedColor(AIndexedColor, xicsBiffIcv, ARole,
NilSystemColorResolver, Resolution);
// XLSX workbook'ta typed local ile aynı düzeltme
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;
Bunun gerçek bir language-level farkı olduğuna ve define'larla çevrilmeye değer bir compiler bug'ı olmadığına dikkat edin. Zero-initialized variable matrix'teki her sürümde doğrudur ve bir satıra mal olur; dolayısıyla burada conditional compilation yoktur. {$IF CompilerVersion} yalnızca platform release'ler arasında gerçekten farklı olduğunda kullanın; bu batch'te durum yalnızca bir kez böyledir
Protected VCL method'ları release'ler arasında yer değiştirir
TPicture.LoadFromStream güncel VCL'de public, HotXLS'in desteklediği eski sürümlerde protected'dır; bu nedenle doğrudan çağrı şimdi derlenir, sonra başarısız olur. HotXLS onu worksheet background image payload'ının gerçekten decode olduğunu doğrulamak için kullanır; bu, HTML exporter'ın baytları embed etmeyi commit etmesinden önce çalışan bir signature check'tir. Klasik Pascal cevabı geçerlidir: yalnızca visibility'yi genişletmek için aynı unit içinde bir descendant declare edin ve call site'ta onun üzerinden cast edin
type
// TPicture.LoadFromStream eski VCL sürümlerinde protected'dır ve
// library onları destekler; aynı unit descendant'ı onu açığa çıkarır
TXlsxPictureAccess = class(TPicture);
// ...
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);
Accessor-class hilesi burada güvenlidir; çünkü descendant field eklemez ve hiçbir zaman örneklenmez. Cast yalnızca compiler'ın adlandırmanıza izin vereceği şeyi değiştirir. Yine de declaration yanında bir comment bulundurmaya değer; yalnızca güncel IDE'de build eden bir okuyucu aksi halde anlamsız bir type görecektir. Background image handling, aynı decoded payload'ın ekrandaki sheet'i beslediği custom VCL grid rendering path içinde yeniden karşımıza çıkar
GdiplusStartup token type'ı iki kez değişti
Batch'teki gerçekten conditional compilation gerektiren tek ret, VCL nesilleri arasında her yerde geçerli tek bir yazımı bırakmayacak biçimde değişen GdiplusStartup'ın var parameter type'ıdır. Sürüm sürüm probing gerçek davranışı sabitledi: 12.0 ile 20.0 arasındaki leg'ler yalnızca Cardinal, 21.0 ve 22.0 leg'leri yalnızca THandle veya ULONG_PTR, 23.0 ve 37.0 ise ikisini de kabul eder. Release adlarıyla XE5'ten 10.3 Rio'ya kadar Cardinal, 10.4 Sydney'den itibaren THandle'dır. 12.0 ile 22.0 arasında iki kabul aralığı örtüşmediği için koşulsuz declaration çalışmaz; guard CompilerVersion >= 34 yani Sydney üzerine kuruludur ve bazı ara sürümlerde unit resolution order'ın farklı bir declaration seçememesi için çağrı tamamen Winapi.GDIPAPI.GdiplusStartup olarak nitelenir
function TXLSPageImageExporter.EncodeTiff(Stream: TStream): Integer;
var
StartupInput: TGdiplusStartupInput;
// GDIPAPI'nin GdiplusStartup var-parameter type'ı VCL generation'a
// göre değişir: Rio'ya kadar Cardinal, Sydney'den itibaren THandle
{$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');
// ... encode ...
end;
Bu, page image exporter'ın TIFF branch'idir; yanlış yapmanın blast radius'u bir cell range'i tek görüntü olarak export etme yolları dahil bütün raster export yüzeyidir. Guard'ın ne iddia etmediğine de dikkat edin: ULONG_PTR ve THandle her iki platformda da aynı genişliktedir; seçim 32 bit ile 64 bit doğruluğu değil declaration'ın hangi identifier'ı adlandırdığıyla ilgilidir
İlk probe run'ı neden hiçbir şey bildirmedi?
Version probe ilk çalışmasında hiçbir şey bildirmedi, çünkü res=$(...) assignment'ları parent'a aktarılmadıkları bir subshell içinde yapılıyordu. dcc32 başarıda 0 döndürür; dolayısıyla exit code yakalanacak doğru sinyaldi, ancak script onu bir satır sonra yok olan bir variable'a yakalıyordu. Her leg boş geldi ve çıktı hiçbir şeyi compile etmemiş bir probe'a benziyordu; aslında tam olarak buydu
İkinci failure daha kötüydü, çünkü hiçbir şey yerine yanlış cevap üretiyordu. Probe, Error eşleşen satırları sayarak bir leg'i sınıflandırıyordu ve Delphi her fatal mesajın önüne bu kelimeyi koymaz. F1026 File not found fatal'dır ama eşleşmez; bu nedenle bir unit'i hiç çözemeyen probe temiz pass olarak puanlanır. XE5 Winapi.GDIPOPS.dcu taşımaz, ilk probe tam buna çarptı ve false green oldu. Buradan çıkan kural dar ama açıkça söylenmeye değer: compiler probe'u üretilen artifact veya compiler'ın kendi summary line'ı ile değerlendirin, output'u bir keyword için grep'leyerek değil. stderr'i Error için grep'lemek, karşılayamayacağınız yönde başarısız olan ve sessizce başarı bildiren bir heuristic'tir
On yıllık compiler'ı desteklemek gerçekte neye mal olur?
Dürüst hesap şu: burada yapılan code change'ler önemsiz, process change'leri değil. Beş reddin dördü version machinery ekleyerek değil, daha sıradan Pascal yazarak düzeltildi: cast'i kaldır, cast yerine divide et, nil'e type ver, accessor class declare et. Yalnızca GdiplusStartup bir {$IF} kazandı. XE5'ten güncel release'e uzanan bir codebase, ilk başta hard cast'lerin ve newest-compiler idiom'larının birikmesine izin vermezseniz conditional define çalılığına dönüşmez
Gerçek maliyet build time ve disiplindir. Kırk üç leg yavaş bir script'tir; tam da bu yüzden packaging zamanına ve sonra hiç çalışmamaya kaydı. Savunulabilir orta yol, iteration için hızlı dört-script loop'u tutmak ve full matrix'i atlanamayacak bir schedule ile çalıştırmaktır; çünkü failure mode fark edeceğiniz broken build değil, on iki release önce sessizce desteğini kaybetmiş bir supported IDE'dir
Bu yükümlülük native bir component yayımlamanın diğer yüzüdür. HotXLS, Excel kurulumu ve COM dependency olmadan yalnızca Object Pascal üzerinden XLS, XLSX ve ODS okur ve yazar; kilitli bir server'da Office-free workbook automation'ı mümkün kılan şey budur. Aynı özellik compiler'ın bütün platform sözleşmesi olduğu anlamına gelir; bu yüzden matrix'teki her sürüm varsayılmak yerine yeniden doğrulanması gereken bir sözdür
Burada anlatılan cross-compiler build matrix'i ve version-safe code, XE5'ten güncel release'e kadar Delphi ve C++Builder'ı destekleyen, her supported IDE için prebuilt library binary'leri bulunan HotXLS Delphi Spreadsheet Component'in parçasıdır