在R包中共享已编译的C代码并从另一个包运行它的首选方法是什么?

Sim*_*onG 8 c r package

假设我在R中有两个包,第一个命名foo,第二个命名bar.我希望在一个独立于平台且与CRAN策略一致的方式中包含C函数foo并共享该功能bar.

这样做的首选方法是什么,我应该如何使用函数注册和动态库?

我的问题的目的是,即使我阅读了我能找到的所有文件,但我没有发生任何明显的事情,我不确定最可持续的行动方案是什么.

例:

假设在一个包中foo,我定义了一个C函数addinc,它增加了两个数字.

#include <R.h>
#include <Rinternals.h>

SEXP addinc(SEXP x_, SEXP y_) {
  double x = asReal(x_);
  double y = asReal(y_);

  double sum = x + y;

  return ScalarReal(sum);
}
Run Code Online (Sandbox Code Playgroud)

在同一个包中,我可以尝试调用通过该接口addinc命名的R函数.addinr.Call

addinr <- function(x,y){
  .Call("addinc", x, y, PACKAGE="foo")
}
Run Code Online (Sandbox Code Playgroud)

但是,在构建,检查和安装软件包时,运行会addinr返回下面的错误,可能是因为该函数尚未在R中注册.

library(foo)
addinr(1,2)
Run Code Online (Sandbox Code Playgroud)

.Call中的错误("addinc",x,y,PACKAGE ="foo"):
"addinc"对于包"foo"的.Call()不可用

在我看来,解决这个问题的最简单方法是通过添加useDynLib(foo)fooNAMESPACE文件为编译代码构建动态库.这似乎解决了这个问题,因为我现在可以addinr()毫无问题地打电话.而且,我可以.Call("addinc", ..., PACKAGE="foo")直接在R内运行.

然而,我的真正问题是,当第二个包bar应该使用foos时addinc.例如,假设bar定义一个函数multiplyinr如下.

multiplyinr <- function(x,y){
  ans <- 0
  for(i in 1:y) ans <- .Call("addinc", ans, x, PACKAGE="foo")
  ans
}
Run Code Online (Sandbox Code Playgroud)

事实上,这完全正常,我可以multiplyinr在R中调用.但是,在构建和检查时bar,我收到一个注释,抱怨bar从不同的包调用外语函数的事实.

外部函数调用另一个包:
.Call("addinc",...,PACKAGE ="foo")
请参阅"Writing R Extensions"手册中的"系统和外语接口"一章.

根据这个问题,该软件包bar不适合提交给CRAN,因为使用.Call()这种方式不像" 写入R扩展"手册中所解释的那样被认为是"可移植的" .

总之,在其NAMESPACE文件中foo包含a 的简单解决方案useDynLib(foo)似乎并没有削减它.因此我的问题是:与其他软件包共享C函数的首选方法是什么?

此外:

使用useDynLib()真正危险还是与CRAN政策不一致?useDynLib()在NAMESPACE文件中声明作为手动注册和构建共享库的替代方法的目的是什么?

手动注册C函数和扩展共享库会改变任何东西(即使用R_RegisterCCallable()R_registerRoutines())吗?

Kev*_*hey 6

一般的想法是,使用eg创建的"本机符号信息"对象useDynLib(<pkg>, <symbol>)不是包的公共API的一部分,因此客户端包不应该直接调用它们(假设它们可以在包的未来版本中更改) ).

有两种方法可以"导出"已编译的例程以供客户端软件包使用:

  1. 只需导出一个R包装函数,foo直接调用本机例程,或者
  2. 使用R_RegisterCCallable()/ R_GetCCallable()pair函数获取指向所需函数的指针.(包foo将调用R_RegisterCCallable()以使某些功能可用;客户端包bar将调用R_GetCCallable()以获取指向该函数的指针)

换句话说,如果包作者'注册'他们的C函数,他们声明它们是其包的公共C API的一部分,并允许客户端包通过该接口使用/调用它.