PDFlibPas 3.538.0, harici JBIG2 kodlayıcısını Free Pascal ve Lazarus programlarına statik olarak bağlar. Proje, Delphi ve C++Builder'ın zaten kullandığı PDFlibJBIG2EncC birimini ekler ve kodlayıcı, yanında dağıtılacak başka bir şey olmadan çalıştırılabilir dosyanın içine girer. Bu, daha önce Free Pascal'ın harici kodlayıcıya yalnızca bir DLL üzerinden erişebileceği yönündeki sonucu tersine çevirir
DLL neden tek seçenek gibi görünüyordu?
DLL tek seçenek gibi görünüyordu, çünkü üç bağlama yolu birbiriyle ilgisiz üç biçimde başarısız oluyordu ve hiçbir derleyici anahtarı bunların hiçbirine ulaşamıyordu. Dahili bağlayıcı, associative COMDAT bölümlerini doğrudan reddediyor. Birlikte gelen binutils üzerinden harici bağlama, Free Pascal'ın 64 bit Windows hedefinde koşulsuz olarak geçirdiği bölüm çöp toplama aşamasında çöküyor. Daha yeni bir binutils ise Free Pascal bağlama betiğini hiç işleyemiyor. C++ tarafını diğer araç zinciriyle yeniden derlemek de bir reddi diğeriyle değiştiriyor, çünkü şablon ve inline örnekleme doğası gereği weak external semboller üretiyor ve Free Pascal bunları Unsupported COFF symbol type 105 olarak bildiriyor. Bu kanıtların hiçbiri yanlış değildi ve JBIG2 kodlayıcı arka uçları ile Free Pascal bağlayıcısının önceki incelemesi, bugün de yeniden üretilebilen her çıkmazı anlatıyor. Yanlış olan, düzeltmenin nerede yaşayabileceğine dair varsayımdı. Her deneme bir derleyiciye veya bağlayıcıya gidiyordu, ancak ikisi de bir nesne dosyasının zaten içerdiği şeyi değiştiremez. Sorun başından beri nesne dosyasındaydı. ObjConv COFF okur ve COFF yazar; Free Pascal'ın takıldığı her yapının kabul ettiği mekanik bir karşılığı vardır
Neden sebebini hiç söylemeyen bir hata alınıyor?
Free Pascal'ın dahili bağlayıcısı pick-any COMDAT'ı yalnızca yarım uygular ve bu yarım uygulama buradaki teşhisi en zorlaştıran şeydir. Biçimin öngördüğü gibi yinelenen tanımları birleştirir. Ancak TExeOutput.RemoveUnreferencedSections, bölümleri kullanılmış olarak işaretlerken exesymbol üzerinden kazanan tanıma yönelirken, TCoffexeoutput.DoRelocationFixup doğrudan objreloc.symbol.objsection değerini okur. Kullanılan bir bölüm, kendi nesnesinin fold işleminde kaybeden kopyasında tanımladığı bir sembole başvurduğunda iki geçiş farklı bölümlere bakar ve bağlama Internal error 200603061 ile durur
Bunu iki yanındaki sınırlarla karşılaştırın. Unsupported COFF symbol type 105 weak external anlamına gelir. Associative or exact match COMDAT sections are not yet supported associative COMDAT'ı söyler ve sorunlu sembolü bile adlandırır. Dahili hata 200603061 ise hiçbir şey söylemez: sembol adı, bölüm adı, dosya adı ve aşama yoktur. Ayrıca bu bir köşe durumu değil, normal durumdur; çünkü MSVC her string literal'i ve her inline veya şablon örneklemesini pick-any COMDAT içine koyar ve bu kodlayıcı kümesindeki 186 nesne boyunca bağlayıcı 2656 fold gerçekleştirmiştir. /Gy- ile derlemek sıradan işlevleri işlev başına COMDAT bölümlerinin dışına çıkarır, ancak string literal'lerini ve şablon örneklemelerini tam olarak oldukları yerde bırakır
C runtime sembollerini stub'lamak neden hep son eklenen bozulmuş gibi görünür?
Çünkü bağlayıcı düzeltme aşamasına ancak bütün semboller çözümlendikten sonra ulaşır. Hâlâ eksik bir şey varken çalışma Undefined symbol ile erken biter ve COMDAT sorununun ortaya çıkma şansı kalmaz. Son C runtime stub'ını eklediğinizde bağlayıcı bir aşama ilerler ve doğrudan dahili hata 200603061'e girer. Bu nedenle saha belirtisi sistematik olarak yanıltıcıdır: başvurulan C sembolleri için Pascal gövdelerini tek tek eklerken her zaman en son eklemenin derlemeyi bozduğu veya yüz civarında stub'lık bir eşik aşıldığı sanılır. İkisi de doğru değildir. Hangi sembolün son eklendiği ve toplam kaç sembol eklendiği önemsizdir, çünkü hata ilk nesneden beri gizlidir ve ancak çözümleme başarılı olduğunda erişilebilir hale gelir. İlişkisiz bir şeyi düzelttikten sonra bağlayıcının şikâyeti değişirse, regresyona mı neden olduğunuzu yoksa yalnızca bir aşamayı mı ilerlettiğinizi sorun
Düzeltme bir derleyici anahtarı değil, tek bir ObjConv geçişi
Tüm düzeltme, derlenmiş her nesne üzerinde çalıştırılan tek bir son işlem komutudur: ObjConv -fcoff64 -xw -xc -xn -np:__imp_:pdflibimp_. Bu çalışma için üç seçenek eklendi. -xw, IMAGE_SYM_CLASS_WEAK_EXTERNAL sembollerini sıradan external sembollere çözer. -xn, Free Pascal'ın Unsupported COFF symbol type 0 olarak bildirdiği _fltused gibi IMAGE_SYM_CLASS_NULL sembollerini normalleştirir. Ağır işi -xc yapar: her COMDAT bölümünü düz bir bölüme düşürür ve tanımladığı sembolleri static hale getirir. COMDAT bölümleri ortadan kalktığı için fold işlemi, bir geçişin yönlendireceği ve diğerinin kaçıracağı kazanan kopya da kalmaz; associative .pdata ve .xdata unwind bölümleri de bununla birlikte gider. Bedeli gerçektir ama küçüktür: meşru biçimde birleşebilecek kopyalar artık ayrı ayrı yaşamaya devam edebilir
-np:__imp_:pdflibimp_ önek yeniden adlandırması ayrı bir çakışmayı çözer. MSVC içe aktarılan Win32 API'lerini __imp_* adlı dolaylı hücreler üzerinden çağırır, Free Pascal bu öneki kendi import mekanizmasına ayırır ve bu adlardan birini doğrudan tanımlamak aynı dahili hata 200603061'i tetikler. Hücreleri yeniden adlandırmak, Pascal tarafının bunları sıradan değişkenler olarak yayımlayıp çalışma zamanında doldurmasına izin verir. Nesnelerin kendisi static-link seçeneğiyle /GS- /Gs999999 /Gy- /Zl /GR- /EHs-c- kullanılarak ve görüntü codec'leri kapatılarak derlenir; böylece ölü dosya G/Ç ve codec yolları için çok daha az yalnızca bağlama amaçlı stub gerekir. Bunlar Lib\thirdparty\Win64f içine düşer, Delphi ve C++Builder yolu ise kendi Win64x kümesini değiştirmeden bağlamayı sürdürür; bu da yalnızca tek bir araç zincirini kapsayan taşınabilirlik düzeltmesi için doğru sonuçtur
Pascal tarafının hâlâ dışa aktarması gerekenler
Free Pascal, bir C nesnesinin import'unu sembol adıyla çözer ve bu adın açıkça yazılmasına ihtiyaç duyar; bu nedenle bir C giriş noktasının yerine geçen her Pascal yordamı açık bir public name cümlesi taşır. Delphi, yordam adını sembol adı olarak alır ve hiçbir cümleye ihtiyaç duymaz; bu yüzden tek birim, cümleleri {$IFDEF FPC} altında tutarak her iki derleyiciye hizmet eder. Tuzak şudur: external 'msvcrt.dll' bildirimi hiçbir şeyi karşılamaz; bir import oluşturur, bağlı bir nesnenin bağlanabileceği bir tanım oluşturmaz. İletme gövdesinin gerçekten var olması gerekir
// Harici bildirim yalnızca bir import oluşturur. Bağlanan hiçbir nesne
// buna bağlanamaz.
function crt_memcmp(Buf1, Buf2: Pointer; Count: NativeUInt): Integer; cdecl;
external 'msvcrt.dll' name 'memcmp';
// Tam C sembol adı altında yayımlanan Pascal gövdesi, nesne kümesinin
// gerçekten bağlandığı şeydir.
function jbig2_memcmp(Buf1, Buf2: Pointer; Count: NativeUInt): Integer; cdecl;
public name 'memcmp';
begin
Result := crt_memcmp(Buf1, Buf2, Count);
end;
Değişken sayıda argüman alan giriş noktaları bu kalıbı bozar, çünkü Pascal sarmalayıcısı kendi varargs değerlerini başka bir varargs çağrıcısına iletemez. Çözüm, sarmalayıcı olmaktan çıkmaktır: C adı altında çıplak bir yordam dışa aktarın ve çağrıcının düzenlediği argüman register'ları ile stack'i aynen koruyarak gerçek uygulamaya tail-jump yapın. JPEG 2000 katmanı snprintf ve vsnprintf için bunu zaten yapar; düz adlar yalnızca UCRT tarafından dışa aktarıldığı için alt çizgi önekli msvcrt yazımlarına atlar. Aynı dahili hatadan gelen bir başka kısıt da yeniden adlandırılmış import hücrelerinin static initializer'lar yerine bir initialization bölümünden GetModuleHandleA ve GetProcAddress ile doldurulmasıdır; çünkü bir initializer içinde içe aktarılan yordamın adresini almak, derleyicinin işleyemediği bir fixup üretir ve yeniden 200603061 ile başarısız olur
function crt_sprintf(Buffer: PAnsiChar; Fmt: PAnsiChar): Integer; cdecl; varargs;
external 'msvcrt.dll' name 'sprintf';
var
SPrintfTarget: Pointer = @crt_sprintf;
// Varargs bir Pascal sarmalayıcısından iletilemez; dışa aktarılan
// sembol, frame'i çağrıcının kurduğu haliyle bırakarak tail-jump yapar.
procedure jbig2_sprintf; assembler; nostackframe;
public name 'sprintf';
asm
jmp qword ptr [rip + SPrintfTarget]
end;
Bir Free Pascal projesi artık neyi farklı yapıyor?
uses cümlesindeki birim adı dışında hiçbir şeyi ve artık dağıtılacak bir dosya da yok. Arka uç, kendi initialization bölümü üzerinden RegisterJBIG2EncoderBackend ile kendini kaydeder ve çağıranlar onu eskisi gibi ister: değeri 4 olan PDF_JBIG2_OPTION_EXTERNAL_ENCODER seçenek bitiyle veya genişletilmiş giriş noktalarının UseExternalEncoder argümanıyla. İstemek hâlâ bir tercihtir, garanti değildir; çünkü birimi dışarıda bırakan bir derleme sessizce yerel Pascal MMR kodlayıcıya geri döner ve hata yerine daha büyük dosyalar üretir
uses
Classes, SysUtils,
PDFlibrary,
PDFlibJBIG2EncC; // Delphi, C++Builder ve 3.538.0'dan itibaren 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;
İki sınırı açıkça belirtmek gerekir. Yalnızca Win64 nesne kümesi vardır; bu nedenle diğer tüm Free Pascal hedeflerinde harici encode giriş noktası başarısızlık bildirir ve işi yerel Pascal kodlayıcı üstlenir. Bütün bunları denetleyen regresyon ise boyut kontrolü değil render karşılaştırmasıdır: aynı kaynakta iki kodlayıcı da kayıpsızdır, bu yüzden çıktıları render edilir ve byte byte karşılaştırılır; Lazarus paketi bu test dahil 26 testin 26'sını da geçer. Sıkıştırılmış akış boyutlarını karşılaştırmak hiçbir şeyi kanıtlamazdı, çünkü ters çevrilmiş bir sayfa doğru olanla yaklaşık aynı boyuta sıkışır
Daha geniş ders JBIG2'nin ötesine geçer. Sınır gerçekten dinamik olduğunda DLL doğru biçimdir; DLL, ActiveX ve dylib entegrasyon yüzeylerinin hizmet ettiği durum budur. Ancak sınır yalnızca bir COFF okuyucusuna geçici çözüm olduğu zaman DLL yanlış biçimdir; her kurulum programına bir dosya, her dağıtıma bir arama yolu ve statik bağlamanın sahip olamayacağı bir sürüm kayması hata biçimi ekler. Üst akış da önemlidir, çünkü iki seviyeli görüntünün nasıl üretildiği son boyutu kodlayıcıdan daha fazla belirler ve Delphi'de bölge tabanlı monokrom render hattın bu yarısını kapsar. Araç zinciri kapsamı, derleyici başına nesne kümeleri ve desteklenen hedefler losLab PDF Developer Library ürün sayfasında listelenir