ruby-bindgen and FFI Bindings
proj4rb talks to the PROJ C library through Ruby FFI, removing the need to create a compiled C extension. The FFI function signatures are auto-generated by ruby-bindgen, which reads the PROJ C headers and produces Ruby code. On top of that generated layer are hand written Ruby classes to provide a more natural, object-oriented API you use day-to-day.
Architecture
The binding stack has three layers:
flowchart TD
A["Your code"] --> B["Ruby API — lib/proj/"]
B --> C["FFI — lib/api/"]
C --> D["PROJ — libproj"]
lib/api/ — Auto-generated by ruby-bindgen. Contains FFI struct definitions, enum mappings, and attach_function calls that map Ruby methods to C functions. Do not hand-edit these files (they include a header comment indicating they are generated).
lib/api/proj_version.rb — The one hand-maintained file in lib/api/. It detects the installed PROJ version at runtime by calling proj_info() and sets PROJ_VERSION, which the generated files use for version guards.
lib/proj/ — Hand-written Ruby classes (~36 files) that wrap the raw FFI calls into an idiomatic Ruby API. This is where Crs, Transformation, Context, and the rest of the public interface live.
Version Guards
PROJ adds new C functions with each release. ruby-bindgen wraps each group of functions in a version check so the gem works with older PROJ installs:
# Auto-generated in lib/api/proj.rb
if proj_version >= 90600
attach_function :proj_trans_bounds_3D, ...
end
The version groups are defined in ffi-bindings.yaml under symbols.versions. Each key is an integer encoding of the PROJ version (major × 10000 + minor × 100 + patch) and the value is a list of C function names introduced in that release.
ffi-bindings.yaml
This YAML file is the single source of truth that drives code generation. See the ruby-bindgen configuration reference for the full list of supported options.
The proj4rb-specific things to know:
symbols.skip—proj_infoandPJ_INFOare excluded because they live in the hand-maintainedproj_version.rb.symbols.versions— Each key is an integer-encoded PROJ version (major × 10000 + minor × 100 + patch) mapping to the C functions introduced in that release. This is what drives the version guards.symbols.overrides— Manual signature corrections for cases where auto-detection gets it wrong::boolreturns (Ruby's0is truthy),:size_tparameters (different size than:ulongon 64-bit Windows), buffer pointers, and struct array parameters.
Generated Files
| File | Contents |
|---|---|
lib/api/proj_ffi.rb |
Preamble: loads the shared library, then requires the other files |
lib/api/proj.rb |
Bindings for proj.h — structs, enums, and attach_function calls |
lib/api/proj_experimental.rb |
Bindings for proj_experimental.h |
lib/api/proj_version.rb |
Hand-maintained — runtime PROJ version detection |
Regenerating Bindings
Install ruby-bindgen, then run:
ruby-bindgen ffi-bindings.yaml
This re-parses the PROJ C headers on your system and regenerates the three auto-generated files in lib/api/. The hand-maintained proj_version.rb is never overwritten.
Regenerate when you:
- Add support for a new PROJ version (add a
symbols.versionsblock) - Add or change function overrides
- Update to a newer PROJ system install with new headers
Adding Support for a New PROJ Release
- Install the new PROJ version so its headers are available.
- Add a new version block to
symbols.versionsinffi-bindings.yamllisting the new C functions. - Add any needed signature overrides.
- Run
ruby-bindgen ffi-bindings.yaml. - Write Ruby wrapper code in
lib/proj/for the new functionality. - Add tests.