在R中存储C对象

Chr*_*ois 18 c r

在R Extensions手册中,我找到了有关从C访问R对象的信息.但是,在我的情况下,我正在使用其他人的C代码,它具有专门的数据结构(称之为Foo).我的目标是让R函数:

  • 初始化一个Foo对象.我不想在R的列表或矩阵中存储其中一个.
  • 更新Foo对象.我不想重新创建Foo对象来执行此操作,而是在适当的位置修改它.此外,此功能可能会返回有关更新是否成功的信息.
  • 从内存中删除Foo对象.

换句话说,我想在R环境中保留一个C对象,使用R函数(由C函数支持)来创建,修改和删除它.

提前感谢任何建议.

Mar*_*gan 30

如上所述,想法是使用外部指针."写入R扩展"手册中对此进行了描述.这是一个简单的例子.

包括相关的R头

#include <Rdefines.h>
Run Code Online (Sandbox Code Playgroud)

我们将创建一个可以容纳长度最多为15的C char*的对象

const int N_MAX=15;
Run Code Online (Sandbox Code Playgroud)

当外部指针不再由任何R对象表示时,外部指针就像一个终结器.我们在这里安全地玩它,在释放之前检查指针地址是否有效(使用Free,因为我们将使用Calloc进行分配 - 这些是C级内存分配函数,这些函数在调用C时持续存在,与R_alloc不同)并清除指针表示它已经完成.

static void
_finalizer(SEXP ext)
{
    if (NULL == R_ExternalPtrAddr(ext))
        return;
    Rprintf("finalizing\n");
    char *ptr = (char *) R_ExternalPtrAddr(ext);
    Free(ptr);
    R_ClearExternalPtr(ext);
}
Run Code Online (Sandbox Code Playgroud)

这是我们的构造函数.它需要一些R'信息'它随身携带(在本例中稍后不再使用)然后为短字符串分配一些内存.我们用x和info构造一个外部指针(R_NilValue是一个'tag',按照惯例我们可以用来标记我们的对象 - mkString("MyCObject")或类似的).我们将终结器与外部指针相关联.PROTECT/UNPROTECT用于防止通过调用R_RegisterCFinalizerEx触发垃圾收集器.当R分配内存时,可以调用垃圾收集器; 很难知道这种情况何时发生(我们可以跟踪代码流),所以我们安全地播放它并在创建它时将外部指针添加到PROTECT.

SEXP
create(SEXP info)
{
    char *x = Calloc(N_MAX, char);
    snprintf(x, N_MAX, "my name is joe");
    SEXP ext = PROTECT(R_MakeExternalPtr(x, R_NilValue, info));
    R_RegisterCFinalizerEx(ext, _finalizer, TRUE);
    UNPROTECT(1);

    return ext;
}
Run Code Online (Sandbox Code Playgroud)

这是getter,只是引用外部指针地址并将其作为R字符返回(1)

SEXP
get(SEXP ext)
{
    return mkString((char *) R_ExternalPtrAddr(ext));
}
Run Code Online (Sandbox Code Playgroud)

以及获取外部指针和字符(1)的setter,将str的第一个元素的C表示复制到我们的对象中.我们返回逻辑(1),但可以返回任何内容.

SEXP
set(SEXP ext, SEXP str)
{
    char *x = (char *) R_ExternalPtrAddr(ext);
    snprintf(x, N_MAX, CHAR(STRING_ELT(str, 0)));
    return ScalarLogical(TRUE);
}
Run Code Online (Sandbox Code Playgroud)

如果这是在文件tmp.c中,我们编译

R CMD SHLIB tmp.c
Run Code Online (Sandbox Code Playgroud)

或者将其作为文件集成在一个包中src/tmp.c并正常构建包.使用:

> dyn.load("tmp.so")
> x <- .Call("create", list("info could be any R object", 1:5))
> .Call("get", x)
[1] "my name is joe"
> ## reference semantics!
> .Call("set", x, "i am sam i am")
[1] TRUE
> .Call("get", x)
[1] "i am sam i am"
> x <- NULL
> gc()
finalizing
         used (Mb) gc trigger (Mb) max used (Mb)
Ncells 339306 18.2     467875   25   407500 21.8
Vcells 202064  1.6     786432    6   380515  3.0
Run Code Online (Sandbox Code Playgroud)

这是第二个示例,其中包含一个增加的int的结构.

#include <Rdefines.h>

struct Foo {
    int x;
};

static void
_finalizer(SEXP ext)
{
    struct Foo *ptr = (struct Foo*) R_ExternalPtrAddr(ext);
    Free(ptr);
}

SEXP
create()
{
    struct Foo *foo = Calloc(1, struct Foo);
    foo->x = 0;
    SEXP ext = PROTECT(R_MakeExternalPtr(foo, R_NilValue, R_NilValue));
    R_RegisterCFinalizerEx(ext, _finalizer, TRUE);
    UNPROTECT(1);

    return ext;
}

SEXP
incr(SEXP ext)
{
    struct Foo *foo = (struct Foo*) R_ExternalPtrAddr(ext);
    foo->x += 1;
    return ScalarInteger(foo->x);
}
Run Code Online (Sandbox Code Playgroud)

  • 这是一个非常有价值的答案.这样一个清晰,完整的例子应该是"Writing R Extensions"手册的一部分. (6认同)
  • @Martin Morgan,你的 stackoverflow 答案总是绝对令人惊叹。对我来说,这些可能是人们可以在核心 R 语言上找到的最有价值的答案。你应该写一本书。我是一个粉丝! (2认同)