R package with both .c and .cpp files with Rcpp

五迷三道 提交于 2019-12-22 12:56:05

问题


I'm trying to build an R package which contains both C (in the form of .c files) and C++ code (in the form of .cpp files) using the Rcpp package as a dependency.

I have a couple of questions.

  1. First, is it actually possible to do this? Can one call C scripts and C++ scripts that are in the same R package?
  2. If the previous is possible, how then does one properly register the functions in the C and C++ scripts.

To help with this, I have set up a little example which is available on my GitHub page (https://github.com/tpbilton/testrcpp). I have used Rcpp.package.skeleton("testrcpp") to initialize the package and added some functions (from this tutorial https://cran.r-project.org/web/packages/Rcpp/vignettes/Rcpp-introduction.pdf) and then ran Rcpp::compileAttributes(). I installed the package and the c++ function convolve_cpp works fine but the convolve_c is not registered and I have no idea how to do this properly and my attempts at trying to register both functions have gone nowhere.


回答1:


First, is it actually possible to do this? Can one call C scripts and C++ scripts that are in the same R package?

Yes. Rcpp very famously is taking advantage of R's C API. (c.f. Section 1.6.4 Portable C and C++ code of Writing R Extensions .

If the previous is possible, how then does one properly register the functions in the C and C++ scripts.

Ideally, only surface aspects from the C++ script. Otherwise, you're stuck writing the glue.

I've taken this approach. The post goes on to detail the slight changes. A working example can be found off-site at:

https://github.com/r-pkg-examples/rcpp-and-c


In short, we'll create a header file for the function definitions and include it with the C code. From there, we'll create a third file that is in C++ and export that function into R using _Rcpp.

convolve_in_c.h

Here we use an inclusion guard via #ifndef and #define to ensure the function definitions are not repeated if we reuse the header file multiple times.

#ifndef CONVOLVE_C_H
#define CONVOLVE_C_H

SEXP convolve_c(SEXP a, SEXP b);

#endif /* CONVOLVE_C_H */

convolve_in_c.c

Now, let's modify the file to allow for our custom header.

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

// Incorporate our header
#include "convolve_in_c.h"

SEXP convolve_c(SEXP a, SEXP b) {
  int na, nb, nab;
  double *xa, *xb, *xab;
  SEXP ab;
  a = PROTECT(coerceVector(a, REALSXP));
  b = PROTECT(coerceVector(b, REALSXP));
  na = length(a); nb = length(b);
  nab = na + nb - 1;
  ab = PROTECT(allocVector(REALSXP, nab));
  xa = REAL(a); xb = REAL(b); xab = REAL(ab);
  for(int i = 0; i < nab; i++)
    xab[i] = 0.0;
  for(int i = 0; i < na; i++)
    for(int j = 0; j < nb; j++)
      xab[i + j] += xa[i] * xb[j];
  UNPROTECT(3);
  return ab;
}

convolve_from_c_to_rcpp.cpp

Finally, we incorporate the C code using extern within our C++ file to have the function name in C++ align with the C linkage. In addition, we manipulate the data type from SEXP to NumericVector.

#include "Rcpp.h"

// Define the method signature

#ifdef __cplusplus
extern "C" {
#endif

#include "convolve_in_c.h"

#ifdef __cplusplus
}
#endif

//' Call C function from Rcpp
//' 
//' Uses the convolve_c function inside of a C++ routine by Rcpp.
//' 
//' @param a,b A `numeric` vector.
//' 
//' @return 
//' A `numeric` vector of length \eqn{N_a + N_b}.
//' 
//' @examples
//' 
//' convolve_from_c(1:5, 5:1)
//' 
//' @export
// [[Rcpp::export]]
Rcpp::NumericVector convolve_from_c(const Rcpp::NumericVector& a,
                                    const Rcpp::NumericVector& b) {

  // Compute the result in _C_ from _C++_.
  SEXP ab = convolve_c(a, b);

  // Cast as an _Rcpp_ NumericVector 
  Rcpp::NumericVector result( ab );

  // Alternatively:
  // Rcpp::NumericVector result( convolve_c(a, b) );

  // Return result
  return result;
}



回答2:


It helps to step back and review. Consider two packages:

  • convolve_c which you write by hand as a C only package in 'long-form' and do everything manually, including handcrafting the initialization and registration
  • convolve_cpp which you write using Rcpp -- and compileAttributes() and other tools do everything for you.

In essence, you question amount to also having the C part done for you by Rcpp and it just doesn't work that way. Rcpp does not 'see' your src/convolvec.c so it won't add it.

But if you look at how these function registrations work -- thousand of CRAN packages to look at, and a manual to peruse -- then you can fill it by hand.

Or you could punt. Just add a third function, in C++, which calls your C function. Rcpp will take care of everything, and you're done. Your choice: easy, or elaborate.

Edit: To be more explicit, option 3 consists of adding

#include <Rcpp.h>

extern "C" SEXP convolve_c(SEXP a, SEXP b);

// [[Rcpp::export]]
SEXP callCconvolve(SEXP a, SEXP b) {
    return convolve_c(a, b);
}

Then run compileAttributes() and all is good. The mixing and matching will work too for the usual reason but is more work -- see "Writing R Extensions" for all the details.

Illustration of it working:

R> library(testrcpp)
R> a <- as.double(1:10)
R> b <- as.double(10:1)
R> identical(convolve_cpp(a, b), callCconvolve(a, b))
[1] TRUE
R> 


来源:https://stackoverflow.com/questions/54000015/r-package-with-both-c-and-cpp-files-with-rcpp

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!