问题
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.
- First, is it actually possible to do this? Can one call C scripts and C++ scripts that are in the same R package?
- 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 registrationconvolve_cpp
which you write using Rcpp -- andcompileAttributes()
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