我想在我自己编译的C ++代码中检查是否在R中加载了一个库包(如果没有,加载它),从该库调用一个函数并将结果返回到我的C ++代码中
有人能指出我正确的方向吗?似乎有很多关于R的信息以及从C ++调用R的不同方式,反之亦然,但我没有想到我想要做的事情。
感谢。
答案 0 :(得分:48)
Dirk可能是正确的,RInside让生活更轻松。但对于顽固派来说......本质来自Writing R Extensions第8.1和8.2节,以及与R一起分发的例子。下面的内容包括构建和评估呼叫;处理返回值是一个不同的(在某种意义上更容易)主题。
让我们假设一个Linux / Mac平台。第一件事是必须编译R以允许链接到共享或静态R库。我在目录~/src/R-devel
中使用R的源代码的svn副本。我切换到其他目录,称之为~/bin/R-devel
,然后是
~/src/R-devel/configure --enable-R-shlib
make -j
这会生成~/bin/R-devel/lib/libR.so
;也许你正在使用的任何发行版都有这个? -j
标志并行运行,这大大加快了构建速度。
嵌入示例位于~/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;
}
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);
}
我们现在准备将所有东西放在一起。编译器需要知道标头和库的位置
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
将e
指向的地址添加到保护堆栈,当内存不再需要保护时,地址从堆栈弹出(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操作系列的函数的包)。
上面的最后一段代码构造并评估example("ns")
。 Rf_tryEval
返回函数调用的结果,所以
SEXP result;
PROTECT(result = Rf_tryEval(e, R_GlobalEnv, &errorOccurred));
// ...
UNPROTECT(1);
将捕获该值以供后续处理。
答案 1 :(得分:34)
有Rcpp允许您使用C ++代码轻松扩展R,并且还将该C ++代码调用回R。包中包含的示例显示了这一点。
但也许您真正想要的是保留您的C ++程序(即您拥有main()
)并呼叫R?这可以通过以下方式轻松完成
RInside允许您非常轻松地将R嵌入到C ++应用程序中 - 并且库的测试,如果需要加载和函数调用则非常容易,并且(包括十几个)包含的示例显示你该怎么做。 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);
}
这个rinside_sample2.cpp
,包中有更多的例子。要构建它,你只需说'make rinside_sample2',因为提供的Makefile
被设置为找到R,Rcpp和RInside。