Technical Article

Diagnosing Silent Stub Failures in a Pascal PDF Library

When a Delphi library grows a build configuration without the visual framework, the substitute classes are where the bugs live. Not the platform, not the compiler: the stand-ins. PDFlibPas has a graphics layer that provides bitmap, canvas, font, metafile and printer equivalents for builds without the VCL, and porting it to Free Pascal surfaced every failure mode a stand-in can have. They sort neatly by diagnosis cost, and the ordering is the opposite of what intuition suggests

A stand-in that raises is cheap to find; the exception names the method. A stand-in that returns empty data is expensive, because the failure appears several layers away from its cause. A stand-in that returns success is the worst of all, because the return code is valid, the error code is zero, no exception is raised, and the only evidence anything went wrong is in the bytes that came out

Three shapes of silent stub failure in a Pascal PDF library ranked by diagnosis cost, from a raising stub to success over empty output
A raising stand-in is cheap to diagnose, empty data is expensive, and a success return over an empty artifact is the worst to find

Shape three: a valid image identifier over an empty XObject

The vector metafile converter was an empty procedure body in the non-VCL configuration. Everything above it kept working. The EMF import entry points and the canvas-capture entry point ran to completion and returned a legal image identifier, which the caller then placed on a page. What landed in the file was a form XObject with a content length of zero. The page rendered white

Nothing reported a problem, and that includes the library's own demonstration program for this feature, which drew a blank page and did not notice. There was no failing return value to check, because the sequence of calls genuinely all succeeded; the only thing that was wrong was the size of the produced stream. Diagnosing this class of defect means asking a different question: not "did the call fail" but "is the artifact plausible". A form XObject of length zero, an image of zero pixels, a page of zero content bytes, these are the assertions that catch it

The fix has two halves and the second half is easy to forget. First, make the empty implementation raise, so the failure has a channel at all. Second, convert that exception into a null result at the image factory and add null checks at the two places that consume an image identifier, because otherwise "clean failure" turns straight into an access violation as the page tree dereferences nothing. A stub that throws is only an improvement if the callers were prepared for a failure they had never previously been able to receive

Shape two: empty data, three layers from the crash

The metafile canvas stand-in did not fill in its physical dimensions. That value divides into a page geometry calculation, so the calculation produced zero, so the bounding-box computation divided by zero. A bare exception handler swallowed that, the image factory returned a null result, and the access violation finally happened in the page tree when the null was used. Three layers between cause and symptom, with an exception handler in the middle erasing the evidence

Failure chain of an empty metafile canvas stand-in in PDFlibPas reaching an access violation three layers after the division by zero
The empty dimension divides a geometry calculation to zero, a bare handler erases the exception, and the null identifier crashes the page tree

The same unit had two more instances of the pattern. The font class had empty Assign and constructor bodies, which matters more than it looks because the canvas font property is read-only: assigning into it is the only way to deliver a font, so an empty implementation makes font selection silently ineffective and the text comes out in whatever the default was. And a pixels-per-inch value of zero made every caller that sizes a canvas from font metrics produce a zero-by-zero canvas, which yields a blank page and a success return

// The shape to look for in a stand-in unit: a method that neither
// raises nor does anything. Both of these compile and both of them
// produce "success" with no output
procedure TMetafileCanvasStandIn.Create(...);
begin
  // no inherited call, no field initialisation
end;

function TBitmapStandIn.LoadFromStream(Stream: TStream): Boolean;
begin
  Result := True;    // and the bitmap is still empty
end;

The wide structure that keeps only the first character

This one is not a stand-in problem at all, but it belongs in the same catalog because the symptom is equally far from the cause. The printer enumeration structure was declared with all twelve of its string members typed as pointers to single-byte characters, while the function that fills it is the wide-character variant of the enumeration API

Pointer sizes are identical, so the structure layout is correct and nothing crashes. What happens instead is that reading a UTF-16 string as a single-byte string stops at the first zero byte, which for any ASCII printer name is the high half of the second character. Every printer name came back as exactly one character. Downstream, name validation failed, printer creation failed and printing failed for every real printer on the machine, and none of those symptoms points at a structure declaration

UTF-16 printer name truncated to one character after a wide Win32 structure is declared with PAnsiChar members instead of PWideChar
Single-byte members read a UTF-16 name only to its first zero byte, so every printer name comes back with exactly one character
// Wrong: right size, wrong element type. No compile error, no crash,
// every string truncated to one character
type
  TPrinterInfo2Wrong = record
    pServerName: PAnsiChar;
    pPrinterName: PAnsiChar;
    // ... ten more
  end;

// Right: a *W structure has wide members throughout
type
  TPrinterInfo2W = record
    pServerName: PWideChar;
    pPrinterName: PWideChar;
    // ... ten more
  end;

The rule that comes out of it is mechanical and worth applying without thinking: for any Win32 structure whose name ends in W, verify every string member is the wide variant, field by field. Mixing the ANSI and wide worlds produces neither a compiler diagnostic nor a crash, only silent truncation, and the same applies in reverse to the ANSI variants

A bare exception handler is the actual adversary

Every one of these investigations was slowed by the same construct: a handler that catches everything and converts it to a false return value. It is a reasonable thing to write around an image decoder, since a corrupt image should not take down a document job. It is also a device for deleting the one piece of information you need

The practical response is to make the handler temporarily loud. Dumping the exception class, message and backtrace from inside the bare handler, under a debug conditional, converts an unexplained null return into a named exception with a location. In two of the three cases above that single step ended the investigation, because the exception was a division by zero or an access violation in a stand-in method whose name said everything

Checklist for adopting a stand-in path

Four items, in the order they pay off. Before calling into a substitute class, read the methods you are about to use and confirm each has a real body; an empty body is not an implementation detail, it is a missing feature. Prefer stand-ins that raise over stand-ins that return neutral values, and pair that with null checks at the places a factory can now legitimately return nothing. Verify a feature by inspecting the artifact, not the return code, since the whole failure mode here is a clean return code over an empty artifact; a byte-level breakdown of what a document actually contains is the fastest way to see it, and the file size audit article covers that tooling. And when a feature has no viable substitute implementation, route the affected samples to the path that does work and say why in a comment, rather than leaving a demonstration that quietly produces blank output

The broader point applies well beyond one library. Any codebase with a conditional second implementation, a mock layer, a headless mode, a platform shim, is exposed to shape three. The reason it hides so well is that every quality gate a team normally relies on, return codes, error codes, exceptions, exit statuses, is a status channel, and shape three keeps all of them clean. Only the output betrays it. That is also the reasoning behind checking artifacts rather than statuses when handling untrusted input, described in the untrusted PDF parsing article, and behind comparing rendered output across engines rather than trusting one, described in multi-engine rendering

PDFlibPas is a native Object Pascal PDF library for Delphi, C++Builder and Free Pascal, and its non-VCL configuration is what makes headless and cross-toolchain builds possible; current configuration coverage is listed on the losLab PDF Developer Library product page