废话般的引言
脚本语言的牛叉在于其将一般语言的“编辑-编译-链接-运行”的过程改变为“编辑-运行”的简单方式,使其在系统管理层面有广泛的应用,shell script便是有力的证明。此外,脚本语言都有丰富的三方库,应用场景更是更加广泛。从web server到图像处理再到电脑游戏,只要有相应的三方库,脚本语言变能发挥“胶水”的作用,将各种库“粘合”起来,极大降低了开发的代价。
Perl作为一门古老的、由语言学家发明语言,在文字处理方面Perl有着无可比拟的优势,在互联网初期,perl更是CGI的必备工具。Perl也有强大的三方库,CPan上几乎有你想要的任何东西。其实,我们也可以开发自己的perl三方库。今天这篇文章就做一个小小的尝试,抛砖引玉!
需要知道的介绍
在C/C++中,如果我们需要使用一个库的时候,一般需要一个.h头文件和动态或者静态链接库,前者定义了接口,后者封装了实现。同样,我们也可以用一个C/C++的头文件和库,封装一个perl的package。可以把这个过程看作从一个语言到另一个语言的映射,完成这个映射需要做两件事情:一是数据类型的映射,二是接口的映射。完成了这两项映射,也就基本能够在一种语言中使用另外一个语言的库了。
XS是一种用于描述接口的文件格式,当我们希望把我们的C/C++库映射成Perl的package时,需要在一个.xs文件中描述接口的映射。另外,我们还需要进行数据类型的映射,下文提到的perlobject.map可以在这里找到。
C++中的鸭子
我们将以一只鸭子作为今天的例子。我们定义一个Duck类,封装一下鸭子的名字,再给出一个swim的方法,其中我们会使用stl的数据类型作为成员变量,但是我们的接口不会包含复杂的数据类型(因为这个现成的数据类型文件只定义了基本的数据类型映射)。好吧,这只鸭子是这样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
| /*
Duck.h ---
*/
#ifndef INCLUDED_DUCK_H
#define INCLUDED_DUCK_H 1
#include < string >
using std::string;
class Duck
{
public:
Duck(char*);
const char* getName();
void swim();
~Duck(){}
private:
bool swimming;
string name;
};
#endif /* INCLUDED_DUCK_H */
/*
Duck.cpp ---
*/
#include "Duck.h"
#include < cstdio >
using namespace std;
Duck::Duck(char* n) :
swimming(false), name(n)
{
}
const char* Duck::getName()
{
return name.c_str();
}
void Duck::swim()
{
if (!swimming)
{
printf("%s, ok .. go swimming\n", name.c_str());
swimming = true;
}
else
{
printf("%s is already swimming , stop\n", name.c_str());
swimming = false;
}
return;
} |
然后我们编译他们,得到libduck.so
g++ -g -Wall -fpic -shared -o libduck.so Duck.cpp
这样,我们就可以拿着这个libduck.so和Duck.h进入下一个流程,加工我们的Perl Package了!当然,你也可以写一个test.cpp来测试下这个.h和.so能不能用,需要注意的是,需要指定.so的路径
export LD_LIBRARY_PATH=/path/to/the/.so/:$LD_LIBRARY_PATH
游向Perl的鸭子
这里我们使用h2xs来创建这个Perl Package的初始环境。h2xs,顾名思义就是把.h转换为.xs,不过这里 我们还是手动来创建:
h2xs -A -n DuckPackage
然后,DuckPackage目录下就有了一些东西,我们需要关注的是DuckPackage.xs, Makefile.PL。DuckPackage.xs是定义接口映射的,Makefile.PL是一个Perl脚本,在里头定义编译和链接相关的选项。
接着,把之前提到的perlobject.map文件复制到当前目录中,把Duck.h和libduck.so也放进来。再创立一个文件叫typemap,内容如下:
TYPEMAP
Duck* O_OBJECT
perlobject.map定义了C/C++基础数据类型到Perl的映射,这也是为什么Duck.h中的的接口都使用基础数据类型,当然你也可以自己做string、map、set等等到Perl数据类型的映射,做好了麻烦告诉我一下~
好,我们可以开始修改DuckPackage.xs了,以下是修改完成后的样子,粗体是修改的部分
#ifdef __cplusplus
extern “C”{
#endif
#include “EXTERN.h”
#include “perl.h”
#include “XSUB.h”
#ifdef __cplusplus
}
#endif
#include “ppport.h”
#include <Duck.h>
using namespace std;
MODULE = DuckPackage PACKAGE = DuckPackage
Duck*
Duck::new(char * name)
char*
Duck::getName()
void
swim()
void
Duck::DESTROY()
为什么要加上extern “C”呢?因为我们是C++!接下来你会看到,在编译的时候使用我们使用g++。所以,对于那些Perl提供的C Api,我们要用extern “C”把他们包围。在extern “C”之外,我们就可以把Duck.h include进来了。对于一般的成员方法,映射的写法跟.h里头没有什么区别,注意两点就好:1. 末尾没有分号;2. 参数一定要写全类型+参数名。对于构造函数和析构函数,按照上述的方法写就好了~~
接下来看Makefile.PL:
use 5.008005;
use ExtUtils::MakeMaker;
$CC = “g++”;
# See lib/ExtUtils/MakeMaker.pm for details of how to influence
# the contents of the Makefile that is written.
WriteMakefile(
NAME => ‘DuckPackage’,
VERSION_FROM => ‘lib/DuckPackage.pm’, # finds $VERSION
PREREQ_PM => {}, # e.g., Module::Name => 1.1
($] >= 5.005 ? ## Add these new keywords supported since 5.005
(ABSTRACT_FROM => ‘lib/DuckPackage.pm’, # retrieve abstract from module
AUTHOR => ‘A. U. Thor <xiafei@localdomain>’) : ()),
LIBS => ['-L./ -lduck'], # e.g., ‘-lm’
DEFINE => ”, # e.g., ‘-DHAVE_SOMETHING’
INC => ‘-I.’, # e.g., ‘-I. -I/usr/include/other’
# Un-comment this if you add C files to link with later:
# OBJECT => ‘$(O_FILES)’, # link all the C files too
‘CC’ => $CC,
‘LD’ => ‘$(CC)’,
‘XSOPT’ => ‘-C++’,
‘TYPEMAPS’ => ['perlobject.map' ],
);
Makefile.PL没有什么好说的,注意LIBS和INC两个地方别写错了就好。
然后,我们再写一个test.pl吧,同样放在这个目录下。
1
2
3
4
5
6
7
| use DuckPackage;
my $duck = new DuckPackage("Dan");
my $name = $duck->getName();
$duck->swim();
$duck->swim();
print "$name\n"; |
好了,我们可以开始了!
perl Makefile.PL
make
make test
如果它告诉你Result: PASS,那么恭喜恭喜,可以接着make install了。如果不过,再回头看看文章吧,good luck!
p.s. 原文地址:http://chunyemen.org/archives/493, 欢迎访问纯爷们的小生活!