如何在R包之间的基于Rcpp的库中共享C++函数?

bsk*_*ggs 5 c++ r rcpp

我正在Rcpp开发一个简单的库来构建Huffman树.它有一个可以从其他包调用的工作R接口,但我也想直接从我正在开发的其他基于Rcpp的包中的C++代码调用C++函数.

我已经想出如何将第一个包的头部放在inst/include目录中,以便它在第二个包中可用.但是,当useDynLib在第二个包的NAMESPACE文件中调用它来加载它调用第一个包中的函数的C++代码时,我得到了一个我想要使用的函数的未定义符号错误.我在第二个包的列出的第一个包DESCRIPTION下的文件Import,DependsLinkingTo.

这是我第一次尝试使用任何非基于R的包,我正在通过Rstudio的"Build&Reload"命令进行所有开发,并在创建包时使用"Package w/Rcpp"选项来生成初始目录结构体.

Kev*_*hey 7

在R中执行此操作的一般机制是通过R_RegisterCCallable和使函数指针可用R_GetCCallable.请参阅R-exts示例.

这意味着符号会根据需要动态解析 - 您实际上并不需要"链接"到其他包本身; 您只需要标题,以便以后在执行代码时可以正确解析符号.请注意,该LinkingTo:字段实际上是一个误称 - 它只是给你标题,它实际上并没有链接到(生成的库)包.

值得庆幸的是,这可以通过Rcpp::interfaces属性实现自动化,该属性实质上是自动生成R_RegisterCCallable入口点RcppExports.cpp,并R_GetCCallable在生成的头文件中提供包装函数.

例如,假设我有一个傻包叫做RcppInterfaces,含有这种在src/test.cpp(与DESCRIPTION具有RcppIncludes:LinkingTo:).请注意// [[Rcpp::interfaces(r, cpp)]]注释,它Rcpp表示此文件应同时获得R导出和C++标头导出.

// [[Rcpp::interfaces(r, cpp)]]

#include <Rcpp.h>

// [[Rcpp::export]]
void hello() {
    Rcpp::Rcout << "Hello!\n";
}
Run Code Online (Sandbox Code Playgroud)

如果我打电话Rcpp::compileAttributes(),你会看到以下"东西"写到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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,大多数早期的东西都是样板文件,可确保函数的异常安全版本可调用; 最后,您基本上具有为其他包注册可调用函数的机制.在inst/include/RcppInterfaces_RcppExports.h,我们有:

// 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__
Run Code Online (Sandbox Code Playgroud)

这是一个更加异常安全的样板,但有趣的部分是R_GetCCallable允许其他包作者"只使用"该函数的调用,内R_GetCCallable联函数和直接在函数调用中管理(使用静态指针一次填充)必要时).

所以,就这个RcppInterfaces软件包的用户而言,他们只能打电话

RcppInterfaces::hello()
Run Code Online (Sandbox Code Playgroud)

在他们的代码中,我们只是自动确保在运行时使用R自己的机制查找和使用(安全!)函数指针.


Dir*_*tel 3

是的,链接步骤比较困难,但仍然可行。

例如,看看 RcppXts 包如何导入 xts 包导出的符号。这一切都非常乏味。

我认为 Kevin 在他的 Kmisc 包中提供了一些帮助来完成所需的注册步骤。我一直想阅读这些内容,但还不需要它们/还没有时间。