Developing Python Modules with Pascal/ru

From Lazarus wiki
Jump to navigationJump to search

English (en) русский (ru)

Введение

Python — популярный скриптовый язык, который часто используется для добавления функциональности другими приложениями, такими как OpenOffice и Quantum GIS. На вашем компьютере уже может быть установлена какая-то версия Python. Если нет, можно загрузить Python с официального вебсайта: http://www.python.org/.

Можно расширять Python путём разработки скомпилированных библиотек (называемых модулями), которые добавляют функции в Python. В этом разделе рассматривается как создать библиотеку на Pascal (Delphi или Free Pascal).

Эта статья описывает очень низкий уровень, подход «с нуля». Для установки моста Python-Pascal, смотрите Python4Delphi/ru.

Минимальное Python API

Скопируйте и сохраните с помощью текстового в файл PyAPI.pas:

unit PyAPI;
 
{ 
 
  Минимальный набор деклараций функций Python для библиотек модулей.
 
  Author: Фил (MacPgmr на fastermac.net).
 
  Для добавления других деклараций функций Python, смотрите заголовочные файлы (.h), включённые в любой дистрибутив Python.
 
}
 
{$IFDEF FPC}
 {$MODE Delphi}
{$ENDIF} 
 
interface

{$DEFINE IS32BIT}
{$IFDEF CPUX64}  {Delphi}
 {$UNDEF IS32BIT}
{$ENDIF}
{$IFDEF CPU64}  {FPC}
 {$UNDEF IS32BIT}
{$ENDIF}
 
const
{$IFDEF MSWINDOWS}
 {$IFDEF USE_PYTHON23}
  PythonLib = 'python23.dll';
 {$ELSE}
  PythonLib = 'python27.dll';
 {$ENDIF}
{$ENDIF} 
 
{$IFDEF LINUX}
 {$IFDEF USE_PYTHON23}
  PythonLib = 'python23.so';
 {$ELSE}
  PythonLib = 'python27.so';
 {$ENDIF}
{$ENDIF} 
 
{$IFDEF DARWIN}
  PythonLib = '';  //Связывает с Python.framework (-k'-framework Python').
                   // Для ссылки на конкретную версию Python, передайте
                   // полный путь к этой версии библиотеки, например,
                   //  -k'/System/Library/Frameworks/Python.framework/Versions/2.6/Python'
{$ENDIF} 
 
type
{$IFDEF IS32BIT}
  c_long = LongInt;
  c_ulong = LongWord;
  c_int  = LongInt;
{$ELSE}
  c_long = Int64;
  c_ulong = UInt64;
  c_int = Int64;  //"int" также будет 8-байтным в 64-битном Python
{$ENDIF}
 
  PyMethodDef = packed record
    name  : PAnsiChar;  //имя функции Python
    meth  : Pointer;    //Адрес реализуемой функции
    flags : c_int;      //METH_xxx флаги; описывает аргументы функции
    doc   : PAnsiChar;  //Описание функции
    end;
 
  PyObject = Pointer;

const
{$IFDEF USE_PYTHON23}
  PYTHON_API_VERSION = 1012;  //Используется также вместе с Python 2.4
{$ELSE}
  PYTHON_API_VERSION = 1013;
{$ENDIF}
  METH_VARARGS = 1;
 
function Py_InitModule(    name    : PAnsiChar;
                       var methods : PyMethodDef;
                           doc     : PAnsiChar = nil;
                           self    : PyObject = nil;
                           apiver  : c_int = PYTHON_API_VERSION) : PyObject; cdecl; 
          external PythonLib name {$IFDEF IS32BIT}'Py_InitModule4'{$ELSE}'Py_InitModule4_64'{$ENDIF};
 
function PyArg_ParseTuple(args   : PyObject; 
                          format : PAnsiChar) : c_int; cdecl; varargs; external PythonLib;
 //Заметьте, что varargs позволяет нам эмулировать переменное количество аргументов в  C (...).
 
function PyInt_FromLong(along : c_long) : PyObject; cdecl; external PythonLib;
 
function PyLong_FromLong(along : c_long) : PyObject; cdecl; external PythonLib;
 
function PyLong_FromUnsignedLong(aulong : c_ulong) : PyObject; cdecl; external PythonLib; 

function PyString_FromString(astr : PAnsiChar) : PyObject; cdecl; external PythonLib;
 
implementation
 
 
end.

Если у вас разные версии Python, определите USE_PYTHON23 как ссылку на версию 2.3 или просто измените имя библиотеки (PythonLib). Если вам нужны другие функции API Python, просто добавьте их PyAPI.pas, следуя примеру PyInt_FromLong.

A simple module example

Here is a simple library that uses this Python API unit. Copy and paste this code into a text editor and save it as file PyMinMod.dpr:

library PyMinMod;
 
{
 
  Minimal Python module (library) that includes simple functions.
 
  Author: Phil (MacPgmr at fastermac.net).
 
  To compile this module:
    - With Delphi: Open this .dpr file and compile.
    - With Lazarus: Open .lpi file and compile.
 
  To deploy module:
    - With Delphi: Rename compiled .dll to .pyd.
    - With Lazarus on Windows: Rename compiled .so to .pyd.
    - With Lazarus on OS X and Linux: .so extension is okay.
 
}
 
uses
  SysUtils,
  PyAPI;
 
 
function SumTwoIntegers(Self : PyObject;
                        Args : PyObject) : PyObject; cdecl;
var
  Arg1 : Integer;
  Arg2 : Integer;
begin
  PyArg_ParseTuple(Args, 'ii', @Arg1, @Arg2);  //Get the two int arguments
  Result := PyInt_FromLong(Arg1 + Arg2);  //Add them together and return sum
//  Result := PyLong_FromLong(Arg1 + Arg2);
//  Result := PyLong_FromUnsignedLong(Arg1 + Arg2);
end;
 
 
function ConcatTwoStrings(Self : PyObject;
                          Args : PyObject) : PyObject; cdecl;
 {From Python documentation for "s" format: "You must not provide storage for 
   the string itself; a pointer to an existing string is stored into the 
   character pointer variable whose address you pass."
  From Python documentation for PyString_FromString: "Return a new string 
   object with a copy of the string v as value on success".}
var
  Arg1 : PAnsiChar;
  Arg2 : PAnsiChar;
begin
  PyArg_ParseTuple(Args, 'ss', @Arg1, @Arg2);  //Get the two string arguments
  Result := PyString_FromString(PAnsiChar(AnsiString(Arg1) + AnsiString(Arg2)));  
             //Concatenate and return string
end;
 
 
var
  Methods : packed array [0..2] of PyMethodDef;
 
procedure initPyMinMod; cdecl;
begin
  Methods[0].name := 'SumTwoIntegers';
  Methods[0].meth := @SumTwoIntegers;
  Methods[0].flags := METH_VARARGS;
  Methods[0].doc := 'Tests passing ints to and from module function';
 
  Methods[1].name := 'ConcatTwoStrings';
  Methods[1].meth := @ConcatTwoStrings;
  Methods[1].flags := METH_VARARGS;
  Methods[1].doc := 'Tests passing strings to and from module function';
 
  Methods[2].name := nil;
  Methods[2].meth := nil;
  Methods[2].flags := 0;
  Methods[2].doc := nil;
 
  Py_InitModule('PyMinMod', Methods[0]);
end;
 
 
exports
  initPyMinMod;
 
end.

You can add more functions to the module by following the example in initPyMinMod.

With Delphi, just open PyMinMod.dpr and compile.

With FPC, just compile from the command line. For example, to create a 64-bit module on OS X:

ppcx64 -Sd -k'-framework Python' -oPyMinMod.so PyMinMod.dpr

With Lazarus, you'll probably want to create a project file. You can do that yourself or just copy and paste this project file into a text editor and save it as file PyMinMod.lpi:

<?xml version="1.0"?>
<CONFIG>
  <ProjectOptions>
    <PathDelim Value="/"/>
    <Version Value="6"/>
    <General>
      <MainUnit Value="0"/>
      <IconPath Value="./"/>
      <TargetFileExt Value=".exe"/>
      <UseAppBundle Value="False"/>
      <ActiveEditorIndexAtStart Value="0"/>
    </General>
    <PublishOptions>
      <Version Value="2"/>
      <IgnoreBinaries Value="False"/>
      <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/>
      <ExcludeFileFilter Value="*.(bak|ppu|ppw|o|so);*~;backup"/>
    </PublishOptions>
    <RunParams>
      <local>
        <FormatVersion Value="1"/>
        <LaunchingApplication PathPlusParams="/usr/X11R6/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/>
      </local>
    </RunParams>
    <Units Count="1">
      <Unit0>
        <Filename Value="PyMinMod.dpr"/>
        <IsPartOfProject Value="True"/>
        <UnitName Value="PyMinMod"/>
        <CursorPos X="1" Y="1"/>
        <TopLine Value="1"/>
        <EditorIndex Value="0"/>
        <UsageCount Value="20"/>
        <Loaded Value="True"/>
        <SyntaxHighlighter Value="Delphi"/>
      </Unit0>
    </Units>
    <JumpHistory Count="0" HistoryIndex="-1"/>
  </ProjectOptions>
  <CompilerOptions>
    <Version Value="8"/>
    <Target>
      <Filename Value="PyMinMod.so"/>
    </Target>
    <Parsing>
      <SyntaxOptions>
        <SyntaxMode Value="Delphi"/>
        <CStyleOperator Value="False"/>
        <AllowLabel Value="False"/>
        <CPPInline Value="False"/>
      </SyntaxOptions>
    </Parsing>
    <CodeGeneration>
      <Checks>
        <IOChecks Value="True"/>
        <RangeChecks Value="True"/>
        <OverflowChecks Value="True"/>
        <StackChecks Value="True"/>
      </Checks>
    </CodeGeneration>
    <Linking>
      <Options>
        <PassLinkerOptions Value="True"/>
        <LinkerOptions Value="-framework Python"/>
        <Win32>
          <GraphicApplication Value="True"/>
        </Win32>
        <ExecutableType Value="Library"/>
      </Options>
    </Linking>
    <Other>
      <CompilerPath Value="$(CompPath)"/>
    </Other>
  </CompilerOptions>
  <Debugging>
    <Exceptions Count="2">
      <Item1>
        <Name Value="ECodetoolError"/>
      </Item1>
      <Item2>
        <Name Value="EFOpenError"/>
      </Item2>
    </Exceptions>
  </Debugging>
</CONFIG>

Once you've compiled the module, rename it if necessary per the comments in PyMinMod.dpr. Then test the module by creating a simple test.py file that contains these three lines:

import PyMinMod
print "Value returned by SumTwoIntegers: " + str(PyMinMod.SumTwoIntegers(1, 2))
print "Value returned by ConcatTwoStrings: " + PyMinMod.ConcatTwoStrings("Hey ", "there")

Note that Python is case sensitive so if your compiled module is in lower-case, change the "PyMinMod" references accordingly.

Now open a terminal window and run the script like this:

python test.py

The script should output the following line:

Value returned by SumTwoIntegers: 3
Value returned by ConcatTwoStrings: Hey there

What we've done here is create a simple Python module that implements two Python functions. Once you've imported the module into your Python script, you can use the functions in the same way that you use built-in Python functions.

Using your module in a host application

If you have OpenOffice or NeoOffice installed, you can test running your module from a Python macro.

Copy and paste this script into a text editor and save it as file test_minmod.py, then place it in the folder specified in the script. You may have to create the python\Library1 folder under Scripts.

# Python macro that tests Pascal module by creating new document and inserting module function result.

import sys, os
# Tell Python where to look for our Pascal module
if sys.platform == 'win32':
  sys.path.append(os.path.expanduser('~\Application Data\OpenOffice.org2\user\Scripts\python\Library1'))
elif sys.platform == 'darwin':
  sys.path.append(os.path.expanduser('~/Library/Preferences/NeoOffice-2.2/user/Scripts/python/Library1'))

# Import Pascal module that contains SumTwoIntegers function
import PyMinMod

import uno

def TestMinMod():
  ctx = uno.getComponentContext()
  smgr = ctx.ServiceManager
  desktop = smgr.createInstance('com.sun.star.frame.Desktop')
  doc = desktop.loadComponentFromURL('private:factory/swriter', '_blank', 0, ())
  textCursor = doc.Text.createTextCursor()
  doc.Text.insertString(textCursor, 'Sum of 1 + 2 = ' + str(PyMinMod.SumTwoIntegers(1, 2)), 0)

Since OO probably includes Python 2.3, compile your module against 2.3 by adding this on the Lazarus Compiler Options dialog's Other tab:

-dUSE_PYTHON23

With Delphi, enter USE_PYTHON23 on the Project Options dialog's Directories/Conditionals tab.

Rename your compiled module if necessary, then place it in the same folder as test_minmod.py. Now test running it from OO by choosing Tools | Macros | Organize Macros | Python and running the TestMinMod macro.

See also