Direct dyncall() calls are useful for one-off
experiments. Larger bindings usually need names, wrappers, constants,
types, and repeatable loading. That is where dynbind() and
dynport() fit.
Use this article when you already know several function signatures
and want to move beyond address-by-address calls. It shows when to keep
a small binding in an environment with dynbind(), and when
to put binding metadata in a DynPort file so dynport() can
generate and load an R package.
The three layers have different jobs:
| Layer | Input | Output | Best use |
|---|---|---|---|
dyncall() |
a function pointer and one call signature | one foreign call | probes and one-off calls |
dynbind() |
library candidates and hand-written signatures | R wrappers in an environment | small, explicit bindings |
dynport() |
a DCF .dynport file |
a generated R package | repeatable bindings from metadata |
| porter | C headers | a .dynport file |
generating metadata outside rdyncall |
porter is not
required at run time by rdyncall. It is the tool used to generate
DynPort files from headers before dynport() loads those
files.
Bind a small function set
dynbind() takes a library name or handle and a library
signature. It installs thin R wrappers in an environment.
math_names <- c("msvcrt", "m", "m.so.6")
math <- new.env(parent = globalenv())
info <- dynbind(
math_names,
paste(
"sqrt(d)d",
"cos(d)d",
"sin(d)d",
sep = ";"
),
envir = math
)
c(
sqrt = math$sqrt(49),
cos = math$cos(0),
sin = math$sin(pi / 2)
)
#> sqrt cos sin
#> 7 1 1The generated wrapper still calls the resolved C function through rdyncall. The wrapper just hides the address and signature from the user-facing call.
Inspect unresolved symbols
The return value reports the library handle and unresolved symbols. This makes it possible to fail early when a platform lacks a function you require.
str(info$unresolved.symbols)
#> chr(0)For exploratory bindings, unresolved optional symbols can be reported to the user. For required bindings, fail before exposing partially working wrappers.
Library discovery
Character library names that look like file paths are loaded
directly. Other character values are resolved with
dynfind().
libc_names <- c("msvcrt", "c", "c.so.6")
libc_handle <- dynfind(libc_names)
is.nullptr(libc_handle)
#> [1] FALSEUse a vector of candidate names for cross-platform bindings. For
example, Windows may use msvcrt, while Unix-like systems
commonly expose C runtime symbols through c or
c.so.6.
From bindings to generated packages
dynport() reads a DCF .dynport file and
generates a real R package in an rdyncall-managed library. The generated
package namespace is populated from the metadata in the DynPort
file.
The default DynPort library is:
dynport_lib()
#> [1] "/home/runner/.cache/R/rdyncall/dynports/R-4.6.1"Generated package names use the option
rdyncall.dynport.package.prefix, whose default value is
"dyn.", plus the Package field in the DynPort
file.
DynPort file shape
A minimal DynPort file records the package name, library candidates, function bindings, and optionally constants and aggregate types.
Package: SDL3
Version: 3.4.10
Library:
SDL3
SDL3-0
SDL3-3
Function:
SDL_GetPlatform()Z;
The package ships one current-format DynPort file:
inst/dynports/SDL3.dynport. It is generated from SDL3
headers with porter
and kept in the package as a realistic, non-toy binding example.
rdyncall intentionally does not bundle a broad catalog
of old DynPort files. The supported in-package example is SDL3. For
other libraries, generate a fresh DCF file for the headers and library
version you want to bind.
For other C libraries, generate a .dynport file with porter and pass it to
dynport(portfile = ...). A regeneration script for SDL3
follows this shape once the SDL3 header directory is known:
library(porter)
sdl3_header <- "/path/to/include/SDL3/SDL.h"
sdl3_include <- dirname(dirname(sdl3_header))
header <- tempfile(fileext = ".h")
writeLines("#include <SDL3/SDL.h>", header)
sdl3 <- port(header,
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")When to choose each layer
- Use
dyncall()for a single function address or a small experiment. - Use
dynbind()when you know a small set of functions and want ordinary R wrappers in one environment. - Use
dynport()when the binding metadata should live in a data file and load as a generated R package. - Use porter when the metadata should come from C headers rather than from hand-written signatures.
All three layers use the same underlying signatures, so start by getting the C declarations and signatures correct.
Next steps
- Use signatures before writing library
signatures or DynPort
Functionentries. - Use Creating DynPort files with porter for the header-to-DynPort workflow, including the SDL3 example that produces the bundled DynPort file.
- Use Non-GUI demos for examples that run without opening windows.
- Use troubleshooting when generated packages fail to find a library, resolve a symbol, or load into the current session.