Developing Python Modules with Pascal/ru

From Free Pascal 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.

Пример простого модуля

Вот простая библиотека, которая использует модуль Python API. Скопируйте этот код и сохраните его в файле PyMinMod.dpr:

library PyMinMod;
 
{
 
  Минимальный модуль (библиотека) Python, включающий простые функции.
 
  Автор: Фил (MacPgmr на fastermac.net).
 
  Для компиляции этого модуля:
    - На Delphi: Откройте этот файл .dpr и скомпилируйте.
    - На Lazarus: Откройте файл .lpi и скомпилируйте.
 
  Для раздачи модуля:
    - С Delphi: Переименуйте скомпилированный .dll в .pyd.
    - С Lazarus на Windows: Переименуйте скомпилированный .so в .pyd.
    - С Lazarus на OS X и Linux: Оставьте расширение .so extension.
 
}
 
uses
  SysUtils,
  PyAPI;
 
 
function SumTwoIntegers(Self : PyObject;
                        Args : PyObject) : PyObject; cdecl;
var
  Arg1 : Integer;
  Arg2 : Integer;
begin
  PyArg_ParseTuple(Args, 'ii', @Arg1, @Arg2);  //Получает 2 аргумента типа int
  Result := PyInt_FromLong(Arg1 + Arg2);  //Суммируем их и возвращаем sum
//  Result := PyLong_FromLong(Arg1 + Arg2);
//  Result := PyLong_FromUnsignedLong(Arg1 + Arg2);
end;
 
 
function ConcatTwoStrings(Self : PyObject;
                          Args : PyObject) : PyObject; cdecl;
 {Из документации Python про формат «s»: «Вы не должны выделять память под саму строку; указатель 
  на существующую строку сохранён в переменной типа указатель на символ (character pointer) чей адрес вы передаёте.»
  Из документации Python по PyString_FromString: «Вернуть новый строковый объект с копией строки v в случае успеха».
 }
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