假设我在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 的一部分,因此客户端包不应直接调用它们(假设它们可以在包的未来修订版中更改(。
有两种方法可以"导出"已编译的例程以供客户端包使用:
- 只需导出直接调用本机例程的 R 包装器函数
foo
,或者 - 使用
R_RegisterCCallable()
/R_GetCCallable()
对函数获取指向所需函数的指针。(包foo
将调用R_RegisterCCallable()
以使某些函数可用;客户端包bar
将调用R_GetCCallable()
以获取指向该函数的指针(
换句话说,如果包作者"注册"他们的 C 函数,他们声明它是其包的公共 C API 的一部分,并允许客户端包通过此接口使用/调用它。