这次我要演讲的主题是Perl&XS,是描述如何用c/c++这种语言方便的编写perl扩展,有兴趣的朋友不妨互相切磋切磋哟,这里有几篇实例供大家演练一番。
四、二个样例
1,创建模板
[cnangel@localhost works]$module-starter
--module=perlxs::test -mi \
--author =Cnangel --email=junliang.li#alibaba-inc.com
--license=GPLv3
Created starter directories and files
[cnangel@localhost works]$cd perlxs-test/
[cnangel@localhost perlxs-test]$ ll
total 24
drwxrwxr-x 3 cnangel cnangel 4096 09-05 13:14
lib
drwxrwxr-x 2 cnangel cnangel 4096 09-05 13:14
t
-rw-rw-r-- 1 cnangel cnangel 196 09-05 13:14 Makefile.PL
-rw-rw-r-- 1 cnangel cnangel 111 09-05 13:14 Changes
-rw-rw-r-- 1 cnangel cnangel 1344 09-05 13:14
README
-rw-rw-r-- 1 cnangel cnangel 92 09-05 13:14 MANIFEST
2,编辑Makefile.PL
生成的Makefile.PL内容如下:
use inc::Module::Install;
name
'perlxs-test';
all_from 'lib/perlxs/test.pm';
author
'Cnangel <junliang.li#alibaba-inc.com>';
license
'GPLv3';
build_requires 'Test::More';
auto_install;
WriteAll;
模块inc::Module::Install是一个离线的模块,随着第一次运行perl Makefile.PL,便会产生,该工程有了模块inc::Module::Install不会因为平台不同、运行环境不同而在编译过程中发生模块依赖。
读者可以根据Module::Install模块的用法,添加相应的方法,Module::Install模块有如下方法:
(1)包相关信息
name
all_from
abstract
abstract_from
author
author_from
version
version_from
license
license_from
perl_version
perl_version_from
(2)包依赖相关信息
recommends:运行需要依赖的模块,没有也可以,最好有
requires:运行必须依赖的模块
test_requires:测试需要依赖的模块
configure_requires:配置需要依赖的模块
requires_external_bin:需要依赖的命令
若要使用XS,需要使用C/C++的源代码库,则需要使用:
cc_files:C/C++源码的文件
cc_inc_paths:include头文件路径
若是提供的lib库,则需要:
cc_lib_paths:lib库路径
cc_lib_links:链接lib库的名字
甚至可以指定编译的选项:
cc_optimize_flags
可以检查一些环境:
can_use
can_run
can_cc
(3)依赖包信息检查
如果使用了其他非Perl的库,这些库是如何检查的呢?有2个perl模块提供了检查库是否存在的方法:
Devel::CheckLib
Module::Install::CheckLib
模块Devel::CheckLib会用方法check_lib_or_exit来调用库,如下所示:
check_lib_or_exit( lib => [ 'log4cpp',
'libconfig' ], header => 'libconfig.h' );
模块Module::Install::CheckLib
checklibs lib => [ 'log4cpp', 'libconfig'
], header => 'libconfig.h';
相对来说,Module::Install::CheckLib利用了Module::Install的框架,可以使用离线兼容的模式来检查,是个不错的选择。
(4)包的安装
对于包的安装,无外乎数据、可执行文件和库,Module::Install也提供了几种方法供包的安装:
installdirs, install_as_*
no_index
WriteAll
最终Makefile.PL文件如下所示:
use strict;
use lib '.';
use inc::Module::Install;
name 'perlxs-test';
all_from 'lib/perlxs/test.pm';
author 'Cnangel
<junliang.li#alibaba-inc.com>';
license 'GPLv3';
requires 'File::Find' => 1.00;
requires 'Conf::Libconfig' => 0.010;
test_requires
'Test::Exception' => 0.27;
test_requires
'Test::Deep' => 0.103;
checklibs lib => [qw(check log4c)],
header => [qw(check.h log4c.h)];
cc_inc_paths
'/usr/include',
'/usr/local/include';
cc_lib_paths
'/usr/lib' , '/usr/lib64', '/usr/local/lib', '/usr/local/lib64';
cc_lib_links
'check', 'log4c';
can_cc or die 'This module requires
C compiler.';
no_index directory => qw(t inc);
auto_install();
WriteAll();
如果在lib/perlxs/test.pm下没有定义license和perl version,则会提示一些警告信息;
若库check和log4c以及其头文件没有的话,则执行命令perl Makefile.PL失败后直接退出。
修正的办法有很多,不是用all_from 方法,或者在test.pm模块里面添加对应信息。而库check和log4c的缺失可以下载相关源文件或者安装二进制包即可。
3,建立test.xs
根据前面所述,可以使用一些工具建立模板,比如h2xs,得到内容如下:
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"
#include "const-c.inc"
MODULE = perlxs::test PACKAGE = perlxs::test
INCLUDE: const-xs.inc
接着const-c.inc文件里面写C相关函数,const-xs.inc写XS语言接口。为了不产生多余的两个文件,直接分别替换#include "const-c.inc"和INCLUDE: const-xs.inc两行:
int lc(char *str, size_t strLen)
{
if (strLen == 0) return 1;
int i = 0;
for (; i < strLen; i++) {
if (str[i] >= 'A' && str[i] <= 'Z') {
str[i] = str[i] + ' ';
}
}
return 0;
}
int uc(char *str, size_t strLen)
{
if (strLen == 0) return 1;
int i = 0;
for (; i < strLen; i++) {
if (str[i] >= 'a' && str[i] <= 'z') {
str[i] = str[i] - ' ';
}
}
return 0;
}
然后新建一个perl函数lc和uc;值得注意的是,如果直接写成lc和uc会和上面的函数冲突,解决冲突的办法是使用前面提到的前缀方式:
PREFIX = perlxs_
开始写XS函数替换INCLUDE: const-xs.inc了,内容如下:
SV *
perlxs_lc(str)
char *str
PREINIT:
SV *sv = newSV(0);
CODE:
{
if (str) {
lc(str, strlen(str));
sv = newSVpvn(str,
strlen(str));
}
RETVAL = sv;
}
OUTPUT:
RETVAL
SV *
perlxs_uc(str)
char *str
PREINIT:
SV *sv = newSV(0);
CODE:
{
if (str) {
uc(str, strlen(str));
sv = newSVpvn(str,
strlen(str));
}
RETVAL = sv;
}
OUTPUT:
RETVAL
4,修改test.pm
修改perl模块:lib/perlxs/test.pm
除了添加一些pm的特征和pod文档外,还需要加载我们刚才编写的库(最终代码会编译成动态链接库的形式),形式如下:
require XSLoader;
XSLoader::load('perlxs::test', $VERSION);
至此,样例基本完成,可以使用:
perl Makefile.PL
make
make test
末了,根据实际情况再做调整,所示如下:
package perlxs::test;
use 5.006001;
use warnings;
use strict;
use Carp;
require Exporter;
our @ISA = qw(Exporter);
our %EXPORT_TAGS = ( 'all' => [ qw(
) ] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
our @EXPORT = qw(
mylc
myuc
);
our $VERSION = '0.01';
require XSLoader;
XSLoader::load('perlxs::test', $VERSION);
sub mylc {
my $str = shift;
return mylc($str);
}
sub myuc {
my $str = shift;
return myuc($str);
}
1;
__END__
...
至此,c编写perl扩展的样例基本完成了。
5,C++库的设计
比如有一个c++类Goods,其中有add、update、drop三种方法。
(1)XS头部
#ifdef __cplusplus
extern "C" {
#endif
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#ifdef __cplusplus
}
#endif
在XS文件内包含c++类Goods的头文件goods.h:
#include <goods.h>
Using namespace goods;
(2)申明包名
MODULE = China::Goods PACKAGE = China::Goods
(3)构造对象
Goods *
Goods::new(const char *cfgfile)
此处是直接调用了Goods的构造函数,类似于:
Goods goods;
(4)定义类型
由于perl并不识别Goods *是何物,所以需要在工程下建立一个typemap文件,里面存放所有perl不能识别的c或c++的各种数据结构和本身perl类型的一一对应关系;此处类型映射关系如下所示:
TYPEMAP
Goods * O_OBJECT
由于没有输入类型和输出类型的映射,这里忽略过。
(5)方法重载
int
Goods::add(int goodsid)
int
Goods::update(int goodsid)
int
Goods::drop(int goodsid)
(6)析构
和C++一样,perl也提供自动析构的函数,如下所示:
void
Goods::DESTROY()
值得注意的是,只需要在模块(pm文件)里面加入:
require XSLoader;
XSLoader::load('Conf::Libconfig', $VERSION);
而无需额外的函数(除非另外想实现其它的功能)。
C++编写 perl扩展的基本样例也算完成了。
五、总结
若是一般系统管理员,不需要特别掌握XS接口编程语言,一般纯perl的代码基本上就能够满足系统设计的要求,但如果对于一个开发者或者更高级层次的设计者来说,perl扩展所带来的效率,以及它的设计模式带来的灵活性是不可估量的。开发者们可以广泛地利用各种c/c++库做自己的业务,扩展自己的系统。
用C/C++写perl扩展并不是很困难,熟悉perlapi后,和c/c++一样,重点在于设计和内存的管理上。如果你是一位从未接触perl而写过不少c/c++项目的工程师,可以尝试使用perl,体会它的便捷;如果你是一位系统管理员,也可以尝试写写perl扩展,在不断锻炼你c/c++的写作能力的同时,你会感受到perl扩展的高效以及perl的强大,同时还能接触到perl语言的核心。
希望大家越用perl越开心!