我正在Rcpp中开发一个构建霍夫曼树的简单库。它有一个可以从其他软件包调用的工作R接口,但我也想在我正在开发的其他基于Rcpp的软件包中直接从C ++代码调用C ++函数。
我已经弄明白了如何将第一个包的标头放在inst/include
目录中,以便它可以在第二个包中使用。但是,当在第二个包的useDynLib
文件中调用NAMESPACE
来加载它调用第一个包中的函数的C ++代码时,我得到一个未定义的符号错误函数我想用。我在DESCRIPTION
,Import
和Depends
下的第二个包LinkingTo
文件中列出了第一个包。
这是我第一次尝试使用任何非基于R的软件包,我通过Rstudio"构建& amp; amp;刷新"命令并使用"包w / Rcpp"我创建包时生成初始目录结构的选项。
答案 0 :(得分:5)
在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";
}
如果我致电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_RegisterCCallable
和R_GetCCallable
的好描述。就个人而言,我认为所有代码都可以由您的包或标头中的其他包使用。这是IMO不那么脆弱。