强强联合——Perl 外部程序语言子程序与类的编程
Author:天水-S.Tanshuai Email:
OICQ: 66552 ICQ: 25856530 URL: Http://Www.Tanshuai.Net Location: FS, LN, P.R.C
Date: 2001-5-19 20:07 Edition: Manuscript Version: 1.0 Current Language: Simplified Chinese
简介
安装
程序设计语言方法实现:
C
C++
Java
Python
其它语言
联合使用
另一种方式——CPR
结语
简介
我们知道Perl是目前最古老最强大的脚本程序语言,它的特点显而易见,就是快速开发应用和拥有广域的扩展性。Perl是一门高级,它和Java、Python一样在处理某些特殊任务的时候,仍显得力不从心,需要C/C++这些底层语言的支持,这就出现了扩展功能,例如Perl需要XS,Java需要JNI。然而实现这些方法并非易事。首先每一种扩展都会有它的特性,有它的不同,必须按照扩展对象的规定进行相应编写。如果你写了一个扩展程序,那么如果要用在Perl和Java上,你就要分别去写不同的接口。因此出现了很多更好的解决方案,这些不仅仅是对程序语言的扩展,我们知道,每一种程序语言都有它们的优点与其弱点。然而常常又因为开发周期和成本而无法用一种语言进行解决,因为这样很浪费时间。同时,不是每个人都对同一语言都是那么了解,每个人有自己擅长的程序语言。在Win32系统上,有ActiveX控件,和COM的实现方法,来解决这样的问题。然而这些仍然不是那么完善,Perl在Win32做控件,也是一种可行的方式。然而在Unix-Base系统上如何呢?而且控件仍然有其局限。
我想很多程序员也有和我同样的想法,如果流行的(当然所有的更好)程序语言都能够在一种环境下进行完美的编写那么该多好啊?但是这个想法在Perl语言中实现起来并不难。
另外我想说,本文除了对Perl程序员有很大的帮助之外,也绝对适合C、Java和Python程序员。因为不论在控制结构和函数调用方面都非常相似,甚至是一样的。这些程序员最多用一天就可以了解Perl的基本编写方法。如果你正在为多语言联合编程犯愁,那么用这个方法也不失为一种解决方案。
//C Programming
#include <stdio.h>
main (){
char *str = "C 语言\n"
printf("%s",str);
}
//Java Programming
class Hello {
public void static main(String argvs[]) {
String Str = " Java 语言\n";
System.out.print(str);
}
}
#Python Programming
String = "Python 语言\n"
print String
//Perl Programming Perl6开始支持该种注释方法
$String = "Perl 语言\n";
print $String
//
这样,感觉起来有些不可思议,但是的确可以做到,这里面都是实现的方法(void模式),但是我们一般编程都需要函数见的数值传递,这里虽然没有体现到但是仍然可以做到。
Perl强大的嵌入功能,在这个应用上又一次体选出来了。Perl嵌入HTML 编程已经不是什么新鲜事务,我们也可以PerlDoc和Perl.COM的文章中找到如何把Perl嵌入在C中(利用Perl API),已经如何把C嵌入在Perl 里面(PerlXS),以及Perl发布代码中也包括如何把Perl嵌入在Java代码中(JPL),用Perl的语法调用Java对象等。但是这些方法非常复杂,兼容能力也很差,有一定的特性,所以利用起来也不是那么顺手。
安装
Perl这一外部程序语言子程序编程的功能也是最近才发布,所以Perl本身的版本是没有附带的,需要到CPAN下载该功能模块。这个模块叫做"Inline"。可以在任何操作系统平台上应用。
你需要有一个Perl5的解析器,当然最好是最新版本的Perl5.6.1 Stable。然后下载核心模块Inline,继而根据需求不同的程序语言来下载相应的子模块。目前Inline支持汇编(ASM)、C、C++、Java、Python、TCL程序语言,其中还包括一个CPR的Perl嵌入C的特殊实现模式。
下载地址:
核心模块(默认包含C的嵌入功能):http://www.cpan.org/authors/id/I/IN/INGY/Inline-0.34.tar.gz
子模块
汇编语言(ASM):http://www.cpan.org/authors/id/N/NE/NEILW/Inline-ASM-0.02.tar.gz
C++语言:http://www.cpan.org/authors/id/N/NE/NEILW/Inline-CPP-0.20.tar.gz
Java语言:http://www.cpan.org/authors/id/P/PA/PATL/Inline-Java-0.21.tar.gz
Python语言:http://www.cpan.org/authors/id/N/NE/NEILW/Inline-Python-0.14.tar.gz
TCL语言:http://www.cpan.org/authors/id/R/RR/RRS/Inline-Tcl-0.08.tar.gz
CPR功能:http://www.cpan.org/authors/id/I/IN/INGY/Inline-CPR-0.11.tar.gz
另外,你必须保证每个语言的编译器或解析器的存在。例如,你想要使用C/C++嵌入,你必须保证你拥有C++编译器,Win32是CL(VC),Unix是CC或者GCC;Java当然需要JDK;Python需要Python解析器。而且你也要保证需求模块、类、库等调用对象的存在,例如C的函数库,Java的类库,Python的模块等。
下载相应模块后,进行编译安装,你必须拥有一个C语言的编译器,如果是ActivePerl,可以利用PPM。首先安装的是核心模块。所有的Perl模块的安装流程都是一样的:
perl Makefile.PL<Win32/Unix>
这个时候一般自动完成,并结束,但是有时候需要你输入一些量,例如安装目录等,核心模块是会问你是否默认安装C嵌入功能。
nmake <Win32> make<Unix>
进行编译,如果没有PerlXS,直接建立目的文件的目录结构。
nmake test<Win32> make test<unix>
这个步骤一般不需要做,但是如果你希望检查是否编译正确。
nmake install<Win32> make install<unix>
进行安装,把编译好的和建立的目录结构一同复制于目的目录中。
程序设计语言方法实现
一切安装配置就需有,就可以正式进行多语言编程了。本文选择几种常用语言进行讲解,其它语言读者可根据需求,阅读相应文档,也可以直接咨询与我。
C语言:
把C语言直接写入Perl代码中,轻而易举,我们知道在源代码中如果想把一个数据写入变量,是需要遵循一定的规则,例如:
char *t = "XXX";是正确的,然而,我们平时写程序的时候,变量内容不一定是这样的,例如Web编程时,就需要使用HTML,那么实现起来 是非常费力的,我们需要在遇到“"”的时候,添加控制符号“\”,即使在Java 也没有充分解决这个问题,然而Perl就不需要。因为Perl的字符处 理功能实在是太强大了。
简单的实现:
#!perl
greet('天水');#是Perl语言。
greet('Sam Tanshuai');
use Inline C => <<'END_OF_C_CODE';
#include <stdio.h>
void greet(char* name) {
printf(您好: %s!\n", name);
}
END_OF_C_CODE
输出结果是:
D:\Documents\Tests\Perl>perl InC.pl
您好: 天水!
您好: Sam Tanshuai!
============================
我们注意到,C代码是在调用语句的后面,按照一般的编写原理,这样是不可以的,但是use是在使用的时候是最高优先级,所以 use所属语句具有优先执行的能力,否则的话,必定出错。你发现了,在Perl里面的调用方法,不需要什么改动哦。这也就是我们为什么说“ Perl 外部程序语言子程序编程”,而不是多语言联合编程。它就是把一种语言当作自己的子程序去执行。
上面我们介绍的是向C语言子程序传递函数,进行操作,如果需要C语言返回函数,该如何去做呢?方法很简单:
#!perl
$add = add(1, 1);
$subtract = subtract(9, 100);
print "一加一等于:$add\n";
print "九减去一百等于:$subtract\n";
use Inline C => <<'END_OF_C_CODE';
#include <stdio.h>
int add(int x, int y) {
return x + y;
}
int subtract(int x, int y) {
return x - y;
}
END_OF_C_CODE
输出结果是:
D:\Documents\Tests\Perl>perl InC.pl
一加一等于:2
九减去一百等于:-91
=============================
至此程序编写的void方式和return方式都已经告知大家了,C可以使用指针,但是Perl不能引用C的指针,所以这是一个“遗憾”,因为如果 实现这种方法是存在难度的。
环境定义配置:
我们知道在编译C 的时候需要各种参数,例如INC和Lib的位置。因为嵌入编程,是外部调用的方式,这种方式类似于JSP,是编译后调用 。一般情况,直接perl就可以,但是有些时候考虑到移植等特殊问题,就需要稍微改动,这些功能可以在Perl初始化Inline的时候完成。
AUTO_INCLUDE : 自动使用 include功能,代替在C语言中使用的#include语句
use C => Config => AUTO_INCLUDE => '#include "yourheader.h"';
CC :你希望使用哪一个C语言编译器,一般情况下,它会使用编译Perl时候的编译器,如果你的系统有多个编译器,希望指定,可 以使用该方法。
INC: 也就是 C 头文件的搜索路径。
use C => Config => INC => '-I/inc/path';
LIBS: 库文件搜索路径。
use C => Config => LIBS => '-lyourlib';
TYPEMAPS :指定使用额外的typemape文件
use C => Config => TYPEMAPS => '/your/path/typemap';
目前在Perl Inline-C的模式中支持一下数据类型:
- int
- long
- double
- char*
- void
- SV* Perl的API数组
C++:
这里的C++和C没有太大的差异。只是要在编译的时候进行具体分析:
use Inline CPP => <<'END';
int doodle() { }
class Foo {
public:
Foo();
~Foo();
int get_data() { return data; }
void set_data(int a) { data = a; }
private:
int data;
};
Foo::Foo() { cout << "创建Foo()" << endl; }
Foo::~Foo() { cout << "删除 Foo()" << endl; }
END
在这里涉及到了面向对象的程序设计的方法,所以需要特别注意。
#!perl
use Inline CPP;
my $q = new Queue;#创建对象 Queue,这个对象是C++的。
$q->q(50);#访问对象Queue的q函数,并传送整数"50"。
$q->q("我是谁?");#访问对象Queue的q函数,并传送字符串。
$q->q("我就是我。");
print "一共有", $q->size, "查询项目。\n";
while($q->size) {
print "关于: ", $q->peek, "\n";
print "实际: ", $q->dq, "\n";
}
my $s = new Stack;
$s->push(42);
$s->push("什么?");
print "一共有 ", $s->size, "查询项目。\n";
while($s->size) {
print "关于 : ", $s->peek, "\n";
print "实际: ", $s->pop, "\n";
}
__END__
__CPP__
class Queue {
public:
Queue(int sz=0) { q = newAV(); if (sz) av_extend(q, sz-1); }
~Queue() { av_undef(q); }
int size() {return av_len(q) + 1; }
int q(SV *item) { av_push(q, SvREFCNT_inc(item)); return av_len(q)+1; }
SV *dq() { return av_shift(q); }
SV *peek() { return size() ? SvREFCNT_inc(*av_fetch(q,0,0)): &PL_sv_undef;}
private:
AV *q;
};
class Stack {
public:
Stack(int sz=0) { s = newAV(); if (sz) av_extend(s, sz-1); }
~Stack() { av_undef(s); }
int size() { return av_len(s) + 1; }
int push(SV *i) { av_push(s, SvREFCNT_inc(i)); return av_len(s)+1; }
SV *pop() { return av_pop(s); }
SV *peek() { return size() ? SvREFCNT_inc(*av_fetch(s,size()-1,0)) : &PL_sv_undef; }
private:
AV *s;
};
在这里我们不难发现,Perl调用C++ 的对象和调用Perl的对象是一样的,是不是感觉还顺手呢?不知道什么时候也可以在其它程序语言中 这样做。
Java:
我除了喜欢Perl之外,其次就是Java啦。Java的确很不错,但是在编程的速度上,的确不如Perl,Java可以用好多不同的方法,实现一个功 能,的确很灵活,但是也非常麻烦。我用Java读一个文件的所有内容赋值到一个变量上,都搞了半天。
Java也是一个面向对象的程序设计语言,所以我们又要涉及对象概念了,我们按照上面C的例子,用Java来实现:
#!perl
my $java = new java();#创建新的对象
$add = $java->add(1, 1);#返回输出数据计算结果
$subtract = $java->subtract(9, 100);#返回输出数据计算结果
$java->prt("一加一等于:$add");#利用Java函数进行输出
$java->prt("九减去一百等于:$subtract");#利用Java函数进行输出
use Inline Java => <<'END_OF_JAVA_CODE';
class java {
public java(){
}
public int add(int i, int j){
return i + j ;
}
public int subtract(int i, int j){
return i - j ;
}
public void prt(String s){
System.out.println(s);
}
}
END_OF_JAVA_CODE
方法,我们知道创建一个对象的时候后,可以向该对象发送初始化的信息。在Perl中:
#!perl
use XXX;
$ooXX = new XXX (Name=>xxx, Value=>0000);
$ooXX->do();
在Java中也可以实现这种方式,调用方法也是一样的:
use Inline Java => <<'END';
class javaMethod {
public Foo() {
...
}
public Foo2(int i, String str, Foo k) {
...
}
}
END
my $obj = new Foo() ;
my $obj2 = new Foo2(001, "String", $obj) ;
环境定义配置:
Java也需要进行相应的配置,当然一般的默认情况下,都可以完成的。
BIN:设置Java二进制执行文件目录路径,放置java解析器和javac编译器文件的位置。
CLASSPATH:设置Java类的所在位置,一般情况通过系统环境变量也可以完成。
一个例子:
use Inline {
Java => 'DATA',
BIN => '/usr/jdk1.3/bin/',
CLASSPATH=>'/yourhome/path/;anyjarfile';
) ;
Python:
Python是一个很好的脚本程序语言,它的语法非常简洁,就好像写一篇文章一样,类似本文,但是也因此,有的时候程序总是出现莫名 其妙的错误,原来只是因为一个制表符 TAB。4月1日愚人节那天,Perl.com和Python.org发布了一条新闻,声称Perl和Python语言进行合并, 并且取名为"Parrot",但是我都被骗了。
不过即使没有"Parrot",Perl也可以完全适用Python。
按照上面的程序继续下来,我们来些Python版本的加加减减:
#!perl
$add = add(1, 1);
$subtract = subtract(9, 100);
print "一加一等于:$add\n";
print "九减去一百等于:$subtract\n";
use Inline Python => <<'END_OF_PYTHON_CODE';
def add(x,y):
return x + y
def subtract(x,y):
return x - y
END_OF_PYTHON_CODE
方法和C是一样的,但是Python也涉及面向对象的程序设计。这里使用Data::Dumper模块来输出数据结构。
#!perl
use Inline Python => <<'END';
class Foo:
def __init__(self):
print "new Foo object being created"
self.data = {}
def get_data(self): return self.data
def set_data(self,dat):
self.data = dat
END
use Data::Dumper;
my $obj = new Foo;
print Dumper $obj;
print Dumper $obj->get_data();
$obj->set_data({string => 'hello',
number => 0102,
array => [1, 2, 3],
});
print Dumper $obj->get_data();
输出结果为:
D:\Documents\Tests\Perl>perl Python.pl
$VAR1 = bless( do{\(my $o = 135870536)}, 'main::Foo' );
$VAR1 = {};
$VAR1 = {
'string' => 'hello',
'array' => [
'1',
'2',
'3'
],
'number' => '102'
};
==========================================
Eval:
因为Python和Perl一样是一个很纯脚本语言,所以它们都支持eval功能,这是一个很强大的功能,在perl中也可以直接使用python的 eval来完成相应功能。
方法如下:
$SC = "print 'hello'";
eval_python("$SC")
eval_python("<perl 包>", "<python 函数>", <参数>...)
eval_python("<perl 包>", "<python 方法>", <对象>, <参数>...)
实现例程:
use Inline::Python qw(eval_python);
eval_python("def test(): return {'第一': 1, '第二': 2}"); # 返回一行内容
eval_python("test()"); #返回整数 1。
Python的功能越来越强大,也受到商业的支持,如果你在Perl编程方面已经有很好的造诣,那么学习Python决非难事,一般情况下两三天 之内就可以掌握。
其它语言:
因为我目前擅长的语言中,只有C、Java、Perl和Python,所以其它语言在此就不详细见解,例如:汇编、TCL等并不流行,再此我将讲 解Inline模块的通用使用方法,大家再根据以上例子进行对照,就很容易掌握。
下载相应的语言子模块,使用Inline进行调用:
use Inline <语言名称>
例如:use Inline C;#C语言
use Inline CPP;#C++语言
use Inline Java;#Java语言
.....
程序结构:
标准结构:
use Inline <语言名称> => <<'END_OF_XXX_CODE';
<相应程序代码主体>
END_OF_XXX_CODE
简明结构:
use Inline <语言名称>;
<Perl调用代码主体>
__END__
__ <语言名称>__
<相应程序代码主体>
复合规范:
use Inline C;
use Inline Java;
use Inline Python;
<Perl应用代码主体>
__C__
<C 语言代码主体>
__C__
<第二个 C语言代码主体>
__JAVA__
<Java语言代码主体>
__Python__
<Python语言代码主体>
使用选项:
我们在使用其它程序语言的时候,需要对编译、解析等方法进行定制,这里介绍如何定制某个语言的选项。
C选项的例子:
use Inline (C => Config =>
DIRECTORY => './inline_dir',
LIBS => '-lfoo',
INC => '-I/foo/include',
PREFIX => 'XXX_',
NOWARN => 1,
);
use Inline C => <<'END_OF_C_CODE';
联合应用
我们把话题转回最初的设想,把Perl、C、Java、Python这四个语言,写在一个程序内。
#!perl
use Inline C;
use Inline Java;
use Inline Python;
#Perl Programming Perl6开始支持该种注释方法
$String = "Perl 语言\n";
print $String
#
__C__
//C Programming
#include <stdio.h>
main (){
char *str = "C 语言\n"
printf("%s",str);
}
__JAVA__
//Java Programming
class Hello {
public void static main(String argvs[]) {
String Str = " Java 语言\n";
System.out.print(str);
}
}
__Python__
#Python Programming
String = "Pthyon 语言\n"
print String
输出结果为:
Perl 语言
C 语言
Java 语言
Python 语言
这是一个简单的联合例子,让我们在复杂一些,用这四种语言做不同分工,由C语言计算加法,Java语言计算减法,Python语言计算乘法 ,最有由Perl进行除法后输出。
#!perl
use Inline C;
use Inline Java;
use Inline Python;
my $java = new subtract();
my $C = add(5,6);
my $J = $java->do($C,3);
my $P = x($J,10);
my $R = $P/3;
print "最终结果: $R\n";
__C__
//C Programming
#include <stdio.h>
int add (int x,int y){
return x+y;
}
__JAVA__
//Java Programming
class subtract {
public int do(int x,int y) {
return x-y;
}
}
__Python__
#Python Programming
def x(x,y):
return x * y
输出结果为:
最终结果:26.6666666666667
==============================
另一种方式——简单的Perl嵌入C
CPR(C Perl Run)是另一种的功能,是把Perl代码放入C中,不同的是这个C是不能成为正式的编译的C。而是一种Script脚本模式。但是它仍然比利用API方法嵌入的C方便得多,在这里是一个eval函数功能。
方法如下:
int main(void) {
printf("Hello World, I'm running under Perl version %s\n",
CPR_eval("use Config; $Config{version}")
); //CPR_eval就是使用了Perl的语言方式。
return 0;
}
它和Perl Inline的标准模式不一样,不使用Perl进行解析,而是输入cpr xxx.cpr就可以运行。但是该程序不支持Win32系统平台。
目前CPR只能在以下函数原型运行:
int main(void);
不可以在以下方式运行:
int main(int argc, char* argv[]);
结语
C语言是采用API动态加载模式运行的,即启动Perl解析器后自动加载API扩展。开始我以为是启动了另一个进程做线程通讯,但是我通过Windows“任务管理器”查看进程的时候,只有一个Perl,说明Perl在处理嵌入式多语言方面的能力的确强大。
正是因为Perl有用强大的字符串处理能力,才可以让不同格式代码写入在一个程序中。之前我们一般使用COM CORBA这些技术来进行不同程序语言的通讯,它们有它们的好处。但是作为一个小型应用开发,是不可能启动一两个服务器端进行调用,而且仍然要涉及到平台移植的问题。
Perl强大的功能,将会是您提高开发效率的有力工具。