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.
In R, a package is a collection of R functions, data and compiled code. The location where the packages are stored is called the library. If there is a particular functionality that you require, you can download the package from the appropriate site and it will be stored in your library.
We can use the function sourceCpp to read a function written in C++ into R interactively. The function takes care of the compilation using R CMD SHLIB and automatically generates an R wrapper for the underlying function.
Rcpp sugar brings a higher-level of abstraction to C++ code written using the Rcpp API. Rcpp sugar is based on expression templates (Abrahams and Gurtovoy, 2004; Vandevoorde and Josuttis, 2003) and provides some 'syntactic sugar' facilities directly in Rcpp.
The Regional Conservation Partnership Program (RCPP), established through the 2014 Farm Bill, allows conservation partners with similar missions to collaborate with USDA's Natural Resources Conservation Service (NRCS) to further the conservation, restoration, and sustainable use of soil, water, and wildlife habitat in ...
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With