External pointers to C objects

knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
knitr::knit_engines$set(callme = callme:::callme_engine)
library(callme)

```{css, echo=FALSE} .callme { background-color: #E3F2FD; } pre.callme span { background-color: #E3F2FD; }

## Introduction

`External pointers` are a method for keeping a reference to a C 
object across multiple calls.

A common usecase is when a `struct` in C is used to keep context and this
context must be initialised once and then passed in to every subsequent
function call.

## Wrapping a C struct as an External Pointer

```{callme}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// The struct we will allocate and use in multiple calls
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typedef struct {
   double *a;
   int N;
} cdata_t;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Finalize struct - free all allocated memory and clear the pointer
// This will be called by R's garbage collected when the variable 
// falls out of scope
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void cdata_finalizer(SEXP cdata_) {
  Rprintf("cdata finalizer called to free the C pointer memory\n");

  cdata_t *cdata = R_ExternalPtrAddr(cdata_);
  if (cdata != NULL) {
    free(cdata->a);
    free(cdata);
    R_ClearExternalPtr(cdata_);
  }
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Allocate and initialise the struct by copying the floating point
// data in 'values' argument
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SEXP create_cdata(SEXP values) {
  int N = length(values);
  cdata_t *cdata = calloc(1, sizeof(cdata_t));
  if (cdata == NULL) {
    error("Couldn't allocate 'cdata'");
  }

  cdata->a = calloc(N, sizeof(double));
  if (cdata->a == NULL) {
    error("Couldn't allocate 'cdata->a'");
  }

  cdata->N = N;
  memcpy(cdata->a, REAL(values), N * sizeof(double));

  SEXP cdata_extptr = PROTECT(R_MakeExternalPtr(cdata, R_NilValue, R_NilValue));
  R_RegisterCFinalizer(cdata_extptr, cdata_finalizer);
  setAttrib(cdata_extptr, R_ClassSymbol, mkString("cdata_extptr"));

  UNPROTECT(1);
  return cdata_extptr;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Print the struct
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SEXP print_cdata(SEXP cdata_extptr) {
  if (!inherits(cdata_extptr, "cdata_extptr")) {
    error("Expecting 'cdata' to be an 'cdata_extptr' ExternalPtr");
  }

  cdata_t *cdata = TYPEOF(cdata_extptr) != EXTPTRSXP ? NULL : (cdata_t *)R_ExternalPtrAddr(cdata_extptr);
  if (cdata == NULL) {
    error("MyCStruct pointer is invalid/NULL");
  }

  for (int i = 0; i < cdata->N; i++) {
    Rprintf("%.2f ", cdata->a[i]);
  }
  Rprintf("\n");

  return R_NilValue;
}
cdata <- create_cdata(c(1, 2, pi))
cdata
print_cdata(cdata)


Try the callme package in your browser

Any scripts or data that you put into this service are public.

callme documentation built on April 4, 2025, 2:37 a.m.