Lua 輕量級、可嵌入的特性使其成為遊戲邏輯、配置腳本、插件系統的首選。當 Lua 標準庫無法滿足性能敏感或系統級操作時,編寫 C 擴展模塊成為必經之路。本文通過完整案例,演示從 C 代碼編寫、編譯鏈接到 Lua 調用的全過程。
Lua 與 C 交互遵循 棧式 API:
lua_State* 讀取參數、推入返回值int 指定math.sin 綁定hello_lib.c#include <math.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
// C 函數簽名:固定模式 lua_CFunction
static int hello_sin(lua_State *L) {
// 1. 從棧位置 1 取數值 (索引從 1 開始)
double d = luaL_checknumber(L, 1);
// 2. 計算並推入結果
lua_pushnumber(L, sin(d));
// 3. 返回 1 個結果
return 1;
}
// 模塊函數註冊表:{函數名, 函數指針} 數組,以 {NULL, NULL} 結尾
static const struct luaL_Reg hello_lib[] = {
{"hello_sin", hello_sin},
{NULL, NULL}
};
// 模塊入口函數:命名規範 luaopen_<模塊名>
// Lua 5.1 使用 luaL_register,5.2+ 推薦 luaL_newlib
int luaopen_hello_lib(lua_State *L) {
// Lua 5.1 兼容寫法
luaL_register(L, "hello_lib", hello_lib);
return 1; // 返回模塊表
// Lua 5.2+ 現代寫法 (取消註釋使用)
// luaL_newlib(L, hello_lib);
// return 1;
}
build.sh#!/bin/bash
# 生成位置無關代碼 (-fPIC) + 共享庫 (-shared)
# macOS 需改為 -dynamiclib -undefined dynamic_lookup
gcc hello_lib.c -fPIC --shared -o hello_lib.so
# 驗證導出符號
nm -D hello_lib.so | grep luaopen_hello_lib
test.lua-- 1. 加載模塊 (搜索 package.cpath 指定路徑)
local M = require("hello_lib")
-- 2. 調用導出函數
local result = M.hello_sin(1.0) -- 參數為弧度
print("sin(1.0) = " .. result) -- 輸出: 0.8414709848079
-- 3. 批量測試
for i = 0, 6 do
local rad = i * math.pi / 6
print(string.format("sin(%.2f) = %.6f", rad, M.hello_sin(rad)))
end
Lua 通過 package.cpath 查找 C 模塊,默認模式如:
./?.so;./?51.so;/usr/local/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/?51.so
確保 hello_lib.so 位於搜索路徑中,或設置環境變量:
export LUA_CPATH="./?.so;;$LUA_CPATH"
| 特性 | Lua 5.1 | Lua 5.2+ |
|---|---|---|
| 註冊函數 | luaL_register | luaL_newlib |
| 模塊返回 | 隱式全局表 | 顯式返回表 |
| 環境變量 | package.path/cpath | 同上 + package.searchers |
// 使用用戶數據 保存 C 結構體指針
typedef struct { int counter; } Counter;
static int counter_new(lua_State *L) {
Counter *c = (Counter*)lua_newuserdata(L, sizeof(Counter));
c->counter = 0;
luaL_getmetatable(L, "CounterMeta");
lua_setmetatable(L, -2);
return 1;
}
static int counter_inc(lua_State *L) {
Counter *c = (Counter*)luaL_checkudata(L, 1, "CounterMeta");
c->counter++;
lua_pushinteger(L, c->counter);
return 1;
}
cmake_minimum_required(VERSION 3.10)
project(hello_lib LANGUAGES C)
find_package(Lua 5.1 REQUIRED)
add_library(hello_lib SHARED hello_lib.c)
target_link_libraries(hello_lib PRIVATE ${LUA_LIBRARIES})
target_include_directories(hello_lib PRIVATE ${LUA_INCLUDE_DIRS})
# 設置輸出名稱去除前綴 (libhello_lib.so -> hello_lib.so)
set_target_properties(hello_lib PROPERTIES
PREFIX ""
SUFFIX ".so")
gdb --args lua test.lua 設置斷點於 C 函數valgrind --leak-check=full lua test.luaprint(debug.traceback())| 錯誤現象 | 原因 | 修復 |
|---|---|---|
module 'hello_lib' not found | .so 文件不在 cpath | 複製到項目目錄或修改 LUA_CPATH |
undefined symbol: luaopen_hello_lib | 導出函數名不匹配 | 確保函數名為 luaopen_<模塊名> |
| 段錯誤 | 棧操作不平衡 | 檢查 lua_push/pop 配對,返回值正確 |
編譯報錯 lua.h not found | 未安裝 Lua 開發包 | apt install liblua5.1-dev / brew install lua |
C 擴展 Lua 的核心流程:編寫符合 lua_CFunction 簽名的函數 → 註冊到 luaL_Reg 表 → 實現 luaopen_<模塊名> 入口 → 編譯為 .so/.dll → Lua require 加載調用。掌握棧操作、用戶數據、元表機制,即可將任意 C/C++ 庫無縫嵌入 Lua,構建高性能可腳本化系統。