第一个Python Extension 【delphi】

http://hi.baidu.com/s_jqzhang/blog/item/d6bd4a09da64afc83bc76380.html

以下的例子是可以直接使用的,只要拷贝如下代码,存放到ExAdd.dpr,直接用Delphi编译,就可以成为一个Python Extension 。 我们可以首先看到效果,然后在分析程序。

最小的例子:

{文件名 ExAdd.dpr}
library ExAdd;
uses SysUtils,Classes,PythonEngine;
{$E pyd}
var
FModule : TPythonModule;
FEngine:TPythonEngine ;
function Add( Self, Args : PPyObject ) : PPyObject; far; cdecl;
var
a, b : Integer;
begin
with GetPythonEngine do
begin
if PyArg_ParseTuple( args, 'ii:Add', [@a, @b] ) <> 0 then
begin
Result := PyInt_FromLong( a + b );
end
else
Result := nil;
end;
end;
procedure initExAdd; cdecl;
begin
FEngine := TPythonEngine.Create(nil);
FModule := TPythonModule.Create(nil);
FModule.Engine := FEngine;
FModule.ModuleName := 'ExAdd';
FModule.AddMethod( 'exadd', @Add, 'exadd(a,b) -> a+b ' );
FEngine.LoadDll;
end;
exports
initExAdd;
var
OldExitProc: pointer;
procedure MyExitProc;
begin
FModule.Free;
FEngine.Free;
ExitProc := OldExitProc;
end;
begin
OldExitProc := ExitProc;
ExitProc := @MyExitProc;
end.
// 测试代码
//from ExAdd import *
//print exadd(1,10)

这是一个最小的例子,只要一个文件ExAdd.dpr ,不需要任何其他的Pas Unit文件就可以了。 当我们把他放到py的syspath内,比如<pythonhome>libsite-packages,在pywin内,可以做如下测试:

>>> from ExAdd import *
>>> print exadd(1,10)
11
>>>

可以看到,Python内的程序确实成功的调用了通过Delphi写的扩展。如何做到的?

当Python内执行

from ExAdd import *

时,将会到syspath内寻找ExAdd.pyd,这里的pyd就是一般的dll,只不过还有一些约定。 当Py找到这个文件后,就调用引出函数initExAdd,这个函数的命名就是python程序和.pyd模块的的一个约定----函数命名必须为init+module名称。 一般来说,在init函数内,就进行引擎的初始化,模块的注册,函数,类型的注册等等工作。这里例子内,我们使用了TPythonEngine, TPythonModule两个P4D提供的类,帮助我们做这些工作。 注册模块时,要注意

FModule.ModuleName := 'ExAdd';

内的ModuleName就是在Python内使用的模块完全一致,当然我们可以使用其他的名字,比如ExQuickAdd,只要

from ExAdd import *

内使用的模块名称一致即可。为了方便和一致,我们可以约定dll的名字,python内的module,delphi内的TPythonModule名字完全一致。 这在语法上并非必须,不过这样做是一个很好的习惯。

任何一个按照如下原型注册的函数,都可以被注册为PyExtention的函数。

function Add( Self, Args : PPyObject ) : PPyObject; far; cdecl;

其中cdecl说明服从C语言的调用规范,而不是Pascal或者其他。毕竟Python是C语言写就的,当然按照C语言的习惯来。 这个函数原型中,参数将会包括Self,Args,返回值得也是一个PPyObject,熟悉Python语言的都知道,任何一个Python函数在被调用时都会传递一个Self 指针进来,并且以Tuple的方式传递参数列表,这个Add函数的实现约定上也就表现出来了,所有的类型都是对象。比如Add(3,4)这个的Python调用,参照Add在Delphi中函数原型, 上,那么"3,4"作为一个Tuple对象,伴随Self,也是一个PPyObject,返回值7也是一个PPyObject来表达。 要不怎么都说Python慢呢?本来一个加操作可以直接对应汇编中的一个指令,现在又是对象又是指针,当然很难快了。

一旦有了这样的声明,就可以这样注册函数。

FModule.AddMethod( 'exadd', @Add, 'add(a,b) -> a+b ' );

以上语句向Python系统声明,exadd函数的实现在add内,最后参数作为__docstring__。当IDE内使用这个函数时,可以通过codeinsight,或者help来获得函数的使用说明。 现在来看add的实现代码。 一眼看过去,PyArg_ParseTuple,PyInt_FromLong是两个特别的东西。 PyArg_ParseTuple负责把传进来的args变成简单的Delphi类型,在Ppyobject内存储的3,4,分别存放到a,b:Integer内,就是

PyArg_ParseTuple( args, 'ii:Add', [@a, @b] ) <> 0

其中第二个参数 'ii:Add' ,有些像是Format格式,i指明类型为Integer,两个I指明有两个整数,:add是可选的,当出错的时候,有:add,可以帮助程序员更好的找到错误。 这样就把PPyobeject所表达的PythonType转为一般Delphi类型; 而PyInt_FromLong这是想法,他把Delphi的Long类型转换为PyObject的Integer;从而可以让结果可以为Python识别。 这两个函数尽管是P4d实现的,但是和Python/C interface手册内规定的函数名称一致,因此具体的调用方法也可以看Python/C interface手册。

实际上Python实现内的对象表达采用了一个结构(Struct),很有一些复杂,我们现在可以在很高层的去看,要感谢P4D所做的工作。