2022年 11月 4日

delphi与python_Delphi与Python结合之二

实现一个类第一个例子可以工作,并且能够演示注册模块,函数和一些基本的Python Ext的概念。 对于长期使用Delphi这样的OO语言,仅仅公开函数当然不够方便,我们需要的是全OO编程,即使跨越了语言,也不会放弃这样的习惯。 我们现在要让Delphi的类可以为Python。

你首先看到的依然是一个例子,我们要把Delphi中的TPoint公开出来,让python可以调用,模块名称为dpoint,最终我们要在pythonIDE内看到的效果:>>> from dpoint import *

>>> print SmallPoint(222,111)

>>> SmallPoint.__doc__

‘wrapper for Delphi TPoint typen’

P4D为注册类这样的工作提供了TPyDelphiWrapper类,在这个例子里,我们围绕这TPyDelphiWrapper来分析。

例子代码:library dpoint;

uses

Sharemem ,SysUtils,Classes,WrapDelphi,Types,PythonEngine;

{$E pyd}

var

FModule : TPythonModule;

FEngine:TPythonEngine ;

FDelphiWrapper : TPyDelphiWrapper;

procedure initdpoint; cdecl;

begin

FEngine := TPythonEngine.Create(nil);

FModule := TPythonModule.Create(nil);

FModule.Engine := FEngine;

FModule.ModuleName := ‘dpoint’;

FDelphiWrapper := TPyDelphiWrapper.Create(nil);

FDelphiWrapper.Engine := FEngine;

FDelphiWrapper.Module := FModule;

FEngine.LoadDll;

end;

exports

initdpoint;

var

OldExitProc: pointer;

procedure MyExitProc;

begin

FModule.Free;

FEngine.Free;

ExitProc := OldExitProc;

end;

type

TPyDelphiPoint = class(TPyObject)

private

fValue: TPoint;

protected

public

constructor CreateWith( APythonType : TPythonType; args : PPyObject ); override;

class procedure SetupType( PythonType : TPythonType ); override;

end;

Type

TTypesRegistration = class(TRegisteredUnit)

public

function Name : String; override;

procedure RegisterWrappers(APyDelphiWrapper : TPyDelphiWrapper); override;

end;

function TTypesRegistration.Name: String;

begin

Result := ‘Types’;

end;

procedure TTypesRegistration.RegisterWrappers(APyDelphiWrapper: TPyDelphiWrapper);

begin

inherited;

APyDelphiWrapper.RegisterHelperType(TPyDelphiPoint);

end;

constructor TPyDelphiPoint.CreateWith(APythonType: TPythonType;

args: PPyObject);

var

x, y : Integer;

begin

inherited;

if APythonType.Engine.PyArg_ParseTuple( args, ‘ii:Create’, [@x, @y] ) <> 0 then

begin

fValue.X := x;

fValue.Y := y;

end

end;

class procedure TPyDelphiPoint.SetupType(PythonType: TPythonType);

begin

inherited;

PythonType.TypeName := ‘SmallPoint’;

PythonType.TypeFlags := PythonType.TypeFlags + [tpfBaseType];

PythonType.DocString.Text := ‘12345’;

end;

begin

RegisteredUnits.Add(TTypesRegistration.Create);

OldExitProc := ExitProc;

ExitProc := @MyExitProc;

end.

一个类必然要属于某一个模块,注册一个类就涉及到注册一个模块。关于注册模块,在例子中占据了不少带代码,但是它和第二部分完全一样,我们掠过不看。 本来注册一个类是有些复杂度的,如果想要知道这个复杂度,可以先看看参考文献1内的描述。不过采用P4D的类型注册框架就简单多了。 我们的例子pyd命名为dpoint ,我们准备把TPoint类型公开到Python内。

在initdpoint函数内,TPythonEngine,TPythonModule照样的初始化,比起函数注册来说,不同的地方在于创建了TPyDelphiWrapper的实例gDelphiWrapper, 并且指明他所属的PythonEngine,PythonModule:procedure initdpoint;

begin

gEngine := TPythonEngine.Create(nil);

gEngine.AutoFinalize := False;

gModule := TPythonModule.Create(nil);

gModule.Engine := gEngine;

gModule.ModuleName := ‘dpoint’;

gDelphiWrapper := TPyDelphiWrapper.Create(nil);

gDelphiWrapper.Engine := gEngine;

gDelphiWrapper.Module := gModule;

gEngine.LoadDll;

end;

gDelphiWrapper将会在RegisteredUnitList寻找RegisteredUnit,并且调用 这个类别内的RegisterWrappers方法,通过这个方法或者需要注册的Python类的Delphi包装类。 因此,我们要做的事情就是: 约定实现两个类,一个是需要公开的类型的Wrapper,这里就是TPyDelphiPoint,一个是注册这个Wrapper的注册类,本例子内就是TTypesRegistration。 TTypesRegistration只要实现两个覆盖基类的方法,从而达到通知TPyDelphiWrapper需要注册的类是TPyDelphiPoint。function Name : String; override; procedure RegisterWrappers(APyDelphiWrapper : TPyDelphiWrapper); override;

我们更多的注意力,尤其是以后的更多对PythonExtension特性的利用,集中于TPyDelphiPoint上。 TPyDelphiPoint,作为一个PythonType,最少要实现的方法有:constructor CreateWith( APythonType : TPythonType; args : PPyObject ); override;

class procedure SetupType( PythonType : TPythonType ); override;

我们可以注意到,CreateWith传递的args依然是PPyObject类型,和前文谈到的add方法对参数和返回值的处理都是一致的。 SetupType将会指明在Python内如何使用这个类型,根据源代码知道,SetupType指明这个类型在Python内的类型为SmallPoint,提供基本服务(fvbase),类型文档__doc__为 ‘12345’, 测试用例3.1代码如果正常运行,就自然的证实了这一点。

以上例子很简单,但是可以表达主旨,是进一步了解和把握P4D编写扩展的基础。 从3.1的测试用例看:>>> print SmallPoint(222,111)

这样的输出很不友好,我们希望他是这样的:>>> print SmallPoint(222,111)

222,111

这样的服务在py内早已存在,它的名字叫做repr,每个对象如果希望打印友好,都应该支持这样的服务。 在Delphi编写的Py扩展中,如何做到这样的效果?

一旦框架铺陈完毕,编写具体的功能就很简单了。repr服务只要覆盖一个方法,加上对返回参数的包装就可以了:function Repr : PPyObject; override;

..

implementation

..

function TPyDelphiPoint.Repr: PPyObject;

begin

with GetPythonEngine do

Result := PyString_FromString(PChar(Format(”, [Value.X, Value.Y])));

end;

设置属性,需要覆盖RegisterGetSets方法:class procedure TPyDelphiPoint.RegisterGetSets(PythonType: TPythonType);

begin

inherited;

with PythonType do

begin

AddGetSet(‘X’, @TPyDelphiPoint.Get_X, @TPyDelphiPoint.Set_X,

‘Provides access to the X coordinate of a point’, nil);

AddGetSet(‘Y’, @TPyDelphiPoint.Get_Y, @TPyDelphiPoint.Set_Y,

‘Provides access to the Y coordinate of a point’, nil);

end;

end;

别忘了在SetupType内加入一行:PythonType.Services.Basic := PythonType.Services.Basic+[bsGetAttrO, bsSetAttrO];

告诉Python你的服务中有属性的支持。

允许dpoint之间比较大小,需要覆盖Compare方法:function TPyDelphiPoint.Compare(obj: PPyObject): Integer;

var

_other : TPyDelphiPoint;

begin

if IsDelphiObject(obj) and (PythonToDelphi(obj) is TPyDelphiPoint) then

begin

_other := TPyDelphiPoint(PythonToDelphi(obj));

Result := CompareValue(Value.X, _other.Value.X);

if Result = 0 then

Result := CompareValue(Value.Y, _other.Value.Y);

end

else

Result := 1;

end;

同样别忘了在SetupType内加入一行:PythonType.Services.Basic := PythonType.Services.Basic+[bsCompare];

告诉Python你的服务中有bsCompare的支持。编写扩展后做什么?P4d的代码值得已读,因为基于注册的架构,dll直接到类TDynamicDll值得看,了解Python的内部实现,P4d本身就是Python和Delphi结合编程的良好榜样。

可以很好的了解Python类型的内部实现,比如整数的结构里面有那些字段,有那些基本类型服务的方法使用P4D还可以怎么样?这个图景在我自己还并不很清楚。但是Python和Delphi的结合的愿望在我却一直很强烈,希望有更多的朋友参与进来,提出更多的想法。 参考: www.atug.com/andypatterns/pythonDelphiTalk.htm