20

最近我一直在尝试使用 Rcpp(内联)来生成 DLL,这些 DLL 在提供的 R 输入上执行各种任务。给定一组特定的 R 输入,我希望能够逐行调试这些 DLL 中的代码。(我在 Windows 下工作。)

为了说明,让我们考虑一个任何人都应该能够运行的特定示例......

下面的代码是一个非常简单的 cxx 函数,它只是将输入向量加倍。但是请注意,还有一个额外的变量myvar可以更改几次值但不影响输出 - 已添加此变量,以便我们能够看到调试过程何时正确运行。

library(inline)
library(Rcpp)

f0 <- cxxfunction(signature(a="numeric"), plugin="Rcpp", body='
    Rcpp::NumericVector xa(a);
    int myvar = 19;
    int na = xa.size();
    myvar = 27;
    Rcpp::NumericVector out1(na);
    for(int i=0; i < na; i++) {
        out1[i] = 2*xa[i];
        myvar++;
    }
    myvar = 101;
    return(Rcpp::List::create( _["out1"] = out1));
')

在我们运行上面之后,输入命令

getLoadedDLLs()

在 R 会话中显示 DLL 列表。列出的最后一个应该是上述过程创建的 DLL - 它有一个随机的临时名称,在我的例子中是

file7e61645c

“文件名”列显示 cxxfunction 已将此 DLL 放在 locationtempdir()中,对我来说目前是

C:/Users/TimP/AppData/Local/Temp/RtmpXuxtpa/file7e61645c.dll

现在,调用 DLL 的明显方法是 via f0,如下

> f0(c(-7,0.7,77))

$out1
[1] -14.0   1.4 154.0

但我们当然也可以使用以下.Call命令直接按名称调用 DLL:

> .Call("file7e61645c",c(-7,0.7,77))

$out1
[1] -14.0   1.4 154.0

所以我已经达到了直接使用 R 输入(这里是 vector c(-7,0.7,77))调用独立 DLL 并让它正确地将答案返回给 R的地步。

不过,我真正需要的是一个用于逐行调试的工具(我猜是使用 gdb),它可以让我观察myvar设置为 19、27、28、29、30 和最后 101 的值代码进展。上面的示例是故意设置的,因此调用 DLL 不会告诉我们有关 myvar 的任何信息。

澄清一下,这里的“获胜条件”是能够观察 myvar 的变化(看到值 myvar=19 将是第一步!)而不向代码主体添加任何其他内容。这显然可能需要更改代码的编译方式(是否有调试模式设置可以打开?),或者调用 R 的方式 - 但我不知道从哪里开始。如上所述,所有这些都是基于 Windows 的。

最后说明:在我的实验中,我实际上对 cxxfunction 的副本进行了一些小修改,以便输出 DLL - 以及其中的代码 - 接收用户定义的名称并位于用户定义的目录中,而不是临时名称和位置。但这并不影响问题的本质。我提到这一点只是为了强调如果有人轻推我,更改编译设置应该相当容易:)

为了完整起见,在上面的原始 cxxfunction 调用中设置 verbose=TRUE 显示编译参数为以下形式:

C:/R/R-2.13.2/bin/i386/R CMD SHLIB file7e61645c.cpp 2> file7e61645c.cpp.err.txt 
g++ -I"C:/R/R-213~1.2/include"    -I"C:/R/R-2.13.2/library/Rcpp/include"      -O2 -Wall  -c file7e61645c.cpp -o file7e61645c.o
g++ -shared -s -static-libgcc -o file7e61645c.dll tmp.def file7e61645c.o C:/R/R-2.13.2/library/Rcpp/lib/i386/libRcpp.a -LC:/R/R-213~1.2/bin/i386 -lR

我的改编版本有一个与上面相同的编译参数,除了字符串“file7e61645c”被用户选择的名称(例如“testdll”)替换并且相关文件复制到更永久的位置。

在此先感谢您的帮助:)

4

1 回答 1

19

一些Rcpp用户对内联包及其cxxfunction(). 是的,它确实非常有用,而且它肯定进一步推动了Rcpp的采用,因为它使快速实验变得如此容易。是的,它允许我们在源代码中使用 700 多个单元测试。是的,我一直使用它来在这里演示示例,在rcpp-devel 列表中,甚至在演示文稿中。

但这是否意味着我们应该在每项任务中使用它?这是否意味着它没有“成本”,例如临时目录中的随机文件名等pp?Romain 和我在我们的文档中提出了不同的观点。

最后,就目前而言,动态加载的 R 模块的调试很困难。在(强制性)Writing R Extensions中有一个完整的部分关于它,Doug Bates 曾经或两次发布了一篇关于如何通过ESS和 Emacs 做到这一点的教程(尽管我总是忘记他在哪里发布它;曾经是rcpp上的 IIRC -开发列表)。

编辑 2012 年 7 月 7 日:

这是您的一步一步:

  • (前言:我已经使用 gcc 和 g++ 很多年了,即使我添加了 -g,我也不总是将 -O2 变成 -O0。我真的不确定你是否需要它,但正如你所要求的那样...... .)

  • 将环境变量 CXXFLAGS 设置为“-g -O0 -Wall”。有很多方法可以做到这一点,有些是平台相关的(例如 Windows 控制面板),因此不太通用和有趣。我~/.R/Makevars在 Windows 和 Unix 上使用。您可以使用它,或者您可以覆盖 R 的系统范围 $RHOME/etc/Makeconf 或者您可以使用 Makeconf.site 或 ... 查看完整文档 --- 但正如我所说,~/.R/Makevars这是我的首选方式,因为它不是干扰 R 之外的编译。

  • 现在每个编译 R 通过 R CMD SHLIB, R CMD COMPILE, R CMD INSTALL, ... 将使用。因此,您使用内联或本地包不再重要。继续内联...

  • 对于其余部分,我们主要遵循“编写 R 扩展”的“第 4.4.1 节在动态加载的代码中查找入口点”:

  • 使用 R -d gdb 启动另一个 R 会话。

  • 编译你的代码。为了

fun <- cxxfunction(signature(), plugin="Rcpp", verbose=TRUE, body='
   int theAnswer = 42;
   return wrap(theAnswer);
')

我明白了

[...]
Compilation argument:
 /usr/lib/R/bin/R CMD SHLIB file11673f928501.cpp 2> file11673f928501.cpp.err.txt 
 ccache g++-4.6 -I/usr/share/R/include -DNDEBUG   -I"/usr/local/lib/R/site- library/Rcpp/include"   -fpic  -g -O0 -Wall -c file11673f928501.cpp -o file11673f928501.o
g++-4.6 -shared -o file11673f928501.so file11673f928501.o -L/usr/local/lib/R/site-library/Rcpp/lib -lRcpp -Wl,-rpath,/usr/local/lib/R/site-library/Rcpp/lib -L/usr/lib/R/lib -lR
  • 调用 egtempdir()来查看临时目录,cd 到上面使用的这个临时目录和上面dyn.load()构建的文件:
 dyn.load("file11673f928501.so")
  • 现在通过发送中断信号来挂起 R(在 Emacs 中,从下拉菜单中选择一个简单的选项)。

  • 在 gdb 中,设置一个断点。上面的单个作业对我来说变成了第 32 行,所以

break file11673f928501.cpp 32
cont
  • 回到 R,调用函数:

    乐趣()

  • Presto,在我们想要的断点处的调试器中:

R> fun()

Breakpoint 1, file11673f928501 () at file11673f928501.cpp:32
32      int theAnswer = 42;
(gdb) 
  • 现在“只是”由你来发挥 gdb 的魔力

现在,正如我在第一次尝试中所说的那样,通过一个Rcpp.package.skeleton()可以为您编写的简单程序包(在我看来),这一切都会变得更容易,因为您不必处理随机目录和文件名。但各有各的...

于 2012-07-06T13:26:05.440 回答