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.
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.
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.
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 */
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;
}
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;
}
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 registrationconvolve_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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With