Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the preferred method for sharing compiled C code in an R package and running it from another?

Tags:

c

package

r

Assume I have two packages in R, the first named foo, the second named bar. I want to include a C function in foo and share that functionality with bar in a way that is platform independent and consistent with the CRAN policies.

What is the preferred method of doing this, and how should I go about using function registration and dynamic libraries?

The purpose of my question is that, even though I read through all the documentation I could find, there is nothing that occured to me as the obvious thing to do, and I am unsure what the most sustainable course of action is.

Example:

Assume that in one package foo, I define a C function addinc which adds two numbers.

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

SEXP addinc(SEXP x_, SEXP y_) {
  double x = asReal(x_);
  double y = asReal(y_);

  double sum = x + y;

  return ScalarReal(sum);
}

In the same package, I can try calling addinc in an R function named addinr via the .Call interface.

addinr <- function(x,y){
  .Call("addinc", x, y, PACKAGE="foo")
}

However, when building, checking and installing the package, running addinr returns the error below, presumably because the function is not yet registered within R.

library(foo)
addinr(1,2)

Error in .Call("addinc", x, y, PACKAGE = "foo") :
"addinc" not available for .Call() for package "foo"

As it seems to me, the easiest way to solve this is to build a dynamic library for the compiled code by adding useDynLib(foo) to foos NAMESPACE file. This appears to solve the problem because I can now call addinr() without problems. Moreover, I can run .Call("addinc", ..., PACKAGE="foo") directly from within R.

My real question, however, occurs when a second package, say bar, is supposed to use foos addinc. For example, assume that bar defines a function multiplyinr as follows.

multiplyinr <- function(x,y){
  ans <- 0
  for(i in 1:y) ans <- .Call("addinc", ans, x, PACKAGE="foo")
  ans
}

This, in fact, works completely fine, and I can call multiplyinr within R. However, when building and checking bar, I receive a Note complaining about the fact that bar is calling foreign language functions from a different package.

Foreign function call to a different package:
.Call("addinc", ..., PACKAGE = "foo")
See chapter ‘System and foreign language interfaces’ in the ‘Writing R Extensions’ manual.

According to this question, the package bar would not be suitable for submission to CRAN because using .Call() in this way is not considered "portable" as explained in the Writing R Extensions manual.

In conclusion, the simple solution of having foo include a useDynLib(foo) in its NAMESPACE file does not quite seem to cut it. Therefore my question: What is the preferred method to share a C function with other packages?

Moreover:

Is using useDynLib() truly dangerous or inconsistent with the CRAN policies? What is the purpose of declaring useDynLib() in the NAMESPACE file as an alternative to registering and building the shared library manually?

Would manually registering the C function and bulding the shared library change anything (i.e., using R_RegisterCCallable() or R_registerRoutines())?

like image 639
SimonG Avatar asked Jan 30 '16 15:01

SimonG


People also ask

How do you call C code in R?

The most basic method for calling C code from R is to use the . C() function described in the System and foreign language interfaces section of the Writing R Extensions manual. Other methods exist including the . Call() and .

Can R code be compiled?

In fact, you can take any R script and compile it into a report that includes commentary, source code, and script output. Reports can be compiled to any output format including HTML, PDF, MS Word, and Markdown. The first call to render creates an HTML document, whereas the second creates a PDF document.

What is a namespace in R?

Namespaces allow the package writer to hide functions and data that are meant only for internal use, Namespaces prevent functions from breaking when a user (or other package writers) picks a name that clashes with one in the package, and. Namespaces provide a way to refer to an object within a particular package.

How do I submit a package to Cran?

To manually submit your package to CRAN, you create a package bundle (with devtools::build() ) then upload it to https://cran.r-project.org/submit.html, along with some comments which describe the process you followed.


1 Answers

The general idea is that the 'native symbol info' objects created by using e.g. useDynLib(<pkg>, <symbol>) are not part of the public API of a package, and so client packages should not be calling them directly (it's assumed they could be changed in future revisions of the package).

There are two ways to 'export' a compiled routine for use by client packages:

  1. Just export an R wrapper function in foo that calls the native routine directly, or
  2. Use the R_RegisterCCallable() / R_GetCCallable() pair of functions to get a pointer to the function you want. (Package foo would call R_RegisterCCallable() to make some function available; client package bar would call R_GetCCallable() to get a pointer to that function)

In other words, if a package author 'registers' their C functions, they're declaring that to be part of the public C API of their package, and allow client packages to use / call that through this interface.

like image 190
Kevin Ushey Avatar answered Nov 05 '22 21:11

Kevin Ushey