This article explains the
systems-level design behind gdalraster.windows: why the
package exists, how the GDAL runtime bundle is built, and why runtime
activation works the way it does.
GDAL’s Algorithm API registers algorithms through static C++
constructors — file-scope objects whose constructors insert entries into
a global registry when libgdal is loaded. Under some
Windows toolchain states (notably Rtools/MXE builds where dependencies
are static archives), the linker’s dead-code elimination discarded those
self-registration translation units, leaving the registry empty. The
visible symptom:
The upstream fix landed in GDAL 3.12.2 (OSGeo/gdal#13592); muparser (required by parts of the Algorithm API) was added to the Rtools GDAL build in release 6768. Until a fixed GDAL ships in the default toolchain — and for anyone needing a pinned, feature-rich GDAL — this package provides a known good runtime: a custom GDAL build plus the activation logic to load it reliably.
| Term | What it is |
|---|---|
| MinGW-w64 | GCC-based toolchain producing native Win32
.exe/.dll (no POSIX emulation layer) |
| MSYS2 | Distribution + pacman package manager shipping several
MinGW-w64 toolchain variants; the build environment, not the
runtime target |
| UCRT | Microsoft’s Universal C Runtime (default since VS2015); mixing
binaries linked against different C runtimes is unsafe (incompatible
heap allocators, FILE*, etc.) |
| UCRT64 | The MSYS2 environment targeting UCRT — the variant compatible with Rtools |
| Rtools45 | CRAN’s Windows toolchain for R 4.5/4.6; UCRT64/MinGW-based, so C++ ABI-compatible with MSYS2 UCRT64 builds of the same GCC line |
The invariant this package maintains: GDAL and
gdalraster are both compiled with compatible MinGW/UCRT
toolchains.
gdalraster must be rebuilt from sourceC++ has no standardized ABI across compilers. Name mangling, vtable
layout, exception unwinding, and object layout all differ between MSVC
and MinGW/GCC, and can drift between incompatible GCC configurations.
gdalraster binds to GDAL’s C++ API through Rcpp (not a C
extern "C" shim), so gdalraster.dll and
libgdal-*.dll must come from the same ABI world.
That is why install_gdalraster() builds from source
against the bundle’s headers and import library rather than reusing the
CRAN binary (which is linked — statically — against Rtools’ own
GDAL):
Internally this uses withr::with_makevars(), which
writes a scoped Makevars file and points the
R_MAKEVARS_USER environment variable at it for the duration
of the install — compile/link flags never leak into your persistent
configuration.
From tools/build_gdal.sh:
| Flag | Purpose |
|---|---|
-DGDAL_USE_MUPARSER=ON |
Algorithm API support (expression evaluation) |
-DGDAL_USE_ARROW/PARQUET/HDF5/NETCDF/GEOS/SPATIALITE=ON |
Extended driver profile beyond the lean Rtools build |
-DGDAL_HIDE_INTERNAL_SYMBOLS=ON |
Restricts the export table to the public API (PE/COFF DLLs cap named exports at 65,535; GDAL’s full symbol set exceeds it) |
-Wl,--kill-at |
Strips @N stdcall decoration from exports so symbol
names match what loaders expect |
-static-libgcc -static-libstdc++ + whole-archive
winpthread |
Embeds the GCC runtime into the DLLs — end users need neither Rtools nor MSYS2 at runtime |
collect_dlls.shProducing libgdal-*.dll is half the job; it imports
dozens of transitive DLLs (GEOS, PROJ, Arrow’s deep tree, HDF5, …).
tools/collect_dlls.sh walks the full PE import tree with
ntldd -R, copies every dependency that resolves to the
UCRT64 prefix into the bundle’s bin/, and
fails if any non-Windows-system dependency remains
unresolved. The bundle is therefore a verified-closed set: the only
external imports are Windows system DLLs.
Final bundle layout:
<gdal_home>/
├── bin/ libgdal-*.dll + closed transitive DLL set + GDAL executables
├── include/ headers (compile-time, for install_gdalraster())
├── lib/ libgdal.dll.a import library (compile-time)
├── share/ gdal/ + proj/ runtime data
└── python/ osgeo_utils (pure-python, for embedded-python algorithms)
Windows resolves a DLL’s import table at load time using a fixed
search order (executable directory, System32, then PATH).
When library(gdalraster) loads gdalraster.dll,
Windows immediately needs libgdal-*.dll — if the bundle’s
bin/ is not discoverable at that exact moment, loading
fails with LoadLibrary failure.
activate_gdal_runtime() handles this,
session-scoped:
<gdal_home>/bin to
PATHGDAL_DATA, PROJ_LIB,
PROJ_DATA (GDAL and PROJ require their runtime data trees
for CRS operations)<gdal_home>/python to
PYTHONPATH (next section)dyn.load(..., local = FALSE, now = TRUE) — mapping it into
the process’s loaded-module list before
gdalraster.dll asks for it; local = FALSE
loads into the global symbol namespace so subsequent DLLs resolve
against itSome GDAL algorithms (e.g. gdal driver gpkg validate)
are thin C++ entry points around Python implementations. At first use,
libgdal locates a python.exe on
PATH, dynamically loads the matching libpython
DLL (no static CPython link), calls Py_Initialize(), and
imports osgeo_utils.samples.validate_gpkg.
osgeo_utils is pure Python — no compiled extension
modules, hence no CPython version/ABI coupling. The bundle ships it
under <gdal_home>/python, version-locked to the built
GDAL tag, and activation exposes it via PYTHONPATH. The
compiled osgeo SWIG bindings are deliberately
not bundled: they would pin the bundle to a single
CPython ABI, and the Python-implemented validators degrade gracefully
without them.
These are independent concerns, and conflating them is the most common source of confusion:
install_gdalraster()): PKG_CPPFLAGS points at
<gdal_home>/include, PKG_LIBS at
<gdal_home>/lib — scoped via withr.PATH/preload state — handled by
activate_gdal_runtime().A successful link does not imply a loadable session, and vice versa.
The bundle is built entirely from the repository — no local machine state. For a new GDAL release:
or dispatch the build workflow with
gdal_version=v3.14.0. The version string drives the GDAL
source checkout, cache key, asset name
(gdal-ucrt64-v3.14.0-windows-x64.zip), and release tag. CI
cache keys hash the build scripts, so any build-logic change forces a
fresh compile.
Local (non-CI) reproduction from an MSYS2 UCRT64 shell: