专注于互联网--专注于架构

最新标签
网站地图
文章索引
Rss订阅

首页 »编程综合 » perl扩展名:Perl 对 C 的扩展接口 »正文

perl扩展名:Perl 对 C 的扩展接口

来源: 发布时间:星期一, 2009年9月7日 浏览:0次 评论:0
  引言

  本文面向 Perl 和 C 开发人员旨在通过对 Perl 和 C 的间 XS 扩展接口介绍让读者了解到通过 Perl C 种思路方法为了更好理解本文读者需要具备 Perl 和 C 编程经验并对 Unix 环境下库文件编译过程和 Makefile 语法有所了解

  什么是 XS 语言

  XS 是个用来在 Perl 和需要在 Perl 内使用 C 代码(或者 C 库)的间创建扩展接口描述文件格式XS 接口为 C 库链接创建了个静态链接到 Perl 或者能被 Perl 动态导入新库XS 接口描述是用 XS 语言写是 Perl 扩展接口核心部分

  当 Perl 代码 C XS 从 Perl 堆栈中获取参数将这些参数转化为 C 所要求正确格式相应 C 并将返回值转化为 Perl 参数格式压入 Perl 堆栈供读取或者直接修改 Perl 所提供变量值

  由于 Perl 提供了比 C 更为自由变量定义和规则在参数转换过程中XS 还必须验证参数合法性抛出异常(或返回 undef 或空值列表)根据参数数目和类型区别区别 C 提供面向对象接口等等

  XS 语言编译器叫做 xsubpp它为接口创建必要数据结构和关系xsubpp 根据 typemaps 来确定如何在 Perl 和 C 的间转换参数和返回值标准 Perl 库自带 typemap 定义了大部分常用 C 变量类型些特殊数据结构和类型需要开发人员通过自定义 typemap 来实现

  .XS 文件

  XSUB 解析

  XS 接口文件以 .xs 为后缀名里面定义了 Perl 和 C 的间接口XSUB 是 XS 接口基本结构单元通过 xsubpp 编译后每个 XSUB 都为相应 C 提供了 Perl 和 C 的间接口

  清单 1 . 个简单 .xs 文件

    
 # "EXTERN.h" 
 # "perl.h" 
 # "XSUB.h" 
    
 MODULE = TEST  PACKAGE = TEST 
 void 
 hello 
 CODE: 
 prf("Hello, world!\n"); 


  其中前 3个 # 声明:EXTERN.hperl.h 和 XSUB.h 应该始终出现在每个 XS 文件开头其后是其他头文件 # 声明

  MODULE= 定义了该 XS 文件所属 Perl 模块(.pm)个 .xs 文件中所有 MODULE= 都应该保持每个 MODULE= 的后则是对应 XSUB 定义直到文件结束或者下个 MODULE= 语句

  PACKAGE= 定义了该所在 Package当同个 .xs 文件需要被划分为多个 Package 时 PACKAGE= 则需要被显式指定PACKAGE= 应该和 MODULE= 放在起并紧随其后

  个最简单 XSUB 由 3部分(section)组成:返回值定义;XSUB 名和参数名;以及参数类型复杂 XSUB 还包括其他部分如 CODE:(代码段)IUPUT:(输入值)OUTPUT:(输出值)等等其中返回值和名必须位于每个 XSUB 开头分行书写并左对齐顶格其余部分格式则没有严格要求

  清单 2 .XSUB 格式

    
 double sin(x)    double         double 
 double x        sin(x)       sin(x) 
            double x        double x 
                   正确 


  Perl 变量堆栈和参数

  Perl 变量堆栈(argument stack)用于存放发送给 XSUB 参数值及其返回值XSUB 可以通过宏 ST(x) 访问该堆栈其中 ST(0) 为该堆栈起始地址

  清单 3 . 操作 ST(x)

    
 membername = (char*)SvPV(ST(2), na); 
 ST(0) = SVpv("Hello World", 0); 


  而宏 SP 代表当前 Perl 堆栈指针从 XSUB 返回时处理堆栈上数据

  清单 4 . 操作 SP

    
 EXTEND(SP, 2); 


  变量 RETVAL 是个特殊 C 变量类型对应于 C 返回值类型xsubpp 编译器会自动为每个 non-void 返回值类型声明该变量用于存放被 C 返回值通常情况下RETVAL 会作为对应 XSUB 返回值存放到 Perl 变量堆栈 ST(0)

  清单 5 .RETVAL 变量

    
  
 is_even(input) 
  input 
 CODE: 
 RETVAL = (input % 2  0); 
 OUTPUT: 
 RETVAL 


  注意: 当 XSUB 返回值为 void 时编译器不会为该声明 RETVAL 变量;当存在 PPCODE: 关键字时不能对 RETVAL 变量进行操作而应该直接操作对应 Perl 变量堆栈 ST(x)

  XSUB 些关键字

  OUTPUT: 关键字指定了当 XSUB 结束时应该返回给方 Perl 参数值在没有 CODE: 段和 PPCODE: 段时RETVAL 变量会被自动指定为 OUTPUT 变量否则需要显式指定 OUTPUT 变量该关键字也能用于指定输入参数为 OUTPUT 变量这在当体改变了某个输入参数值并希望将新值返回给方 Perl 情况下十分有用

  清单 6 .C 原型

    
 bool_t gettime(const char *host, time_t *timep); 


  其将指定 host 上当前系统时间存入指针 timp 对应地址中同时返回布尔型状态值

  清单 7 . 对应 XSUB 定义

    
 bool_t 
 gettime(host,timep) 
 char *host 
 time_t &timep 
 OUTPUT: 
 Timep 


  CODE: 关键字用于对相应 C 做额外操作处理此时 RETVAL 变量仍被声明但并不作为返回值除非被 OUTPUT: 关键字显式指定仍以上面 C gettime (host,timep) 为例如果在 Perl 代码中存在以下:

  Perl 代码

  $status = gettime( "localhost", $timep );

  其中 $status 和 $timep 都用于接收 C 返回值则需要对相应 C 做额外处理

  清单 9. 对应 XSUB 定义

    
 bool_t 
 gettime(host,timep) 
 char *host 
 time_t timep 
 CODE: 
 RETVAL = gettime( host, &timep ); 
 OUTPUT: 
 timep 
 RETVAL 


  通过元运算符 &当 xsubpp 编译器 C gettime传给 C 参数 &timep 是指向 time_t 指针 time_t *同时将得到时间值存储在 timep 中返回给 Perl

  PPCODE: 关键字是对 CODE: 补充用于直接操作 Perl 变量堆栈这在当 XSUB 存在多个返回值时十分有用此时必须在 PPCODE: 中显式将返回值列表压入堆栈顶需要注意在同个 XSUB 中 CODE: 和 PPCODE: 不能同时出现

  PPCODE: 通常直接操作 SP通过 PUSH* 宏将返回值列表压入 Perl 堆栈而不是将其作为返回值传送给 Perl因此其返回值类型般为 void用于告诉 xsubpp 编译器不需要声明和创建 VETVAL 变量

  清单 10.PPCODE: 关键字

    
 void 
 gettime(host) 
 char *host 
 PREINIT: 
 time_t timep; 
 bool_t status; 
 PPCODE: 
 status = gettime( host, &timep ); 
 EXTEND(SP, 2); 
 PUSHs(sv_2mortal(SViv(status))); 
 PUSHs(sv_2mortal(SViv(timep))); 


  通过 PUSH 宏将 C gettime 返回值 status 和 timep 依次压入 Perl 堆栈有了上面 XSUB 定义则在 Perl 代码中可以这样上面 C gettime

  Perl 代码

  ($status, $timep) = gettime("localhost");

  编译 XS 文件

  编译命令 h2xs

  使用 h2xs 来编译并生成 XS 扩展接口所必要系列文件h2xs 用于根据 C 头文件 .h 生成相应 Perl扩展其扩展模块名字由 -n 参数指定当没有 -n 参数时则自动使用第个 .h 头文件名字并将其首字母大写作为扩展模块名字

  表 1. h2xs 常用参数

参数名 介绍说明
-A --omit-autoload 忽略 autoload 机制
-O --overwrite-ok 允许覆盖已存在扩展文件
-n --name=module_name 指定扩展模块名字



  更为详细和完整参数列表可参阅相关文档 perldoc-h2xs

  生成文件

  当执行命令 “h2xs -A -n Mytest” 后系统在当前目录下创建个子目录 Mytest并在其下生成系列文件:MANIFESTMakefile.PLMytest.pmMytest.xsMytest.t 和 Changes

  MANIFEST

  MANIFEST 文件包含了在 Mytest 目录下创建所有文件名字

  清单 11 .MANIFEST 文件内容

    
 Changes 
 Makefile.PL 
 MANIFEST 
 Mytest.xs 
 ppport.h 
 README 
 Mytest.t 
 lib/Mytest.pm 
 mylib/Makefile.PL 
 mylib/test.c 
 mylib/test.h 


  Changes

  Changes 文件记录了扩展接口创建以及后续修改动作

  清单 12.Changes 文件内容

    
 Revision history for Perl extension Mytest. 
 
 0.01 Tue Jun 2 15:23:11 2009 
 - original version; created by h2xs 1.23 with options 
 -A -O -n Mytest ./Mytest/mylib/test.h 


  Makefile.PL

  Makefile.PL 文件是个 Perl 脚本用于自动生成 Makefile以创建扩展接口当执行 “Perl Makefile.PL” 命令后系统生成相应 Makfile然后执行 “make” 会在当前目录下生成 blib 子目录用于存放将要使用到共享库文件 (shared library)

  清单 13 . 个简单 Makefile.PL

    
use ExtUtils::MakeMaker; 
 # See lib/ExtUtils/MakeMaker.pm for details of how to influence 
 # the contents of the Makefile that is written. 
 WriteMakefile( 
 NAME     => 'Mytest', 
 VERSION_FROM => 'Mytest.pm', # finds $VERSION 
 LIBS     => [''],  # e.g., '-lm' 
 DEFINE    => '',   # e.g., '-DHAVE_SOMETHING' 
 INC     => '',   # e.g., '-I/usr//other' 
 ); 


  Mytest.pm

  Mytest.pm 文件是个模块文件定义了 Perl 如何加载该扩展接口当在 Perl 代码中出现 “use Mytest;” 时Perl 会在 @INC 中定义目录列表里搜索 Mytest.pm 并加载然后 Perl 代码就可以直接 Mytest.xs 扩展中定义 C

  清单 14 .Mytest.pm 框架

    
 package Mytest; 
 
 use 5.008008; 
 
 use strict; 
 use warnings; 
 
 require Exporter; 
 our @ISA = qw(Exporter); 
 our %EXPORT_TAGS = ( 'all' => [ qw( 
 )]); 
 
 our $VERSION = '0.01'; 
 require XSLoader; 
 XSLoader::load('Mytest', $VERSION); 
 
 # Preloaded methods go here. 
 1;  
 __END__ 
 # Below is the stub of documentation for your module. You better edit it! 


  Mytest.xs

  Mytest.xs 实现了 Perl 扩展接口通过该接口 Perl 代码可以对应 C 文件中定义实现

  Mytest.t

  Mytest.t 文件是代码测试脚本可以通过执行 “make test” 来测试扩展模块编译是否正确

  清单 15 .Mytest.t 框架

    
use Test::More tests => 4; 
 BEGIN { use_ok('Mytest') }; 
 
 # Insert your test code below, the Test::More module is useed here so read 
 # its man page ( perldoc Test::More ) for help writing this test script. 


  编译过程

  通常个 XS 扩展接口编译过程为以下几步:

  清单 16 . 编译步骤

    
 perl Makefile.PL   
 make          
 make test 
 make  


  首先运行 ”perl Makefile.PL” 在当前目录生成 Makefile;然后运行 ”make” 编译并创建所需库文件;的后用 ”make test” 测试编译结果是否正确;最后运行 ”make ” 将库文件安装到系统目录至此整个编译过程结束

  个 XS 例子

  在当前目录创建个子目录 Mytest在 Mytest 目录下创建子目录 mylib并将已写好 C 头文件和源代码放在 mylib 目录下

  清单 17 . 头文件 test.h

    
 # TESTVAL  3 
 extern double  add(, long); 
 extern   max(, ); 


  清单 18 . 源文件 test1.c

    
 # "./test.h" 
 double add( a, long b) 
 { 
  (a + b + TESTVAL); 
 } 


  清单 19 . 源文件 test2.c

    
 # "./test.h" 
  max( a,  b) 
 { 
  ((a>b)? a:b); 
 } 


  在 Mytest/mylib 目录下创建 Makefile.PL 文件以保证在 Mytest 目录运行 ”make” 时会自动该 Makefile.PL 并生成相应 Makefile

  清单 20 .Mytest/mylib 目录下 Makefile.PL

    
use ExtUtils::MakeMaker; 
 $Verbose = 1; 
 WriteMakefile( 
 NAME  => 'Mytest::mylib', 
 SKIP  => [qw(all  _lib dynamic dynamic_lib)], 
 clean => {'FILES' => 'libmylib$(LIB_EXT)'}, 
 ); 
 
 sub MY::top_targets { 
' 
 all ::  
 pure_all ::  
  :: libmylib$(LIB_EXT) 
 libmylib$(LIB_EXT): $(O_FILES) 
 $(AR) cr libmylib$(LIB_EXT) $(O_FILES) 
 $(RANLIB) libmylib$(LIB_EXT) 
'; 
 } 


  在 MY::top_targets 中通过 ar 将 mylib 子目录下 test1.o 和 test2.o 编译为静态库 libmylib.a并通过 ranlib 更新静态库 libmylib.a 符号索引表

  注意: $(AR) 和 $(RANLIB) 前面应该是 ‘ Tab ’ 而不是空格否则 Make 会报 “missing separator” 并终止编译

  在 Mytest 上级目录中执行命令 “h2xs -A -O -n Mytest ./Mytest/mylib/test.h” 以生成扩展接口系列文件

  注意:Perl 会提示覆盖 Mytest 目录并在 Mytest 中生成上节介绍系列文件这也是要将源文件放在 /Mytest/mylib/ 下原因以免被自动生成文件覆盖

  Perl 在 Mytest 下自动生成 Makefile.PL 并不知道子目录 mylib 存在因此需要修改该 Makefile.PL

  清单 21 . 修改 Mytest 目录下 Makefile.PL

    
 WriteMakefile( 
 'NAME'     => 'Mytest', 
 'VERSION_FROM' => 'Mytest.pm', # finds $VERSION 
 'LIBS'     => [''],  # e.g., '-lm' 
 'DEFINE'    => '',   # e.g., '-DHAVE_SOMETHING' 
 'INC'     => '',   # e.g., '-I/usr//other' 
 'MYEXTLIB'   => 'mylib/libmylib$(LIB_EXT)', 
 ); 


  指定了 MYEXTLIB 为 mylib 子目录下 libmylib.a

  清单 22 . 在 Makefile.PL 文件最后添加 MY::postamble

    
 sub MY::postamble { 
' 
 $(MYEXTLIB): mylib/Makefile 
 cd mylib && $(MAKE) $(PASSTHRU) 
'; 
 } 


  在 MY::postamble 中进入 mylib 子目录并运行其下 Makefile 进行编译以生成静态库 libmylib.a

  注意: ‘ cd ’ 前面应该是 ‘ Tab ’ 而不是空格否则 Make 会报 “missing separator” 并终止编译

  修改 MANIFEST 文件使其能够正确包含该扩展接口所有内容

  清单 23 . 修改 MANIFEST 文件

    
 mylib/Makefile.PL 
 mylib/test1.c 
 mylib/test2.c 
 mylib/test.h 


  修改 Mytest.xs 文件并添加定义

  清单 24 . 修改 # test.h

    
 # "mylib/test.h" 


  修改路径为 mylib/test.h 并将尖括号 <> 改为双引号””以使编译能正确找到 mylib 子目录下面 test.h 头文件

  清单 25 . 添加 add 和 max 定义

    
 double 
 add(a,b) 
  a 
 long b 
 
  
 max(a,b) 
   a 
   b 


  提供 Perl 和 C 的间接口使得 Perl 代码通过 Mytest.xs 中接口可以直接相应 C add(a,b) 和 max(a,b)

  在 Mytest 目录下运行 ”perl Makefile.PL” 生成 Makefile

  清单 26 . 运行 perl Makefile.PL

    
 % perl Makefile.PL 
 Checking  your kit is complete... 
 Looks good 
 Writing Makefile for Mytest::mylib 
 Writing Makefile for Mytest 
 % 


  编译器根据 Makefile.PL 自动生成相应 Makefile 和 mylib 子目录下 Makefile

  运行 ”make” 生成需要库文件

  清单 27 . 运行 make

    
 % make 
 gcc -c <compile_flag>  test1.c 
 gcc -c  <compile_flag> test2.c 
 ar cr libmylib.a test1.o test2.o 
 : libmylib.a 
 perl xsubpp -typemap typemap Mytest.xs > Mytest.xsc && mv Mytest.xsc Mytest.c 
 Please specy prototyping behavior for Mytest.xs (see perlxs manual) 
 gcc -c <compile_flag>  Mytest.c 
 rm -f blib/arch/auto/Mytest/Mytest.so 
 gcc <compile_flag> Mytest.o -o blib/arch/auto/Mytest/Mytest.so mylib/libmylib.a 
 
 chmod 755 blib/arch/auto/Mytest/Mytest.so 
 cp Mytest.bs blib/arch/auto/Mytest/Mytest.bs 
 chmod 644 blib/arch/auto/Mytest/Mytest.bs 
 Manying blib/man3/Mytest.3pm 
 % 


  修改 Mytest.t 文件添加测试代码

  清单 28 . 修改 Mytest.t

    
 is( &Mytest::add(1, 2), 6 ); 
 is( &Mytest::add(3, 1), 7 ); 
 is( &Mytest::max(1, 2), 2 ); 
 is( &Mytest::max(3, 1), 3 ); 


  测试代码以区别参数 Mytest::add 和 Mytest::max 并同预期结果比较以确认相应 C add 和 max 被正确

  运行命令 ”make test”确保所有测试结果正确

  清单 29 . 运行 make test

    
 % make test 
 PERL_DL_NONLAZY=1 /usr/bin/perl "-MExtUtils::Command::MM" "-e" "test_harness 
 (0, 'blib/lib', 'blib/arch')" *.t 
 Mytest....ok                                
 All tests successful. 
 Files=1, Tests=4, 0 wallclock secs ( 0.03 cusr + 0.01 csys = 0.04 CPU) 
 % 


  从结果可以看到测试全部通过介绍说明相应 C 并计算得到了正确结果

  运行命令 ”make 将生成库文件安装到系统目录中

  至此我们就能在自己 Perl 代码中直接 test1.c 和 test2.c 里面定义 add 和 max

  清单 30 .Perl 代码

    
 use Mytest; 
 my $sum = &Mytest::add(1, 2); 
 my $max = &Mytest::max(2, 3); 
 pr “Sum of 1 add 2 is: $sum\n”; 
 pr “Max of 2 and 3 is: $max\n”; 


  结束语

  本文介绍了如何在 Unix 上编写和编译 Perl 对 C 扩展接口 XS 使得 Perl 可以 C 代码(或者 C 库)中定义 读者通过本文介绍详细步骤可以自行编写 XS 扩展接口并编译成静态或动态库文件供 Perl 代码

0

相关文章

读者评论

发表评论

  • 昵称:
  • 内容: