How can I share C ++ functions in Rcpp libraries between R packages?
I am developing a simple library in Rcpp that builds Huffman trees. It has a working R interface that I can call from other packages, but I would also like to call C ++ functions directly from C ++ code in other Rcpp-based packages I develop.
I figured out how to put the header for the first package in the directory inst/include
so that it is available in the second package. However, when useDynLib
called in the second package NAMESPACE
to load the C ++ code that calls the function in the first package, I get an undefined character error for the function I'm trying to use. I have the first package specified in the second packet DESCRIPTION
in the file Import
, Depends
and LinkingTo
.
This is my first foray into doing any non-R based packages and I am doing all my development with Rstudio's "Build and Reload" command and used the "Package w / Rcpp" option when I created the packages to generate the original structure catalogs.
source to share
The general mechanism for this in R is to make function pointers accessible via R_RegisterCCallable
and R_GetCCallable
. See R-exts
for an example.
This means that symbols are resolved dynamically as needed β you don't actually need to "bind" to another package per se; you just need the headers so that characters can be correctly resolved later when the code is executed. Note that the field is LinkingTo:
really wrong - it just gives you the headers, it doesn't actually link you to the package (generated by the library for).
Fortunately, this can be automated with an attribute Rcpp::interfaces
that essentially automatically generates entry points R_RegisterCCallable
to RcppExports.cpp
, and provides wrapper functions using R_GetCCallable
a header in the generated file.
For example, suppose I have a stupid package called RcppInterfaces
that contains this in src/test.cpp
(from DESCRIPTION
with Rcpp
to Includes:
and LinkingTo:
). Note the comment // [[Rcpp::interfaces(r, cpp)]]
that signals Rcpp
that this file should receive both the R export and the C ++ header header.
// [[Rcpp::interfaces(r, cpp)]]
#include <Rcpp.h>
// [[Rcpp::export]]
void hello() {
Rcpp::Rcout << "Hello!\n";
}
If I name it Rcpp::compileAttributes()
, you will see the following "stuff" recorded in 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 much of the early material is a template that ensures that the non-safety version of a function becomes callable; at the end, you essentially have a mechanism for registering called 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 another pattern to protect against an exception, but with an interesting part is a call R_GetCCallable
that allows other package authors to "just use" this function using strings R_GetCCallable
and managed directly in the function call (with a static pointer that is filled once when needed ).
So, as far as the users of this package are concerned RcppInterfaces
, they can simply call
RcppInterfaces::hello()
in our code, and we just automatically guarantee that the function pointer will be looked up and used (safely!) at runtime using R.'s own mechanisms.
source to share
Yes, the linking step is more difficult, but still possible.
Take a look for example. how the RcppXts package imports the symbols that the xts package exports. This is all pretty boring.
I think Kevin has helpers for the required registration step in his Kmisc package. I meant to read on those, but didn't need them / had time yet.
source to share