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:
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.
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.
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.
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).
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With