PDFlibPas builds under Free Pascal for 32-bit Windows, and the hard part was never Pascal. It was the object files: the AES and OpenJPEG objects the Delphi build links are OMF, the Free Pascal internal linker requires COFF, and converting between the two produces section names and section-definition symbols that make the linker fail with internal errors rather than diagnostics
Anyone who has linked C objects into a Pascal library knows this territory. Win64 is comparatively civil, with one object format, one calling convention and no name decoration. Win32 preserves every layer of history the platform accumulated, and a library that statically links third-party C code meets all of them at once
The compiler directory does not tell you the target
Start with the build entry point, because getting this wrong wastes hours before any object file is involved. A Free Pascal installation directory name identifies where the main compiler lives, not what it produces. A 32-bit host compiler can invoke a cross-compiler sitting beside it and emit 64-bit code when you pass the right target switches, so inferring the target from a path is guesswork that happens to work until someone reorganizes their toolchain
The reliable approach is to ask the compiler. Query the actual target processor and operating system through the compiler own information switches, and accept both common installation layouts, the flat binary directory and the version-nested one, because different installers and toolchain managers produce different shapes. A build script that hard-codes either layout works on exactly one machine
Why does a converted object file break the internal linker?
Because the conversion preserves the OMF section naming convention and synthesizes section-definition symbols that do not match what the COFF linker expects. Converting the OMF objects to COFF is necessary and not sufficient: the resulting files carry the classic _TEXT, _DATA and _BSS section names, plus section-definition symbol names derived from them, and feeding that to the Free Pascal internal linker produces internal compiler errors rather than a message about section naming
An internal error is the worst failure mode for a build problem, because it says nothing about what was wrong with the input. The fix is a post-conversion normalization pass over the COFF file: rewrite the section names to the expected form and rewrite the corresponding section-definition symbols to match, while leaving the symbol index, the code bytes and the relocations untouched. That last constraint is the entire difficulty. A rewrite that renumbers symbols or shifts offsets produces an object that links and then crashes
There is a preliminary step for one of the two object sets. The OpenJPEG objects built by the classic 32-bit C++ compiler depend on Delphi private 64-bit integer helper routines, which Free Pascal does not provide, so no amount of format conversion makes them usable. Those are rebuilt with the Clang-based compiler first, which does not emit those dependencies, and converted afterward
// Objects for the FPC target live in their own directory. They do not
// replace the Delphi object set, because both toolchains build from the
// same source tree and each needs its own link inputs
//
// Lib\thirdparty\Win32 Delphi OMF objects, unchanged
// Lib\thirdparty\Win32f FPC COFF objects, converted and normalized
//
// Build entry points:
// build-Win32-Lib-FPC.cmd
// build-Win64-Lib-FPC.cmd
Compiler-private helpers are not portable, and neither are their conventions
The Delphi runtime supplies assembly trampolines for 64-bit integer operations on 32-bit x86, and precompiled C objects built for Delphi call into them. Free Pascal has its own arrangement, so those references have to be satisfied differently rather than redirected. The detail that makes redirection impossible is the calling convention: the timing helper used by the imaging code has its four-byte argument cleaned by the callee, while the 64-bit division helper cleans sixteen bytes and returns its result in the classic register pair. Two helpers, two conventions, and a trampoline written for one silently corrupts the stack for the other
Name decoration adds the second half of the problem. On Win32, Free Pascal prefixes external C imports with an underscore automatically while exporting public name declarations verbatim, so the import side and the export side of the same bridge follow different rules. The C runtime bridge that OpenJPEG needs therefore has to export the exact C symbol names, and the variadic entry points need a 32-bit indirect jump rather than a direct one. None of this is exotic once stated. All of it fails as a link error naming a symbol nobody wrote
What made a Win32 executable die before main?
A 64-bit DLL on the search path, reached because the Free Pascal zlib unit binds dynamically rather than linking statically. The symptom was an immediate exit with the invalid-image status code, before any Pascal code in the program ran, which sends you looking at the program you just built when the fault is in the loader resolving an import against the wrong architecture
The lesson is about assumptions rather than zlib. A unit named after a compression library does not necessarily contain one; it may be a binding that expects a shared library at run time, and a dynamic dependency you did not intend is a deployment liability even when it happens to resolve. Switching to the pure Pascal stream implementation gives both targets a statically included compression path with no external dependency at all, which is what a library embedded in someone else application should have in the first place
The same instinct applies to the external JBIG2 encoder backend. On the 32-bit target the external encoder is not linked, so requests fall back to the built-in Pascal encoder, and the test that verifies this has to check the registration state of the current target rather than treating a successful encode as proof the external backend is present. A fallback that works is exactly the thing that hides a missing dependency, which is the failure pattern examined in diagnosing silent stub failures. The 64-bit static linking work is covered in static linking jbig2enc under FPC
32-bit arithmetic on a memory stream
Code that manipulates buffer sizes with pointer-width unsigned arithmetic is correct on Win64 and one large image away from overflow on Win32. The in-memory stream that feeds the JPEG 2000 codec grows by doubling and advances by addition, and on a 32-bit target both operations can wrap on inputs that are large but entirely legitimate
Every write, skip, seek and initial allocation therefore checks before it computes, and the capacity ceiling is the maximum signed pointer-width value, chosen to match what the block move routine and the callback return values can express. The behavioral requirement when a request is refused is easy to get wrong: refusing must not change the stream position or its length. A partial mutation followed by an error leaves the stream in a state the caller cannot reason about, and the next operation compounds it
// Check before you compute. On Win32 both of these wrap on inputs a
// large JPEG 2000 image produces legitimately
if Needed > NativeUInt(High(NativeInt)) - FPosition then
Exit(False); // refuse, leave position and size alone
NewCapacity := FCapacity;
while (NewCapacity < FPosition + Needed) do
begin
if NewCapacity > NativeUInt(High(NativeInt)) shr 1 then
Exit(False); // doubling would overflow
NewCapacity := NewCapacity shl 1;
end;
Two build-output traps that outlive the port
Separating test and sample executables by target architecture into per-target output directories is obviously right and immediately breaks anything that located its test data by counting directory levels upward. The fix is to search upward for the asset directory rather than assume a fixed depth, with one deliberate restriction: the signing sample accepts a certificate fallback only from its own project directory, never from an arbitrary ancestor, because a same-named certificate found further up the tree is a security surprise rather than a convenience
The second trap survives every port and is worth carrying to any FPC project. After a compiler upgrade, having the compiler reject stale PPU files is not enough, because the linker still prefers leftover object files in the unit search path even when the PPU it loaded came from the correct directory, and adding an explicit object output path does not override that preference. The only reliable answer is a fresh temporary unit directory per build round. Anything less produces a binary linked from two compiler versions, which fails in ways that look like source bugs
Platform conditionals are the last piece, and choosing the right axis matters more than it appears. The right question is usually whether the code is Windows-specific rather than whether a particular widget library is present, as the metafile conversion work in EMF vector import and platform conditionals showed: switching that guard from a control-library condition to a platform condition turned a supposed rewrite into a change of one directive. Free Pascal and Lazarus support for both Windows targets ships with the PDFlibPas Delphi PDF library, built from the same sources as the Delphi and C++Builder packages