dll这种方法在windows下才会用到,在*nix系统下,或者MAC OS中的类似方法不在本文的描述范围内。
首先,我们需要一个C语言的编译器,本文中我会选择DevCpp这个free的集成编译器,也许是为了符合R的精神(DevCpp的介绍在此处省略,需要的朋友可以到其主页上浏览)。接着你打开DevCpp,选择“文件”-“新建”-“工程”,选择“DLL”并且赋予工程名称为dllTest,选择“C工程”选项;随后点击“确定”按钮,将该DLL工程存在E:\dllTest目录下(可以依照您的实际情况选择目录,因为随后要用到这个目录所以这里强调一下)。
DevCpp默认会生两个文件:“dllmain.c”和“dll.h”,意思现在一目了然了。在dllmain.c文件中有一个HelloWorld的函数,自然也有DLL必备的DllMain函数了。暂且不必理会DllMain函数的内容,直接将其放在一边好了。现在我们发现HelloWorld函数其实调用了MessageBox函数——所以引用了头文件windows.h了,是一个WinApi。情况如下:
#include "dll.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
DLLIMPORT void HelloWorld ()
{
MessageBox (0, "Hello World from DLL!\n", "Hi", MB_ICONINFORMATION);
}
BOOL APIENTRY DllMain (HINSTANCE hInst /* Library instance handle. */ ,
DWORD reason /* Reason this function is being called. */ ,
LPVOID reserved /* Not used. */ )
{
switch (reason)
{
case DLL_PROCESS_ATTACH:
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
/* Returns TRUE on success, FALSE on failure */
return TRUE;
}
现在我们再看看头文件dll.h中的内容,也相当的简单:
#ifndef _DLL_H_
#define _DLL_H_
#if BUILDING_DLL
# define DLLIMPORT __declspec (dllexport)
#else /* Not BUILDING_DLL */
# define DLLIMPORT __declspec (dllimport)
#endif /* Not BUILDING_DLL */
DLLIMPORT void HelloWorld (void);
#endif /* _DLL_H_ */
对上面的文件暂且不需要修改,事实上这时候我们可以直接“ctrl+F11”,用来编译生成dll文件。发现结果一切ok!
随后我们在路径E:\dllTest下发现我们需要的dllTest.dll文件了。这时候将R打开启动,建立一个R脚本,包含如下内容:
(当然这里你可以使用函数setwd,将默认目录设置为E:\\dllTest啦)
dyn.load("E:\\dllTest\\dllTest.dll")
Hello<-function()
{
.C("HelloWorld") # 封装并调用DLL中的函数HelloWorld了。
}
Hello() # 是不是出现了一个对话框呢!大功告成!
dyn.unload("E:\\dllTest\\dllTest.dll") # 做事情要有始有终哦!
不过话说回来了,一般使用C语言(或者其他编译性质的语言)是为了提高执行的效率,这种简单的调用似乎不足以显示这种方法的好处,那么下面就结合R手册上面的那个例子来改编一下下啦。(见手册R-exts第56、57页)
于是,将dllmain.c文件添加一些内容,变成如下:
/* Replace "dll.h" with the name of your header */
#include "dll.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
DLLIMPORT void HelloWorld ()
{
MessageBox (0, "Hello World from DLL!\n", "Hi", MB_ICONINFORMATION);
}
// 添加函数convolve了
DLLIMPORT void convolve(double *a, int *na, double *b, int *nb, double *ab)<br />
{<br />
int i, j, nab = *na + *nb - 1;<br />
for(i = 0; i < nab; i++)<br />
ab[i] = 0.0;<br />
for(i = 0; i < *na; i++)<br />
for(j = 0; j < *nb; j++)<br />
ab[i + j] += a[i] * b[j];<br />
}<br />
<br />
BOOL APIENTRY DllMain (HINSTANCE hInst /* Library instance handle. */ ,<br />
DWORD reason /* Reason this function is being called. */ ,<br />
LPVOID reserved /* Not used. */ )<br />
{<br />
switch (reason)<br />
{<br />
case DLL_PROCESS_ATTACH:<br />
break;<br />
<br />
case DLL_PROCESS_DETACH:<br />
break;<br />
<br />
case DLL_THREAD_ATTACH:<br />
break;<br />
<br />
case DLL_THREAD_DETACH:<br />
break;<br />
}<br />
<br />
/* Returns TRUE on success, FALSE on failure */<br />
return TRUE;<br />
}<br />
同时,将dll.h也增加一行内容,结果如下:<br />
<br />
#ifndef _DLL_H_<br />
#define _DLL_H_<br />
<br />
#if BUILDING_DLL<br />
# define DLLIMPORT __declspec (dllexport)<br />
#else /* Not BUILDING_DLL */<br />
# define DLLIMPORT __declspec (dllimport)<br />
#endif /* Not BUILDING_DLL */<br />
<br />
<br />
DLLIMPORT void HelloWorld (void);<br />
<br />
// 添加如下一行<br />
DLLIMPORT void convolve(double *a, int *na, double *b, int *nb, double *ab);<br />
<br />
<br />
#endif /* _DLL_H_ */
接着立刻重新编译一次,一切ok。胜利在望了!随后立刻将DLL中的函数封装一下下,立刻试一试,发现立刻获得了成功
dyn.load("E:\\dllTest\\dllTest.dll")
conv <- function(a, b)
.C("convolve", # 同样开始封装并引用DLL中的函数
as.double(a), # 这样子可以方便随后使用,减少麻烦
as.integer(length(a)),
as.double(b),
as.integer(length(b)),
ab = double(length(a) + length(b) - 1))$ab
conv(1:10, 2:3) # 这行下面会有输出,此处省略
dyn.unload("E:\\dllTest\\dllTest.dll")
至此,R调用DLL的最简易入门示例就此结束,也许可以多尝试一下其他例子吧,比如SPlus手册上面的那个arsim的例子。
补记:
1、在李东风老师的主页上面主要说的是BC编译器下面的例子,也许会让一些类似我这种只会使用鼠标操作的人有点无所适从的,所以才有了上面的这个例子;
2、我个人比较反对使用这种方式,毕竟R的pkgs非常完备了,该有的算法基本上都有了,而且R本来就不是用来做这种体力活的,还是原型开发好了找C或者Fortran吧。当然,如果您要写自己的pkg就另当别论了;
3、这个例子中使用的方法只是调用外部函数最入门的一种了,除了使用.C函数之外,还有
.C(name, ..., NAOK = FALSE, DUP = TRUE, PACKAGE)
.Fortran(name, ..., NAOK = FALSE, DUP = TRUE, PACKAGE)
.External(name, ..., PACKAGE)
.Call(name, ..., PACKAGE)
.External.graphics(name, ..., PACKAGE)
.Call.graphics(name, ..., PACKAGE)
这些函数,都可以类似尝试一下。不过尤其要注意在和R交互使用时候的一些细节问题,主要材料当然是R-exts手册了,参考一下R-ints就更好了。