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.

+3


source to share


3 answers


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.

+4


source


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.

+2


source


Kevin's good description of R_RegisterCCallable

and R_GetCCallable

. Personally, I would say that all codes that can be used by your package or other packages in the headers. It is IMO less fragile.

+1


source







All Articles