delphi打包python_使用Delphi 编写Python Extension
使用Delphi 編寫Python Extension
作者:1000copy
摘要:
在互聯(lián)網(wǎng)公共可訪問領(lǐng)域內(nèi),關(guān)于Python/C interface的介紹,手冊都是比較多的。Py直接支持C編寫擴(kuò)展,對于Delphi程序員,P4D是一個很好的選擇。
不幸的是,通過P4D[2]編寫PyExtention,并沒有一個很好的入門文檔,本文試圖填寫這個空白。
本文風(fēng)格完全模仿Writing Python Extensions[1],希望以例子為本,讓大家很快的進(jìn)入狀態(tài)。
1. 引言:
本文假設(shè)你:
* 懂得Python
* 懂得Delphi
* 想要通過P4D編寫Python Extension
* 已經(jīng)安裝了Delphi7,P4D,Python2.4以上。
2. 第一個Python Extension
以下的例子是可以直接使用的,只要拷貝如下代碼,存放到ExAdd.dpr,直接用Delphi編譯,就可以成為一個Python Extension 。
我們可以首先看到效果,然后在分析程序。
2.1 最小的例子
{文件名 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)
2.2 解說
這是一個最小的例子,只要一個文件ExAdd.dpr ,不需要任何其他的Pas Unit文件就可以了。
當(dāng)我們把他放到py的syspath內(nèi),比如libsite-packages,在pywin內(nèi),可以做如下測試:
>>> from ExAdd import *
>>> print exadd(1,10)
11
>>>
可以看到,Python內(nèi)的程序確實(shí)成功的調(diào)用了通過Delphi寫的擴(kuò)展。如何做到的?
2.3 如何注冊一個模塊
當(dāng)Python內(nèi)執(zhí)行from ExAdd import *時,將會到syspath內(nèi)尋找ExAdd.pyd,這里的pyd就是一般的dll,只不過還有一些約定。
當(dāng)Py找到這個文件后,就調(diào)用引出函數(shù)initExAdd,這個函數(shù)的命名就是python程序和.pyd模塊的的一個約定----函數(shù)命名必須為init+module名稱。
一般來說,在init函數(shù)內(nèi),就進(jìn)行引擎的初始化,模塊的注冊,函數(shù),類型的注冊等等工作。這里例子內(nèi),我們使用了TPythonEngine,
TPythonModule兩個P4D提供的類,幫助我們做這些工作。
注冊模塊時,要注意
FModule.ModuleName := 'ExAdd';
內(nèi)的ModuleName就是在Python內(nèi)使用的模塊完全一致,當(dāng)然我們可以使用其他的名字,比如ExQuickAdd,只要from ExAdd import *內(nèi)使用的
模塊名稱一致即可。為了方便和一致,我們可以約定dll的名字,python內(nèi)的module,delphi內(nèi)的TPythonModule名字完全一致。
這在語法上并非必須,不過這樣做是一個很好的習(xí)慣。
2.4 如何注冊一個函數(shù)
任何一個按照如下原型注冊的函數(shù),都可以被注冊為PyExtention的函數(shù)。
function Add( Self, Args : PPyObject ) : PPyObject; far; cdecl;
其中cdecl說明服從C語言的調(diào)用規(guī)范,而不是Pascal或者其他。畢竟Python是C語言寫就的,當(dāng)然按照C語言的習(xí)慣來。
這個函數(shù)原型中,參數(shù)將會包括Self,Args,返回值得也是一個PPyObject,熟悉Python語言的都知道,任何一個Python函數(shù)在被調(diào)用時都會傳遞一個Self
指針進(jìn)來,并且以Tuple的方式傳遞參數(shù)列表,這個Add函數(shù)的實(shí)現(xiàn)約定上也就表現(xiàn)出來了,所有的類型都是對象。比如Add(3,4)這個的Python調(diào)用,參照Add在Delphi中函數(shù)原型,
上,那么"3,4"作為一個Tuple對象,伴隨Self,也是一個PPyObject,返回值7也是一個PPyObject來表達(dá)。
要不怎么都說Python慢呢?本來一個加操作可以直接對應(yīng)匯編中的一個指令,現(xiàn)在又是對象又是指針,當(dāng)然很難快了。
一旦有了這樣的聲明,就可以這樣注冊函數(shù)。
FModule.AddMethod( 'exadd', @Add, 'add(a,b) -> a+b ' );
以上語句向Python系統(tǒng)聲明,exadd函數(shù)的實(shí)現(xiàn)在add內(nèi),最后參數(shù)作為__docstring__。當(dāng)IDE內(nèi)使用這個函數(shù)時,可以通過codeinsight,或者h(yuǎn)elp來獲得函數(shù)的使用說明。
現(xiàn)在來看add的實(shí)現(xiàn)代碼。
一眼看過去,PyArg_ParseTuple,PyInt_FromLong是兩個特別的東西。
PyArg_ParseTuple負(fù)責(zé)把傳進(jìn)來的args變成簡單的Delphi類型,在Ppyobject內(nèi)存儲的3,4,分別存放到a,b:Integer內(nèi),就是
PyArg_ParseTuple( args, 'ii:Add', [@a, @b] )? 0
其中第二個參數(shù) 'ii:Add' ,有些像是Format格式,i指明類型為Integer,兩個I指明有兩個整數(shù),:add是可選的,當(dāng)出錯的時候,有:add,可以幫助程序員更好的找到錯誤。
這樣就把PPyobeject所表達(dá)的PythonType轉(zhuǎn)為一般Delphi類型;
而PyInt_FromLong這是想法,他把Delphi的Long類型轉(zhuǎn)換為PyObject的Integer;從而可以讓結(jié)果可以為Python識別。
這兩個函數(shù)盡管是P4d實(shí)現(xiàn)的,但是和Python/C interface手冊內(nèi)規(guī)定的函數(shù)名稱一致,因此具體的調(diào)用方法也可以看Python/C interface手冊。
實(shí)際上Python實(shí)現(xiàn)內(nèi)的對象表達(dá)采用了一個結(jié)構(gòu)(Struct),很有一些復(fù)雜,我們現(xiàn)在可以在很高層的去看,要感謝P4D所做的工作。
3. 實(shí)現(xiàn)一個類
第一個例子可以工作,并且能夠演示注冊模塊,函數(shù)和一些基本的Python Ext的概念。
對于長期使用Delphi這樣的OO語言,僅僅公開函數(shù)當(dāng)然不夠方便,我們需要的是全OO編程,即使跨越了語言,也不會放棄這樣的習(xí)慣。
我們現(xiàn)在要讓Delphi的類可以為Python。
3.1 又一個例子
你首先看到的依然是一個例子,我們要把Delphi中的TPoint公開出來,讓python可以調(diào)用,模塊名稱為dpoint,最終我們要在pythonIDE內(nèi)看到的效果:
>>> from dpoint import *
>>> print SmallPoint(222,111)
>>> SmallPoint.__doc__
'wrapper for Delphi TPoint typen'
P4D為注冊類這樣的工作提供了TPyDelphiWrapper類,在這個例子里,我們圍繞這TPyDelphiWrapper來分析。
3.2 例子代碼
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.
3.3 注冊過程
一個類必然要屬于某一個模塊,注冊一個類就涉及到注冊一個模塊。關(guān)于注冊模塊,在例子中占據(jù)了不少帶代碼,但是它和第二部分完全一樣,我們掠過不看。
本來注冊一個類是有些復(fù)雜度的,如果想要知道這個復(fù)雜度,可以先看看參考文獻(xiàn)1內(nèi)的描述。不過采用P4D的類型注冊框架就簡單多了。
我們的例子pyd命名為dpoint ,我們準(zhǔn)備把TPoint類型公開到Python內(nèi)。
在initdpoint函數(shù)內(nèi),TPythonEngine,TPythonModule照樣的初始化,比起函數(shù)注冊來說,不同的地方在于創(chuàng)建了TPyDelphiWrapper的實(shí)例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,并且調(diào)用
這個類別內(nèi)的RegisterWrappers方法,通過這個方法或者需要注冊的Python類的Delphi包裝類。
因此,我們要做的事情就是:
約定實(shí)現(xiàn)兩個類,一個是需要公開的類型的Wrapper,這里就是TPyDelphiPoint,一個是注冊這個Wrapper的注冊類,本例子內(nèi)就是TTypesRegistration。
TTypesRegistration只要實(shí)現(xiàn)兩個覆蓋基類的方法,從而達(dá)到通知TPyDelphiWrapper需要注冊的類是TPyDelphiPoint。
function Name : String; override;
procedure RegisterWrappers(APyDelphiWrapper : TPyDelphiWrapper); override;
我們更多的注意力,尤其是以后的更多對PythonExtension特性的利用,集中于TPyDelphiPoint上。
TPyDelphiPoint,作為一個PythonType,最少要實(shí)現(xiàn)的方法有:
constructor CreateWith( APythonType : TPythonType; args : PPyObject ); override;
class procedure SetupType( PythonType : TPythonType ); override;
我們可以注意到,CreateWith傳遞的args依然是PPyObject類型,和前文談到的add方法對參數(shù)和返回值的處理都是一致的。
SetupType將會指明在Python內(nèi)如何使用這個類型,根據(jù)源代碼知道,SetupType指明這個類型在Python內(nèi)的類型為SmallPoint,提供基本服務(wù)(fvbase),類型文檔__doc__為
'12345',
測試用例3.1代碼如果正常運(yùn)行,就自然的證實(shí)了這一點(diǎn)。
4.充分利用Python的特性
4.1 repr服務(wù)
以上例子很簡單,但是可以表達(dá)主旨,是進(jìn)一步了解和把握P4D編寫擴(kuò)展的基礎(chǔ)。
從3.1的測試用例看,
>>> print SmallPoint(222,111)
這樣的輸出很不友好,我們希望他是這樣的:
>>> print SmallPoint(222,111)
222,111
這樣的服務(wù)在py內(nèi)早已存在,它的名字叫做repr,每個對象如果希望打印友好,都應(yīng)該支持這樣的服務(wù)。
在Delphi編寫的Py擴(kuò)展中,如何做到這樣的效果?
4.2 例子
一旦框架鋪陳完畢,編寫具體的功能就很簡單了。repr服務(wù)只要覆蓋一個方法,加上對返回參數(shù)的包裝就可以了。
function? Repr : PPyObject; override;
..
implementation
..
function TPyDelphiPoint.Repr: PPyObject;
begin
with GetPythonEngine do
Result := PyString_FromString(PChar(Format('', [Value.X, Value.Y])));
end;
4.3 更多
設(shè)置屬性,需要覆蓋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內(nèi)加入一行:
PythonType.Services.Basic := PythonType.Services.Basic+[bsGetAttrO, bsSetAttrO];
告訴Python你的服務(wù)中有屬性的支持。
允許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內(nèi)加入一行:
PythonType.Services.Basic := PythonType.Services.Basic+[bsCompare];
告訴Python你的服務(wù)中有bsCompare的支持。
總結(jié)
以上是生活随笔為你收集整理的delphi打包python_使用Delphi 编写Python Extension的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 孙武是哪国的军事家?
- 下一篇: 权力的游戏前传龙族第一季未删减百度云(天