lua——alien庫實現lua呼叫C動態連結庫(dll、so)

R-B發表於2021-09-09

我們知道,lua透過lua_State堆疊可以很方便的與C語言進行互動

http://blog.csdn.net/sm9sun/article/details/68946343

也可以呼叫專門為lua呼叫而封裝的C庫。

具體步驟:

1.原C檔案中引入lua相關標頭檔案


#include "lua.h"#include "lualib.h"#include "lauxlib.h"

2.宣告呼叫函式



static int c_hello(lua_State *L)   //引數一律為lua_State堆疊



3.註冊一個全域性函式對映


static const luaL_reg functions[] = {{"hello", c_hello},{0,0}};


4.open函式(主函式)



int luaopen_*(lua_State *L)//*是想打成so的包名{luaL_register(L, "*", functions);return 1;}

5.打包成so檔案(注意引入lua)


6.lua呼叫


require('*')*.hello


但是這種方式畢竟太侷限,我們希望的是lua可以呼叫普通的c庫,lua的第三方庫alien就可以實現這一功能

安裝alien可以透過git ,更好的是藉助luarocks安裝

LuaRocks是Lua模組的軟體包管理器,相信常用lua的人應該不陌生。

首先在當前產品所需要的lua版本下安裝luarocks(這個是必須的,例如我用skynet框架裡lua5.3版本,但是我係統是lua5.1,這樣後期就會不相容)


wget *.tar.gz  tar zxpf luarocks-*.tar.gzcd luarocks-*./configuremake bootstrap

*為版本號,安裝好後確定版本


圖片描述

然後luarocks install alien安裝alien


alien需要libffi,如果沒有,請先安裝

圖片描述



yum install -y libffi-devel


如果你的系統裡有python2.6,這裡可能會出現一個python不相容的情況,如沒有請無視

升級python 2.6.5到2.7.13以上,能相容yum
# 安裝所有的開發工具包
yum groupinstall -y "Development tools"
# 安裝其它的必需包
yum install -y zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel

# 下載、編譯和安裝 Python 2.7.13
wget
tar zxf Python-2.7.13.tgz
cd Python-2.7.13
./configure
make && make install

/usr/local/bin/python2.7 -V
mv /usr/bin/python /usr/bin/python2.6.6 
ln -s /usr/local/bin/python2.7 /usr/bin/python
python -V
讓yum install仍然走2.6.6
vim /usr/bin/yum
#!/usr/bin/python 改成#!/usr/bin/python2.6.6
git clone git://github.com/waylan/Python-Markdown.git python-markdown
cd python-markdown
python setup.py install
cp /usr/bin/markdown_py /usr/bin/markdown
markdown


也可以安裝LuaJIT,其包含ffi

wget
tar zxvf LuaJIT-2.0.5.tar.gz
cd LuaJIT-2.0.5
make && make install
export LUAJIT_LIB=/usr/local/lib
export LUAJIT_INC=/usr/local/include/luajit-2.0


確定安裝好ffi後透過luarocks install alien安裝alien

如還報ffi.h找不到,則指定路徑
luarocks install alien FFI_DIR=/usr/lib64/libffi-3.0.5 FFI_LIBDIR=/usr/lib64


檢視安裝的alien資訊

圖片描述


安裝完畢後在lua環境下呼叫alien模組,若不報錯則安裝成功


require("alien_c")require("alien")


透過alien呼叫c庫的方法:



alien = require("alien_c") --1.載入alienlibc = alien.load("*.so") -- 2.載入動態連結庫so,dll都可以libc.hello:types("string","string") -- 3.說明引數型別:例如輸入一個json,返回一個jsonin_json=""out_json="ret"out_json=libc.hello(in_json) -- 呼叫print(out_json)


以下為alien官方api文件譯文


基本用法載入動態庫非常簡單; Alien在預設情況下假定一個名為 .dylib的OSX 命名方案和其他Unix系統的lib 名稱 .so。如果名稱不是alien模組匯出的函式之一,那麼可以使用庫獲取引用alien.name。否則(例如,要載入一個名為libwrap.so的庫),您必須使用alien.load("wrap")。您還可以透過alien.load使用路徑或相應的副檔名(如 alien.load("mylibs/libfoo.so")或)呼叫庫來指定庫的全名alien.load("libfoo.so")。無論哪種方式,您都可以獲得對庫訪問的參考,以訪問其功能。您還可以使用當前正在執行的模組的引用 alien.default,這樣可以引用模組匯出的任何函式及其對ELF和Mach-O系統的傳遞依賴性。一旦你有一個對庫的引用,你可以使用libref.funcname獲得一個匯出函式的引用。例如:> def=alien.default> =def.putsalien function puts, library defaults>要使用一個函式,首先必須告訴Alien函式原型,使用func:types(ret_type,arg_types ...),其中的型別是以下字串之一:“void”,“int”,“uint”,“double” ,“char”,“string”,“pointer”,“ref int”,“ref uint”,“ref double”,“ref char”,“callback”,“short”,“ushort”長“,”烏龍“,”浮“。大多數直接對應C型別; 位元組是一個帶符號的char,字串是const char *,指標是void *, 回撥是一個通用函式指標,ref char,ref int 和ref double是透過參考版本的C型別。繼續以前的例子:> def.puts:types("int", "string")> def.puts("foo")foo>您可以看到,在定義原型後,您可以像Lua函式一樣呼叫該函式。外來人將Lua號轉換為C數字型別,將nil轉換為NULL,將Lua字串轉換為const char *作為 字串,並將nil轉換為NULL,將userdata 轉換為void *作為 指標。對於返回值,轉換工作相反(指標型別轉換為light userdata)。參考型別是特殊的; Alien在堆疊中為引數分配空間,複製傳遞給它的Lua號(適當轉換),然後使用該空間的地址呼叫該函式。然後將該值轉換回Lua號,並在函式正常返回值後返回。一個例子,使用scanf:> scanf = alien.default.scanf> scanf:types("int", "string", "ref int", "ref double")> _, x, y = scanf("%i %lf", 0, 0)23 42.5> =x23> =y42.5即使功能沒有使用,您也必須傳遞一個值,如上所述。指定型別的另一種方法是將表傳遞給func:types。這個表的陣列部分每個引數都有一個專案,你也可以傳遞兩個雜湊鍵,ret,函式的返回型別(預設int為常規), abi是函式的呼叫約定(對Windows有用,可以指定“stdcall”作為__stdcall函式的ABI,預設的ABI始終為“default”,所有系統還支援通常的C呼叫約定“cdecl”。在沒有stdcall約定的系統中,“stdcall”是與“預設”相同。這是使用此替代定義的上一個示例:> scanf = alien.default.scanf> scanf:types{ ret = "int", "string", "ref int", "ref double" }> _, x, y = scanf("%i %lf", 0, 0)23 42.5> =x23> =y42.5如果您獲得原始的函式指標(從函式返回,例如或傳遞給回撥),則可以將其轉換為異常函式alien.funcptr(fptr)。這返回一個Alien函式物件,您可以正常鍵入和呼叫函式。緩衝區基本用法足以與C程式碼進行大量介面,特別是處理自己的記憶體分配的良好的庫(Lua C API是這樣的API的一個很好的例子)。但是有些庫不能匯出這樣一個良好的API,並且需要您分配由庫突變的記憶體。這樣可以防止您將Lua字串傳遞給他們,因為Lua字串必須是不可變的,所以Alien提供了一個緩衝區抽象。功能 alien.buffer分配一個新的緩衝區。如果沒有引數呼叫它,它將為您的平臺分配一個標準緩衝區大小的緩衝區。如果使用一個數字來呼叫它,它將分配一個具有這個位元組數的緩衝區。如果你傳遞一個字串,它將分配一個作為字串副本的緩衝區。如果您傳遞一個light userdata,它將使用該userdata作為緩衝區(請注意)。製作緩衝區後,您可以傳遞它來代替字串或指標型別的任何引數 。要獲取您使用的緩衝區的內容,buf:tostring您可以再次傳遞要從緩衝區讀取的字元數,也可以不傳遞任何內容,將緩衝區視為C字串(讀取直到找到 0)。您也可以呼叫buf:len,呼叫strlen緩衝區。最後, tostring(buf)是一樣的buf:tostring()。一個使用緩衝區的例子:> gets = alien.default.gets> gets:types("pointer", "string")> buf = alien.buffer()> gets(buf)Foo bar> =tostring(buf)Foo bar>您可以訪問緩衝區的第i個字元buf[i],並可以設定其值buf[i] = v。請注意,這些是C字元(位元組),而不是Lua 1個字元的字串,因此您需要使用string.char和string.byte 轉換Lua字元和C字元。從Lua訪問Alien緩衝區是基於1而不是0的。您還可以使用buf:get(offset,type)獲取並設定其他值,並透過buf:set(offset,val,type)設定它。偏移量以位元組為單位,而不是元素,所以如果buf有三個“int”值,則它們的偏移量分別為1,5和9,假設每個“int”為四個位元組長。所有獲取和設定操作都不執行邊界檢查,因此要特別小心,或者使用構建在緩衝區之上的更安全的alien.array抽象。陣列陣列是緩衝區,頂部有一層額外的安全和糖。您建立一個陣列alien.array(type, length),其中type是陣列元素的Alien型別,長度是陣列中有多少個元素。建立陣列後ARR你可以得到與元素的型別arr.type,它有多少個元素與arr.length每個單元的,和大小(以位元組為單位)arr.size。底層緩衝區是arr.buffer。您可以使用arr [i]訪問第i個元素,並使用arr [i] = val進行設定。型別轉換與緩衝區或函式呼叫相同。將陣列中的字串或使用者資料儲存在陣列中,以使其在陣列中不被收集。為了方便起見,alien.array還可以接受其他兩種形式:alien.array(type, tab)建立一個與選項卡長度相同的陣列,並使用它的值進行初始化; alien.array(type, length, buf)使用buf建立一個陣列作為底層緩衝區。您也可以使用陣列的內容進行迭代arr:ipairs()。以下示例顯示了陣列的使用:local function sort(a, b)  return a - bendlocal compare = alien.callback(sort, "int", "ref int", "ref int")local qsort = alien.default.qsortqsort:types("void", "pointer", "int", "int", "callback")local nums = alien.array(t, { 4, 5, 3, 2, 6, 1 })qsort(nums.buffer, nums.length, nums.size, compare)for i, v in nums:ipairs() do print(v) end這將在控制檯上列印數字1到6。結構外星人也對宣告性結構有基本的支援,也可以在基本緩衝區上實現一層糖。該alien.defstruct(description)函式建立一個具有給定描述的結構型別,它是具有每個欄位的名稱和型別的對的列表,其中型別是任何基本的外來型別(結構體內部沒有結構體)。例如:rect = alien.defstruct{{ "left", "long" },{ "top", "long" },{ "right", "long" },{ "bottom", "long" }}這將建立一個新的結構型別,其中包含四個型別為“long”的欄位,並儲存在其中rect。要建立一個此結構的例項(由緩衝區支援)呼叫rect:new()。然後,您可以像在Lua表上一樣設定結構體的欄位,例如r.left = 3。要獲取底層緩衝區(例如將其傳遞給C函式),您必須呼叫該例項r()。繼續的例子:r = rect:new()r.left = 2doubleleft = alien.rectdll.double_leftdoubleleft:types("void", "pointer")doubleleft(r()))assert(r.left == 4)您還可以將緩衝區或其他使用者資料傳遞給newstruct型別的方法,在這種情況下,這將是您正在建立的struct例項的後備儲存。這對於解包C函式返回的外部結構很有用。指標開箱Alien還提供了三個方便的功能,可讓您取消引用指標並將值轉換為Lua型別:alien.tostring使用userdata(通常從具有指標返回值的函式返回),將其轉換為char *,並返回一個Lua字串。您可以提供一個可選的大小引數(如果您不先Alien 在緩衝區呼叫strlen)。alien.toint使用userdata,將其轉換為int *,取消引用它並將其作為數字返回。如果你傳遞一個數字,它假定userdata是一個包含這個元素數量的陣列。alien.toshort,alien.tolong,alien.tofloat,和 alien.todouble像alien.toint,但與各自的型別轉換工作。還提供無符號版本。數字alien.to 型別函式使用一個可選的第二個引數來告訴從使用者資料解壓縮多少專案。例如,如果ptr是一個指向四個浮點陣列的指標,則以下程式碼解包該陣列:> fs = alien.tofloat(ptr, 4)> =#fs4>請仔細使用這些功能,不進行任何安全檢查。對於更高階的解組使用alien.struct.unpack 功能。標籤從C庫中包裝物件時的常見模式是在完整的使用者資料中放置一個指向此物件的指標,然後將該使用者資料與與字串標籤關聯的metatable關聯。此標記用於檢查使用者資料是否在使用它的每個函式中的有效使用者資料。由於userdata是一個完整的使用者資料,它還可以有一個 __gc資源回收的方法。外星人有三個功能可以讓您在擴充套件上覆制此模式:alien.tag(*tagname*)使用標籤標記名建立一個新的metatable(如果不存在),或者使用該標籤返回metatable。標籤的名稱空間是全域性的,所以一個很好的模式是使用模組的名稱(如mymod_mytag)將標籤名稱字首。alien.wrap(*tagname*, ...)建立一個完整的userdata,使用與tagname相關聯的metatable進行標記,儲存您傳遞的值,然後返回完整的userdata。有效值為nil,整數和其他使用者資料。alien.unwrap(*tagname*, obj)測試如果obj用 標記名標記,如果不是,則丟擲錯誤,然後返回之前儲存的值。alien.rewrap(*tagname*, obj, ...)用新值替換obj上的元素。如果您傳遞的值超過obj,則以前會忽略額外的值。如果你透過較少的tehn obj充滿了 零。例如,假設libfoo有一個create_foo返回Foo*物件的函式。destroy_foo當不再使用這些物件時,必須透過呼叫來妥善處理這些物件 。這很容易實現:local tag_foo = alien.tag("libfoo_foo")alien.foo.create_foo:types("pointer")alien.foo.destroy_foo_types("void", "pointer")function new_foo()  local foo = alien.foo.create_foo()return alien.wrap("libfoo_foo", foo)endtag_foo = {__gc = function (obj)local foo = alien.unwrap("libfoo_foo", obj)alien.foo.destroy_foo(foo)end}然後在任何對Foo*型別進行操作的函式上,首先解開它來獲取指標,然後將其傳遞給libfoo中的函式。錯誤程式碼幾個作業系統函式在一個名為errno的特殊變數上返回錯誤。用外部電話 獲取errno的值alien.errno()。回撥一些庫具有函式,可以使用回撥函式,函式庫可以呼叫。大多數GUI庫都使用回撥函式,但即使是C庫也有qsort。外星人讓您可以從Lua功能建立回撥alien.callback。你傳遞函式和庫預期的回撥原型。Alien將返回一個回撥物件,您可以傳入任何回撥型別的引數。一個簡單的例子,使用qsort:local function cmp(a, b)  return a - bendlocal cmp_cb = alien.callback(sort, "int", "ref char", "ref char")local qsort = alien.default.qsortqsort:types("void", "pointer", "int", "int", "callback")local chars = alien.buffer("spam, spam, and spam")qsort(chars, chars:len(), alien.sizeof("char"), cmp_cb)assert(chars:tostring() == "   ,,aaaadmmmnpppsss")在快速排序函式對就地陣列,因此我們必須使用一個緩衝區。回撥可以像Lua一樣呼叫,就像任何其他的Alien功能一樣,你可以用他們的“型別”方法自由地改變它們的型別。魔法數字C庫充滿了真正的魔術數字的符號常量,因為在C編譯器有機會看到它們之前,它們被前處理器所取代。這意味著所有這些常量都在標頭檔案上。這也包括圖書館所依賴的結構的佈局和大小等。所有這些資訊可以從版本到圖書館版本,或從平臺更改為平臺。Alien提供了一個稱為常數的實用程式指令碼,可以更輕鬆地處理這些數字。此實用程式在命令列中使用三個引數:定義檔案,要生成的C檔案的名稱以及在編譯和執行時C檔案將生成的Lua檔案的名稱。定義檔案可以包含預處理程式指令,空白行和具有表單識別符號或lua_identifier = c_identifier的定義的行。第一種形式相當於identifier = identifier。最好透過例子解釋(從libevent繫結):#include #include EV_SIZE = sizeof(struct event)EV_READEV_WRITEEV_TIMEOUTEVLOOP_NONBLOCKEVLOOP_ONCE具有前處理器指令的行將逐字複製到C檔案 常量生成。上述定義檔案生成此C檔案:/* Generated by Alien constants */#include #include #include #define LUA_FILE "event_constants.lua"int main() {FILE *f = fopen(LUA_FILE, "w+");fprintf(f, "-- Generated by Alien constantsnn");fprintf(f, "%s = %in", "EV_SIZE ",  sizeof(struct event));fprintf(f, "%s = %in", "EV_READ", EV_READ);fprintf(f, "%s = %in", "EV_WRITE", EV_WRITE);fprintf(f, "%s = %in", "EV_TIMEOUT", EV_TIMEOUT);fprintf(f, "%s = %in", "EVLOOP_NONBLOCK", EVLOOP_NONBLOCK);fprintf(f, "%s = %in", "EVLOOP_ONCE", EVLOOP_ONCE);fclose(f);}哪些在編譯和執行時在Linux / Intel系統上生成此檔案:-- Generated by Alien constantsEV_SIZE  = 84EV_READ = 2EV_WRITE = 4EV_TIMEOUT = 1EVLOOP_NONBLOCK = 2EVLOOP_ONCE = 1這些步驟(生成C檔案,編譯,生成Lua檔案)最好在擴充套件的構建步驟中完成。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4560/viewspace-2804656/,如需轉載,請註明出處,否則將追究法律責任。

相關文章