从C++调用R函数

Eri*_*son 34 c++ r

我想在我自己编译的C++代码中检查是否在R中加载了一个库包(如果没有,加载它),从该库调用一个函数并将结果返回到我的C++代码中.

有人能指出我正确的方向吗?似乎有大量的关于R的信息以及从C++调用R的不同方式,反之亦然,但我没有想到我想要做的事情.

谢谢.

Mar*_*gan 50

Dirk可能是正确的,RInside让生活更轻松.但对于顽固派来说......本质上来自编写R扩展部分8.1和8.2,以及与R一起分发的示例.下面的内容包括构建和评估调用; 处理返回值是一个不同的(在某种意义上更容易)主题.

建立

我们假设一个Linux/Mac平台.第一件事是必须编译R以允许链接到共享或静态R库.我在目录中使用R的源代码的svn副本~/src/R-devel.我切换到其他目录,调用它~/bin/R-devel,然后

~/src/R-devel/configure --enable-R-shlib
make -j
Run Code Online (Sandbox Code Playgroud)

这会产生~/bin/R-devel/lib/libR.so; 也许你正在使用的任何发行版都有这个?该-j标志运行make并行,大大加快了构建.

嵌入的例子是~/src/R-devel/tests/Embedding,并且可以用它们制作cd ~/bin/R-devel/tests/Embedding && make.显然,这些示例的源代码非常有用.

为了说明,创建一个文件embed.cpp.首先包括定义R数据结构的头和R嵌入接口; 它们位于bin/R-devel/include,并作为主要文档.我们还有一个功能原型,可以完成所有工作

#include <Rembedded.h>
#include <Rdefines.h>

static void doSplinesExample();
Run Code Online (Sandbox Code Playgroud)

工作流程是启动R,完成工作,结束R:

int
main(int argc, char *argv[])
{
    Rf_initEmbeddedR(argc, argv);
    doSplinesExample();
    Rf_endEmbeddedR(0);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

下面的示例Embedding包括一个调用library(splines),设置命名选项,然后运行一个函数example("ns").这是执行此操作的例程

static void
doSplinesExample()
{
    SEXP e, result;
    int errorOccurred;

    // create and evaluate 'library(splines)'
    PROTECT(e = lang2(install("library"), mkString("splines")));
    R_tryEval(e, R_GlobalEnv, &errorOccurred);
    if (errorOccurred) {
        // handle error
    }
    UNPROTECT(1);

    // 'options(FALSE)' ...
    PROTECT(e = lang2(install("options"), ScalarLogical(0)));
    // ... modified to 'options(example.ask=FALSE)' (this is obscure)
    SET_TAG(CDR(e), install("example.ask"));
    R_tryEval(e, R_GlobalEnv, NULL);
    UNPROTECT(1);

    // 'example("ns")'
    PROTECT(e = lang2(install("example"), mkString("ns")));
    R_tryEval(e, R_GlobalEnv, &errorOccurred);
    UNPROTECT(1);
}
Run Code Online (Sandbox Code Playgroud)

编译并运行

我们现在准备将所有东西放在一起.编译器需要知道头和库的位置

g++ -I/home/user/bin/R-devel/include -L/home/user/bin/R-devel/lib -lR embed.cpp
Run Code Online (Sandbox Code Playgroud)

编译的应用程序需要在正确的环境中运行,例如,R_HOME设置正确; 这可以很容易地安排(显然,部署的应用程序会想采取更广泛的方法)

R CMD ./a.out
Run Code Online (Sandbox Code Playgroud)

根据您的抱负,编写R扩展的第8节的某些部分是不相关的,例如,需要回调来在R之上实现GUI,而不是评估简单的代码块.

一些细节

详细介绍...... SEXP(S表达式)是R表示基本类型(整数,逻辑,语言调用等)的基础数据结构.这条线

    PROTECT(e = lang2(install("library"), mkString("splines")));
Run Code Online (Sandbox Code Playgroud)

制作符号library和字符串"splines",并将它们放入由两个元素组成的语言构造中.这构造了一个未经评估的语言对象,大致相当于quote(library("splines"))在R.中lang2返回一个从R的内存池中分配的SEXP,它需要PROTECT从垃圾收集中编辑.PROTECT将指向的地址添加e到保护堆栈中,当内存不再需要保护时,地址从堆栈弹出(用UNPROTECT(1)几行向下).这条线

    R_tryEval(e, R_GlobalEnv, &errorOccurred);
Run Code Online (Sandbox Code Playgroud)

试图e在R的全球环境中进行评估.errorOccurred如果发生错误,则设置为非0.R_tryEval返回表示函数结果的SEXP,但我们在这里忽略它.因为我们不再需要分配存储的内存library("splines"),所以我们告诉R它不再是PROTECT'ed.

下一块代码类似,评估options(example.ask=FALSE),但调用的构造更复杂.创建的S表达式lang2是一对列表,在概念上具有节点,左指针(CAR)和右指针(CDR).指向e符号的左指针options.指向e对列表中另一个节点的右指针,其左指针是FALSE(右指针R_NilValue指示语言表达式的结束).一对列表中的每个节点可以具有a TAG,其含义取决于节点所扮演的角色.这里我们附上一个参数名称.

    SET_TAG(CDR(e), install("example.ask"));
Run Code Online (Sandbox Code Playgroud)

下一行评估我们构造的表达式(options(example.ask=FALSE)),NULL用于表示我们将忽略函数评估的成功或失败.构建和评估该呼叫的不同方式在此示出,在此R-devel/tests/Embedding/RParseEval.c适用

PROTECT(tmp = mkString("options(example.ask=FALSE)"));
PROTECT(e = R_ParseVector(tmp, 1, &status, R_NilValue));
R_tryEval(VECTOR_ELT(e, 0), R_GlobalEnv, NULL);
UNPROTECT(2);
Run Code Online (Sandbox Code Playgroud)

但这似乎不是一个好的策略,因为它混合了R和C代码,并且不允许在R函数中使用计算参数.而是在R中编写和管理R代码(例如,创建具有执行复杂R操作系列的函数的包),C代码使用这些代码.

上面的最后一段代码构造和评估example("ns").Rf_tryEval返回函数调用的结果,所以

SEXP result;
PROTECT(result = Rf_tryEval(e, R_GlobalEnv, &errorOccurred));
// ...
UNPROTECT(1);
Run Code Online (Sandbox Code Playgroud)

将捕获该信息用于后续处理.


Dir*_*tel 35

Rcpp允许您使用C++代码轻松扩展R,并且还将C++代码调用回R.包中包含的示例显示了这一点.

但也许你真正想要的是保留你的C++程序(即你拥有main())并呼叫R?这可以通过RInside轻松完成, 它允许您非常轻松地将R嵌入到C++应用程序中 - 并且对库进行测试,如果需要加载和函数调用则非常容易,而且(超过12个)包含的例子告诉你如何.而RCPP仍然可以帮助你得到的结果来回.

编辑:由于马丁很友好地用官方的方式展示我无法帮助的东西,并将其与其中一个与RInside一起发布的例子进行对比.我曾经快速写过这篇文章来帮助那些曾经在r-help上询问如何加载(投资组合优化)库并使用它的人.它满足您的要求:加载库,访问一些数据,将权重向量从C++传递到R,部署R并获得结果.

// -*- mode: C++; c-indent-level: 4; c-basic-offset: 4;  tab-width: 8; -*-
//
// Simple example for the repeated r-devel mails by Abhijit Bera
//
// Copyright (C) 2009         Dirk Eddelbuettel 
// Copyright (C) 2010 - 2011  Dirk Eddelbuettel and Romain Francois

#include <RInside.h>                    // for the embedded R via RInside

int main(int argc, char *argv[]) {

    try {
        RInside R(argc, argv);          // create an embedded R instance 

        std::string txt = "suppressMessages(library(fPortfolio))";
        R.parseEvalQ(txt);              // load library, no return value

        txt = "M <- as.matrix(SWX.RET); print(head(M)); M";
        // assign mat. M to NumericMatrix
        Rcpp::NumericMatrix M = R.parseEval(txt); 

        std::cout << "M has " 
                  << M.nrow() << " rows and " 
                  << M.ncol() << " cols" << std::endl;

        txt = "colnames(M)";        // assign columns names of M to ans and
        // into string vector cnames
        Rcpp::CharacterVector cnames = R.parseEval(txt);   

        for (int i=0; i<M.ncol(); i++) {
            std::cout << "Column " << cnames[i] 
                      << " in row 42 has " << M(42,i) << std::endl;
        }

    } catch(std::exception& ex) {
        std::cerr << "Exception caught: " << ex.what() << std::endl;
    } catch(...) {
        std::cerr << "Unknown exception caught" << std::endl;
    }

    exit(0);
}
Run Code Online (Sandbox Code Playgroud)

这个rinside_sample2.cpp,包中有更多的例子.要构建它,你只需要设置'make rinside_sample2' Makefile来设置R,Rcpp和RInside.

  • +1我的印象是Rcpp也做了嵌入部分.删除了我的答案,因为你的答案明显优越. (3认同)
  • Rcpp允许您访问函数等,但是如果您来自独立的C ++程序,则实际上没有R进程。R Embedding API提供了这一点,而RInside通过将很多细节抽象化,使其更易于使用。 (2认同)