将Perl解释器嵌入应用程序中,扩展功能[原创]
将Perl解释器嵌入应用程序中,扩展功能[原创]
summary:
本文讲述在应用程序中应嵌入Perl解释器的方法和理由
如果系统需要提供扩展性,让用户可以轻松的加入一些对自己很有用的功能,
在应用程序中嵌入一个解释器无疑是最好的选择,你当然可以提供API让用户扩展,
但解释型语言简单易用,一般都带有自动垃圾收集,对用户更合适。
这个解释器可以是任何语言的,但以比较流行的语言为最佳,这样用户扩展功能
时比较容易,因为参考资料和能提供帮助的人都比较多。
近来需要在一个应用中提供一个扩展就是让用户可以写一些算术公式,公式中的
一些变量是应用程序内部的数据,显然公式肯定是千奇百怪的,不可能全在应用程序
内部实现,只能提供一个解释器,来执行这些公式,自己实现不会有什么困难,但
时间和质量将不能取得最佳效果,所以要考虑从Free的软件项目中获益了,除了C++和Lisp,
我最喜欢Perl,因为Perl项目是自由的,而写Perl程序时更自由。
好了,下面就说说怎样嵌入Perl解释器(也可以嵌入你喜欢的其他语言的解释器,请阅读相关手册),
Perl的手册页:perlembed,perlapi,perlcall,perlguts都是不错的参考资料。
一,需要的包含文件和导入库文件都在perl的'lib'目录下的'CORE'中,编译时要在最后
加上如下选项:-L"PERL_ROOT/lib/CORE" -lperl58 -I"PERL_ROOT/lib/CORE",
PERL_ROOT请替换为自己的perl根目录。
程序中包含如下代码:
#include <perl.h> // 放在最后很重要
#include <EXTERN.h>
static PerlInterpreter *my_perl;
二,实现构造、初始Perl解释器和析构、清除Perl解释器的两个函数
/*!
\brief 用来构造Perl解释器
在执行 perl_parse 时,如果直接用perl_parse(my_perl,NULL,0,&arg,NULL); ///< char *arg="";
会停下等待你输入,使用"-e"就可以避免这个问题了,参见 char* dummyArg[] = {"","-e",""};
*/
void __init_pe()
{
char* dummyArg[] = {"","-e",""};
PERL_SYS_INIT3(NULL, NULL, NULL);
my_perl = perl_alloc();
perl_construct(my_perl);
perl_parse(my_perl, NULL, 3, dummyArg, NULL);
PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
perl_run(my_perl);
}
/*!
用来销毁Perl解释器
*/
void __uninit_pe()
{
perl_destruct(my_perl);
perl_free(my_perl);
PERL_SYS_TERM();
}
三,执行perl代码,使用的是eval_pv,还有很多种选择,这个是最简单。
将一个perl程序传给eval_pv执行时,主要的是怎样和perl解释器交换数据,
将应用程序中的数据放入perl解释器中,执行perl脚本后,再将结果取出来。
标量(SV)的存和取相对最简单,也是最基础的。数组(AV)和哈希(HV)内部
使用的都是SV。
标量的例子,很简单:
/// $a=3也可以get_sv("a",true)创建出来:
eval_pv("$a=3;$a**=9;$a=log($a);",true); ///< 执行perl程序
std::cout << "a=" << SvIV(get_sv("a",false)) << '\n'; ///< 取出结果
我主要举个操作perl数组的例子,可以让用户扩展写出算术表达式,对应用程序中的数据进行处理,
要用到的perl API:get_av,av_fill,av_store,newSVnv,av_len,av_fetch,SvOK,SvNV
首先,要将变量的命名规范先处理好,比如参数用a1,a2,a3...,结果用res,这里都是数组,表达式中要符合这个规范,
然后,要将应用程序中的数据,假设为:std::vector<float> res,data1,data2,data3... 存入perl解释器中,
再执行用户的表达式
最后,从perl解释器中获取运算结果
例子:
/// 存储数据data1到perl解释器中,res,data2,data3...方法类似
AV *pAV = get_av( std::string("a1").data(), true ); // 创建参数数组
av_fill(pAV, data1.size());
for(int i=data1.size()-1; i>=0; --i)
{
SV** pResult = av_store(pAV, i, newSVnv(data1[i]));
assert(pResult);
}
/// 执行脚本,外层加上for包装EXPRESSION并替换@为$,可以直接进行向量运算
eval_pv("EXPRESSION", true); ///< EXPRESSION为用户所写的算术表达式
/// 获取结果
AV *pResultAV = get_av("res", false); // 这里不能创建结果数组,必须存在
assert( pResultAV );
for(int i=res.size()-1; i>=0; --i)
{
SV** pSV = av_fetch(pResultAV, i, 0);
assert( pSV && SvOK(*pSV) );
if ( !pSV || !*pSV ) return;
res[i] = SvNV(*pSV);
}