10

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.

bskaggs
  • 1,374
  • 2
  • 12
  • 24

3 Answers3

11

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.

Josh O'Brien
  • 159,210
  • 26
  • 366
  • 455
Kevin Ushey
  • 20,530
  • 5
  • 56
  • 88
3

Yes, the linking step is harder but still doable.

Look e.g. at how the RcppXts package imports symbols which the xts package exports. This is all pretty tedious.

I think Kevin has some helpers for the required registration step in his Kmisc package. I have been meaning to read up on those but have not needed them / had time yet.

Dirk Eddelbuettel
  • 360,940
  • 56
  • 644
  • 725
  • Thanks for the pointers; I'll put together a short example when I sort it all out. Also, thanks for Rcpp. :D – bskaggs Nov 22 '14 at 19:11
  • 1
    In this case, `Kmisc` just automates registration of 'native routines', e.g. [here](http://cran.r-project.org/doc/manuals/r-release/R-exts.html#Registering-native-routines). But I recently found out with Hadley that just writing `useDynLib(, , )` in the `NAMESPACE` is effectively equivalent... – Kevin Ushey Nov 22 '14 at 19:52
  • 1
    Portably? That would be a _huge_ help. – Dirk Eddelbuettel Nov 22 '14 at 20:31
  • 1
    I didn't know about C-level function registration, so I've only done it with `useDynLib()` in the `NAMESPACE`. Seems to work cross-platform and be blessed by CRAN – hadley Nov 24 '14 at 21:40
1

Nice description from Kevin about R_RegisterCCallable and R_GetCCallable. Personally, I'd argue in the direction of having all the code that can be either used by your package or other packages in headers. This is IMO less fragile.

Romain Francois
  • 17,432
  • 3
  • 51
  • 77