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

时间:2014-11-22 16:42:43

标签: c++ r rcpp

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

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

这是我第一次尝试使用任何非基于R的软件包,我通过Rstudio"构建& amp; amp;刷新"命令并使用"包w / Rcpp"我创建包时生成初始目录结构的选项。

3 个答案:

答案 0 :(得分:5)

在R中执行此操作的一般机制是通过R_RegisterCCallableR_GetCCallable提供函数指针。有关示例,请参阅R-exts

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

值得庆幸的是,这可以使用Rcpp::interfaces属性自动执行,该属性实际上会自动生成R_RegisterCCallable中的RcppExports.cpp个入口点,并使用标头中的R_GetCCallable提供包装函数文件生成。

例如,假设我有一个名为RcppInterfaces的愚蠢包,其中包含src/test.cpp DESCRIPTION Rcpp Includes:LinkingTo: }})。请注意// [[Rcpp::interfaces(r, cpp)]]注释,该注释向Rcpp发出信号,表明此文件应同时获得R导出和C ++标头导出。

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

#include <Rcpp.h>

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

如果我致电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;
}

请注意,大多数早期的东西都是样板文件,可以确保函数的异常安全版本可以调用;最后,您基本上具有为其他包注册可调用函数的机制。在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__

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

因此,就这个RcppInterfaces包的用户而言,他们可以只调用

RcppInterfaces::hello()

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

答案 1 :(得分:2)

是的,链接步骤更难但仍然可行。

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

我认为Kevin在他的Kmisc软件包中有一些帮助他们完成所需的注册步骤。我一直想读那些但不需要它们/有时间的。

答案 2 :(得分:1)

凯文关于R_RegisterCCallableR_GetCCallable的好描述。就个人而言,我认为所有代码都可以由您的包或标头中的其他包使用。这是IMO不那么脆弱。