Artikel Teknis

Free Pascal Win32: Dekorasi Simbol C di HotPDF

Free Pascal di Win32 otomatis menambahkan underscore di depan setiap impor cdecl; external, sementara public name mengekspor string yang Anda tulis apa adanya, karakter demi karakter. HotPDF harus memenuhi kedua konvensi itu di source tree yang sama, karena build Delphi sudah membawa deklarasi impor yang menuliskan underscore-nya secara manual. Keliru memahami asimetri ini menghasilkan link error yang menyebut simbol yang tidak pernah ditulis siapa pun

Memperluas pustaka Delphi ke Free Pascal biasanya digambarkan sebagai masalah portabilitas, dan di Win64 memang sebagian besar begitu. Win32 berbeda. ABI Windows x86 32-bit membawa tiga puluh tahun konvensi yang menumpuk soal bagaimana simbol C dieja, siapa yang membersihkan stack, dan helper privat kompilator mana yang boleh diasumsikan sebuah translation unit — dan masing-masing adalah tempat dua kompilator Pascal yang sepakat soal bahasa tetap bisa berselisih soal file objek

Mengapa simbol yang sama resolve di Win64 tapi gagal di Win32?

Karena prefiks underscore adalah konvensi 32-bit yang Free Pascal terapkan pada impor tapi tidak pada ekspor. Deklarasikan function deflate(...): Integer; cdecl; external; dan FPC mencari _deflate di file objek pada Win32, dan deflate pada Win64. Itu perilaku yang benar dan cocok dengan yang dipancarkan kompilator C. Jebakannya ada di sisi lain jembatan: rutinitas yang ditandai public name 'deflate' mengekspor persis deflate di kedua target, tanpa prefiks ditambahkan

Sekarang tambahkan detail historis yang membuatnya konkret. Build Delphi sudah mendeklarasikan beberapa entry point ini dengan underscore yang ditulis ke dalam nama, karena memang itulah isi file objeknya sendiri. Berikan deklarasi yang sama ke FPC di Win32 dan kompilator dengan setia menambahkan prefiks lagi, sehingga linker berburu __deflate, simbol yang tidak diekspor apa pun. Solusi yang intuitif — menambah satu underscore di mana-mana — justru merusak impor yang ejaannya sudah benar

Yang berhasil adalah sepasang konstanta prefiks, bukan satu. HPDFFPCZLib dan HPDFFPCCodecStubs memakai satu prefiks untuk impor C polos dan prefiks lain untuk impor yang sudah membawa prefiks sisi Delphi, dan di Win64 kedua konstanta itu kosong sehingga nama link yang sudah ada tetap utuh tanpa disentuh. Dua konstanta alih-alih satu — itulah seluruh perbaikannya, dan itu baru terlihat jelas setelah Anda memisahkan aturan impor dari aturan ekspor

Deklarasi simbol C yang sama di-resolve oleh Free Pascal dan Delphi pada Win64 dan Win32: impor cdecl mendapat underscore hanya di target 32-bit, deklarasi Delphi yang sudah dieja dengan underscore menjadi __deflate dan gagal link, sementara ekspor public name tetap literal di kedua arsitektur
Satu konstanta prefiks tidak bisa melayani kedua aturan: impor cdecl polos dan impor yang sudah membawa underscore Delphi ter-dekorasi berbeda di bawah FPC pada Win32, jadi HotPDF menyimpan dua dan membiarkan keduanya kosong di Win64
// Dua prefiks, bukan satu: impor C polos dan impor yang sudah membawa
// prefiks Delphi tulisan tangan ter-dekorasi berbeda di bawah FPC/Win32
const
{$IF DEFINED(FPC) and DEFINED(CPU32)}
  CPrefix     = '_';   // FPC menambahkannya sendiri untuk cdecl external
  DelphiCName = '';    // sudah ditulis dengan underscore di source
{$ELSE}
  CPrefix     = '';
  DelphiCName = '';
{$IFEND}

// Sisi ekspor: 'public name' literal di setiap target
procedure hpdf_codec_free(P: Pointer); cdecl;
  public name 'hpdf_codec_free';

WIN32 memberi tahu arsitektur, bukan ABI

Ini kesalahan conditional compilation dengan ekor debugging terpanjang, dan layak dinyatakan lugas: WIN32 dan WIN64 mendeskripsikan arsitektur target dan tidak mengatakan apa-apa tentang helper runtime privat kompilator mana yang ada. Free Pascal mendefinisikan kedua simbol itu pada target Windows yang bersesuaian, persis seperti Delphi. Guard yang ditulis {$IFDEF WIN32} di sekeliling kode yang memanggil helper runtime Delphi karenanya berhasil dikompilasi di FPC dan gagal saat link

Secara konkret, tiga keluarga kode jatuh ke jebakan ini. Trampoline integer 64-bit Delphi yang dicapai lewat helper System.@_ll, rutinitas support assembly Win32 MSVC, dan slot impor yang menyertainya — semuanya ada untuk melayani objek C prakompilasi yang ditautkan build Delphi. Free Pascal tidak menautkan objek-objek itu, jadi ia tidak butuh mesinteri apa pun dari semuanya, dan setiap referensi ke sana harus menghilang. Subtilitasnya: deklarasi dan implementasi harus dikeluarkan bersamaan. Keluarkan salah satu saja dan kompilator melaporkan sesuatu yang tak membantu tentang identifier yang tak bisa dicocokkannya ke apa pun

Aturan yang jatuh dari situ pendek saja. Gunakan guard pada kompilator ketika pertanyaannya soal ABI atau dukungan runtime, gunakan guard pada arsitektur ketika pertanyaannya soal lebar pointer atau jumlah register, dan jangan pernah membiarkan salah satunya menggantikan yang lain

Meng-guard deklarasi dan implementasi bersamaan

Blok conditional di section interface gampang masuk tanpa disadari, dan pesan error yang dihasilkannya menunjuk ke mana saja kecuali ke penyebabnya. Tambahkan deklarasi method ke interface sebuah class dan tempat alami untuk meletakkannya adalah di samping method-method yang berkaitan — itu tidak masalah sampai detik ketika tetangga-tetangganya ternyata duduk di dalam blok {$IFDEF} yang sudah ada. Direktif conditional tidak di-indentasi, jadi blok yang dibuka empat puluh baris di atas praktis tak terlihat saat Anda membaca deklarasi di sekitarnya

Yang terjadi berikutnya adalah kompilasi yang sukses di satu toolchain dan menghasilkan kaskade di toolchain lain. Kalau guard di sekitarnya adalah pemeriksaan versi Delphi yang tidak dipenuhi Free Pascal, deklarasinya lenyap bagi FPC sementara implementasi tanpa syarat tetap ada, dan kompilator melaporkan daftar panjang keluhan tentang identifier method yang ia harapkan ada tapi tidak ditemukan. Tidak satu pun pesan menyebut blok conditional yang jadi penyebabnya

Dua kebiasaan mencegah seluruh kelas kegagalan ini. Sebelum menyisipkan ke section interface, lihat ke atas mencari conditional yang masih terbuka alih-alih percaya pada pengelompokan visual. Dan perlakukan test suite Delphi yang hijau sebagai bukti tentang Delphi saja: build pustaka Free Pascal adalah gerbang terpisah, dan satu-satunya cara mengetahui ia lolos adalah menjalankan build-Win32-Lib-FPC.cmd dan build-Win64-Lib-FPC.cmd sebagai bagian dari perubahan yang sama

Apa yang rusak di kode aritmetika 32-bit

Satu batasan bahasa muncul persis di kode yang paling enggan berubah: Free Pascal 32-bit tidak mau menerima UInt64 sebagai variabel kontrol loop for. Di unit kurva eliptik yang membawa X25519 dan X448, loop-loop yang menelusuri array limb ditulis dengan counter 64-bit semata karena semua hal lain di file itu 64-bit

Perbaikannya harus bedah, karena di aritmetika field lebar sebuah variabel adalah bagian dari argumen kebenarannya. Indeks loop menjadi Integer, sebab array limb hanya punya segelintir elemen dan tidak ada indeks yang mendekati rentang 32-bit. Segala yang berpartisipasi dalam aritmetika — limb-nya sendiri, propagasi carry, dan mask-nya — tetap UInt64, karena menyempitkan salah satunya diam-diam mengubah hasil modulo prima field

// FPC 32-bit menolak variabel loop UInt64. Persempit indeksnya saja;
// limb, mask dan carry pertahankan lebarnya, atau matematika field berubah
var
  I: Integer;                 // tadinya UInt64
  Carry, Mask: UInt64;
begin
  Carry := 0;
  for I := 0 to High(Limbs) do
  begin
    Limbs[I] := Limbs[I] + Carry;
    Carry := Limbs[I] shr 51;
    Limbs[I] := Limbs[I] and Mask;
  end;
end;

Verifikasi untuk perubahan seperti ini tidak bisa berupa test round-trip. Mengenkripsi dan mendekripsi dengan implementasi yang rusak sama akan sepakat dengan dirinya sendiri dengan sempurna, dan itulah kenapa known-answer vectors tak bisa ditawar di sini: jalankan test vector X25519 dan X448 yang dipublikasikan lalu bandingkan byte output-nya persis. Itu satu-satunya pemeriksaan yang membedakan implementasi yang benar dari implementasi yang salah tapi konsisten dengan dirinya sendiri, dan berlaku sama untuk primitive simetris yang dibahas di batas codec deflate dan AES Free Pascal

Dua titik patah build Win32 Free Pascal HotPDF: guard {$IFDEF WIN32} di sekeliling helper runtime Delphi yang lolos kompilasi tapi gagal link kecuali deklarasi dan implementasi dikeluarkan bersamaan, dan variabel loop UInt64 pada penelusuran limb X25519 dan X448 yang disempitkan menjadi Integer sementara limb, carry dan mask mempertahankan lebarnya
Pasang guard pada kompilator saat pertanyaannya ABI atau dukungan runtime, dan pada arsitektur saat soalnya lebar pointer, lalu buktikan perubahan aritmetika dengan known-answer vectors yang dipublikasikan, bukan test round-trip

Untuk apa build Win32 Free Pascal itu

Imbalan praktisnya: aplikasi Lazarus yang menyasar Windows 32-bit mendapat mesin dokumen yang sama dengan saudaranya di Delphi, tanpa kontrak biner terpisah yang harus dirawat. Ini paling berarti untuk deployment yang jarang dibicarakan orang: controller industri, terminal point-of-sale, dan perangkat lunak line-of-business berumur panjang tempat runtime 32-bit bukan pilihan warisan melainkan constraint perangkat keras

Kisah Win64 datang lebih dulu dan dibahas di dukungan Free Pascal dan Lazarus di Win64. Win32 bukan putaran ulang kisah itu. Win64 punya satu calling convention, tanpa name decoration, dan tanpa helper integer privat Delphi untuk diakali, jadi hampir semua isi artikel ini spesifik untuk target 32-bit. Unit aritmetika yang butuh perubahan variabel loop adalah unit yang sama yang dibahas di aritmetika Montgomery di atas kurva NIST, tempat disiplin lebar itu dijelaskan lebih dalam

Pelajaran umumnya: pekerjaan portabilitas lintas-kompilator bukan terutama soal fitur bahasa. Kedua kompilator menerima Object Pascal yang sama di sini. Yang berbeda adalah file objeknya: bagaimana simbol dieja, rutinitas helper mana yang diasumsikan disediakan runtime, dan objek prakompilasi mana yang ada di link. HotPDF mengirim paket Free Pascal dan Lazarus berdampingan dengan paket Delphi dan C++Builder dalam komponen PDF Delphi HotPDF, sehingga source tree yang sama menyuplai setiap toolchain alih-alih bercabang per kompilator