从C转换到R——最简单的Helloworld

21

如何编写最简单的C函数来启动R解释器、传入一个小表达式(例如2+2),并获得结果?我正在尝试在Windows上使用MingW进行编译。


好的,理解了。 - jsight
4个回答

12
你想从C语言中调用R语言吗?
请查看《编写R扩展》手册中的8.1章节。您还应该查看“tests”目录(下载源代码并提取它,您将拥有tests目录)。以前在R-Help上曾经问过类似的问题,这里是一个例子
#include <Rinternals.h> 
#include <Rembedded.h> 

SEXP hello() { 
  return mkString("Hello, world!\n"); 
} 

int main(int argc, char **argv) { 
  SEXP x; 
  Rf_initEmbeddedR(argc, argv); 
  x = hello(); 
  return x == NULL;             /* i.e. 0 on success */ 
} 

R手册中的简单示例如下:

 #include <Rembedded.h>

 int main(int ac, char **av)
 {
     /* do some setup */
     Rf_initEmbeddedR(argc, argv);
     /* do some more setup */

     /* submit some code to R, which is done interactively via
         run_Rmainloop();

         A possible substitute for a pseudo-console is

         R_ReplDLLinit();
         while(R_ReplDLLdo1() > 0) {
           add user actions here if desired
         }
      */
     Rf_endEmbeddedR(0);
     /* final tidying up after R is shutdown */
     return 0;
 }

顺便提一下,您可能想考虑使用Rinside:Dirk在项目主页上提供了一个不错的“hello world”示例

如果您有兴趣从R中调用C,请看我的原始回答:

这不完全是“hello world”,但这里有一些很好的资源:


非常感谢,我错过了 R init 函数调用...哎呀。你(和其他人)提供的例子非常有帮助(可惜我不能给其他一些人接受的答案... :) )。 - jsight

10

这是主函数,但您应该能够将其调整为更通用的函数。此示例从C调用和C字符串中构建R表达式。在Windows上进行编译需要自行解决,但我已经提供了在Linux上的编译步骤:

 /* simple.c */
 #include <Rinternals.h>
 #include <Rembedded.h>
 #include <R_ext/Parse.h>
 int
 main(int argc, char *argv[])
 {
    char *localArgs[] = {"R", "--no-save","--silent"};
    SEXP e, tmp, ret;
    ParseStatus status;
    int i;

    Rf_initEmbeddedR(3, localArgs);

    /* EXAMPLE #1 */

    /* Create the R expressions "rnorm(10)" with the R API.*/
    PROTECT(e = allocVector(LANGSXP, 2));
    tmp = findFun(install("rnorm"), R_GlobalEnv);
    SETCAR(e, tmp);
    SETCADR(e, ScalarInteger(10));

    /* Call it, and store the result in ret */
    PROTECT(ret = R_tryEval(e, R_GlobalEnv, NULL));

    /* Print out ret */
    printf("EXAMPLE #1 Output: ");
    for (i=0; i<length(ret); i++){
        printf("%f ",REAL(ret)[i]);
    }
    printf("\n");

    UNPROTECT(2);


    /* EXAMPLE 2*/

    /* Parse and eval the R expression "rnorm(10)" from a string */
    PROTECT(tmp = mkString("rnorm(10)"));
    PROTECT(e = R_ParseVector(tmp, -1, &status, R_NilValue));
    PROTECT(ret = R_tryEval(VECTOR_ELT(e,0), R_GlobalEnv, NULL));

    /* And print. */
    printf("EXAMPLE #2 Output: ");
    for (i=0; i<length(ret); i++){
        printf("%f ",REAL(ret)[i]);
    }
    printf("\n");

    UNPROTECT(3);
    Rf_endEmbeddedR(0);
    return(0);
 }

编译步骤:

$ gcc -I/usr/share/R/include/ -c -ggdb simple.c
$ gcc -o simple simple.o  -L/usr/lib/R/lib -lR
$ LD_LIBRARY_PATH=/usr/lib/R/lib R_HOME=/usr/lib/R ./simple
EXAMPLE #1 Output: 0.164351 -0.052308 -1.102335 -0.924609 -0.649887 0.605908 0.130604 0.243198 -2.489826 1.353731
EXAMPLE #2 Output: -1.532387 -1.126142 -0.330926 0.672688 -1.150783 -0.848974 1.617413 -0.086969 -1.334659 -0.313699

它能在VS2008(MS编译器)下编译吗?有哪些参数? - Sergey

8

我认为以上回答并没有回答问题 - 问题是如何计算 2 + 2 ;). 使用字符串表达式的方法应该是:

#include <Rinternals.h>
#include <R_ext/Parse.h>
#include <Rembedded.h>

int main(int argc, char **argv) {
    SEXP x;
    ParseStatus status;
    const char* expr = "2 + 2";

    Rf_initEmbeddedR(argc, argv);

    x = R_ParseVector(mkString(expr), 1, &status, R_NilValue);
    if (TYPEOF(x) == EXPRSXP) { /* parse returns an expr vector, you want the first */
        x = eval(VECTOR_ELT(x, 0), R_GlobalEnv);
        PrintValue(x);
    }

    Rf_endEmbeddedR(0);

    return 0;
}

这个显然缺乏错误检查,但是可以工作:
Z:\>gcc -o e.exe e.c -IC:/PROGRA~1/R/R-213~1.0/include -LC:/PROGRA~1/R/R-213~1.0/bin/i386 -lR
Z:\>R CMD e.exe
[1] 4

要获取您的R使用正确的命令,请使用R CMD SHLIB e.c,这将为您提供相关的编译器标志。

如果表达式足够简单,您也可以手动构建它 - 例如,对于rnorm(10),您将使用以下命令:

SEXP rnorm = install("rnorm");
SEXP x = eval(lang2(rnorm, ScalarInteger(10)), R_GlobalEnv);

它能在Visual Studio(MS编译器)下编译吗?参数是什么? - Sergey
不,VC(Visual C++)并没有得到官方支持,本地编译器是MinGW。也许有将VC代码与R(例如通过DLL)相结合的方法,但不能保证其有效。 - Simon Urbanek
我已经在Visual Studio(MS编译器)下编译了代码:)。为此,需要手动创建R.lib库。 - Sergey

3

我认为你不能找到比inline包更好的选择(它支持C、C++和Fortran):

library(inline)
fun <- cfunction(signature(x="ANY"), 
                 body='printf("Hello, world\\n"); return R_NilValue;')
res <- fun(NULL)

这段代码会输出“Hello, World”。你甚至不知道编译器和链接器是在何时、何地被调用的。这里使用的.Call()签名需要返回一个SEXP,而R_NilValue是R中SEXP的NULL版本。请参阅《Writing R Extensions》手册(在此无法避免)。
然后,你可以将这样的代码打包到一个软件包中。我们在Rcpp单元测试(现在已经有200多个)和一些示例中使用inline取得了巨大成功。
哦,还有这个inline示例可在任何操作系统上运行。即使在Windows上,只要安装了R包构建工具链,并将其添加到PATH等位置。
编辑:我误读了问题。你想要的基本上就是littler前端所做的事情(使用纯C),以及RInside类为C++分离出来的内容。
Jeff和我从未费心将littler移植到Windows,但RInside在最近的版本中确实可以在Windows上工作。因此,您应该能够查看构建配方并创建一个仅使用C的RInside变体,以便将表达式传递给嵌入式R进程。我认为如果不了解相关信息,你仍然需要像Rcpp一样进行学习。
编辑2:正如Shane所提到的,R源代码中的tests/Embedding/中有一些示例和Makefile.win。如果你愿意学习R内部知识,那么这可能是最简单的起点。

没错,但那是C++... 我现在更想要一个C的例子。不过Rcpp看起来非常简单易用。 - jsight
是的,我已经相应地进行了编辑。即使没有Rcpp,内联也可以正常工作。但是一旦您实际上想要交换数据,我会选择Rcpp,尽管您可能觉得C++比C更复杂。但它不必如此。 - Dirk Eddelbuettel
1
这很有趣,但与我寻找的相反。我对从(可能是遗留的)C代码调用R感兴趣,而不是相反的情况...如果原始问题不清楚,我很抱歉。不过,这些信息也很有帮助。 :) - jsight
哦,我误解了。在这种情况下,请尝试使用“littler”和“RInside”,我会修改我的答案。 - Dirk Eddelbuettel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接