Creating DynPort files with porter
Source:vignettes/articles/creating-dynports.Rmd
creating-dynports.Rmdrdyncall loads DynPort metadata; it does not parse C
headers by itself. porter is the companion
tool used to turn C headers into DCF .dynport files that
dynport() can load.
Use this article when you want to create a DynPort file for a C library that you use. SDL3 appears here only as a concrete example because rdyncall ships one maintained SDL3 DynPort file.
Workflow overview
The generation boundary is:
- Install or locate the C library headers and the runtime library.
- Write a small umbrella header that includes the public header you want to bind.
- Run porter with the include paths and parse boundary for that library.
- Set DynPort metadata such as
Package,Version, andLibrary. - Write a DCF
.dynportfile and review the generated diff. - Let rdyncall load that file with
dynport().
Only step 6 happens at rdyncall runtime. Header parsing and metadata generation are development-time steps.
Choose the inputs
Before running porter, decide these inputs for the library you want to bind:
| Input | Purpose | Example for SDL3 |
|---|---|---|
| Umbrella header | Keeps the parsed API surface explicit | #include <SDL3/SDL.h> |
| Include flags | Lets the parser find library headers | -I/path/to/include |
| Parse boundary | Avoids accidentally binding unrelated dependency headers | /path/to/include/SDL3 |
| DynPort package name | Names the generated R package | SDL3 |
| Runtime library candidates | Names that dynfind() can resolve on target
systems |
SDL3, SDL3-0,
SDL3-3
|
Small umbrella headers usually make better DynPort files than asking the parser to follow every header installed on the system.
Generate a DynPort file
The exact include path depends on how the target library is installed. This script shows the pattern with SDL3 as the example:
library(porter)
sdl3_header <- "/path/to/include/SDL3/SDL.h"
sdl3_include <- dirname(dirname(sdl3_header))
umbrella <- tempfile(fileext = ".h")
writeLines("#include <SDL3/SDL.h>", umbrella)
sdl3 <- port(
umbrella,
limit = dirname(sdl3_header),
cflags = paste0("-I", sdl3_include)
)
sdl3 <- port_set(
sdl3,
Package = "SDL3",
Version = "3.4.10",
Library = c("SDL3", "SDL3-0", "SDL3-3")
)
port_write(sdl3, "inst/dynports/SDL3.dynport")For another library, replace the header path, umbrella include,
Package, Version, and Library
values with the values for that library.
Keep generated output reviewed in git. A changed header, porter release, or upstream library version can legitimately change function, constant, enum, and aggregate metadata.
Inspect an existing DynPort file
The installed rdyncall package contains one bundled DynPort file for SDL3:
sdl3_portfile <- system.file("dynports", "SDL3.dynport",
package = "rdyncall", mustWork = TRUE)
basename(sdl3_portfile)
#> [1] "SDL3.dynport"The file is plain text DCF metadata. The header shows the package identity and library candidates:
head(readLines(sdl3_portfile), 12L)
#> [1] "Package: SDL3"
#> [2] "Version: 3.4.10"
#> [3] "Library:"
#> [4] " SDL3"
#> [5] " SDL3-0"
#> [6] " SDL3-3"
#> [7] "Constant:"
#> [8] " SDL_PLATFORM_APPLE=1"
#> [9] " SDL_PLATFORM_MACOS=1"
#> [10] " SDL_MIN_UINT64=0"
#> [11] " SDL_PRILL_PREFIX=\"ll\""
#> [12] " SDL_INVALID_UNICODE_CODEPOINT=65533"The Library entries are candidates, not a guarantee that
SDL3 is installed on the current machine. dynport()
resolves them when loading the generated package.
Validate on the rdyncall side
After generating a file, validate the rdyncall side separately from the header-generation step:
portfile <- "inst/dynports/YourLibrary.dynport"
lib <- tempfile("rdyncall-dynport-lib")
generated <- dynport(portfile = portfile, package = "dyn.YourLibraryCheck",
lib = lib, rebuild = TRUE, quiet = TRUE)
generatedIf the target library is installed, call a non-GUI scalar function
first. For SDL3, SDL_GetPlatform() is a good smoke test
because it does not open a window or start an event loop.
What belongs where
| Concern | Home |
|---|---|
| Header parsing and metadata generation | porter |
| Reviewed DCF file | Your package or
inst/dynports/*.dynport
|
| Loading generated packages | dynport() |
| Real function calls | generated package wrappers |
| GUI demos and event loops | separate demos or articles |
The rdyncall package intentionally keeps the bundled DynPort surface small. SDL3 is the maintained example. Additional libraries should be generated from their current headers rather than copied from historical DynPort files.
Next steps
- Use dynbind and DynPort bindings for the rdyncall loading layer.
- Use Non-GUI demos for examples that can run without opening a window.
- Use troubleshooting when a generated DynPort package cannot find its library or reports unresolved symbols.