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



假设我在R中有两个包,第一个命名为foo,第二个名为bar。我想在foo中包含 C 函数,并以独立于平台且符合 CRAN 策略的方式与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);
}

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

addinr <- function(x,y){
  .Call("addinc", x, y, PACKAGE="foo")
}

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

library(foo)
addinr(1,2)

中的错误。Call("addinc", x, y, PACKAGE = "foo"( :
"addinc"不适用于 。Call(( for package "foo">

在我看来,解决此问题的最简单方法是通过将useDynLib(foo)添加到 foo 的 NAMESPACE 文件中来为编译的代码构建一个动态库。这似乎可以解决问题,因为我现在可以毫无问题地拨打addinr()。此外,我可以直接从 R 中运行.Call("addinc", ..., PACKAGE="foo")

然而,我真正的问题发生在第二个包(比如bar(应该使用foo addinc时。例如,假设bar定义了一个函数multiplyinr如下所示。

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

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

对不同包的外部函数调用:
.Call("addinc", ..., PACKAGE = "foo"(
请参阅"编写 R 扩展"手册中的"系统和外语界面"一章。

根据此问题,包bar不适合提交给 CRAN,因为以这种方式使用 .Call() 不被视为"可移植",如编写 R 扩展手册中所述。

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

此外:

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

手动注册 C 函数并构建共享库是否会改变任何东西(即使用 R_RegisterCCallable()R_registerRoutines()(?

一般的想法是,通过使用例如 useDynLib(<pkg>, <symbol>)不是包的公共 API 的一部分,因此客户端包不应直接调用它们(假设它们可以在包的未来修订版中更改(。

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

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

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

相关内容

最新更新