从 C++ 调用 R 函数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7457635/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 16:58:48  来源:igfitidea点击:

Calling R Function from C++

c++r

提问by Erich Peterson

I would like to, within my own compiled C++ code, check to see if a library package is loaded in R (if not, load it), call a function from that library and get the results back to in my C++ code.

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

Could someone point me in the right direction? There seems to be a plethora of info on R and different ways of calling R from C++ and vis versa, but I have not come across exactly what I am wanting to do.

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

Thanks.

谢谢。

回答by Martin Morgan

Dirk's probably right that RInside makes life easier. But for the die-hards... The essence comes from Writing R Extensionssections 8.1 and 8.2, and from the examples distributed with R. The material below covers constructing and evaluating the call; dealing with the return value is a different (and in some sense easier) topic.

德克可能是对的,RInside 让生活更轻松。但是对于顽固分子...本质来自编写 R 扩展第 8.1 和 8.2 节,以及随 R 分发的示例。下面的材料包括构建和评估调用;处理返回值是一个不同的(在某种意义上更容易)主题。

Setup

设置

Let's suppose a Linux / Mac platform. The first thing is that R must have been compiled to allow linking, either to a shared or static R library. I work with an svn copy of R's source, in the directory ~/src/R-devel. I switch to some other directory, call it ~/bin/R-devel, and then

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

~/src/R-devel/configure --enable-R-shlib
make -j

this generates ~/bin/R-devel/lib/libR.so; perhaps whatever distribution you're using already has this? The -jflag runs make in parallel, which greatly speeds the build.

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

Examples for embedding are in ~/src/R-devel/tests/Embedding, and they can be made with cd ~/bin/R-devel/tests/Embedding && make. Obviously, the source code for these examples is extremely instructive.

嵌入的示例在 中~/src/R-devel/tests/Embedding,它们可以用 制作cd ~/bin/R-devel/tests/Embedding && make。显然,这些示例的源代码非常具有指导意义。

Code

代码

To illustrate, create a file embed.cpp. Start by including the header that defines R data structures, and the R embedding interface; these are located in bin/R-devel/include, and serve as the primary documentation. We also have a prototype for the function that will do all the work

为了说明,创建一个文件embed.cpp. 首先包含定义 R 数据结构的标头和 R 嵌入接口;这些位于 中bin/R-devel/include,用作主要文档。我们还有一个函数原型来完成所有的工作

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

static void doSplinesExample();

The work flow is to start R, do the work, and end R:

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

int
main(int argc, char *argv[])
{
    Rf_initEmbeddedR(argc, argv);
    doSplinesExample();
    Rf_endEmbeddedR(0);
    return 0;
}

The examples under Embeddinginclude one that calls library(splines), sets a named option, then runs a function example("ns"). Here's the routine that does this

下面的示例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);
}

Compile and run

编译并运行

We're now ready to put everything together. The compiler needs to know where the headers and libraries are

我们现在准备把所有东西放在一起。编译器需要知道头文件和库在哪里

g++ -I/home/user/bin/R-devel/include -L/home/user/bin/R-devel/lib -lR embed.cpp

The compiled application needs to be run in the correct environment, e.g., with R_HOME set correctly; this can be arranged easily (obviously a deployed app would want to take a more extensive approach) with

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

R CMD ./a.out

Depending on your ambitions, some parts of section 8 of Writing R Extensions are not relevant, e.g., callbacks are needed to implement a GUI on top of R, but not to evaluate simple code chunks.

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

Some detail

一些细节

Running through that in a bit of detail... An SEXP (S-expression) is a data structure fundamental to R's representation of basic types (integer, logical, language calls, etc.). The line

详细介绍一下…… SEXP(S 表达式)是 R 表示基本类型(整数、逻辑、语言调用等)的基础数据结构。线

    PROTECT(e = lang2(install("library"), mkString("splines")));

makes a symbol libraryand a string "splines", and places them into a language construct consisting of two elements. This constructs an unevaluated language object, approximately equivalent to quote(library("splines"))in R. lang2returns an SEXP that has been allocated from R's memory pool, and it needs to be PROTECTed from garbage collection. PROTECTadds the address pointed to by eto a protection stack, when the memory no longer needs to be protected, the address is popped from the stack (with UNPROTECT(1), a few lines down). The line

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

    R_tryEval(e, R_GlobalEnv, &errorOccurred);

tries to evaluate ein R's global environment. errorOccurredis set to non-0 if an error occurs. R_tryEvalreturns an SEXP representing the result of the function, but we ignore it here. Because we no longer need the memory allocated to store library("splines"), we tell R that it is no longer PROTECT'ed.

尝试e在 R 的全局环境中进行评估。errorOccurred如果发生错误,则设置为非 0。R_tryEval返回一个代表函数结果的 SEXP,但我们在这里忽略它。因为我们不再需要分配给 store 的内存library("splines"),所以我们告诉 R 它不再受保护。

The next chunk of code is similar, evaluating options(example.ask=FALSE), but the construction of the call is more complicated. The S-expression created by lang2is a pair list, conceptually with a node, a left pointer (CAR) and a right pointer (CDR). The left pointer of epoints to the symbol options. The right pointer of epoints to another node in the pair list, whose left pointer is FALSE(the right pointer is R_NilValue, indicating the end of the language expression). Each node of a pair list can have a TAG, the meaning of which depends on the role played by the node. Here we attach an argument name.

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

    SET_TAG(CDR(e), install("example.ask"));

The next line evaluates the expression that we have constructed (options(example.ask=FALSE)), using NULLto indicate that we'll ignore the success or failure of the function's evaluation. A different way of constructing and evaluating this call is illustrated in R-devel/tests/Embedding/RParseEval.c, adapted here as

下一行计算我们构造的表达式 ( 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);

but this doesn't seem like a good strategy in general, as it mixes R and C code and does not allow computed arguments to be used in R functions. Instead write and manage R code in R (e.g., creating a package with functions that perform complicated series of R manipulations) that your C code uses.

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

The final block of code above constructs and evaluates example("ns"). Rf_tryEvalreturns the result of the function call, so

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

SEXP result;
PROTECT(result = Rf_tryEval(e, R_GlobalEnv, &errorOccurred));
// ...
UNPROTECT(1);

would capture that for subsequent processing.

将捕获它以进行后续处理。

回答by Dirk Eddelbuettel

There is Rcppwhich allows you to easily extend R with C++ code, and also have that C++ code call back to R. There are examples included in the package which show that.

Rcpp允许您使用 C++ 代码轻松扩展 R,并且还可以让 C++ 代码回调到 R。包中包含的示例表明了这一点。

But maybe what you really want is to keep your C++ program (i.e. you own main()) and call out to R? That can be done most easily with RInsidewhich allows you to very easily embed R inside your C++ application---and the test for library, load if needed and function call are then extremely easy to do, and the (more than a dozen) included examples show you how to. And Rcppstill helps you to get results back and forth.

但也许您真正想要的是保留您的 C++ 程序(即您拥有的main())并调用 R?这可以通过RInside最轻松地完成, 它允许您非常轻松地将 R 嵌入到您的 C++ 应用程序中——然后测试库、加载(如果需要)和函数调用非常容易,并且(超过一打)包含的示例向您展示如何操作。而且Rcpp仍然可以帮助您来回获得结果。

Edit:As Martin was kind enough to show things the official wayI cannot help and contrast it with one of the examples shipping with RInside. It is something I once wrote quickly to help someone who had asked on r-help about how to load (a portfolio optimisation) library and use it. It meets your requirements: load a library, accesses some data in pass a weights vector down from C++ to R, deploy R and get the result back.

编辑:由于 Martin 很友好地以官方方式展示了事物我无法帮助并将其与 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);
}

This rinside_sample2.cpp, and there are lots more examples in the package. To build it, you just say 'make rinside_sample2' as the supplied Makefileis set up to find R, Rcpp and RInside.

This rinside_sample2.cpp,包中还有更多示例。要构建它,您只需说 'make rinside_sample2',因为所提供的Makefile已设置为查找 R、Rcpp 和 RInside。