How do I share C++ functions in Rcpp-based libraries between R packages?
c++, r, rcpp
Solution
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.
Problem
I'm developing a simple library in Rcpp that builds Huffman trees. It has a working R interface I can call from other packages, but I'd also like to call the C++ functions directly from C++ code in other Rcpp-based packages I'm developing. I've figured out how to put the header for the first package in the `inst/include` directory so that it is available in the second package. However, when `useDynLib` is called in the second package's `NAMESPACE` file to load it's C++ code that calls a function in the first package, I get an undefined symbol error for the function I am trying to use. I have the first package listed in the second package's `DESCRIPTION` file under `Import`, `Depends`, and `LinkingTo`. This is my first foray into doing any non-R based packages, and I'm doing all my development via Rstudio's "Build & Reload" command and used the "Package w/ Rcpp" option when I created the packages to generate the initial directory structure.