在 Python 使用 Lazarus 定義的函式庫
如果你已經有一坨 Lazarus 或 Delphi 的程式碼,很想要轉換到 Python 的世界,但又割捨不下原本的心血,那麼使用 Shared Library 即可輕鬆達成目標,以下藥方可配溫開水服用——
- 用 Lazarus 編譯 Shared Library。
- 在 Python 中讀進 Shared Library。
基本概念就是上面兩步。只要跟著接下來的步驟,你就可以在 Python 操作定義在 Lazarus 的資料結構,至於反過來的需求(在 Lazarus 中操作 Python 的資料結構),C/C++ 的經驗是使用 python.h ,但目前還沒有完善的 Pascal 接口,之後有心得再發上來。
下面是具體步驟:
0. 測試平台
以下平台都測試 OK 。
平台 1:
OS: Mac OS X 10.9.3
Python: 2.7.3
Lazarus: 1.0.14
平台 2:
OS: Mint 15 64-bit
Python: 2.7.4
Lazarus: 1.0.14
1. Export Methods in Lazarus
假設我有以下資料結構:
pascal
TCircle = class
public
constructor Create(X, Y, Radius: Integer);
property X: Integer;
property Y: Integer;
property Radius: Integer;
end;
雖然物件化很好,但要透過 Shared Library 溝通,資料結構得『扁平化』才行,意思是要準備一系列的 method ,將對此物件的各個 constructor 、 destructor 、 getters 、 setters 通通獨立出來,學武前的蹲馬步總是比較枯燥的,忍忍就過了:
pascal
procedure Circle_Create(var AObject: Pointer; X, Y, Radius: Integer); cdecl;
procedure Circle_Free(AObject: Pointer); cdecl;
function Circle_X(AObject: Pointer): Integer; cdecl;
function Circle_Y(AObject: Pointer): Integer; cdecl;
function Circle_Radius(AObject: Pointer): Integer; cdecl;
其中 cdecl
是 C 語言的函式呼叫慣例,與 Pascal 預設的 register
不同,故需明確宣告。
為什麼要使用 C 的 呼叫慣例呢?這是因為 Python 天生就可以讀 C 編譯出來的 shared library ,而剛好 Free Pascal 能夠與 C 相容,所以只要 exports method 的參數都與 C 對應,Python 都可正常呼叫。
2. Build Shared Library
接下來在 Lazarus 中開啟一個 Library 工程,將上面的 source code use 進來,然後在 yourproject.lpr 的 exports 欄位中,選擇要讓 shared library 使用者呼叫的 methods:
pascal
exports
Circle_Create, Circle_Free, Circle_X, ...
end.
然後就編譯之!編譯出來的 library 會放在 project 根目錄中,其命名是由 Project > Project Options > Paths > Target file name
決定,如果不希望 lazarus 幫你加上慣例的前後綴字,可將 Apply conventions
的勾勾取消。
3. Load Shared Library in Python
接下來在 Python 端,用以下程式碼將讀 library 的行為用物件包起來,讓使用上跟一般物件沒兩樣:(使用者體驗!)
```python
所有用到的工具都在這了
import ctypes
將 library 讀進來, 在 linux/mac 中使用 cdll, 在 windows 使用 windll
# 之所以放在 module 層級是因為, shared library 只需要讀一次 lib_handle = ctypes.cdll.LoadLibrary(“./library.so”)
如果希望呼叫時 python 幫你做型別檢查, 可使用 argtypes
# ShapeCircle_Create(pointer, integer, integer, integer); lib_handle.Circle_Create.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_int]
ShapeCircle(pointer): integer;
lib_handle.Circle_Radius.argtypes = [ctypes.c_void_p]
同樣的, 回傳值也可以檢查
lib_handle.Circle_Radius.restype = ctypes.c_int
建立 Circle 的包裝
class Circle: def init(self, x, y, radius): # 建立 library 真正操作的物件, 指標類型的形態需使用 c_void_p self._lib_object = ctypes.c_void_p(0)
# 呼叫 library exports 的 constructor
# ctypes.byref: 如果 library method 中有參數為
# call by address/reference, 在 python 端
# 需使用 byref, 這是因為 python 預設都是 call by value.
lib_handle.Circle_Create(ctypes.byref(self._lib_object), x, y, radius)
def __del__(self):
"""使用 destructor 將內部資源清除"""
lib_handle.Circle_Free(self._lib_object)
def radius(self):
return lib_handle.Circle_Radius(self._lib_object)
輕巧地使用它吧!
circle = Circle(10, 10, 50) print “radius: “ + str(circle.radius())
```