Perl 嵌入

作者:John Quillan

本文描述了我将 Perl 嵌入到现有应用程序的经验。选择的应用程序 sc 是一个公共领域的,基于字符的电子表格,通常作为 Linux 发行版的一部分出现。 选择 sc 的原因有两个。 首先,我使用 sc 完成我所有的电子表格类任务。 其次,我对源码有点熟悉,因为我曾经添加过代码来格式化我想要的日期格式。 此外,我一直认为如果 sc 有某种宏语言就好了——所有东西都应该可以编程。

开始

我做的第一件事是获取 sc 6.21 源码并在我的机器上编译它。 这确保了一切从一开始就工作正常,在我开始修改代码之前。

接下来是将必要的代码添加到 sc.c 以嵌入 Perl 解释器。 其基本步骤是

  • 添加以下包含文件

            #include <EXTERN.h>
            #include <perl.h>
    
  • 添加 Perl 解释器的变量

            static PerlInterpreter *MyPerl;
    
  • 清单 1 中的代码放入 sc.c 文件的 main 函数中。

  • 更新 Makefile 以使用正确的参数。 这包括添加从以下命令导出的 CC 选项和链接器选项

            perl -MExtUtils::Embed -e ccopts
            perl -MExtUtils::Embed -e ldopts
    
    这些命令会为您提供编译 Perl 版本时使用的编译器和链接器选项。

不需要做其他任何事情;Perl 解释器现在已经在代码中了。 显然你还不能做任何事情,但你可以解决任何编译问题。 马上,我遇到了一些 #define 语句和 main 函数原型的问题。 EXTIF 是两个有问题的 #define。 我通过在原始 sc 代码中出现的任何地方将 “sc” 附加到它们的末尾来解决这些问题,使它们独一无二。 如果您是从头开始编写应用程序,那么在每个 #define 前面加上一个通用的前缀将是一个不错的主意。

另一方面,Perl 期望 main 有第三个参数 env,所以我添加了它。 我仍然不确定这个参数来自哪里,但它似乎没有造成任何问题。

运行 Perl 代码

一旦基本解释器成功编译,我就需要一种调用函数的方法。 我查看了 sc 源码,发现其中一个按键 ctrl-k 可以供我使用。 我将此用作我的 “调用 Perl” 键命令宏,定义了从 0 到 9 的宏。 当定义了名为 sc_macro_[0-9] 的预定义 Perl 子程序时,此组合将调用它们。 清单 2 中的代码添加了此功能。

函数 call_sc_perl_macro 首先使用 perl_get_cv 检查子程序是否存在。 如果未返回 null,则它调用具有名称 sc_macro_# 的函数,其中 # 是 0 到 9 之间的数字。

perl_call_va 函数来自 Sriram Srinivasan 的书 Advanced Perl Programming,由 O'Reilly 出版。 该代码用于加速我将 Perl 嵌入到 sc 的最终目标。 perl_call_va 的代码可以在文件 ezembed.c 中找到。

在编译 sc 后,我通过在文件 sc.pl 中创建虚拟宏来将一些数据写入临时文件来测试解释器。 一切都正常工作,这告诉我 Perl 解释器在 sc 内部工作。

自动化 sc

由于工作正常的 Perl 解释器嵌入到 sc 中并且能够调用 Perl “宏”,因此需要创建与 sc 中 C 函数的接口以完成有用的工作。 幸运的是,sc 的布局足够好,以至于在大多数情况下,您所要做的就是包装一个已经存在的函数并与它的内部命令解析器交互。

我首先想到可能有用的是移动当前单元格。 如果没有这种能力,我将只能对单个单元格进行操作,这不是很实用。 此外,它是代码中最不复杂的部分之一,并提供了一个良好的开端。

sc_forward_row 的代码显示在 清单 3 中,可以在 sc_perl.c 中找到。 在我描述这段代码之前,让我快速概述一下 Perl 如何处理标量。 每个标量都有许多信息,包括双精度数值,字符串值和指示哪些部分有效的标志。 就我们的目的而言,标量可以保存三种类型的值:整数值 (IV),双精度值 (NV) 和字符串值 (PV)。 对于任何标量值 (SV),您可以使用宏 SvIV,SvNV 和 SvPV 访问它们各自的值。

现在,在清单 3 代码中,XS 是一个 Perl 宏,用于定义函数。 dXSARGS 为 XSub 的其余部分设置了一些东西,例如包含传递给 Perl 参数堆栈上 Xsub 的项目数的变量 items。 如果参数计数不等于 1,则 XS_RETURN_IV 向 Perl 返回 1 以指示错误。 否则,Perl 参数堆栈的顶部元素 ST(0) 将转换为整数值并传递给 forwrow 函数。

请注意,所有 XSub 代码都是手动生成的。 其中一些工作可以使用 Perl 的 xsubpp 或名为 swig 的工具完成,但在这种情况下,我觉得自己编写代码更简单。

最后,使用以下语句告诉 Perl 解释器有关此 Xsub 的信息

newXS("sc_forward_row",sc_forward_row,"sc_perl.c");

第一个参数是 Perl 中子程序的名称。 下一个参数是实际的 C 例程(在这种情况下它们是相同的,但不必如此)。 最后一个参数是定义子程序的文件,用于错误消息。 我选择通过使用 Perl 脚本解析我的 sc_perl.c 文件来创建所有 newXS 函数,这样我就不必每次添加新的 XSub 都做两件事。

动态加载模块

我想要的下一件事是能够动态加载其他 Perl 扩展,例如 Tk 扩展,数据库扩展或任何其他可能有用的东西。 这需要在 perl_parse 中使用 xs_init 函数代替 NULL,如下所示。

perl_parse(MyPerl, xs_init, 2, my_argv, env);

要创建 xs_init,我使用了以下代码

perl -MExtUtils::Embed -e xsinit -- -o - >xs_init.c
xs_init 的功能是初始化静态链接的扩展模块。 我静态链接的唯一模块是 DynaLoader 模块。 通过此模块,我们可以动态加载其他所有内容。 当我最初这样做时,我遇到了许多问题。 它们与我使用的 Perl 版本 (5.003_07) 相关。 在我安装 5.004_04 之后,一切都正常工作了。
其他问题

我遇到的第一个问题之一是 Perl 将 yypars 重新定义为 Perl_yypars 这一事实。 我通过在我在 sc 的 yypars 使用的地方周围放置新的 #define 语句来解决此问题。 这产生了许多编译器警告,但确实允许代码正确编译。

我遇到的另一个问题是 SvIOK 和 SvNOK 宏。 这些检查 SV 是否为数字或整数,或者更准确地说,它们检查 SV 的双精度值部分在该代码点是否有效。

最初,我在我期望发送整数的任何代码周围都放置了 SvIOK 和 SvNOK 宏。 问题是这不会接受像下面这样的代码,

sc_put_num_val("34.3"); # this is in perl

因为它被传递了一个字符串值,并且 SV 的数字部分当时无效。 即使它不是有效的字符串,SvIV 和 SvNV 宏也会将其转换为数字。 我正在使用正则表达式从文件中解析字符串,并且我在 $1 中获得的值将是一个字符串,即使它是一个数字。 一旦我意识到 SvNV 会为我生成一个数字,我的测试脚本就开始工作了。

总结

此示例不是将 Perl 嵌入到应用程序的最干净的实现。 它旨在作为解决问题的快速解决方案。 通过嵌入式 Perl 解释器,sc 比以前强大了很多。 源代码中包含的一个示例是一个抵押贷款计算器,它从 CNN Financial News 网站获取利率。 借助所有可用的 Perl 模块,可能性是无限的。

资源

Perl Embedding
John Quillan 是凤凰城的软件工程师。 他做工具锻造。 不工作的时候,他喜欢山地自行车,与女友 Niki (Ohh) 共度时光以及在 Linux 下编程 Perl。 可以通过 quillan@doitnow.com 与他联系。
加载 Disqus 评论