轉自:http://blog.csdn.net/weiwangchao_/article/details/16880401
由來
FFI庫,是LuaJIT中最重要的一個擴充庫。它允許從純Lua代碼調用外部C函數,使用C資料結構。有了它,就不用再像Lua標準math庫一樣,編寫Lua擴充庫。把開發人員從開發Lua擴充C庫(語言/功能綁定庫)的繁重工作中釋放出來。 FFI簡介
FFI庫,允許從純Lua代碼調用外部C函數,使用C資料結構。
FFI庫最大限度的省去了使用C手工編寫繁重的Lua/C綁定的需要。不需要學習一門獨立/額外的綁定語言——它解析普通C聲明。這樣可以從C標頭檔或參考手冊中,直接剪下,粘貼。它的任務就是綁定很大的庫,但不需要搗鼓脆弱的綁定產生器。
FFI緊緊的整合進了LuaJIT(幾乎不可能作為一個獨立的模組)。JIT編譯器為Lua代碼直接存取C資料結構而產生的代碼,等同於一個C編譯器應該生產的代碼。在JIT編譯過的代碼中,調用C函數,可以被內連處理,不同於基於Lua/C API函數調用。
這一頁將簡要介紹FFI庫的使用方法。 激勵範例:調用外部C函數
真的很用容易去調用一個外部C庫函數:
① local ffi = require("ffi") ② ffi.cdef[[ int printf(const char* fmt, ...); ]]③ ffi.C.printf("Hello %s!", "world")
以上操作步驟,如下:
① 載入FFI庫② 為函數增加一個函式宣告。這個包含在`中括弧`對之間的部分,是標準C文法。.③ 調用命名的C函數——非常簡單
事實上,背後的實現遠非如此簡單:③ 使用標準C庫的命名空間ffi.C。通過符號名("printf")索引這個命名空間,自動綁定標準C庫。索引結果是一個特殊類型的對象,當被調用時,執行printf函數。傳遞給這個函數的參數,從Lua對象自動轉換為相應的C類型。
Ok,使用printf()不是一個壯觀的樣本。你也可能使用了io.write()和string.format()。但你有這個想法…… 以下是一個Windows平台彈出訊息框的樣本:
local ffi = require("ffi") ffi.cdef[[ int MessageBoxA(void *w, const char *txt, const char *cap, int type); ]] ffi.C.MessageBoxA(nil, "Hello world!", "Test", 0)
Bing! 再一次, 遠非如此簡單,不?
和要求使用Lua/C API去綁定函數的努力相比: 建立一個外部C檔案, 增加一個C函數,遍曆和檢查Lua傳遞的參數,並調用這個真實的函數,
傳統的處理方式 增加一個模組函數列表和對應的名字, 增加一個luaopen_*函數,並註冊所有模組函數, 編譯並連結為一個動態庫(DLL), 並將庫檔案遷移到正確的路徑, 編寫Lua代碼,載入模組 等等…… 最後調用綁定函數。
唷。(很不爽呀。) 激勵樣本: 使用C資料結構
FFI庫允許你建立,並訪問C資料結構。當然,其主要應用是C函數介面。但,也可以獨立使用。
Lua構建在進階資料類型之上。它們很靈活、可擴充,而且是動態。這就是我們大家都喜歡Lua的原因所在。唉,針對特殊任務,你需要一個低級的資料結構時,這可能會低效。例如,一個超大的不同結構的數組,需要通過一張超大的表,儲存非常多的小表來實現。這需要大量的記憶體開銷以及效能開銷。
這裡是一個庫的草圖,操作一個彩圖,以及一個基準。首先,樸素的Lua版本,如下:
local floor = math.floor local function image_ramp_green(n) local img = {} local f = 255/(n-1) for i=1,n do img[i] = { red = 0, green = floor((i-1)*f), blue = 0, alpha = 255 } end return img end local function image_to_grey(img, n) for i=1,n do local y = floor(0.3*img[i].red + 0.59*img[i].green + 0.11*img[i].blue) img[i].red = y; img[i].green = y; img[i].blue = y end end local N = 400*400 local img = image_ramp_green(N) for i=1,1000 do image_to_grey(img, N) end
以上代碼,建立一個160.000像素的一張表,其中每個元素是一張持有4個範圍0至255的數字值的表。首先,建立了一張綠色斜坡的圖(1D,為了簡單化),然後進行1000次灰階轉換操作。實在很蠢蛋,可是我需要一個簡單樣本……
以下是FFI版本代碼。其中,被修改的部分加粗標註:
① local ffi = require("ffi")ffi.cdef[[typedef struct { uint8_t red, green, blue, alpha; } rgba_pixel;]]② local function image_ramp_green(n) local img = ffi.new("rgba_pixel[?]", n) local f = 255/(n-1)③ for i=0,n-1 do④ img[i].green = i*f img[i].alpha = 255 end return imgendlocal function image_to_grey(img, n)③ for i=0,n-1 do⑤ local y = 0.3*img[i].red + 0.59*img[i].green + 0.11*img[i].blue img[i].red = y; img[i].green = y; img[i].blue = y endendlocal N = 400*400local img = image_ramp_green(N)for i=1,1000 do image_to_grey(img, N)end
Ok, 這是不是太困難:
① 首先,載入FFI庫,聲明底層資料類型。這裡我們選擇一個資料結構,持有4位元組欄位,每一個由4x8 RGBA像素組成。
② 通過ffi.new()直接建立這個資料結構——其中'?'是一個預留位置,變長數組元素個數。
③ C資料是基於0的(zero-based),所以索引必須是0 到 n-1。你可能需要分配更多的元素,而不僅簡化轉換一流代碼。
④ 由於ffi.new()預設0填充(zero-fills)數組, 我們僅需要設定綠色和alpha欄位。
⑤ 調用math.floor()的過程可以省略,因為轉換為整數時,浮點數已經被向0截斷。這個過程隱式的發生在資料被儲存在每一個像素的欄位時。
現在讓我們看一下主要影響的變更:
首先,記憶體消耗從22M降到640K(4004004位元組)。少了35x。所以,表確實有一個顯著的開銷。BTW(By the Way: 順便說一句): 原始Lua