从 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
Calling R Function from C++
提问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 -j
flag 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 Embedding
include 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 library
and 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. lang2
returns an SEXP that has been allocated from R's memory pool, and it needs to be PROTECT
ed from garbage collection. PROTECT
adds the address pointed to by e
to 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 e
in R's global environment. errorOccurred
is set to non-0 if an error occurs. R_tryEval
returns 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 lang2
is a pair list, conceptually with a node, a left pointer (CAR) and a right pointer (CDR). The left pointer of e
points to the symbol options
. The right pointer of e
points 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 NULL
to 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_tryEval
returns 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 Makefile
is set up to find R, Rcpp and RInside.
This rinside_sample2.cpp
,包中还有更多示例。要构建它,您只需说 'make rinside_sample2',因为所提供的Makefile
已设置为查找 R、Rcpp 和 RInside。