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.
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.
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")A non-GUI SDL3 probe
When RDYNCALL_ARTICLE_EXTERNAL=true and SDL3 is
available, this article builds and loads a generated package from the
bundled DynPort, then calls a non-GUI platform query. The chunk does not
open a window or enter an event loop.
sdl3_names <- c("SDL3", "SDL3-0", "SDL3-3")During normal local rendering, RDYNCALL_ARTICLE_EXTERNAL
can stay unset or false; the SDL3 code is shown but
skipped. The pkgdown workflow sets it to true, installs
SDL3 system libraries, and therefore executes this non-GUI probe in
CI.
Sys.setenv(RDYNCALL_ARTICLE_EXTERNAL = "true")
portfile <- system.file("dynports", "SDL3.dynport",
package = "rdyncall", mustWork = TRUE)
lib <- tempfile("rdyncall-dynport-lib")
generated <- dynport(portfile = portfile, package = "dyn.SDL3Article",
lib = lib, rebuild = TRUE, quiet = TRUE)
generated
#> [1] "dyn.SDL3Article"
getExportedValue(generated, "SDL_GetPlatform")()
#> [1] "Linux"If SDL3 is not installed, the code remains visible in the article but is skipped unless external article execution is explicitly requested.
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.
All three layers use the same underlying signatures, so start by getting the C declarations and signatures correct.