Developing Python Modules with Pascal: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: Developing Python Modules with Pascal
| Old | New | Line | |
|---|---|---|---|
| 1 | + | ## Introduction | |
| 2 | + | ||
| 3 | + | 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/>. | |
| 4 | + | ||
| 5 | + | 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). | |
| 6 | + | ||
| 7 | + | This article describes a very low level, "from scratch" approach. For an established python-pascal bridge, see [[Python4Delphi]]. | |
| 8 | + | ||
| 9 | + | ## Minimum Python API | |
| 10 | + | ||
| 11 | + | Copy and paste this code into a text editor and save it as file PyAPI.pas: | |
| 12 | + | ||
| 13 | + | ``` pascal | |
| 14 | + | unit PyAPI; | |
| 15 | + | ||
| 16 | + | { | |
| 17 | + | ||
| 18 | + | Minimum set of Python function declarations for module libraries. | |
| 19 | + | ||
| 20 | + | Author: Phil (MacPgmr at fastermac.net). | |
| 21 | + | ||
| 22 | + | To add other Python function declarations, see the Python header | |
| 23 | + | files (.h) included with every Python distribution. | |
| 24 | + | ||
| 25 | + | } | |
| 26 | + | ||
| 27 | + | {$IFDEF FPC} | |
| 28 | + | {$MODE Delphi} | |
| 29 | + | {$ENDIF} | |
| 30 | + | ||
| 31 | + | interface | |
| 32 | + | ||
| 33 | + | {$DEFINE IS32BIT} | |
| 34 | + | {$IFDEF CPUX64} {Delphi} | |
| 35 | + | {$UNDEF IS32BIT} | |
| 36 | + | {$ENDIF} | |
| 37 | + | {$IFDEF CPU64} {FPC} | |
| 38 | + | {$UNDEF IS32BIT} | |
| 39 | + | {$ENDIF} | |
| 40 | + | ||
| 41 | + | const | |
| 42 | + | {$IFDEF MSWINDOWS} | |
| 43 | + | {$IFDEF USE_PYTHON23} | |
| 44 | + | PythonLib = 'python23.dll'; | |
| 45 | + | {$ELSE} | |
| 46 | + | PythonLib = 'python27.dll'; | |
| 47 | + | {$ENDIF} | |
| 48 | + | {$ENDIF} | |
| 49 | + | ||
| 50 | + | {$IFDEF LINUX} | |
| 51 | + | {$IFDEF USE_PYTHON23} | |
| 52 | + | PythonLib = 'python23.so'; | |
| 53 | + | {$ELSE} | |
| 54 | + | PythonLib = 'python27.so'; | |
| 55 | + | {$ENDIF} | |
| 56 | + | {$ENDIF} | |
| 57 | + | ||
| 58 | + | {$IFDEF DARWIN} | |
| 59 | + | PythonLib = ''; //Link against Python.framework (-k'-framework Python'). | |
| 60 | + | // To link against a specific version of Python, pass the | |
| 61 | + | // full path to that version's library instead, for example, | |
| 62 | + | // -k'/System/Library/Frameworks/Python.framework/Versions/2.6/Python' | |
| 63 | + | {$ENDIF} | |
| 64 | + | ||
| 65 | + | type | |
| 66 | + | {$IFDEF IS32BIT} | |
| 67 | + | c_long = LongInt; | |
| 68 | + | c_ulong = LongWord; | |
| 69 | + | c_int = LongInt; | |
| 70 | + | {$ELSE} | |
| 71 | + | c_long = Int64; | |
| 72 | + | c_ulong = UInt64; | |
| 73 | + | c_int = Int64; //"int" also appears to be 8 bytes with 64-bit Python | |
| 74 | + | {$ENDIF} | |
| 75 | + | ||
| 76 | + | PyMethodDef = packed record | |
| 77 | + | name : PAnsiChar; //Python function name | |
| 78 | + | meth : Pointer; //Address of function that implements it | |
| 79 | + | flags : c_int; //METH_xxx flags; describe function's arguments | |
| 80 | + | doc : PAnsiChar; //Description of funtion | |
| 81 | + | end; | |
| 82 | + | ||
| 83 | + | PyObject = Pointer; | |
| 84 | + | ||
| 85 | + | const | |
| 86 | + | {$IFDEF USE_PYTHON23} | |
| 87 | + | PYTHON_API_VERSION = 1012; //Also used with Python 2.4 | |
| 88 | + | {$ELSE} | |
| 89 | + | PYTHON_API_VERSION = 1013; | |
| 90 | + | {$ENDIF} | |
| 91 | + | METH_VARARGS = 1; | |
| 92 | + | ||
| 93 | + | function Py_InitModule( name : PAnsiChar; | |
| 94 | + | var methods : PyMethodDef; | |
| 95 | + | doc : PAnsiChar = nil; | |
| 96 | + | self : PyObject = nil; | |
| 97 | + | apiver : c_int = PYTHON_API_VERSION) : PyObject; cdecl; | |
| 98 | + | external PythonLib name {$IFDEF IS32BIT}'Py_InitModule4'{$ELSE}'Py_InitModule4_64'{$ENDIF}; | |
| 99 | + | ||
| 100 | + | function PyArg_ParseTuple(args : PyObject; | |
| 101 | + | format : PAnsiChar) : c_int; cdecl; varargs; external PythonLib; | |
| 102 | + | //Note varargs allows us to simulate C variable number of arguments (...). | |
| 103 | + | ||
| 104 | + | function PyInt_FromLong(along : c_long) : PyObject; cdecl; external PythonLib; | |
| 105 | + | ||
| 106 | + | function PyLong_FromLong(along : c_long) : PyObject; cdecl; external PythonLib; | |
| 107 | + | ||
| 108 | + | function PyLong_FromUnsignedLong(aulong : c_ulong) : PyObject; cdecl; external PythonLib; | |
| 109 | + | ||
| 110 | + | function PyString_FromString(astr : PAnsiChar) : PyObject; cdecl; external PythonLib; | |
| 111 | + | ||
| 112 | + | implementation | |
| 113 | + | ||
| 114 | + | ||
| 115 | + | end. | |
| 116 | + | ``` | |
| 117 | + | ||
| 118 | + | 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. | |
| 119 | + | ||
| 120 | + | ## A simple module example | |
| 121 | + | ||
| 122 | + | 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: | |
| 123 | + | ||
| 124 | + | ``` pascal | |
| 125 | + | library PyMinMod; | |
| 126 | + | ||
| 127 | + | { | |
| 128 | + | ||
| 129 | + | Minimal Python module (library) that includes simple functions. | |
| 130 | + | ||
| 131 | + | Author: Phil (MacPgmr at fastermac.net). | |
| 132 | + | ||
| 133 | + | To compile this module: | |
| 134 | + | - With Delphi: Open this .dpr file and compile. | |
| 135 | + | - With Lazarus: Open .lpi file and compile. | |
| 136 | + | ||
| 137 | + | To deploy module: | |
| 138 | + | - With Delphi: Rename compiled .dll to .pyd. | |
| 139 | + | - With Lazarus on Windows: Rename compiled .so to .pyd. | |
| 140 | + | - With Lazarus on OS X and Linux: .so extension is okay. | |
| 141 | + | ||
| 142 | + | } | |
| 143 | + | ||
| 144 | + | uses | |
| 145 | + | SysUtils, | |
| 146 | + | PyAPI; | |
| 147 | + | ||
| 148 | + | ||
| 149 | + | function SumTwoIntegers(Self : PyObject; | |
| 150 | + | Args : PyObject) : PyObject; cdecl; | |
| 151 | + | var | |
| 152 | + | Arg1 : Integer; | |
| 153 | + | Arg2 : Integer; | |
| 154 | + | begin | |
| 155 | + | PyArg_ParseTuple(Args, 'ii', @Arg1, @Arg2); //Get the two int arguments | |
| 156 | + | Result := PyInt_FromLong(Arg1 + Arg2); //Add them together and return sum | |
| 157 | + | // Result := PyLong_FromLong(Arg1 + Arg2); | |
| 158 | + | // Result := PyLong_FromUnsignedLong(Arg1 + Arg2); | |
| 159 | + | end; | |
| 160 | + | ||
| 161 | + | ||
| 162 | + | function ConcatTwoStrings(Self : PyObject; | |
| 163 | + | Args : PyObject) : PyObject; cdecl; | |
| 164 | + | {From Python documentation for "s" format: "You must not provide storage for | |
| 165 | + | the string itself; a pointer to an existing string is stored into the | |
| 166 | + | character pointer variable whose address you pass." | |
| 167 | + | From Python documentation for PyString_FromString: "Return a new string | |
| 168 | + | object with a copy of the string v as value on success".} | |
| 169 | + | var | |
| 170 | + | Arg1 : PAnsiChar; | |
| 171 | + | Arg2 : PAnsiChar; | |
| 172 | + | begin | |
| 173 | + | PyArg_ParseTuple(Args, 'ss', @Arg1, @Arg2); //Get the two string arguments | |
| 174 | + | Result := PyString_FromString(PAnsiChar(AnsiString(Arg1) + AnsiString(Arg2))); | |
| 175 | + | //Concatenate and return string | |
| 176 | + | end; | |
| 177 | + | ||
| 178 | + | ||
| 179 | + | var | |
| 180 | + | Methods : packed array [0..2] of PyMethodDef; | |
| 181 | + | ||
| 182 | + | procedure initPyMinMod; cdecl; | |
| 183 | + | begin | |
| 184 | + | Methods[0].name := 'SumTwoIntegers'; | |
| 185 | + | Methods[0].meth := @SumTwoIntegers; | |
| 186 | + | Methods[0].flags := METH_VARARGS; | |
| 187 | + | Methods[0].doc := 'Tests passing ints to and from module function'; | |
| 188 | + | ||
| 189 | + | Methods[1].name := 'ConcatTwoStrings'; | |
| 190 | + | Methods[1].meth := @ConcatTwoStrings; | |
| 191 | + | Methods[1].flags := METH_VARARGS; | |
| 192 | + | Methods[1].doc := 'Tests passing strings to and from module function'; | |
| 193 | + | ||
| 194 | + | Methods[2].name := nil; | |
| 195 | + | Methods[2].meth := nil; | |
| 196 | + | Methods[2].flags := 0; | |
| 197 | + | Methods[2].doc := nil; | |
| 198 | + | ||
| 199 | + | Py_InitModule('PyMinMod', Methods[0]); | |
| 200 | + | end; | |
| 201 | + | ||
| 202 | + | ||
| 203 | + | exports | |
| 204 | + | initPyMinMod; | |
| 205 | + | ||
| 206 | + | end. | |
| 207 | + | ``` | |
| 208 | + | ||
| 209 | + | You can add more functions to the module by following the example in initPyMinMod. | |
| 210 | + | ||
| 211 | + | With Delphi, just open PyMinMod.dpr and compile. | |
| 212 | + | ||
| 213 | + | With FPC, just compile from the command line. For example, to create a 64-bit module on OS X: | |
| 214 | + | ||
| 215 | + | ``` pascal | |
| 216 | + | ppcx64 -Sd -k'-framework Python' -oPyMinMod.so PyMinMod.dpr | |
| 217 | + | ``` | |
| 218 | + | ||
| 219 | + | 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: | |
| 220 | + | ||
| 221 | + | ``` xml | |
| 222 | + | <?xml version="1.0"?> | |
| 223 | + | <CONFIG> | |
| 224 | + | <ProjectOptions> | |
| 225 | + | <PathDelim Value="/"/> | |
| 226 | + | <Version Value="6"/> | |
| 227 | + | <General> | |
| 228 | + | <MainUnit Value="0"/> | |
| 229 | + | <IconPath Value="./"/> | |
| 230 | + | <TargetFileExt Value=".exe"/> | |
| 231 | + | <UseAppBundle Value="False"/> | |
| 232 | + | <ActiveEditorIndexAtStart Value="0"/> | |
| 233 | + | </General> | |
| 234 | + | <PublishOptions> | |
| 235 | + | <Version Value="2"/> | |
| 236 | + | <IgnoreBinaries Value="False"/> | |
| 237 | + | <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/> | |
| 238 | + | <ExcludeFileFilter Value="*.(bak|ppu|ppw|o|so);*~;backup"/> | |
| 239 | + | </PublishOptions> | |
| 240 | + | <RunParams> | |
| 241 | + | <local> | |
| 242 | + | <FormatVersion Value="1"/> | |
| 243 | + | <LaunchingApplication PathPlusParams="/usr/X11R6/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/> | |
| 244 | + | </local> | |
| 245 | + | </RunParams> | |
| 246 | + | <Units Count="1"> | |
| 247 | + | <Unit0> | |
| 248 | + | <Filename Value="PyMinMod.dpr"/> | |
| 249 | + | <IsPartOfProject Value="True"/> | |
| 250 | + | <UnitName Value="PyMinMod"/> | |
| 251 | + | <CursorPos X="1" Y="1"/> | |
| 252 | + | <TopLine Value="1"/> | |
| 253 | + | <EditorIndex Value="0"/> | |
| 254 | + | <UsageCount Value="20"/> | |
| 255 | + | <Loaded Value="True"/> | |
| 256 | + | <SyntaxHighlighter Value="Delphi"/> | |
| 257 | + | </Unit0> | |
| 258 | + | </Units> | |
| 259 | + | <JumpHistory Count="0" HistoryIndex="-1"/> | |
| 260 | + | </ProjectOptions> | |
| 261 | + | <CompilerOptions> | |
| 262 | + | <Version Value="8"/> | |
| 263 | + | <Target> | |
| 264 | + | <Filename Value="PyMinMod.so"/> | |
| 265 | + | </Target> | |
| 266 | + | <Parsing> | |
| 267 | + | <SyntaxOptions> | |
| 268 | + | <SyntaxMode Value="Delphi"/> | |
| 269 | + | <CStyleOperator Value="False"/> | |
| 270 | + | <AllowLabel Value="False"/> | |
| 271 | + | <CPPInline Value="False"/> | |
| 272 | + | </SyntaxOptions> | |
| 273 | + | </Parsing> | |
| 274 | + | <CodeGeneration> | |
| 275 | + | <Checks> | |
| 276 | + | <IOChecks Value="True"/> | |
| 277 | + | <RangeChecks Value="True"/> | |
| 278 | + | <OverflowChecks Value="True"/> | |
| 279 | + | <StackChecks Value="True"/> | |
| 280 | + | </Checks> | |
| 281 | + | </CodeGeneration> | |
| 282 | + | <Linking> | |
| 283 | + | <Options> | |
| 284 | + | <PassLinkerOptions Value="True"/> | |
| 285 | + | <LinkerOptions Value="-framework Python"/> | |
| 286 | + | <Win32> | |
| 287 | + | <GraphicApplication Value="True"/> | |
| 288 | + | </Win32> | |
| 289 | + | <ExecutableType Value="Library"/> | |
| 290 | + | </Options> | |
| 291 | + | </Linking> | |
| 292 | + | <Other> | |
| 293 | + | <CompilerPath Value="$(CompPath)"/> | |
| 294 | + | </Other> | |
| 295 | + | </CompilerOptions> | |
| 296 | + | <Debugging> | |
| 297 | + | <Exceptions Count="2"> | |
| 298 | + | <Item1> | |
| 299 | + | <Name Value="ECodetoolError"/> | |
| 300 | + | </Item1> | |
| 301 | + | <Item2> | |
| 302 | + | <Name Value="EFOpenError"/> | |
| 303 | + | </Item2> | |
| 304 | + | </Exceptions> | |
| 305 | + | </Debugging> | |
| 306 | + | </CONFIG> | |
| 307 | + | ``` | |
| 308 | + | ||
| 309 | + | 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: | |
| 310 | + | ||
| 311 | + | ``` python | |
| 312 | + | import PyMinMod | |
| 313 | + | print "Value returned by SumTwoIntegers: " + str(PyMinMod.SumTwoIntegers(1, 2)) | |
| 314 | + | print "Value returned by ConcatTwoStrings: " + PyMinMod.ConcatTwoStrings("Hey ", "there") | |
| 315 | + | ``` | |
| 316 | + | ||
| 317 | + | Note that Python is case sensitive so if your compiled module is in lower-case, change the "PyMinMod" references accordingly. | |
| 318 | + | ||
| 319 | + | Now open a terminal window and run the script like this: | |
| 320 | + | ||
| 321 | + | ``` bash | |
| 322 | + | python test.py | |
| 323 | + | ``` | |
| 324 | + | ||
| 325 | + | The script should output the following line: | |
| 326 | + | ||
| 327 | + | Value returned by SumTwoIntegers: 3 | |
| 328 | + | Value returned by ConcatTwoStrings: Hey there | |
| 329 | + | ||
| 330 | + | 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. | |
| 331 | + | ||
| 332 | + | ## Using your module in a host application | |
| 333 | + | ||
| 334 | + | If you have OpenOffice or NeoOffice installed, you can test running your module from a Python macro. | |
| 335 | + | ||
| 336 | + | 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. | |
| 337 | + | ||
| 338 | + | ``` python | |
| 339 | + | # Python macro that tests Pascal module by creating new document and inserting module function result. | |
| 340 | + | ||
| 341 | + | import sys, os | |
| 342 | + | # Tell Python where to look for our Pascal module | |
| 343 | + | if sys.platform == 'win32': | |
| 344 | + | sys.path.append(os.path.expanduser('~\Application Data\OpenOffice.org2\user\Scripts\python\Library1')) | |
| 345 | + | elif sys.platform == 'darwin': | |
| 346 | + | sys.path.append(os.path.expanduser('~/Library/Preferences/NeoOffice-2.2/user/Scripts/python/Library1')) | |
| 347 | + | ||
| 348 | + | # Import Pascal module that contains SumTwoIntegers function | |
| 349 | + | import PyMinMod | |
| 350 | + | ||
| 351 | + | import uno | |
| 352 | + | ||
| 353 | + | def TestMinMod(): | |
| 354 | + | ctx = uno.getComponentContext() | |
| 355 | + | smgr = ctx.ServiceManager | |
| 356 | + | desktop = smgr.createInstance('com.sun.star.frame.Desktop') | |
| 357 | + | doc = desktop.loadComponentFromURL('private:factory/swriter', '_blank', 0, ()) | |
| 358 | + | textCursor = doc.Text.createTextCursor() | |
| 359 | + | doc.Text.insertString(textCursor, 'Sum of 1 + 2 = ' + str(PyMinMod.SumTwoIntegers(1, 2)), 0) | |
| 360 | + | ``` | |
| 361 | + | ||
| 362 | + | 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: | |
| 363 | + | ||
| 364 | + | -dUSE_PYTHON23 | |
| 365 | + | ||
| 366 | + | With Delphi, enter USE_PYTHON23 on the Project Options dialog's Directories/Conditionals tab. | |
| 367 | + | ||
| 368 | + | 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. | |
| 369 | + | ||
| 370 | + | ## See also | |
| 371 | + | ||
| 372 | + | - [[Python4Delphi]] | |
| 373 | + | ||
| 374 | + | --- | |
| 375 | + | *From the Free Pascal wiki, page "Developing Python Modules with Pascal", as it stood on 2023-06-03 after a change by AMXS52, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* | |
| 376 | + | Category: Other programming languages | |
| 377 | + | Category: Python | |
| 378 | + | Category: Tutorials |
