【问题标题】:R package with both .c and .cpp files with Rcpp带有 .c 和 .cpp 文件的 R 包,带有 Rcpp
【发布时间】:2019-01-02 00:26:00
【问题描述】:

我正在尝试使用 Rcpp 包作为依赖项构建一个包含 C(.c 文件形式)和 C++ 代码(.cpp 文件形式)的 R 包。

我有几个问题。

  1. 首先,真的可以这样做吗?可以调用同一个 R 包中的 C 脚本和 C++ 脚本吗?
  2. 如果前一种可行,那么如何在 C 和 C++ 脚本中正确注册函数。

为了帮助解决这个问题,我在我的 GitHub 页面 (https://github.com/tpbilton/testrcpp) 上设置了一个小示例。我使用Rcpp.package.skeleton("testrcpp") 来初始化包并添加了一些功能(来自本教程https://cran.r-project.org/web/packages/Rcpp/vignettes/Rcpp-introduction.pdf)然后运行Rcpp::compileAttributes()。我安装了包,c++ 函数 convolve_cpp 工作正常,但 convolve_c 未注册,我不知道如何正确执行此操作,我尝试注册这两个函数的尝试无济于事。

【问题讨论】:

    标签: c rcpp r-package


    【解决方案1】:

    首先,这真的有可能吗?可以调用同一个R包中的C脚本和C++脚本吗?

    是的。 Rcpp 非常有名地利用了 RC API。 (参考Section 1.6.4 Portable C and C++ codeWriting R Extensions

    如果前一种是可能的,那么如何在 C 和 C++ 脚本中正确注册函数。

    理想情况下,仅来自 C++ 脚本的表面方面。否则,你会被卡住写胶水。

    我采用了这种方法。这篇文章继续详细介绍了细微的变化。可以在非现场找到一个工作示例:

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


    简而言之,我们将为函数定义创建一个头文件,并将其包含在 C 代码中。从那里,我们将在 C++ 中创建第三个文件,并使用 _Rcpp 将该函数导出到 R

    convolve_in_c.h

    这里我们通过#ifndef#define 使用包含保护,以确保在我们多次重用头文件时不会重复函数定义。

    #ifndef CONVOLVE_C_H
    #define CONVOLVE_C_H
    
    SEXP convolve_c(SEXP a, SEXP b);
    
    #endif /* CONVOLVE_C_H */
    

    convolve_in_c.c

    现在,让我们修改文件以允许我们的自定义标题。

    #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

    最后,我们在 C++ 文件中使用 extern 合并 C 代码,以使 C++ 中的函数名称与 C 联动。此外,我们将数据类型从SEXP 操作为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;
    }
    

    【讨论】:

    • 这与我的答案大致相同,但更长。其实也不是强迫自己重写他的C函数。
    • 谁需要简洁? ;) 说真的,这是通过工作包实现的第三个选项的更大实现。
    • 实际上并不是对 C 函数的重写。我只是将 C 函数定义放在头文件中并从 C++ 调用它,而您正在执行 extern 包含在 C++中> 文件。
    • 较小的增量。更少的变化。与 OP 方法的差异较小。
    【解决方案2】:

    它有助于退后一步进行审查。考虑两个包:

    • convolve_c,您将其作为“长格式”的仅 C 包手工编写,并手动完成所有操作,包括手工初始化和注册
    • 您使用 Rcpp 编写的 convolve_cppcompileAttributes() 以及其他工具可以为您完成所有工作。

    从本质上讲,您的问题相当于 Rcpp 也为您完成了 C 部分,但它只是不能那样工作。 Rcpp 没有“看到”您的src/convolvec.c,因此不会添加它。

    但是,如果您了解这些函数注册的工作原理 - 数千个 CRAN 包可供查看,还有一本手册可供细读 - 那么您可以手动填写。

    或者你可以踢。只需在 C++ 中添加第三个函数,它调用您的 C 函数。 Rcpp 将处理所有事情,您就完成了。您的选择:简单或精致。

    编辑:更明确地说,选项 3 包括添加

    #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);
    }
    

    然后运行compileAttributes(),一切都很好。由于通常的原因,混合和匹配也可以工作,但工作量更大——请参阅“编写 R 扩展”了解所有详细信息。

    它的工作说明:

    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> 
    

    【讨论】:

    • 谢谢德克。因此,如果我理解正确,我必须有两个包,一个用于 C 代码,另一个用于 C++ 代码?我不能在compileAttributes()注册的convolve_cpp之上手动注册convolve_c
    • 你误会了。我列出了两个选项(使用两个包)试图解释通过 one Jointyou 仍然需要完成 两半。或者,您可以将一个简单的 C++ 调用程序添加到您的 c 函数中。那会被包裹起来的。这也听起来你没有明显的原因使事情过于复杂,所以也许你想重新考虑为什么和如何。但简而言之,您也可以始终手动完成由助手为您完成的所有操作。没有什么能阻止你。
    • 哦,对了,我现在明白你的意思了(对不起,我有把事情复杂化的倾向)。
    • 作为理解事物的研究工具并没有错。只要记住时不时退后一步,以获得更大的图景。
    猜你喜欢
    • 2021-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-09
    • 1970-01-01
    相关资源
    最近更新 更多