Developing Python Modules with Pascal
Introduction
Python is a popular scripting language that is often used to add functionality to other applications such as OpenOffice and Quantum GIS. Your computer may already have a version of Python installed. If not, you can download Python from the official Website: http://www.python.org/.
You can extend Python by developing compiled libraries (called modules) that add functions to Python. This topic discusses how to create a library with Pascal (Delphi or Free Pascal).
Minimum Python API
Copy and paste this code into a text editor and save it as file PyAPI.pas:
unit PyAPI;
{
Minimum set of Python function declarations for module libraries.
Author: Phil (MacPgmr at fastermac.net).
To add other Python function declarations, see the Python header
files (.h) included with every Python distribution.
}
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
interface
const
{$IFDEF MSWINDOWS}
{$IFDEF USE_PYTHON23}
PythonLib = 'python23.dll';
{$ELSE}
PythonLib = 'python25.dll';
{$ENDIF}
{$ENDIF}
{$IFDEF LINUX}
{$IFDEF USE_PYTHON23}
PythonLib = 'python23.so';
{$ELSE}
PythonLib = 'python25.so';
{$ENDIF}
{$ENDIF}
{$IFDEF DARWIN}
PythonLib = ''; //Link against Python.framework (-k-framework -kPython).
// To link Python 2.3, add -k-F/System/Library/Frameworks
{$ENDIF}
type
PyMethodDef = packed record
name : PChar; //Python function name
meth : Pointer; //Address of function that implements it
flags : Integer; //METH_xxx flags; describe function's arguments
doc : PChar; //Description of funtion
end;
PyObject = Pointer;
const
{$IFDEF USE_PYTHON23}
PYTHON_API_VERSION = 1012; //Also used with Python 2.4
{$ELSE}
PYTHON_API_VERSION = 1013;
{$ENDIF}
METH_VARARGS = 1;
function Py_InitModule( name : PChar;
var methods : PyMethodDef;
doc : PChar = nil;
self : PyObject = nil;
apiver : LongInt = PYTHON_API_VERSION) : PyObject; cdecl; external PythonLib name 'Py_InitModule4';
function PyArg_ParseTuple(args : PyObject;
format : PChar) : Integer; cdecl; varargs; external PythonLib;
//Note varargs allows us to simulate C variable number of arguments (...).
function PyInt_FromLong(along : LongInt) : PyObject; cdecl; external PythonLib;
implementation
end.
If you have a different version of Python, define USE_PYTHON23 to link against version 2.3 or just change the library name (PythonLib). If you need other Python API functions, just add them to PyAPI.pas following the example of 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 a single function.
Author: Phil (MacPgmr at fastermac.net).
For a good explanation of modules from a C perspective, see:
http://superjared.com/entry/anatomy-python-c-module/
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 input arguments
Result := PyInt_FromLong(Arg1 + Arg2); //Add them together and return sum
end;
var
Methods : packed array [0..1] of PyMethodDef;
procedure initPyMinMod; cdecl;
begin
Methods[0].name := 'SumTwoIntegers';
Methods[0].meth := @SumTwoIntegers;
Methods[0].flags := METH_VARARGS;
Methods[0].doc := 'Tests argument passing to and from module function';
Methods[1].name := nil;
Methods[1].meth := nil;
Methods[1].flags := 0;
Methods[1].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 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 two lines:
import PyMinMod
print "Value returned by SumTwoIntegers: " + str(PyMinMod.SumTwoIntegers(1, 2))
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
What we've done here is create a simple Python module that implements a Python function for adding two integers. Once you've imported the module into your Python script, you can use the function 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.