Calling R Function from C++

后端 未结 2 1952
情话喂你
情话喂你 2020-11-28 20:23

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 b

相关标签:
2条回答
  • 2020-11-28 20:57

    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.

    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 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. And Rcpp still helps you to get results back and forth.

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

    // -*- 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.

    0 讨论(0)
  • 2020-11-28 20:58

    Dirk's probably right that RInside makes life easier. 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; dealing with the return value is a different (and in some sense easier) topic.

    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

    ~/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.

    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.

    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

    #include <Rembedded.h>
    #include <Rdefines.h>
    
    static void doSplinesExample();
    

    The work flow is to start R, do the work, and end 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

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

    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

        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 PROTECTed 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

        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.

    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.

        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

    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.

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

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

    would capture that for subsequent processing.

    0 讨论(0)
提交回复
热议问题