Appel d'une fonction R dans un programme C

Auteur.e.s
Annie Bouvier, Inra
Résumé

Ce document contient des exemples de programmes C qui appellent des fonctions R, soit des fonctions du système R, soit des fonctions écrites par l’utilisateur. On montre aussi comment accéder à des structures de données R au sein d’un programme C. Dans tous les cas, le programme C est lui-même appelé depuis R.

Plus précisément, sont illustrés ici:

  • l’appel, dans un programme C, d’une fonction de R;
  • l’appel, dans un programme C, d’une fonction R écrite par l’utilisateur;
  • l’accès, dans un programme C, aux attributs d’une structure R.

Notations: Le terme “programme” est ici utilisé pour désigner un programme ou une “subroutine” C. On réserve le terme “fonction” pour ce qui est du code R.

Appel, dans un programme C, d’une fonction R, celle-ci étant passée en argument

Dans l’exemple suivant, un programme C appelle une fonction de R pour générer nobs nombres aléatoires. La fonction R est rnorm() ou runif(), selon la valeur de l’argument fct. Ces fonctions ont toutes deux pour argument le nombre de nombres aléatoires voulus, et retournent un vecteur de cette longueur.

NB: il s’agit d’un exemple pour illustration. En pratique, pour générer des nombres aléatoires dans un programme C, il est préférable d’utiliser les programmes de l’API R pour C, en l’occurrence unif_rand et norm_rand (voir la fiche Appel d’un programme interne à R dans un programme C).

  1. Le programme C, contenu dans le fichier calcrandom.c.
/*--------------- INCLUDES -----------------------*/
#include <Rmath.h>
#include <R.h>
#include <Rinternals.h>

void calcrandom(  int  *nobs,
       void *fct,  void *env,
      double *sortie)
{
/*--------------------------------------------------
ENTREES:
  nobs: le nombre de nombres aléatoires
  fct: la fonction R de génération de nombres aléatoires
     valeurs valides: rnorm, runif
  env: l'environnement d'exécution de la fonction R
SORTIE:
  sortie: vecteur des nobs nombres aléatoires
--------------------------------------------------*/

  SEXP rho, f, args,resultsxp, callsxp;

  /* Mettre la fonction R et son environnement dans des variables de type SEXP */
  f= (SEXP)fct;
  rho=(SEXP)env;

//Vérification des entrées
//  Rprintf("nobs=%d\n", *nobs); //Vérification des entrées
  
 /* Allouer l'argument d'entrée de la fonction R */
  PROTECT(args=allocVector(REALSXP, (1)));
/* Affecter nobs à l'argument d'entrée */
  REAL(args)[0]=*nobs;
/* Construction d'une liste d'appel:
le 1ier élément est la fonction, le 2ième est l'argument d'entrée */
  PROTECT(callsxp=lang2( f, args));
/* Evaluation de la fonction */
  PROTECT(resultsxp=eval(callsxp,rho));
/* Récupération du retour de la fonction */
   for (int i=0; i < (*nobs); i++) {
     sortie[i]= REAL(resultsxp)[i];
   }
  UNPROTECT(3); // Autant que de PROTECT
} /* fin calcrandom */     

Les instructions PROTECT protègent l’allocation mémoire contre les effets du “garbage collector” de R.

  1. Compilation/édition de liens
R CMD SHLIB  calcrandom.c
  1. Utilisation
dyn.load("calcrandom.so")  #chargement de la librairie
nobs <- 10
sortie <- rep(0, nobs)  #allocation des sorties
resrnorm <- .C("calcrandom", as.integer(nobs), rnorm, new.env(), sortie = as.double(sortie))$sortie
print(resrnorm)
[1]  0.36018620  1.45796202 -0.19825620 -1.98185504 -0.61853963  0.91102390
[7]  0.66668466  0.01637709  0.11726470 -0.21034171
resrunif <- .C("calcrandom", as.integer(nobs), runif, new.env(), sortie = as.double(sortie))$sortie
print(resrunif)
[1] 0.4238203 0.3551720 0.8804349 0.9978546 0.9815494 0.4427076 0.9379984
[8] 0.2909782 0.8107538 0.1486810

Appel d’une fonction R de l’utilisateur dans un programme C, la fonction R étant passée en argument

Cet exemple montre comment appeler une fonction R définie par l’utilisateur depuis le C. Plusieurs configurations sont successivement présentées ici, selon le nombre et le type des entrées-sorties de la fonction R de l’utilisateur.

La fonction R a un argument d’entrée vectoriel et retourne un vecteur

La fonction R de l’utilisateur. Cette fonction, volontairement très simple, retourne le vecteur composé des éléments positifs du vecteur en entrée:

h <- function(x) {
    return(x[x > 0])
}

Le programme C. Le programme C reçoit en entrée un vecteur, x, la fonction R, fct(), et son environnement d'exécution,env. Il fait exécuter la fonction et met son retour dans l'argument de sortiesortie. Il détermine la longueur de celui-ci et la met dans l'argument de sortielsortie.

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

void Rg(double *x, int *nobs,
        void **fct,void *env,
    int *lsortie, double *sortie) {
  SEXP f, rho, args, resultsxpg, s;
  int i;

  /*  Allocation de l'argument d'entrée de la fonction R */
   PROTECT(args=allocVector(REALSXP, ( *nobs )));
   /*  Allocation de l'argument de sortie */
   PROTECT(resultsxpg=allocVector(REALSXP,( *nobs )));
  /* Affecter les valeurs des arguments d'entrée */
  for (i =0; i<*nobs; i++) 
    REAL(args)[i] = x[i];

/* Mettre la fonction R et son environnement dans des variables de
  type SEXP */
  f=(SEXP)fct;
  rho=(SEXP)env;

 /* Appel de la fonction R.
Construction d'une liste à 2  éléments.
Le 1ier est la fonction R, le suivant est l'argument d'entrée */
 PROTECT(s=lang2( f, args));
   /* Evaluation de la fonction R */
 PROTECT(resultsxpg=eval(s,rho));

 /* Récupération du résultat : length donne la longueur de celui-ci */
 *lsortie = length(resultsxpg);
for (int i =0; i< (*lsortie); i++) 
   sortie[i]= REAL(resultsxpg)[i];
 /* Autant de UNPROTECT qu'il y a eu de PROTECT */
 UNPROTECT(4); 
} // fin Rg

La fonction R qui appelle le C. Le programme C est appelé par une fonction R, dont les arguments d’entrée sont la longueur du vecteur et la fonction de l’utilisateur. Son retour est le retour de la fonction de l’utilisateur.

g <- function(nobs, fct) {
    # Calculer les entrées:
    x <- rnorm(nobs)
    print(x)
    
    # Allouer les sorties
    sortie <- rep(0, nobs)
    lsortie <- 0
    
    # Appeler le C
    ret <- .C("Rg", as.double(x), as.integer(nobs), fct, new.env(), lsortie = as.integer(lsortie), 
        sortie = as.double(sortie))
    return(ret$sortie[1:ret$lsortie])
}  # fin g

Compilation/édition de liens

R CMD SHLIB  Rg.c

Utilisation

# Chargement de la librairie et des fonctions
dyn.load("Rg.so")
source("g.R")
source("h.R")
print(g(10, h))
[1] -0.31004284 -0.73359363  0.60183074  0.30529793  0.03692354 -0.61471173
[7] -2.22724649  0.98960815 -1.84608152 -2.09573926
[1] 0.60183074 0.30529793 0.03692354 0.98960815

La fonction R de l’utilisateur a 2 arguments d’entrée, un vecteur et un scalaire, plus, éventuellement, des arguments additionnels passés par .... Son retour est un scalaire.

La fonction R de l’utilisateur. Exemple d’une fonction sans arguments additionnels:

hs <- function(arg, phase) {
    ff <- sin(arg[1]) * cos(arg[2]) * exp(arg[3]) * phase
    return(ff)
}  # fin hs

Exemple d’une fonction avec arguments additionnels :

ha <- function(arg, phase, ...) {
    ff <- sin(arg[1]) * cos(arg[2]) * exp(arg[3]) * phase
    print(ff, ...)
    return(ff)
}  # fin ha

Le programme C

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

void Rg2(int *nobs, int *phase, double *xx,
             void ** fct,void *env, double *valuef)
 {
  SEXP f, rho, args, argphase, s, t, resultsxpf;
  int i;

  /*  Allocation des arguments d'entrée de la fonction R */
   PROTECT(args=allocVector(REALSXP, ( *nobs )));
   PROTECT(argphase=allocVector(REALSXP, (1 )));
   /*  Allocation de l'argument de sortie */
   PROTECT(resultsxpf=allocVector(REALSXP, 1));
  /* Affecter les valeurs des arguments d'entrée */
  for (i =0; i<*nobs; i++) 
    REAL(args)[i] = xx[i];
  REAL(argphase)[ 0] = *phase;
 
  /* Mettre la fonction R et son environnement dans des variables de
  type SEXP */
  f=(SEXP)fct;
  rho=(SEXP)env;
  /* Construction de la liste d'appel.
Celle-ci comporte 3 éléments: la fonction R et les 2 arguments.
Il n'y a pas d'équivalent à "lang2" pour cela: on alloue la
liste, et on affecte ses éléments un par un */
   PROTECT(t = s = allocList(3));
   SET_TYPEOF(s, LANGSXP);
   SETCAR(t, f); t = CDR(t);
   SETCAR(t,  args); t = CDR(t);
   SETCAR(t, argphase); 
   /* Evaluation de la fonction R */
 PROTECT(resultsxpf=eval(s,rho));
 /* Récupération du résultat */
 *valuef= REAL(resultsxpf)[0];
 /* Autant de UNPROTECT qu'il y a eu de PROTECT */
 UNPROTECT(5); 
} // fin Rg2

La fonction R qui appelle le C

g2 <- function(nobs, f, ..., phase = 1) {
    # Alias pour tenir compte des arguments optionnels de f
    fff <- if (length(list(...)) && length(formals(f)) > 2) 
        function(x, phase) f(x, phase, ...) else f
    # Calculer les entrées:
    x <- rnorm(nobs)
    
    # Allouer les sorties
    sortie <- 0
    ret <- .C("Rg2", as.integer(nobs), as.integer(phase), as.double(x), 
        fff, new.env(), sortie = as.double(sortie))$sortie
    return(ret)
}  # fin g2

Compilation/édition de liens

R CMD SHLIB  Rg2.c

Utilisation

dyn.load("Rg2.so")
source("g2.R")
source("hs.R")
source("ha.R")
nobs <- 10
print(g2(nobs, hs))
[1] 0.1922205
print(g2(nobs, hs, phase = 2))
[1] -1.119422
print(g2(nobs, ha, phase = 2, digits = 5))
[1] -0.96194
[1] -0.961936

La fonction R de l’utilisateur a un argument d’entrée vectoriel et retourne une liste

La fonction R de l’utilisateur. La fonction R a un argument vectoriel, x, et renvoie une liste à deux composants: le premier composant est le vecteur composé des éléments positifs de x, et le deuxième composant est le vecteur de ses éléments négatifs.

hl <- function(x) {
    return(list(sortiep = x[x > 0], sortien = x[x < 0]))
}

Le programme C

#include <R.h>
#include <Rinternals.h> 
void Rgl(double *x, int *nobs,
        void **fct,void *env,
     int *lsortiep, double *sortiep,
     int *lsortien, double *sortien) {
  SEXP f, rho, args,  s,resultsxpg;
  int i;

  /* Verification des entrées */
  Rprintf("Verification des entrées dans le C\n");
  Rprintf("nobs=%d\n", *nobs);
  Rprintf("X[0]=%g X[%d]=%g\n", (*nobs-1), x[0], x[*nobs-1]);

  /*  Allocation de l'argument d'entrée de la fonction R */
   PROTECT(args=allocVector(REALSXP, ( *nobs )));
  /* Affecter les valeurs des arguments d'entrée */
  for (i =0; i<*nobs; i++) 
    REAL(args)[i] = x[i];
   /*  Allocation de l'argument de sortie:
la liste en sortie comprend 2 vecteurs, chacun d'une
longueur maximale nobs */
   PROTECT(resultsxpg=allocVector(REALSXP, 2*(*nobs)));
/* Mettre la fonction R et son environnement dans des variables de
  type SEXP */
  rho= (SEXP)(env);
  f= (SEXP)(fct);
 /* Appel de la fonction R.
Construction d'une liste à 2  éléments.
Le 1ier est la fonction R, le suivant est l'argument d'entrée */
 PROTECT(s=lang2( f, args));
   /* Evaluation de la fonction R */
 PROTECT(resultsxpg=eval(s,rho));
 /* Récupération du résultat: length donne le nombre de composants,
ici 2 */
 printf("Nombre de composants de la liste=%d\n", length(resultsxpg));
 /* Récupération des composants */
 *lsortiep= length(VECTOR_ELT(resultsxpg, 0));
 *lsortien= length(VECTOR_ELT(resultsxpg, 1));
 for (i =0; i< (*lsortiep); i++) 
   sortiep[i]= REAL(VECTOR_ELT(resultsxpg,0))[i];
 for (i =0; i< (*lsortien); i++) 
   sortien[i]= REAL(VECTOR_ELT(resultsxpg,1))[i];
 /* Autant de UNPROTECT qu'il y a eu de PROTECT */
 UNPROTECT(4); 
} // fin Rgl

La fonction R qui appelle le C

gl <- function(nobs, fct) {
    # Calculer les entrées: n'importe quoi dans cet exemple
    x <- rnorm(nobs)
    cat("Les entrées générées\n")
    print(x)
    
    # Allouer les sorties
    sortiep <- sortien <- rep(0, nobs)
    lsortiep <- lsortien <- 0
    
    # Appeler le C
    ret <- .C("Rgl", as.double(x), as.integer(nobs), fct, new.env(), lsortiep = as.integer(lsortiep), 
        sortiep = as.double(sortiep), lsortien = as.integer(lsortien), 
        sortien = as.double(sortien))
    return(list(sortiep = ret$sortiep[1:ret$lsortiep], sortien = ret$sortien[1:ret$lsortien]))
}  # fin gl

Compilation/édition de liens

R CMD SHLIB  Rgl.c

Utilisation

dyn.load("Rgl.so")
source("gl.R"); source("hl.R")
print(gl(10, hl)

Les entrées générées

 [1]  1.31183343  0.76683794 -0.04040777 -0.81997709  1.87882936  0.67823536
 [7]  0.95888482 -1.32211071 -0.16324265 -0.88874762

Vérification des entrées dans le C

nobs=10
X[0]=1.31183 X[9]=-0.888748
Nombre de composants de la liste=2
$sortiep
[1] 1.3118334 0.7668379 1.8788294 0.6782354 0.9588848

$sortien
[1] -0.04040777 -0.81997709 -1.32211071 -0.16324265 -0.88874762

Dans les exemples précédents, nous avons utilisé la fonction length() qui renvoie la longueur totale d’une structure.

Quelques autres exemples utiles :

  • Récupérer les dimensions d’une matrice:
nlignes=INTEGER(getAttrib(resultsxp, R_DimSymbol))[0];
ncolonnes=INTEGER(getAttrib(resultsxp, R_DimSymbol))[1];
  • Affecter des labels de colonnes à une matrice. Dans cet exemple, la matrice a 2 colonnes:
... 
SEXP dimnames, elmt;
// Les labels d'une matrice sont contenus dans une liste, dimnames, 
// à 2 composants. Allocation de la liste:
PROTECT(dimnames = allocVector(VECSXP, 2));
// Création du vecteur qui contiendra les labels des 2 colonnes.
 PROTECT( elmt = allocVector(STRSXP, 2));
 // Affectation des labels
 SET_STRING_ELT( elmt,0, mkChar("lower"));
 SET_STRING_ELT( elmt,1, mkChar("upper"));
 // Affectation du vecteur au 2ieme composant de dimnames,
// celui qui correspond aux labels des colonnes
 SET_VECTOR_ELT(dimnames, 1, elmt);
 // Affectation de dimnames a la matrice
setAttrib(mat, R_DimNamesSymbol, dimnames);
...
UNPROTECT();

Références

La documentation de R Writing Extensions.

Versions des outils utilisés
R version 3.4.2 (2017-09-28)
Thèmes de la fiche
Thèmes