轻松手动加载库

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++ 中的 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


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2007 年 12 月 6 日下午 2:20 GMT (差异)