pdfium-core-raw-ffi
What this skill covers
The high-level idiomatic interface of pdfium-render is built on top of raw
FFI bindings to the PDFium C API, defined in the PdfiumLibraryBindings trait.
That trait is the escape hatch: it exposes the flat FPDF_* function surface
directly, so a Rust program can port existing C or C++ PDFium code, or call a
PDFium function that pdfium-render does not yet wrap, while still keeping
late binding and WASM compatibility.
This skill covers obtaining the bindings, the unsafe contract a caller takes
on, mixing raw calls with the RAII handles, and the 0.8.x to 0.9.x traps. For
the binding setup itself (bind_to_*, feature flags, library files) see
pdfium-core-bindings-setup. For the ownership tree see
pdfium-core-architecture.
Quick reference
| Item | Detail |
|---|---|
| Trait | PdfiumLibraryBindings (in the prelude) |
| Accessor | pdfium.bindings() returns a reference to the active PdfiumLibraryBindings |
| Raw functions | every FPDF_* method; unsafe fn since 0.9.0 |
| Raw handle types | FPDF_DOCUMENT, FPDF_PAGE, FPDF_TEXTPAGE, and the rest |
| Import | use pdfium_render::prelude::*; |
Raw calls return raw handles and integer codes. They do NOT return
Result<_, PdfiumError> and do NOT run any RAII cleanup. The caller owns every
handle a raw call produces.
When to use raw FFI, and when NOT to
ALWAYS prefer the high-level API. Reach for raw FFI ONLY when one of these is true:
- Porting existing C or C++ PDFium code with minimal changes.
- Calling a specific
FPDF_*function thatpdfium-renderdoes not expose through a high-level type.
NEVER drop to raw FFI for work the high-level API already covers (loading
documents, rendering, text extraction). The high-level API gives you
PdfiumError handling and RAII cleanup; raw FFI gives you neither and makes
crashes easy.
Does a high-level pdfium-render type already do this?
YES -> use the high-level API. Stop.
NO -> Is it a one-off unwrapped FPDF_* call?
YES -> get pdfium.bindings(), call it in an `unsafe` block,
close every handle you create yourself.
NO (porting a whole C/C++ module) -> work through the
PdfiumLibraryBindings trait directly.
Getting the bindings
A constructed Pdfium instance hands out its bindings with bindings():
use pdfium_render::prelude::*;
let pdfium = Pdfium::default();
let bindings = pdfium.bindings();
bindings() is the 0.9.x accessor. The 0.8.x and earlier name
Pdfium::get_bindings() was deprecated since 0.7.18 and removed in 0.9.0.
The bind_to_* functions return a Box<dyn PdfiumLibraryBindings> directly, so
raw calls are also possible without ever constructing a Pdfium:
let bindings = Pdfium::bind_to_system_library()?;
// `bindings` is a Box<dyn PdfiumLibraryBindings>; raw FPDF_* calls work on it.
The canonical raw-call pattern
This is the raw-FFI example from the pdfium-render README, quoted verbatim:
let pdfium = Pdfium::default();
let bindings = pdfium.bindings();
let test_doc = "test.pdf";
unsafe {
bindings.FPDF_InitLibrary();
let doc = bindings.FPDF_LoadDocument(test_doc, None);
// ... do something with doc
bindings.FPDF_CloseDocument(doc);
bindings.FPDF_DestroyLibrary();
}
Every FPDF_* call sits inside an unsafe block because, since 0.9.0, every
FPDF_* method on PdfiumLibraryBindings is an unsafe fn.
The unsafe contract: caller responsibilities
When you call an unsafe FPDF_* function you take on PDFium's C-level
contract. ALWAYS hold to these rules:
- Close every handle you open. A raw
FPDF_LoadDocumentreturns anFPDF_DOCUMENTthat YOU own. Pair it withFPDF_CloseDocument. A rawFPDF_LoadPageis paired withFPDF_ClosePage.pdfium-renderdoes not track raw handles and will not free them. - Close in reverse order of opening. Close pages before their document. Closing a document while a page from it is still open is undefined behavior.
- Null-check every handle. Raw loaders return a null handle on failure,
NOT a
PdfiumError. Test the handle before use; on failure readFPDF_GetLastError()for the reason. - Never use a handle after its source is gone. A raw handle is only valid
while the
PdfiumLibraryBindings(and the loaded library) are alive. - Never pass a handle from one document into a raw call on another.
Breaking any of these is a memory-safety bug: a crash, a segfault, or silent corruption, with no Rust error to catch.
Mixing raw calls with high-level handles
The high-level types (PdfDocument, PdfPage, and so on) are RAII wrappers:
each owns an underlying raw handle and frees it on Drop. Mixing is safe ONLY
under one rule:
- NEVER call a
FPDF_Close*orFPDF_Destroy*function on a handle that a high-level type owns. The high-level type will free it again onDrop, which double-frees and crashes. - A handle YOU obtained with a raw
FPDF_Load*call is yours alone. Close it yourself.pdfium-renderdoes not see it.
The safe shape: use the high-level API to load and manage documents and pages,
and use pdfium.bindings() only to call extra FPDF_* functions that take
PDFium handles, without taking over ownership of handles the high-level API
created.
Version traps: 0.8.x to 0.9.x
| Item | 0.8.x | 0.9.x |
|---|---|---|
FPDF_* on PdfiumLibraryBindings |
safe fn |
unsafe fn (since 0.9.0) |
| Bindings accessor | Pdfium::get_bindings() (deprecated since 0.7.18) |
bindings() (get_bindings removed in 0.9.0) |
PdfiumLibraryBindingsAccessor trait |
pub(crate) |
pub since 0.9.2 |
The 0.9.2 change makes PdfiumLibraryBindingsAccessor public specifically to
ease raw FPDF_* use by crate consumers. Code targeting 0.9.0 or 0.9.1 can
still call bindings(); 0.9.2 makes the accessor trait nameable.
NEVER carry 0.8.x raw-FFI code into a 0.9.x build unchanged: the calls now
require unsafe blocks, and get_bindings() no longer exists.
Common failures (quick triage)
| Symptom | Likely cause | Fix |
|---|---|---|
| compile error: call to unsafe function | FPDF_* call outside an unsafe block on 0.9.x |
wrap the call in unsafe { } |
compile error: get_bindings not found |
0.8.x accessor name on 0.9.x | use bindings() |
| segfault on a raw call | handle is null, freed, or from another document | null-check, respect lifetime, never reuse a freed handle |
| double free / crash on drop | raw FPDF_Close* called on a handle a high-level type owns |
let the high-level type free its own handle |
| memory leak | raw handle opened, never FPDF_Close*d |
close every handle you open |
Reference files
references/methods.md: thePdfiumLibraryBindingstrait shape, thebindings()accessor, verifiedFPDF_*signatures, raw handle types, and the version matrix.references/examples.md: verified raw-FFI code for getting bindings, calling an unwrapped function, and the 0.8.x to 0.9.xunsafedifference.references/anti-patterns.md: real raw-FFI failures, why each breaks memory safety, and the fix.
Companion skills
pdfium-core-bindings-setup: binding to the library, feature flags, library files.pdfium-core-architecture: the ownership tree and the late-binding model.pdfium-core-memory: drop order and lifetime-bound handles for the high-level API.pdfium-errors-binding: diagnosing a binding that fails at run time.