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 文件 其中前 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 格式 Perl 变量堆栈和参数 Perl 变量堆栈(argument stack)用于存放发送给 XSUB 参数值及其返回值XSUB 可以通过宏 ST(x) 访问该堆栈其中 ST(0) 为该堆栈起始地址 清单 3 . 操作 ST(x) 而宏 SP 代表当前 Perl 堆栈指针当从 XSUB 返回时处理堆栈上数据 清单 4 . 操作 SP 变量 RETVAL 是个特殊 C 变量它类型对应于 C 返回值类型xsubpp 编译器会自动为每个 non-void 返回值类型声明该变量用于存放被 C 返回值通常情况下RETVAL 会作为对应 XSUB 返回值存放到 Perl 变量堆栈 ST(0) 清单 5 .RETVAL 变量 注意: 当 XSUB 返回值为 void 时编译器不会为该声明 RETVAL 变量;当存在 PPCODE: 关键字时不能对 RETVAL 变量进行操作而应该直接操作对应 Perl 变量堆栈 ST(x) XSUB 些关键字 OUTPUT: 关键字指定了当 XSUB 结束时应该返回给方 Perl 参数值在没有 CODE: 段和 PPCODE: 段时RETVAL 变量会被自动指定为 OUTPUT 变量否则需要显式指定 OUTPUT 变量该关键字也能用于指定输入参数为 OUTPUT 变量这在当体改变了某个输入参数值并希望将新值返回给方 Perl 情况下十分有用 清单 6 .C 原型 其将指定 host 上当前系统时间存入指针 timp 对应地址中同时返回布尔型状态值 清单 7 . 对应 XSUB 定义 CODE: 关键字用于对相应 C 做额外操作处理此时 RETVAL 变量仍被声明但并不作为返回值除非被 OUTPUT: 关键字显式指定仍以上面 C gettime (host,timep) 为例如果在 Perl 代码中存在以下: Perl 代码 $status = gettime( "localhost", $timep ); 其中 $status 和 $timep 都用于接收 C 返回值则需要对相应 C 做额外处理 清单 9. 对应 XSUB 定义 通过元运算符 &当 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: 关键字 通过 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 Changes 文件记录了扩展接口创建以及后续修改动作 清单 12.Changes 文件内容 Makefile.PL Makefile.PL 文件是个 Perl 脚本用于自动生成 Makefile以创建扩展接口当执行 “Perl Makefile.PL” 命令后系统生成相应 Makfile然后执行 “make” 会在当前目录下生成 blib 子目录用于存放将要使用到共享库文件 (shared library) 清单 13 . 个简单 Makefile.PL Mytest.pm Mytest.pm 文件是个模块文件定义了 Perl 如何加载该扩展接口当在 Perl 代码中出现 “use Mytest;” 时Perl 会在 @INC 中定义目录列表里搜索 Mytest.pm 并加载然后 Perl 代码就可以直接 Mytest.xs 扩展中定义 C 清单 14 .Mytest.pm 框架 Mytest.xs Mytest.xs 实现了 Perl 扩展接口通过该接口 Perl 代码可以对应 C 文件中定义实现 Mytest.t Mytest.t 文件是代码测试脚本可以通过执行 “make test” 来测试扩展模块编译是否正确 清单 15 .Mytest.t 框架 编译过程 通常个 XS 扩展接口编译过程为以下几步: 清单 16 . 编译步骤 首先运行 ”perl Makefile.PL” 在当前目录生成 Makefile;然后运行 ”make” 编译并创建所需库文件;的后用 ”make test” 测试编译结果是否正确;最后运行 ”make ” 将库文件安装到系统目录至此整个编译过程结束 个 XS 例子 在当前目录创建个子目录 Mytest在 Mytest 目录下创建子目录 mylib并将已写好 C 头文件和源代码放在 mylib 目录下 清单 17 . 头文件 test.h 清单 18 . 源文件 test1.c 清单 19 . 源文件 test2.c 在 Mytest/mylib 目录下创建 Makefile.PL 文件以保证在 Mytest 目录运行 ”make” 时会自动该 Makefile.PL 并生成相应 Makefile 清单 20 .Mytest/mylib 目录下 Makefile.PL 在 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 指定了 MYEXTLIB 为 mylib 子目录下 libmylib.a 清单 22 . 在 Makefile.PL 文件最后添加 MY::postamble 在 MY::postamble 中进入 mylib 子目录并运行其下 Makefile 进行编译以生成静态库 libmylib.a 注意: ‘ cd ’ 前面应该是 ‘ Tab ’ 而不是空格否则 Make 会报 “missing separator” 并终止编译 修改 MANIFEST 文件使其能够正确包含该扩展接口所有内容 清单 23 . 修改 MANIFEST 文件 修改 Mytest.xs 文件并添加定义 清单 24 . 修改 # test.h 修改路径为 mylib/test.h 并将尖括号 <> 改为双引号””以使编译能正确找到 mylib 子目录下面 test.h 头文件 清单 25 . 添加 add 和 max 定义 提供 Perl 和 C 的间接口使得 Perl 代码通过 Mytest.xs 中接口可以直接相应 C add(a,b) 和 max(a,b) 在 Mytest 目录下运行 ”perl Makefile.PL” 生成 Makefile 清单 26 . 运行 perl Makefile.PL 编译器根据 Makefile.PL 自动生成相应 Makefile 和 mylib 子目录下 Makefile 运行 ”make” 生成需要库文件 清单 27 . 运行 make 修改 Mytest.t 文件添加测试代码 清单 28 . 修改 Mytest.t 测试代码以区别参数 Mytest::add 和 Mytest::max 并同预期结果比较以确认相应 C add 和 max 被正确 运行命令 ”make test”确保所有测试结果正确 清单 29 . 运行 make test 从结果可以看到测试全部通过介绍说明相应 C 被并计算得到了正确结果 运行命令 ”make ”将生成库文件安装到系统目录中 至此我们就能在自己 Perl 代码中直接 test1.c 和 test2.c 里面定义 add 和 max 了 清单 30 .Perl 代码 结束语 本文介绍了如何在 Unix 上编写和编译 Perl 对 C 扩展接口 XS 使得 Perl 可以 C 代码(或者 C 库)中定义 读者通过本文介绍详细步骤可以自行编写 XS 扩展接口并编译成静态或动态库文件供 Perl 代码 0
相关文章读者评论发表评论 |
|