HotPDF는 Free Pascal 3.2.2와 Lazarus 아래에서 컴파일되고 실행되며, 그 포트에 대한 정직한 요약은 두 문장입니다. 문서 생성, 로드, 저장, 압축, 해제, 암호화, 복호화는 모두 Pascal 전용 백엔드에서 동작하므로, Lazarus 애플리케이션은 C 의존성 없이 실제 PDF를 만들고 읽을 수 있습니다. 선택적 네이티브 이미지 코덱은 그렇지 않습니다. 미리 빌드된 Win64 오브젝트가 어느 쪽 Free Pascal 링커도 받아들이지 못하는 COFF 변형을 쓰기 때문에, 그 툴체인에서는 진입점이 실패 시 닫히는(fail closed) 스텁으로 해석됩니다
"컴파일된다"에서 "동작한다"로 가는 데는 특정한 수정 집합이 필요했고, 그 하나하나가 Free Pascal로 이동하는 다른 어떤 Delphi 코드베이스도 반드시 만나게 될 함정입니다. 아픈 순서대로 적어 둘 가치가 있습니다
유닛이 컴파일되는 것이 아무것도 증명하지 않는 이유
Pascal 유닛은 아무 유용한 일도 하지 않을 심볼을 참조하면서도 컴파일러를 만족시킬 수 있기 때문입니다. 113개 라이브러리 유닛 전체가 Free Pascal 아래에서 깨끗하게 빌드된 시점에, 아카이브 컨테이너 핸들러는 실제로 동작했습니다. CBZ를 열어 PDF로 변환하는 스모크 테스트가 검증했습니다. XFA 양식 플래트닝은 전혀 동작하지 않았습니다. 플래트닝은 압축된 /XFA 패킷 스트림을 인플레이트해야 하는데 deflate 진입점이 아직 스텁이었기 때문입니다. 빌드 출력에는 이 두 사례를 구분하는 것이 아무것도 없었습니다
여기서 나온 규칙은 짧습니다. 기능이 새 툴체인에서 동작한다고 릴리스 노트에 적기 전에, 그 툴체인에서 기능을 종단부터 끝까지 구동하는 런타임 프로브를 먼저 작성하십시오. 컴파일 커버리지는 전제 조건이지 결코 증거가 아닙니다. 포트가 커버하는 범위의 큰 그림은 Free Pascal 및 Lazarus Win64 지원 노트에 있습니다
cdecl 스텁 안의 raise는 호출자에게 닿지 않습니다
증상이 너무 오도적이라 이것 하나에는 독립 섹션을 줄 가치가 있습니다. 스텁 유닛은 정적 라이브러리가 그러하듯 C 진입점을 노출하므로, 스텁은 다음처럼 보입니다
// 그럴듯해 보입니다. 그렇지 않습니다
function inflate(Strm: Pointer; Flush: Integer): PtrUInt; cdecl;
public name 'inflate';
begin
raise ENotSupportedException.Create('codec unavailable');
end;
Win64용 Free Pascal에서 그 예외는 호출자에게 전파되지 않습니다. 이를 보는 try..except 핸들러가 없습니다. 이렇게 선언된 cdecl 경계를 넘어 되감기하면 Pascal 예외 프레임이 실려 가지 않아 프로세스가 exit code 217로 종료되기 때문입니다. 애플리케이션 쪽에서는 오류도, 메시지도, 로그 줄도 없고, 그저 사라지는 프로그램만 있습니다. 이것은 틀린 답보다 엄격하게 나쁩니다. 틀린 답은 처리할 수 있기 때문입니다
끌리는 수정은 스텁이 대신 실패 코드를 돌려주게 하는 것이고, inflate라면 그것이 맞습니다. zlib에는 잘 정의된 오류 반환값이 있기 때문입니다. 일반적으로는 틀립니다. 0을 돌려주는 jpeg_read_header 스텁은 호출자에게 아무도 초기화하지 않은 구조체로 계속 진행하라고 말합니다. 내구적인 수정은 C 모양 스텁 안이 아니라 Pascal 진입점에서, 그 API가 이미 갖춘 실패 관례를 써서 게이트를 두는 것입니다
function TryDecodeJPEG(const Data: TBytes; out Bitmap: TBitmap): Boolean;
begin
{$IFDEF FPC}
// 스텁에 닿기 전에 거절. cdecl을 넘는 예외 대신
// 이 API 자체의 실패 관례를 사용합니다
Bitmap := nil;
Result := False;
Exit;
{$ENDIF}
Result := DecodeJPEGNative(Data, Bitmap);
end;
paszlib은 zlib이 아니며, 그 차이는 두 개의 문서 클래스입니다
Free Pascal에서 쓸 수 있는 Pascal deflate 구현은 두 프레이밍, 즉 zlib 래퍼와 raw deflate를 다룹니다. gzip 프레이밍은 다루지 않는데 zlib은 windowBits 값 16부터 31까지로 이를 선택하고, 값 32부터 47까지가 고르는 자동 감지 모드도 다루지 않습니다. HotPDF는 둘 다 필요합니다. 안전한 SVG 가져오기 경로는 31을 요청하고, 로더에는 스트림의 프레이밍이 애매할 때 47을 요청하는 폴백 사다리가 있습니다. 어느 하나라도 건너뛰면 문서 한 계열 전체가 열기를 멈추며, 디코딩 오류는 빠진 프레이밍이 아니라 스트림을 가리킵니다
두 번째, 더 날카로운 비호환성이 있습니다. paszlib이 선언하는 z_stream 레코드는 C 것과 같은 메모리 레이아웃을 갖지 않습니다. msg 필드가 포인터가 아니라 short string이고, C ABI가 머신 워드를 쓰는 자리에 total_in과 total_out이 64비트입니다. 따라서 호출자 레코드를 그대로 넘길 수는 없습니다. 통하는 배열은 paszlib 상태를 공개 레코드가 이미 예약해 둔 state 포인터 뒤에 두고, 매 호출 앞뒤로 공개 필드를 복사해 넣고 꺼내는 것입니다. gzip CRC와 8바이트 길이 트레일러도 같은 심 계층에서 처리하는데, 프레이밍 결정을 이미 소유하는 곳이므로 자연스러운 자리입니다
동적 배열을 untyped var 매개변수로 넘기기
지금 여러분 코드에 앉아 있을 가능성이 가장 높은 버그입니다. 동적 배열을 untyped var 매개변수로 넘기면 피호출자가 받는 것은 배열 변수의 주소, 즉 포인터의 주소이지 페이로드의 주소가 아닙니다. 따라서 그 주소로 읽어 들이면 변수 자신과 그 옆에 앉은 것까지 덮어씁니다
var
FBuffer: TBytes;
begin
SetLength(FBuffer, 65536);
// 틀림: FBuffer 변수의 주소를 넘깁니다
FStream.Read(FBuffer, Length(FBuffer));
// 맞음: 첫 페이로드 바이트의 주소를 넘깁니다
FStream.Read(FBuffer[0], Length(FBuffer));
end;
Delphi에서는 틀린 형태가 자주 동작하는 것처럼 보입니다. 망가뜨리는 것이 이후 아무도 읽지 않는 인접 스택 슬롯이기 때문입니다. Free Pascal에서는 같은 줄이 첫 사용에서 세그먼트 폴트를 냅니다. 눈으로 찾기 어려운 이유는 정적 배열에는 이런 문제가 없다는 점입니다. 정적 배열 변수는 자신이 곧 페이로드이므로, 같은 파일 안에서도 몇백 줄 떨어진 선언에 따라 두 표기가 모두 맞을 수 있습니다
System.Zip 없이 ZIP 컨테이너 다루기
Free Pascal에는 RTL zip 유닛에 해당하는 것이 없고, 쓸 수 있는 대안은 API 면도 다르고 오래된 컨테이너 포맷이 여전히 쓰는 구식 암호화도 지원하지 않으므로, 라이브러리 안에 작은 리더를 두는 쪽이 적응하는 것보다 짧았습니다. 시간을 잡아먹고 틀리기 쉬운 포맷 세부가 두 가지입니다
첫째는 암호화 헤더 검사 바이트입니다. 열두 번째 바이트는 보통 CRC의 상위 바이트이지만, 범용 플래그 비트 3이 설정되면, 즉 크기들이 뒤따르는 data descriptor에 있고 CRC가 아직 알려지지 않았으면, 검사 바이트는 대신 수정 시각의 상위 바이트에서 옵니다. CRC 형태만 구현하면 스트리밍 모드로 작성된 모든 아카이브가 올바른 암호를 거부합니다. 둘째는 ZIP64 extra 필드입니다. 세 개의 64비트 필드는 고정 순서로 나타나지만 대응하는 32비트 필드가 포화되었을 때만 기록되므로, 고정 오프셋으로 읽으면 테스트한 아카이브에서는 통하고 다음 것에서 실패합니다. 어느 32비트 필드가 포화되었는지에 따라 위치적으로 해석하십시오
알아 둘 만한 편의 하나: Free Pascal 압축 해제 스트림은 zlib 헤더를 건너뛰는 두 번째 생성자 인자를 받는데, ZIP 엔트리는 raw deflate를 저장하므로 정확히 이것이 필요합니다. 그 경로는 라이브러리 zlib 심을 전혀 건드리지 않으므로 빠진 C 백엔드의 영향을 받지 않습니다
LCL 아래에서 컬러 글리프 투명도
래스터화된 컬러 글리프의 알파 채널을 읽는 것이 직접 번역이 없는 유일한 그래픽스 세부입니다. LCL PNG 클래스에는 알파를 노출하는 스캔라인 접근자가 없고, PNG를 비트맵에 대입하면 알파가 버려지므로, 컬러 이모지가 완전히 불투명하게 도착해 검은 상자를 등에 지고 합성됩니다. 통하는 경로는 인터페이스 이미지입니다. PNG에서 만든 뒤 컬러 접근자로 픽셀을 읽되, 구성 요소가 16비트이고 바이트가 되려면 8비트 시프트 다운이 필요하다는 점을 기억하십시오. 그 표면은 자연스러운 위에서 아래 행 순서도 쓰므로, VCL 스캔라인 코드가 필요로 하는 Height - 1 - Y 반전은 포팅하는 것이 아니라 제거해야 합니다
버그를 등록하기 전의 빌드 시스템 노트 두 가지
풀 리빌드가 가끔 이름이 $crc 접미사와 16진 값으로 끝나는 undefined symbol로 실패합니다. 그 접미사는 파라미터 타입들에서 계산되며, 한 빌드가 같은 패스에서 하나의 유닛을 서로 다른 두 인터페이스 버전에 대해 컴파일하면 일치하지 않습니다. 빌드를 다시 돌리면 사라집니다. 시그니처가 틀린 것이 아닙니다
둘째, Free Pascal 3.2.2에는 익명 메서드가 없으므로, 라이브러리가 병렬 파이프라인을 묶는 데 클로저를 쓴 곳마다 Free Pascal 빌드는 대신 결정적인 직렬 폴백을 택합니다. 출력은 동일하고 처리량은 아닙니다. 병렬 페이지 렌더링에 의존한다면 당분간 Delphi에 남을 이유이며, 파이프라인 설계는 병렬 렌더 파이프라인 문서에서 설명합니다. 이미지 코덱 상황은 툴체인 선택이 속도만이 아니라 능력을 바꾸는 또 다른 자리이므로, Lazarus 배포는 이미지 포맷을 그에 맞게 계획해야 합니다. 현재 툴체인별 매트릭스는 HotPDF Delphi PDF component 제품 페이지에 있습니다