HotPDF biên dịch và chạy dưới Free Pascal 3.2.2 với Lazarus, và bản tóm tắt trung thực của bản chuyển nền đó gồm hai câu. Tạo tài liệu, nạp, lưu, nén, giải nén, mã hóa và giải mã đều chạy trên các backend thuần Pascal, nên một ứng dụng Lazarus có thể tạo và tiêu thụ PDF thật mà không phụ thuộc C nào. Các codec ảnh thuần tùy chọn thì không, vì các object Win64 dựng sẵn dùng một biến thể COFF mà không linker Free Pascal nào tiêu hóa nổi, nên trên toolchain đó các điểm vào phân giải thành những stub thất bại an toàn
Đi từ "biên dịch được" đến "chạy được" cần một bộ sửa chữa cụ thể, và mỗi cái trong số đó là một cái bẫy sẽ tìm đến bất kỳ codebase Delphi nào khác đang chuyển sang Free Pascal. Chúng đáng được ghi lại theo thứ tự chúng gây đau
Vì sao một unit biên dịch được chẳng chứng minh điều gì?
Vì một unit Pascal có thể tham chiếu một symbol mà sẽ chẳng bao giờ làm gì hữu ích cả và vẫn làm hài lòng trình biên dịch. Tại thời điểm cả 113 unit thư viện dựng sạch sẽ dưới Free Pascal, các trình xử lý container lưu trữ thực sự hoạt động, được xác minh bằng một smoke test mở một CBZ và chuyển nó thành PDF. Làm phẳng biểu mẫu XFA thì hoàn toàn không chạy, vì việc làm phẳng phải inflate luồng gói /XFA đã nén và điểm vào deflate vẫn còn là stub. Chẳng gì trong đầu ra build phân biệt được hai trường hợp đó
Quy tắc rút ra từ đó rất ngắn. Trước khi viết vào ghi chú phát hành rằng một tính năng chạy trên một toolchain mới, hãy viết một probe thời gian chạy tác động tính năng từ đầu đến cuối trên toolchain đó. Độ phủ biên dịch là điều kiện tiên quyết, chưa bao giờ là bằng chứng. Bức tranh rộng hơn về những gì bản chuyển nền phủ được nằm trong các ghi chú hỗ trợ Win64 Free Pascal và Lazarus
Một raise bên trong stub cdecl không đến được caller
Cái này đáng có một mục riêng vì triệu chứng gây hiểu lầm đến thế. Các unit stub phơi các điểm vào C theo cách một thư viện tĩnh sẽ làm, nên một stub trông như thế này
// Trông hợp lý. Thực ra không.
function inflate(Strm: Pointer; Flush: Integer): PtrUInt; cdecl;
public name 'inflate';
begin
raise ENotSupportedException.Create('codec unavailable');
end;
Trên Free Pascal cho Win64, ngoại lệ đó không lan truyền đến caller. Không có handler try..except nào nhìn thấy nó, vì việc cuốn ngang qua một biên giới cdecl được khai báo theo cách này không mang theo khung ngoại lệ Pascal; tiến trình kết thúc với exit code 217. Nhìn từ phía ứng dụng, không có lỗi, không thông điệp và không dòng log nào, chỉ có một chương trình biến mất. Đó là điều tệ hơn hẳn một câu trả lời sai, vì một câu trả lời sai còn có thể được xử lý
Bản sửa hấp dẫn là làm cho stub trả về một mã thất bại thay thế, và với inflate thì điều đó đúng vì zlib có mã lỗi trả về được định nghĩa rõ. Nhưng nó sai về mặt tổng quát: một stub cho jpeg_read_header trả về không sẽ bảo caller tiếp tục với một cấu trúc chẳng ai khởi tạo. Bản sửa bền vững là chặn tại điểm vào Pascal thay vì bên trong stub hình dạng C, dùng quy ước thất bại mà API đó vốn đã có
function TryDecodeJPEG(const Data: TBytes; out Bitmap: TBitmap): Boolean;
begin
{$IFDEF FPC}
// Từ chối trước khi stub kịp được chạm tới, dùng quy ước thất bại
// riêng của API này thay vì một ngoại lệ vắt qua cdecl
Bitmap := nil;
Result := False;
Exit;
{$ENDIF}
Result := DecodeJPEGNative(Data, Bitmap);
end;
paszlib không phải zlib, và sự khác biệt là hai lớp tài liệu
Hiện thực deflate Pascal có sẵn trên Free Pascal xử lý hai kiểu khung: wrapper zlib và deflate thô. Nó không xử lý khung gzip, thứ zlib chọn qua các giá trị windowBits từ 16 đến 31, và nó không xử lý chế độ tự dò mà các giá trị 32 đến 47 chọn. HotPDF cần cả hai. Con đường nhập SVG an toàn xin giá trị 31, và loader có một thang dự phòng xin giá trị 47 khi khung của một luồng mơ hồ. Bỏ qua bất kỳ cái nào và cả một họ tài liệu ngừng mở được, kèm một lỗi giải mã chỉ vào luồng thay vì vào khung còn thiếu
Còn một sự không tương thích thứ hai, sắc hơn. Record z_stream mà paszlib khai báo không có bố cục bộ nhớ giống bản C: trường msg của nó là short string thay vì con trỏ, và total_in cùng total_out là 64-bit trong khi ABI C dùng máy từ. Vì vậy record của caller không thể được truyền thẳng qua. Cách sắp xếp khả thi là giữ trạng thái paszlib sau con trỏ state mà record công khai đã dành sẵn, và sao chép các trường công khai vào ra quanh mỗi lệnh gọi. CRC gzip và phần đuôi độ dài tám byte được tính đến trong cùng tầng shim đó, nơi tự nhiên của chúng vì tầng đó vốn đã sở hữu quyết định khung
Truyền một mảng động vào một tham số var không kiểu
Đây là con bug nhiều khả năng đang nằm trong mã của bạn ngay lúc này. Khi bạn truyền một mảng động vào một tham số var không kiểu, thứ callee nhận được là địa chỉ của biến mảng, tức là địa chỉ của một con trỏ, chứ không phải địa chỉ của phần dữ liệu. Vì vậy một lần đọc vào nó sẽ ghi đè chính biến đó và mọi thứ nằm kề bên
var
FBuffer: TBytes;
begin
SetLength(FBuffer, 65536);
// Sai: đưa đi địa chỉ của biến FBuffer
FStream.Read(FBuffer, Length(FBuffer));
// Đúng: đưa đi địa chỉ của byte dữ liệu đầu tiên
FStream.Read(FBuffer[0], Length(FBuffer));
end;
Trên Delphi, dạng sai thường xuất hiện như thể chạy được, vì thứ nó làm hỏng là một ô stack kề bên mà chẳng gì đọc tới sau đó. Trên Free Pascal, cùng dòng đó gây segmentation fault ngay lần dùng đầu. Điều khiến nó khó phát hiện bằng mắt là mảng tĩnh không có vấn đề này, vì biến mảng tĩnh tự thân là phần dữ liệu của nó, nên cả hai cách viết đều đúng trong cùng một tệp, tùy thuộc khai báo cách vài trăm dòng
Container ZIP mà không có System.Zip
Free Pascal không có đối tượng tương đương với unit zip của RTL, và lựa chọn thay thế hiện có vừa có bề mặt API khác vừa không hỗ trợ mã hóa kiểu cũ mà các định dạng container cũ vẫn dùng, nên một trình đọc nhỏ ngay trong thư viện hóa ra ngắn hơn so với việc thích nghi với nó. Hai chi tiết định dạng tốn thời gian và dễ làm sai
Thứ nhất là byte kiểm tra của header mã hóa. Byte thứ mười hai của nó thường là byte cao của CRC, nhưng khi bit 3 của cờ đa dụng được bật — nghĩa là các kích thước nằm trong một data descriptor đuôi và CRC chưa biết — thì byte kiểm tra lấy từ byte cao của thời gian sửa đổi. Chỉ hiện thực dạng CRC và mọi archive được ghi ở chế độ streaming sẽ từ chối một mật khẩu đúng. Thứ hai là trường extra ZIP64: ba trường 64-bit của nó xuất hiện theo một thứ tự cố định nhưng chỉ được ghi khi trường 32-bit tương ứng đã bão hòa, nên việc đọc chúng tại các offset cố định chạy tốt trên những archive bạn đã thử và hỏng trên cái tiếp theo. Hãy phân tích chúng theo vị trí dựa trên trường 32-bit nào đang bão hòa
Một tiện lợi đáng biết: luồng giải nén Free Pascal nhận một đối số constructor thứ hai để bỏ qua header zlib, chính xác là thứ các mục ZIP cần vì chúng lưu deflate thô. Con đường đó không đụng chạm gì vào shim zlib của thư viện, nên nó không bị ảnh hưởng bởi backend C còn thiếu
Độ trong suốt glyph màu dưới LCL
Đọc kênh alpha của một glyph màu đã raster hóa là chi tiết đồ họa duy nhất không có bản dịch trực tiếp. Lớp PNG của LCL không có accessor scanline nào phơi alpha ra, và việc gán một PNG vào bitmap bỏ nó đi, nên một emoji màu đến trong trạng thái hoàn toàn đục và ghép lớp với một ô đen phía sau. Con đường khả thi là ảnh giao diện: tạo nó từ PNG, rồi đọc pixel qua accessor màu, nhớ rằng các thành phần của nó là 16-bit và cần dịch xuống tám bit để thành byte. Bề mặt đó cũng dùng thứ tự hàng từ trên xuống tự nhiên, nên phép đảo Height - 1 - Y mà mã scanline VCL cần phải bị bỏ đi thay vì chuyển nền
Hai ghi chú hệ thống build trước khi bạn nộp một bug report
Một lần dựng lại đầy đủ thỉnh thoảng thất bại với một symbol chưa định nghĩa có tên kết thúc bằng hậu tố $crc và một giá trị thập lục phân. Hậu tố đó được tính từ các kiểu tham số, và nó khớp thất bại khi một lần build biên dịch một unit đối chiếu với hai phiên bản giao diện khác nhau trong cùng lượt. Chạy lại build là sạch; chữ ký không hề sai
Thứ hai, Free Pascal 3.2.2 không có anonymous method, nên ở bất cứ nơi nào thư viện dùng closure để nối một pipeline song song, bản build Free Pascal dùng một phương án dự phòng tuần tự xác định thay thế. Đầu ra giống hệt, thông lượng thì không; nếu bạn dựa vào kết xuất trang song song, đó là lý do để ở lại Delphi trong thời điểm hiện tại, và thiết kế pipeline được mô tả trong bài viết về pipeline kết xuất song song. Tình hình codec ảnh là nơi khác mà lựa chọn toolchain thay đổi năng lực chứ không chỉ tốc độ, nên một bản triển khai Lazarus nên lập kế hoạch định dạng ảnh theo đó; ma trận theo toolchain hiện tại nằm trên trang sản phẩm HotPDF Delphi PDF component