简体   繁体   English

从 C++ 调用 R 函数

[英]Calling R Function from C++

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.谢谢。

Dirk's probably right that RInside makes life easier.德克可能是对的,RInside 让生活更轻松。 But for the die-hards... The essence comes from Writing R Extensions sections 8.1 and 8.2, and from the examples distributed with R. The material below covers constructing and evaluating the call;但是对于顽固分子...本质来自编写 R 扩展第 8.1 和 8.2 节,以及随 R 分发的示例。下面的材料包括构建和评估调用; dealing with the return value is a different (and in some sense easier) topic.处理返回值是一个不同的(在某种意义上更容易)主题。

Setup设置

Let's suppose a Linux / Mac platform.让我们假设一个 Linux / Mac 平台。 The first thing is that R must have been compiled to allow linking, either to a shared or static R library.首先,R 必须经过编译以允许链接到共享或静态 R 库。 I work with an svn copy of R's source, in the directory ~/src/R-devel .我在~/src/R-devel目录中使用 R 源的 svn 副本。 I switch to some other directory, call it ~/bin/R-devel , and then我切换到其他目录,将其命名为~/bin/R-devel ,然后

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

this generates ~/bin/R-devel/lib/libR.so ;这会生成~/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. -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 .嵌入的示例在~/src/R-devel/tests/Embedding ,它们可以使用cd ~/bin/R-devel/tests/Embedding && make Obviously, the source code for these examples is extremely instructive.显然,这些示例的源代码非常具有指导意义。

Code代码

To illustrate, create a file embed.cpp .为了说明,创建一个文件embed.cpp Start by including the header that defines R data structures, and the R embedding interface;首先包含定义 R 数据结构的标头和 R 嵌入接口; these are located in bin/R-devel/include , and serve as the primary documentation.这些位于bin/R-devel/include ,作为主要文档。 We also have a prototype for the function that will do all the work我们还有一个函数原型来完成所有的工作

#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") . Embedding下的示例包括调用library(splines) ,设置命名选项,然后运行函数example("ns") Here's the routine that does this这是执行此操作的例程

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, eg, with R_HOME set correctly;编译后的应用程序需要在正确的环境中运行,例如,正确设置 R_HOME; this can be arranged easily (obviously a deployed app would want to take a more extensive approach) with这可以很容易地安排(显然一个部署的应用程序会想要采取更广泛的方法)

R CMD ./a.out

Depending on your ambitions, some parts of section 8 of Writing R Extensions are not relevant, eg, 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.).详细介绍一下…… SEXP(S 表达式)是 R 表示基本类型(整数、逻辑、语言调用等)的基础数据结构。 The line线

    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.创建一个符号library和一个字符串"splines" ,并将它们放入由两个元素组成的语言结构中。 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.这构造了一个未评估的语言对象,大约相当于 R 中的quote(library("splines"))lang2返回一个已从 R 的内存池分配的 SEXP,它需要从垃圾收集中进行PROTECT ed。 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). PROTECTe指向的地址添加到保护栈中,当内存不再需要保护时,地址从栈中弹出(使用UNPROTECT(1) ,向下几行)。 The line线

    R_tryEval(e, R_GlobalEnv, &errorOccurred);

tries to evaluate e in R's global environment.尝试在 R 的全局环境中计算e errorOccurred is set to non-0 if an error occurs.如果发生错误,则errorOccurred设置为非 0。 R_tryEval returns an SEXP representing the result of the function, but we ignore it here. R_tryEval返回一个表示函数结果的 SEXP,但我们在这里忽略它。 Because we no longer need the memory allocated to store library("splines") , we tell R that it is no longer PROTECT'ed.因为我们不再需要分配给存储library("splines")的内存,所以我们告诉 R 它不再受保护。

The next chunk of code is similar, evaluating options(example.ask=FALSE) , but the construction of the call is more complicated.下一段代码类似,评估options(example.ask=FALSE) ,但调用的构造更复杂。 The S-expression created by lang2 is a pair list , conceptually with a node, a left pointer (CAR) and a right pointer (CDR).lang2创建的 S 表达式是一个对列表,概念上包含一个节点、一个左指针(CAR)和一个右指针(CDR)。 The left pointer of e points to the symbol options . e的左指针指向符号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). e的右指针指向对列表中的另一个节点,其左指针为FALSE (右指针为R_NilValue ,表示语言表达式结束)。 Each node of a pair list can have a TAG , the meaning of which depends on the role played by the node.配对列表的每个节点都可以有一个TAG ,其含义取决于节点所扮演的角色。 Here we attach an argument name.这里我们附上一个参数名称。

    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.下一行计算我们构建的表达式( options(example.ask=FALSE) ),使用NULL表示我们将忽略函数计算的成功或失败。 A different way of constructing and evaluating this call is illustrated in R-devel/tests/Embedding/RParseEval.c , adapted here asR-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.但这一般来说似乎不是一个好的策略,因为它混合了 R 和 C 代码,并且不允许在 R 函数中使用计算参数。 Instead write and manage R code in R (eg, creating a package with functions that perform complicated series of R manipulations) that your C code uses.而是在您的 C 代码使用的 R 中编写和管理 R 代码(例如,创建一个带有执行复杂的 R 操作系列的函数的包)。

The final block of code above constructs and evaluates example("ns") .上面的最后一段代码构造并计算了example("ns") Rf_tryEval returns the result of the function call, so Rf_tryEval返回函数调用的结果,所以

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

would capture that for subsequent processing.将捕获它以进行后续处理。

There is Rcpp which 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 (ie you own main() ) and call out to R?但也许您真正想要的是保留您的 C++ 程序(即您拥有main() )并调用 R? That can be done most easily with RInside which 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.这可以通过RInside最轻松地完成,它允许您非常轻松地将 R 嵌入到您的 C++ 应用程序中——然后测试库、加载(如果需要)和函数调用非常容易,并且(超过一打)包含的示例向您展示如何操作。 And Rcpp still helps you to get results back and forth.而且Rcpp仍然可以帮助您来回获得结果。

Edit: As Martin was kind enough to show things the official way I cannot help and contrast it with one of the examples shipping with RInside.编辑:由于 Martin 很友好地以官方方式展示了事物我无法帮助并将其与 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.这是我曾经快速写的东西,以帮助那些在 r-help 上询问如何加载(投资组合优化)库并使用它的人。 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.它满足您的要求:加载库,访问一些数据,将权重向量从 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.这个rinside_sample2.cpp ,包中有更多的例子。 To build it, you just say 'make rinside_sample2' as the supplied Makefile is set up to find R, Rcpp and RInside.要构建它,您只需说“make rinside_sample2”,因为提供的Makefile已设置为查找 R、Rcpp 和 RInside。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM