R与C的调用接口有好几种,以前介绍过的.C形式是一种,此外还有.Call形式和.External形式。.Call使用最为广泛,功能也比.C形式大很多,因为使用它可以直接操作R的数据结构,可以实现在C/C++中操作R的数据及调用R的函数,然后封装为包供R使用。
R的所有数据类型(无论数据或类或函数)都是对象,都统一定义为结构SEXPREC,由指针SEXP(不要想歪,我也不明白作者为何要取这个名字)所指向。这个结构在Rinternal.h中定义为:
typedef struct SEXPREC {
SEXPREC_HEADER;
union {
struct primsxp_struct primsxp;
struct symsxp_struct symsxp;
struct listsxp_struct listsxp;
struct envsxp_struct envsxp;
struct closxp_struct closxp;
struct promsxp_struct promsxp;
} u;
} SEXPREC, *SEXP;
它包含一个HEADER及一个UNION,HEADER又是由如下宏定义:
#define SEXPREC_HEADER \
struct sxpinfo_struct sxpinfo; \
struct SEXPREC *attrib; \
struct SEXPREC *gengc_next_node, *gengc_prev_node
它包含一个32位的sxpinfo的信息头,一个指向属性的指针,两个指向前后节点的指针构成双向链表。
sxpinfo这个信息头的定义如下:
struct sxpinfo_struct {
SEXPTYPE type : 5;
unsigned int obj : 1;
unsigned int named : 2;
unsigned int gp : 16;
unsigned int mark : 1;
unsigned int debug : 1;
unsigned int trace : 1; /* functions and memory tracing */
unsigned int spare : 1; /* currently unused */
unsigned int gcgen : 1; /* old generation number */
unsigned int gccls : 3; /* node class */
}; /* Tot: 32 */
简单说几个主要字段的作用:type表示数据类型,如数字型、字符型、列表型等等;named控制引用时拷贝与否;mark为垃圾回收所用(GC)。
使用.C调用方式来做R的扩展是最简单的方式,因为不需要理会SEXP这些复杂的R结构,只需要把数据拆解为一个个向量传递给C函数即可,详情参见我以前的描述。如果.C可以实现的功能,倒不必要一定要折腾到.Call,因为后者要牵涉到很多的R API,这些API还都没有文档说明其作用及调用方式,只能根据<Writing R Extensions>以及参考一些包的用法来猜测着使用。不过好处是.Call的功能更丰富而且稳定,.C方式的指针纵横交错,极易使程序崩溃。
这里先用官方文档里的一个例子拿出来加以解说,阐述个大体流程,以后实践深入再结合一些经验来谈谈技巧。
在C里处理R对象,以下是一个计算外积的例子:
在C文件中的源代码为:
#include
#include
SEXP out(SEXP x, SEXP y) // 无论参数,还是返回值,都要定义为SEXP指针类型
{
int i, j, nx, ny;
double tmp, *rx = REAL(x), *ry = REAL(y), *rans; // REAL函数取得SEXP结构中的数据指针(如果是整型,则用INTEGER),以方便对数据的操作
SEXP ans;
nx = length(x); ny = length(y);
PROTECT(ans = allocMatrix(REALSXP, nx, ny)); // 分配空间,返回一个SEXP指向的对象,REALSXP是指实数类型,这里生成一个实数矩阵。PROTECT保护该内存不被垃圾回归机制回收。
rans = REAL(ans);
for(i = 0; i < nx; i++) {
tmp = rx[i];
for(j = 0; j < ny; j++)
rans[i + nx*j] = tmp * ry[j];
}
UNPROTECT(1); // 放弃对以上PROTECT声明内存段的保护,参数1指示放弃1次,次数必须跟上面使用PROTECT次数一致。
return(ans);
}
假如文件存储为out.c,则用R CMD SHLIB out.c命令可以把源代码编译为out.so共享库。然后在R里使用如下代码即可使用:
a=1:3
b=4:6
dyn.load("out.so")
.Call("out", a, b)