Calling R from C

Introduction

Calling an R function from C involves:

  • Creating a special list of function name and arguments
  • Optionally setting the argument names with SET_TAG()
  • Calling eval()

Creating the special list for the call can be done using the helper methods lang1() to lang6(), or for R 4.4.1 or later allocLang() can provide more flexibility if needed.

lang1() - Calling an R function with no arguments

lang1() is for assembling a call to an R function without specifying any arguments.

In this example getwd() is called from C using lang1()

#include <R.h>
#include <Rinternals.h>

SEXP call_r_from_c_with_lang1(void) {

  SEXP my_call = PROTECT(lang1(
    PROTECT(Rf_install("getwd"))
  ));

  SEXP my_result = PROTECT(eval(my_call, R_GlobalEnv)); 

  UNPROTECT(3);
  return my_result;
}
Click to show R code
code = r"(
#include <R.h>
#include <Rinternals.h>

SEXP call_r_from_c_with_lang1(void) {

  SEXP my_call = PROTECT(lang1(
    PROTECT(Rf_install("getwd"))
  ));

  SEXP my_result = PROTECT(eval(my_call, R_GlobalEnv)); 

  UNPROTECT(3);
  return my_result;
}
)"

callme::compile(code)
call_r_from_c_with_lang1()
#> [1] "/tmp/Rtmpu9f27Z/Rbuild7d1472f4c24/callme/vignettes"

lang2() - Calling an R function with a single unnamed argument

lang2() is for assembling a call to an R function and specifying a single argument.

In this example abs(-1) is called from C using lang2()

#include <R.h>
#include <Rinternals.h>

SEXP call_r_from_c_with_lang2(void) {

  SEXP val = PROTECT(ScalarReal(-1));
  SEXP my_call = PROTECT(lang2(
    PROTECT(Rf_install("abs")),
    val
  ));

  SEXP my_result = PROTECT(eval(my_call, R_GlobalEnv)); 

  UNPROTECT(4);
  return my_result;
}
Click to show R code
code = r"(
#include <R.h>
#include <Rinternals.h>

SEXP call_r_from_c_with_lang2(void) {

  SEXP val = PROTECT(ScalarReal(-1));
  SEXP my_call = PROTECT(lang2(
    PROTECT(Rf_install("abs")),
    val
  ));

  SEXP my_result = PROTECT(eval(my_call, R_GlobalEnv)); 

  UNPROTECT(4);
  return my_result;
}
)"

callme::compile(code)
call_r_from_c_with_lang2()
#> [1] 1

lang2() - Calling an R function with a single named argument

In this example tempfile(fileext = ".txt") is called from C using lang2()

#include <R.h>
#include <Rinternals.h>

SEXP call_r_from_c_with_lang2_named(void) {
 
  // Assemble the function + argument with name
  SEXP val = PROTECT(mkString(".txt"));
  SEXP my_call = PROTECT(lang2(
    PROTECT(Rf_install("tempfile")),
    val
  ));

  // Set the argument name
  SEXP t = CDR(my_call);
  SET_TAG(t, Rf_install("fileext"));
  
  // Evaluate the call
  SEXP my_result = PROTECT(eval(my_call, R_GlobalEnv)); 

  UNPROTECT(4);
  return my_result;
}
Click to show R code
code = r"(
#include <R.h>
#include <Rinternals.h>

SEXP call_r_from_c_with_lang2_named(void) {
 
  // Assemble the function + argument with name
  SEXP val = PROTECT(mkString(".txt"));
  SEXP my_call = PROTECT(lang2(
    PROTECT(Rf_install("tempfile")),
    val
  ));

  // Set the argument name
  SEXP t = CDR(my_call);
  SET_TAG(t, Rf_install("fileext"));
  
  // Evaluate the call
  SEXP my_result = PROTECT(eval(my_call, R_GlobalEnv)); 

  UNPROTECT(4);
  return my_result;
}
)"

callme::compile(code)
call_r_from_c_with_lang2_named()
#> [1] "/tmp/RtmpLMIOxS/file8873ad2005d.txt"

Using allocLang() (R >= 4.4.1 only)

The C code is equivalent to this R code:

print(pi , digits = 3)
#include <R.h>
#include <Rinternals.h>

SEXP call_print_from_c(SEXP value, SEXP digits) {
  // Allocate a new call object
  SEXP my_call = PROTECT(allocLang(3));

  // Manipulate a pointer to this call object to 
  // fill in the arguments and set argument names 
  SEXP t = my_call;
  SETCAR(t, install("print")); t = CDR(t);
  SETCAR(t,  value)          ; t = CDR(t);
  SETCAR(t, digits);
  SET_TAG(t, install("digits"));
  eval(my_call, R_GlobalEnv);

  UNPROTECT(1);
  return R_NilValue;
}
Click to show R code
code = r"(
#include <R.h>
#include <Rinternals.h>

SEXP call_print_from_c(SEXP value, SEXP digits) {
  // Allocate a new call object
  SEXP my_call = PROTECT(allocLang(3));

  // Manipulate a pointer to this call object to 
  // fill in the arguments and set argument names 
  SEXP t = my_call;
  SETCAR(t, install("print")); t = CDR(t);
  SETCAR(t,  value)          ; t = CDR(t);
  SETCAR(t, digits);
  SET_TAG(t, install("digits"));
  eval(my_call, R_GlobalEnv);

  UNPROTECT(1);
  return R_NilValue;
}
)"

callme::compile(code)
call_print_from_c(pi, digits = 3)