Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Storing C objects in R

Tags:

c

r

In the R Extensions manual, I found information about accessing R objects from C. In my situation, however, I am working with somebody else's C code which has a specialized data structure (call it Foo). My goal is to have R functions that:

  • initialize a Foo object. I don't want to have to store one of these in terms of R's lists or matrices.
  • update a Foo object. I don't want to have to recreate the Foo object to do this, but rather modify it in place. Also, this function might return information about whether the update was successful.
  • remove the Foo object from memory.

In other words, I would like to keep around a C object in an R environment, using R functions (backed by C functions) to create it, modify it, and delete it.

Thanks in advance for any advice.

like image 861
Christopher DuBois Avatar asked Aug 11 '11 20:08

Christopher DuBois


People also ask

How do you store objects in R?

To save data as an RData object, use the save function. To save data as a RDS object, use the saveRDS function. In each case, the first argument should be the name of the R object you wish to save. You should then include a file argument that has the file name or file path you want to save the data set to.

Where are objects stored in R?

At the end of a session the objects in the global environment are usually kept in a single binary file in the working directory called . RData. When a new R session begins with this as the initial working directory, the objects are loaded back into memory.

What does save () do in R?

save() function writes an external representation of R objects to the specified file. The objects can be read back from the file at a later date by using the function load (or data in some cases).


1 Answers

As mentioned, the idea is to use an external pointer. This is described in the Writing R Extensions manual. Here's a quick example.

Include the relevant R header

#include <Rdefines.h>

We'll create an object that can hold a C char * of length at most 15

const int N_MAX=15;

The external pointer would like a finalizer, to be called when the external pointer is no longer represented by any R object. We play it safe here, checking that the pointer address is valid before freeing (with Free, because we'll allocate with Calloc -- these are C level memory allocation functions that persist across calls in to C, unlike R_alloc) and clearing the pointer to signal that it has already been finalized.

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);
}

Here's our constructor. It takes some R 'info' that it'll carry around with it (not used later in this example) then allocates some memory for a short string. We construct an external pointer with x and info (the R_NilValue is a 'tag' that by convention we could use to label our object -- mkString("MyCObject") or similar). We associate our finalizer with the external pointer. The PROTECT / UNPROTECT are to safeguard against the garbage collector being triggered by the call to R_RegisterCFinalizerEx. The garbage collector may be called when R allocates memory; it's hard to know when this occurs (we could trace the code flow), so we play it safe and add the external pointer to the PROTECT when we create it.

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;
}

Here's the getter, just referencing the external pointer address and returning it as an R character(1)

SEXP
get(SEXP ext)
{
    return mkString((char *) R_ExternalPtrAddr(ext));
}

and a setter that takes the external pointer and a character(1), copying the C representation of the first element of str into our object. We return a logical(1), but could return anything.

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

If this is in a file tmp.c, we compile with

R CMD SHLIB tmp.c

or integrate this in a package as a file src/tmp.c and build the package as normal. To use:

> 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

Here's a second example with a struct holding an int that is incremented.

#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);
}
like image 75
Martin Morgan Avatar answered Oct 08 '22 10:10

Martin Morgan