The general mechanism for doing this in R is to make function pointers available through R_RegisterCCallable
and R_GetCCallable
. See R-exts
for an example.
This implies that the symbols are resolved dynamically as needed -- you don't actually need to 'link' to the other package per-se; you just need the headers so that the symbols can be properly resolved later when the code is executed. Note that the LinkingTo:
field is really a misnomer -- it just gives you headers, it does not actually link you to the (library generated for the) package.
Thankfully, this can be automated with the Rcpp::interfaces
attribute, which essentially auto-generates the R_RegisterCCallable
entrypoints in RcppExports.cpp
, and provides wrapper functions using R_GetCCallable
in the header file generated.
For example, suppose I have a silly package called RcppInterfaces
, containing this in src/test.cpp
(with DESCRIPTION
having Rcpp
in Includes:
and LinkingTo:
). Note the // [[Rcpp::interfaces(r, cpp)]]
comment, which signals to Rcpp
that this file should get both R exports, and C++ header exports.
// [[Rcpp::interfaces(r, cpp)]]
#include <Rcpp.h>
// [[Rcpp::export]]
void hello() {
Rcpp::Rcout << "Hello!\n";
}
If I call Rcpp::compileAttributes()
, you'll see the following 'stuff' written out to RcppExports.cpp
:
// This file was generated by Rcpp::compileAttributes
// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
#include <Rcpp.h>
#include <string>
#include <set>
using namespace Rcpp;
// hello
void hello();
static SEXP RcppInterfaces_hello_try() {
BEGIN_RCPP
{
hello();
}
return R_NilValue;
END_RCPP_RETURN_ERROR
}
RcppExport SEXP RcppInterfaces_hello() {
SEXP __result;
{
Rcpp::RNGScope __rngScope;
__result = PROTECT(RcppInterfaces_hello_try());
}
Rboolean __isInterrupt = Rf_inherits(__result, "interrupted-error");
if (__isInterrupt) {
UNPROTECT(1);
Rf_onintr();
}
Rboolean __isError = Rf_inherits(__result, "try-error");
if (__isError) {
SEXP __msgSEXP = Rf_asChar(__result);
UNPROTECT(1);
Rf_error(CHAR(__msgSEXP));
}
UNPROTECT(1);
return __result;
}
// validate (ensure exported C++ functions exist before calling them)
static int RcppInterfaces_RcppExport_validate(const char* sig) {
static std::set<std::string> signatures;
if (signatures.empty()) {
signatures.insert("void(*hello)()");
}
return signatures.find(sig) != signatures.end();
}
// registerCCallable (register entry points for exported C++ functions)
RcppExport SEXP RcppInterfaces_RcppExport_registerCCallable() {
R_RegisterCCallable("RcppInterfaces", "RcppInterfaces_hello", (DL_FUNC)RcppInterfaces_hello_try);
R_RegisterCCallable("RcppInterfaces", "RcppInterfaces_RcppExport_validate", (DL_FUNC)RcppInterfaces_RcppExport_validate);
return R_NilValue;
}
Note that most of the early stuff is boilerplate that ensures an exception-safe version of the function is made callable; at the end you have essentially the mechanism for registering callable functions for other packages.
In inst/include/RcppInterfaces_RcppExports.h
, we have:
// This file was generated by Rcpp::compileAttributes
// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393
#ifndef __RcppInterfaces_RcppExports_h__
#define __RcppInterfaces_RcppExports_h__
#include <Rcpp.h>
namespace RcppInterfaces {
using namespace Rcpp;
namespace {
void validateSignature(const char* sig) {
Rcpp::Function require = Rcpp::Environment::base_env()["require"];
require("RcppInterfaces", Rcpp::Named("quietly") = true);
typedef int(*Ptr_validate)(const char*);
static Ptr_validate p_validate = (Ptr_validate)
R_GetCCallable("RcppInterfaces", "RcppInterfaces_RcppExport_validate");
if (!p_validate(sig)) {
throw Rcpp::function_not_exported(
"C++ function with signature '" + std::string(sig) + "' not found in RcppInterfaces");
}
}
}
inline void hello() {
typedef SEXP(*Ptr_hello)();
static Ptr_hello p_hello = NULL;
if (p_hello == NULL) {
validateSignature("void(*hello)()");
p_hello = (Ptr_hello)R_GetCCallable("RcppInterfaces", "RcppInterfaces_hello");
}
RObject __result;
{
RNGScope __rngScope;
__result = p_hello();
}
if (__result.inherits("interrupted-error"))
throw Rcpp::internal::InterruptedException();
if (__result.inherits("try-error"))
throw Rcpp::exception(as<std::string>(__result).c_str());
return Rcpp::as<void >(__result);
}
}
#endif // __RcppInterfaces_RcppExports_h__
which is some more exception-safety boilerplate, but with the interesting portion being the R_GetCCallable
call that allows other package authors to 'just use' that function, with the R_GetCCallable
stuff inlines and managed directly in the function call (with a static pointer that gets populated once when necessary).
So, as far as users of this RcppInterfaces
package are concerned, they can just call
RcppInterfaces::hello()
in their code, and we just automatically ensure that the function pointer is looked up and used (safely!) at runtime, using R's own mechanisms.