Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I share C++ functions in Rcpp-based libraries between R packages?

Tags:

c++

r

rcpp

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.

like image 901
bskaggs Avatar asked Nov 22 '14 16:11

bskaggs


People also ask

What is the difference between library and package in R?

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.

Can you use C++ in R?

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.

What is RCPP sugar?

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.

What is 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 ...


1 Answers

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.

like image 191
Kevin Ushey Avatar answered Sep 30 '22 13:09

Kevin Ushey