# Pdfium Core Raw Ffi

> Use when reaching past the high-level pdfium-render API into the raw FPDF_* C functions: porting existing C or C++ PDFium code, or calling a PDFium function that pdfium-render does not yet wrap. Prevents the unsafe-contract mistakes that crash a process, a double free of a raw handle a high-level object already owns, a use-after-free of a raw handle whose bindings dropped, and the 0.8.x assumption that FPDF_* calls are safe. Covers the PdfiumLibraryBindings trait, the bindings() accessor, the unsafe marking added in 0.9.0, the PdfiumLibraryBindingsAccessor visibility change in 0.9.2, and how to mix raw calls with RAII handles across pdfium-render 0.8.x and 0.9.x. Keywords: pdfium-render raw FFI, PdfiumLibraryBindings, FPDF_ functions, bindings(), get_bindings, unsafe fn, FPDF_LoadDocument, FPDF_GetPageCount, FPDF_DOCUMENT, FPDF_PAGE, PdfiumLibraryBindingsAccessor, porting C++ PDFium code, calling an unwrapped function, segfault calling FPDF, double free, use after free, how do I call a raw pdfium function, un

- Skill: `impertio-studio/pdfium-core-raw-ffi` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add impertio-studio/pdfium-core-raw-ffi`
- Raw SKILL.md: https://api.skillmd.com/api/skills/impertio-studio/pdfium-core-raw-ffi/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- License: MIT
- Author: Impertio-Studio (https://skillmd.com/u/impertio-studio)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/impertio-studio/pdfium-core-raw-ffi

---


# 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 that `pdfium-render` does 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()`:

```rust
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`:

```rust
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:

```rust
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:

1. **Close every handle you open.** A raw `FPDF_LoadDocument` returns an
   `FPDF_DOCUMENT` that YOU own. Pair it with `FPDF_CloseDocument`. A raw
   `FPDF_LoadPage` is paired with `FPDF_ClosePage`. `pdfium-render` does not
   track raw handles and will not free them.
2. **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.
3. **Null-check every handle.** Raw loaders return a null handle on failure,
   NOT a `PdfiumError`. Test the handle before use; on failure read
   `FPDF_GetLastError()` for the reason.
4. **Never use a handle after its source is gone.** A raw handle is only valid
   while the `PdfiumLibraryBindings` (and the loaded library) are alive.
5. **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*` or `FPDF_Destroy*` function on a handle that a
  high-level type owns. The high-level type will free it again on `Drop`, which
  double-frees and crashes.
- A handle YOU obtained with a raw `FPDF_Load*` call is yours alone. Close it
  yourself. `pdfium-render` does 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`: the `PdfiumLibraryBindings` trait shape, the
  `bindings()` accessor, verified `FPDF_*` 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.x `unsafe` difference.
- `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.

