簡便手册程序庫加載

lua-users home
wiki

從共用程序庫中手動加載 Lua 5.1 所有函式變得很容易

本文介紹一種方法,讓您可以透過在程式原始碼中僅變更 5 行 C 語法,就能手動將所有 Lua 5.1 函式從共用程序庫載入到您的應用程式中!如果您已經手動載入一些共用程序庫(.dll 或 .so),您可以跳過接下來的四個引言章節。您可以在此處找到原始碼:[lua_dyn.zip]

關於動態載入的共用程序庫

在 Windows 中,共用動態程序庫稱為動態連結庫或 DLL(*.dll)。在 Unix 中,它們通常稱為共用物件(*.so)或共用程序庫(*.sa)。兩者都指的是可執行檔案的相同本質,而我們從現在起將它稱為 SL(共用程序庫)。SL 包含已編譯且連結的機器碼,會將公共函式匯出到應用程式,但本身沒有 main() 函式,因此無法直接執行。相反地,作業系統會將 SL 載入應用程式的記憶體空間中,然後應用程式就可以看到匯出的 SL 函式和變數,就像它們在應用程式內部一樣。

SL 載入模式

應用程式有兩種方法可以載入 SL,一是自動,一是手動。在第一種模式中,作業系統會在應用程式啟動時自動載入 SL,並將所有函式匯出到其中。如果找不到 SL,作業系統會發出錯誤,應用程式程式就無法執行。使用自動載入很簡單:您可以使用 SL 標準包含檔案(在 Windows 中,使用 __declspec(dllimport) 函式前綴)來編譯應用程式,並連結到特殊的靜態程序庫(在 Windows 中為 *.lib,在 Unix 中為 *.a),這樣就完成了。在手動模式中,應用程式本身必須載入 SL(在 Windows 中使用 LoadLibrary 或在 POSIX 系統中使用 dlopen),使用 GetProcAddressdlsym 匯出每個需要的函式,並在完成後使用 FreeLibrarydlclose 關閉 SL。一般來說,您無法使用 SL 標頭檔,但必須對函式原型進行 typedef,實例化其變數,並在呼叫匯出的函式之前將 GetProcAddress 的結果傳遞給此變數。

手動載入使用

儘管手動載入比較複雜且容易出錯,但您為什麼要使用手動載入?有時,您別無選擇。手動載入較為靈活,讓您可以

使用 Lua 5.1 的傳統範例

當然,我們將會示範傳統的 Hello World! 範例。字串將會由 Lua 5.1 的直譯器輸出,並在 luaL_dostring 函數內部執行的。首先,示範使用自動 SL 載入的範例。你必須將它連結到相關的靜態函式庫(類似 -llua)。
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

int main(int argc, char* argv[])
{
	lua_State* L;
	L = lua_open();
	luaL_openlibs(L);
	luaL_dostring(L, "print 'Hello World'");
	lua_close(L);
	return 0;
}
相同的範例,在 Windows 系統中使用一般的資料載入
#include <windows.h>
#include "lua.h"

typedef lua_State * (__cdecl *luaL_newstate_t) (void);
typedef void (__cdecl *luaL_openlibs_t) (lua_State *L); 
typedef int (__cdecl *luaL_loadstring_t) (lua_State *L, const char *s);
typedef int (__cdecl *lua_pcall_t) (lua_State *L, int nargs, int nresults, int errfunc);
typedef void (__cdecl *lua_close_t) (lua_State *L);

luaL_newstate_t   luaL_newstate_ptr;
luaL_openlibs_t   luaL_openlibs_ptr;
luaL_loadstring_t luaL_loadstring_ptr;
lua_pcall_t       lua_pcall_ptr;
lua_close_t       lua_close_ptr;

int main(int argc, char* argv[])
{
	lua_State* L;
	HMODULE module = LoadLibrary("lua5.1.dll");
	if(module == NULL)
		return 1;
	luaL_newstate_ptr   = (luaL_newstate_t)  GetProcAddress(module, "luaL_newstate");
	luaL_openlibs_ptr   = (luaL_openlibs_t)  GetProcAddress(module, "luaL_openlibs");
	luaL_loadstring_ptr = (luaL_loadstring_t)GetProcAddress(module, "luaL_loadstring");
	lua_pcall_ptr       = (lua_pcall_t)      GetProcAddress(module, "lua_pcall");
	lua_close_ptr       = (lua_close_t)      GetProcAddress(module, "lua_close");
	if(luaL_newstate_ptr == NULL || luaL_openlibs_ptr == NULL || lua_close_ptr == NULL
		|| luaL_loadstring_ptr == NULL || lua_pcall_ptr == NULL)
	  	return 1;
	
	L = luaL_newstate_ptr();
	luaL_openlibs_ptr(L);
	/* Cannot use macro luaL_dostring, because lua_pcall is renamed ! */
	luaL_loadstring_ptr(L, "print 'Hello World'") || lua_pcall_ptr(L, 0, LUA_MULTRET, 0);
	lua_close_ptr(L);
	return 0;
}
第二個版本有許多缺點

示範的第三個版本,使用提議的簡化方式。你必須將它與另一個 lua_dyn.c 檔案一起編譯:它可以是 gcc -o hello hello.c lua_dyn.c

#include <windows.h>
#include "lua_dyn.h"

#define LUA_PREFIX LuaFunctions.
lua_All_functions LuaFunctions;

int main(int argc, char* argv[])
{
	lua_State* L;
	HMODULE module = LoadLibrary("lua5.1.dll");
	if(!luaL_loadfunctions(module, &LuaFunctions, sizeof(LuaFunctions)))
		return 1;

	L = lua_open();
	luaL_openlibs(L);
	luaL_dostring(L, "print 'Hello World'");
	lua_close(L);
	return 0;
}
你可以看到,我們只需要變更標頭檔,實例化一個結構、定義一個巨集、載入函式庫,然後從 lua_dyn.c 呼叫一個外部函數 luaL_loadfunctions。接著,可以使用與在自動載入中相同的程式碼,包括所有巨集!

它是如何運作的?

Lua 指令碼 export_h.lua 用於從 Lua 5.1 標頭檔 lua.hlauxlib.hlualib.h 產生檔案 lua_dyn.hlua_dyn.c。它可以在任何標準的 Lua 5.1 直譯器上執行。每次指令碼看到外部函數定義時,它就會用一個 typedef、函數結構中的一個欄位,以及一個 #define 來取代它。範例
LUA_API lua_State *(lua_newstate) (lua_Alloc f, void *ud);
LUA_API void       (lua_close) (lua_State *L);
LUA_API lua_State *(lua_newthread) (lua_State *L);
變成
typedef lua_State * (__cdecl *lua_newstate_t) (lua_Alloc f, void *ud);
typedef void (__cdecl *lua_close_t) (lua_State *L);
typedef lua_State * (__cdecl *lua_newthread_t) (lua_State *L);
...
typedef struct lua_All_functions
{
  lua_newstate_t          Newstate;
  lua_close_t             Close;
  lua_newthread_t         Newthread;
  ...
} lua_All_functions;
...
#define lua_newstate            LUA_PREFIX Newstate
#define lua_close               LUA_PREFIX Close
#define lua_newthread           LUA_PREFIX Newthread
結構 lua_All_functions 包含指向所有外部 Lua API 函數的指標。這允許在單一行中實例化所有函數指標:宣告靜態變數,或者使用 malloc 或使用 C++ 中的新增為它分配記憶體。它也簡化了初始化:單一函數呼叫至 luaL_loadfunctions 將會從函數名稱表匯出所有函數。巨集定義讓你能夠使用標準函數名稱,例如 lua_newstate,而不是結構存取,例如 luaFct.Newstatem_pLuaF->Newstate。此外,它確保相容於建構在其他函數上的 API 巨集。為了編譯這個,你必須將 LUA_PREFIX 定義為 luaFct.m_pLuaF->。由於原始函數宣告已從標頭中移除,因此不會發生命名衝突。

範例:在前一個範例中,第 16 行

luaL_dostring(L, "print 'Hello World'");
會被預處理器取代為以下程式碼
(LuaFunctions. LoadstringL(L, "print 'Hello World'") || LuaFunctions. Pcall(L, 0, (-1), 0));
這會編譯,因為 LuaFunctionsstruct lua_All_functions 的靜態宣告。

自訂

提供的檔案 lua_dyn.clua_dyn.h 使用 Lua 5.1.2 標頭檔建置,且未經修改。出於這個原因,它可能適用於任何 Lua 解譯器的標準套件。已在 Windows 和 Linux 平臺上進行測試。如果您有不同的組態,請採取下列操作

如果您能重新編譯 Lua 共享函式庫,則您可能偏好於在該函式庫內編譯檔案 lua_dyn.c,這樣可以進一步簡化手動載入。若是如此,請將 loaderfct_indll 標記變更為 true,這會定義一個說明巨集 LUA_LOAD_FUNCTIONS。該巨集會從 Lua SL 手動載入函式 luaL_loadfunctions,然後在單一指令中呼叫它。

-- PatrickRapin


近期變更 · 偏好設定
編輯 · 歷程記錄
最後編輯時間為 2007 年 12 月 6 日 下午 2:20 GMT (差異)