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.

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

Вот простая библиотека, которая использует модуль 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);  //Получить два строковых аргумента
  Result := PyString_FromString(PAnsiChar(AnsiString(Arg1) + AnsiString(Arg2)));  
             //Соединить и вернуть строку
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.

Можно добавить в этот модуль ещё функций по примеру в initPyMinMod.

На Delphi, просто откройте PyMinMod.dpr и скомпилируйте.

На FPC, просто скомпилируйте из командной строки. Например, чтобы создать 64-битный модуль на OS X:

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

На Lazarus, вы, возможно, захотите создать файл проекта. Можете сделать его сами или просто скопировать этот проект и сохранить его в файле 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>

После компиляции модуля переименуйте его, если необходимо, как указано в комментариях к PyMinMod.dpr. Затем протестируйте модуль, создав простой файл test.py, который содержит эти три строки:

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

Учтите, что Python различает большие и малые буквы, так что если ваш скомпилированный модуль в нижнем регистре, измените соответственно ссылку на "PyMinMod".

Теперь откройте окно терминала и запустите такой скрипт:

python test.py

Скрипт должен выдать такую строку:

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

Здесь мы создали простой модуль Python, реализующий две функции Python. После того как вы импортировали модуль в свой скрипт Python, вы можете использовать функции так же, как и встроенные функции Python.

Использование вашего модуля в серверных приложениях

Если у вас установлены OpenOffice или NeoOffice, можете протестировать ваш модуль с помощью макроопределений на Python.

Сохраните этот скрипт в файл test_minmod.py, поместите его в папку, указанную в скрипте. Возможно вам придётся создать папку python\Library1 в разделе Scripts.

# Макроопределение Python, тестирующее модуль на Pascal. Создаёт новый документ и вставляет результат функции модуля.

import sys, os
# Укажем Python где искать наш модуль на Pascal
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'))

# Импортировать модуль Pascal, содержащий функцию SumTwoIntegers
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)

Поскольку OpenOffice вероятно включает Python 2.3, компилируйте модуль для версии 2.3, дополнив на вкладку Other диалога Lazarus Compiler Options:

-dUSE_PYTHON23

В Delphi, введите USE_PYTHON23 на вкладке Directories/Conditionals диалога Project Options.

Если надо переименуйте свой модуль, поместите его в ту же папку, что и test_minmod.py. Теперь проверьте, запустив его из OpenOffice выбрав Tools | Macros | Organize Macros | Python и запустив макроопределение TestMinMod.

Смотрите также