我正在Rcpp开发一个简单的库来构建Huffman树.它有一个可以从其他包调用的工作R接口,但我也想直接从我正在开发的其他基于Rcpp的包中的C++代码调用C++函数.
我已经想出如何将第一个包的头部放在inst/include目录中,以便它在第二个包中可用.但是,当useDynLib在第二个包的NAMESPACE文件中调用它来加载它调用第一个包中的函数的C++代码时,我得到了一个我想要使用的函数的未定义符号错误.我在第二个包的列出的第一个包DESCRIPTION下的文件Import,Depends和LinkingTo.
这是我第一次尝试使用任何非基于R的包,我正在通过Rstudio的"Build&Reload"命令进行所有开发,并在创建包时使用"Package w/Rcpp"选项来生成初始目录结构体.
在R中执行此操作的一般机制是通过R_RegisterCCallable和使函数指针可用R_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";
}
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自己的机制查找和使用(安全!)函数指针.
是的,链接步骤比较困难,但仍然可行。
例如,看看 RcppXts 包如何导入 xts 包导出的符号。这一切都非常乏味。
我认为 Kevin 在他的 Kmisc 包中提供了一些帮助来完成所需的注册步骤。我一直想阅读这些内容,但还不需要它们/还没有时间。