轻松手动加载库

lua-users home
wiki

手动加载 Lua 共享库变得轻松

本文介绍了一种方法,只需更改程序源代码中的 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,操作系统会发出错误,并且应用程序无法运行。使用自动加载非常简单:您需要使用 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++ 中使用 new。它也简化了初始化:通过调用一次 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


RecentChanges · preferences
编辑 · 历史
最后编辑于 2007 年 12 月 6 日上午 8:20 GMT (差异)