zoukankan      html  css  js  c++  java
  • 使用Delphi 编写Python Extension

    使用Delphi 编写Python Extension
    作者:1000copy
    摘要:
    在互联网公共可访问领域内,关于Python/C interface的介绍,手册都是比较多的。Py直接支持C编写扩展,对于Delphi程序员,P4D是一个很好的选择。
    不幸的是,通过P4D[2]编写PyExtention,并没有一个很好的入门文档,本文试图填写这个空白。
    本文风格完全模仿Writing Python Extensions[1],希望以例子为本,让大家很快的进入状态。
    1. 引言:
    本文假设你:
        * 懂得Python
        * 懂得Delphi
        * 想要通过P4D编写Python Extension
        * 已经安装了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文件就可以了。
      当我们把他放到py的syspath内,比如libsite-packages,在pywin内,可以做如下测试:
        >>> from ExAdd import *
        >>> print exadd(1,10)
        11
        >>>
      可以看到,Python内的程序确实成功的调用了通过Delphi写的扩展。如何做到的?
    2.3 如何注册一个模块
      当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名字完全一致。
      这在语法上并非必须,不过这样做是一个很好的习惯。
    2.4 如何注册一个函数
      任何一个按照如下原型注册的函数,都可以被注册为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所做的工作。
    3. 实现一个类
      第一个例子可以工作,并且能够演示注册模块,函数和一些基本的Python Ext的概念。
      对于长期使用Delphi这样的OO语言,仅仅公开函数当然不够方便,我们需要的是全OO编程,即使跨越了语言,也不会放弃这样的习惯。
      我们现在要让Delphi的类可以为Python。
    3.1 又一个例子
    你首先看到的依然是一个例子,我们要把Delphi中的TPoint公开出来,让python可以调用,模块名称为dpoint,最终我们要在pythonIDE内看到的效果:
        >>> 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 注册过程
    一个类必然要属于某一个模块,注册一个类就涉及到注册一个模块。关于注册模块,在例子中占据了不少带代码,但是它和第二部分完全一样,我们掠过不看。
    本来注册一个类是有些复杂度的,如果想要知道这个复杂度,可以先看看参考文献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代码如果正常运行,就自然的证实了这一点。
    4.充分利用Python的特性
    4.1 repr服务
    以上例子很简单,但是可以表达主旨,是进一步了解和把握P4D编写扩展的基础。
    从3.1的测试用例看,
            >>> print SmallPoint(222,111)
       
    这样的输出很不友好,我们希望他是这样的:
            >>> print SmallPoint(222,111)
        222,111
    这样的服务在py内早已存在,它的名字叫做repr,每个对象如果希望打印友好,都应该支持这样的服务。
    在Delphi编写的Py扩展中,如何做到这样的效果?
    4.2 例子
    一旦框架铺陈完毕,编写具体的功能就很简单了。repr服务只要覆盖一个方法,加上对返回参数的包装就可以了。
    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 更多
    设置属性,需要覆盖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的支持。

    本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/djcsch2001/archive/2008/09/11/2915406.aspx

  • 相关阅读:
    Android CTS 测试
    Cygwin 不兼容Win7 64
    真滴有太多不懂的的东西,有点扛不住了。
    ffmpeg yasm not found, use disableyasm for a crippled build
    手机搜索不到 Connectify
    Android ICS 横竖屏切换时不销毁Activity
    MinGw\bin\ar.exe: libavcodec/: Permission denied
    Cannot complete the install because one or more required items could not be found.
    Eclipse 启动时报错 JVM terminated. Exit code=1
    【Java 从入坑到放弃】No 5. 控制流程
  • 原文地址:https://www.cnblogs.com/djcsch2001/p/2035193.html
Copyright © 2011-2022 走看看