我希望在我的编译的C++代码中,检查R中是否加载了一个库包(如果没有,则加载它),从该库中调用一个函数,并将结果返回到我的C++代码中。
有人可以指导我吗?似乎有很多关于R和不同的调用方式的信息,但是我没有找到我想做的事情的确切方法。
谢谢。
Dirk提出了RInside可以让生活更轻松,但对于一些顽固分子...本质上来自于Writing R Extensions第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
这将生成~/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();
工作流程是启动R,执行工作,然后结束R:
int
main(int argc, char *argv[])
{
Rf_initEmbeddedR(argc, argv);
doSplinesExample();
Rf_endEmbeddedR(0);
return 0;
}
在嵌入
下的示例包括调用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);
}
现在我们已经准备好将所有东西组合起来了。编译器需要知道头文件和库的位置。
g++ -I/home/user/bin/R-devel/include -L/home/user/bin/R-devel/lib -lR embed.cpp
编译后的应用程序需要在正确的环境中运行,例如正确设置 R_HOME;这可以很容易地安排(显然部署的应用程序希望采取更广泛的方法):
R CMD ./a.out
根据您的目标,写作R扩展的第8节的某些部分可能并不相关,例如,回调函数需要实现R之上的GUI,但不需要评估简单代码块。
稍微详细地讲解一下... SEXP(S表达式)是R表示基本类型(整数、逻辑、语言调用等)的数据结构。这行代码
PROTECT(e = lang2(install("library"), mkString("splines")));
生成一个符号库 library
和一个字符串 "splines"
,并将它们置于由两个元素构成的语言结构中。这将构建一个未评估的语言对象,大致等同于 R 中的 quote(library("splines"))
。lang2
返回一个已从 R 的内存池中分配的 SEXP,并且需要使用 PROTECT
保护免受垃圾收集。当内存不再需要保护时,PROTECT
将指向的地址添加到保护栈中,在弹出地址后,内存可用于其他用途(在几行下面用 UNPROTECT(1)
弹出)。该行
R_tryEval(e, R_GlobalEnv, &errorOccurred);
尝试在R的全局环境中评估e
。如果发生错误,则将errorOccurred
设置为非0。 R_tryEval
返回表示函数结果的SEXP,但在此我们忽略它。因为我们不再需要为library("splines")
分配内存,所以我们告诉R它不再受到PROTECT保护。
下一个代码块类似,评估options(example.ask = FALSE)
,但是调用的构造更加复杂。lang2
创建的S表达式是一组对列表,概念上具有节点、左指针(CAR)和右指针(CDR)。 e
的左指针指向符号options
。 e
的右指针指向对列表中的另一个节点,其左指针为FALSE
(右指针为R_NilValue
,表示语言表达式的结束)。 对列表的每个节点都可以有一个TAG
,其含义取决于节点所起的作用。 在这里,我们附加参数名称。
SET_TAG(CDR(e), install("example.ask"));
下一行代码会评估我们构造的表达式 (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);
但这似乎不是一个好的策略,因为它混合了R和C代码,并且不允许在R函数中使用计算参数。相反,应该在R中编写和管理R代码(例如创建一个带有执行复杂的R操作的函数的包),由您的C代码使用。
以上代码块的最后一个部分构造并评估example("ns")
。Rf_tryEval
返回函数调用的结果,因此
SEXP result;
PROTECT(result = Rf_tryEval(e, R_GlobalEnv, &errorOccurred));
// ...
UNPROTECT(1);
将其捕获以供后续处理。
有一个叫做Rcpp的工具可以让你轻松地用C++扩展R,并且可以让C++代码回调到R。软件包中包含了一些示例。
但是也许你真正想要的是保留你自己的C++程序(即你自己的main()
),并调用R?这可以通过RInside最容易实现,它可以非常轻松地将R嵌入到你的C++应用程序中,测试库、加载(如果需要)和函数调用变得非常容易,而且其中包括了多达十几个的示例来帮助你。同时Rcpp还可以帮助你在C++和R之间传递结果。
编辑:由于Martin友善地展示了官方方式,我无法避免将其与RInside随附的示例之一进行对比。这是我曾经快速编写的东西,以帮助一个在r-help上询问如何加载(投资组合优化)库并使用它的人。它满足您的要求:加载库,在C++中访问一些数据,将权重向量从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);
}
这里有一个名为rinside_sample2.cpp
的文件,而且包里面还有很多其他的例子。要构建它,只需要输入命令'make rinside_sample2',因为提供的Makefile
已经配置好了找到R、Rcpp和RInside。