Lua 与 C/C++ 集成实战
本章教你如何将 Lua 嵌入到 C 和 C++ 项目中,实现多语言混合编程。这是 Lua 最核心的应用场景。
第1节:基本原理
Lua 虚拟栈
C/C++ 与 Lua 之间通过一个虚拟栈来交换数据。所有数据传递都在栈上进行:
Lua 脚本 ←─────── 虚拟栈 ───────→ C/C++ 代码
push / pop / get top
| 栈索引类型 | 说明 |
|---|---|
| 正索引 | 栈底 = 1, 向上递增 → lua_gettop(L) |
| 负索引 | 栈顶 = -1, 向下递减 → -1, -2, -3... |
编译依赖
# 安装开发包(包含头文件和库)
# Ubuntu/Debian
sudo apt install liblua5.4-dev
# Arch
sudo pacman -S lua
# macOS
brew install lua
# 编译时需要链接:
# C: gcc -o app app.c -llua -lm -ldl
# C++: g++ -o app app.cpp -llua -lm -ldl
# # 如果是 LuaJIT: -lluajit-5.1第2节:C 语言嵌入 Lua 完整示例
示例1:C 调用 Lua 脚本
Lua 脚本 calcs.lua
-- calcs.lua
local calcs = {}
function calcs.add(a, b)
return a + b
end
function calcs.multiply(a, b)
return a * b
end
function calcs.getInfo()
return "Lua 5.4", 100, true
end
return calcsC 代码 main.c
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <stdio.h>
int main() {
// 1. 创建 Lua 虚拟机
lua_State* L = luaL_newstate();
// 2. 加载标准库
luaL_openlibs(L);
// 3. 执行 Lua 脚本
if (luaL_dofile(L, "calcs.lua") != LUA_OK) {
fprintf(stderr, "错误: %s\n", lua_tostring(L, -1));
lua_close(L);
return 1;
}
// 现在脚本中的 calcs 表在栈顶(因为 return calcs)
// ---- 调用 calcs.add(10, 20) ----
lua_getfield(L, -1, "add"); // 压入 add 函数
lua_pushinteger(L, 10); // 压入参数1
lua_pushinteger(L, 20); // 压入参数2
if (lua_pcall(L, 2, 1, 0) == LUA_OK) { // 2参数, 1返回值
lua_Integer result = lua_tointeger(L, -1);
printf("add(10, 20) = %lld\n", (long long)result);
lua_pop(L, 1); // 弹出返回值
}
// ---- 调用 calcs.multiply(7, 8) ----
lua_getfield(L, -1, "multiply");
lua_pushinteger(L, 7);
lua_pushinteger(L, 8);
lua_pcall(L, 2, 1, 0);
printf("multiply(7, 8) = %lld\n", (long long)lua_tointeger(L, -1));
lua_pop(L, 1);
// ---- 调用 calcs.getInfo() 多返回值 ----
lua_getfield(L, -1, "getInfo");
lua_pcall(L, 0, 3, 0); // 0参数, 3返回值
const char* name = lua_tostring(L, -3);
lua_Integer val = lua_tointeger(L, -2);
int ok = lua_toboolean(L, -1);
printf("getInfo() -> %s, %lld, %d\n", name, (long long)val, ok);
lua_pop(L, 3); // 弹出3个返回值
// 4. 清理
lua_close(L);
return 0;
}编译与运行
gcc -o main main.c -llua -lm -ldl
./main输出:
add(10, 20) = 30
multiply(7, 8) = 56
getInfo() -> Lua 5.4, 100, 1
示例2:Lua 调用 C 函数 — 注册自定义函数
C 代码 c_math.c
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <stdio.h>
#include <math.h>
// C 函数签名: int (*)(lua_State*)
// 参数从栈中获取,返回值也压入栈中
// ---- 自定义函数1: 计算两点距离 ----
static int l_distance(lua_State* L) {
double x1 = luaL_checknumber(L, 1); // 第1个参数
double y1 = luaL_checknumber(L, 2); // 第2个参数
double x2 = luaL_checknumber(L, 3); // 第3个参数
double y2 = luaL_checknumber(L, 4); // 第4个参数
double dx = x2 - x1;
double dy = y2 - y1;
double dist = sqrt(dx * dx + dy * dy);
lua_pushnumber(L, dist); // 压入返回值
return 1; // 返回值个数
}
// ---- 自定义函数2: 返回多个值 ----
static int l_min_max(lua_State* L) {
int n = lua_gettop(L); // 参数个数
if (n < 1) {
lua_pushnil(L);
lua_pushnil(L);
return 2;
}
double min = lua_tonumber(L, 1);
double max = lua_tonumber(L, 1);
for (int i = 2; i <= n; i++) {
double val = lua_tonumber(L, i);
if (val < min) min = val;
if (val > max) max = val;
}
lua_pushnumber(L, min);
lua_pushnumber(L, max);
return 2; // 返回两个值
}
// ---- 注册函数映射表 ----
static const struct luaL_Reg mylib[] = {
{"distance", l_distance},
{"minmax", l_min_max},
{NULL, NULL} // 终止标记
};
// ---- 库入口: 会被 require("c_math") 调用 ----
int luaopen_c_math(lua_State* L) {
luaL_newlib(L, mylib);
return 1;
}
// ---- 主程序入口 ----
int main() {
lua_State* L = luaL_newstate();
luaL_openlibs(L);
// 注册 C 模块
luaL_requiref(L, "c_math", luaopen_c_math, 1);
lua_pop(L, 1);
// 执行 Lua 脚本
if (luaL_dofile(L, "app.lua") != LUA_OK) {
fprintf(stderr, "错误: %s\n", lua_tostring(L, -1));
}
lua_close(L);
return 0;
}Lua 脚本 app.lua
local cmath = require("c_math")
local dist = cmath.distance(0, 0, 3, 4)
print("距离: " .. dist) -- 距离: 5.0
local min, max = cmath.minmax(3, 7, 2, 9, 1, 5)
print("最小: " .. min .. ", 最大: " .. max) -- 最小: 1, 最大: 9编译
gcc -o c_math c_math.c -llua -lm -ldl
./c_math示例3:完整应用 — C 控制台游戏框架
一个使用 C 实现核心逻辑、Lua 编写游戏脚本的示例。
Lua 脚本 game.lua
-- 游戏配置(由 C 程序读取)
local config = {
title = "我的游戏",
width = 800,
height = 600,
fps = 60,
}
function config.onInit()
print("游戏初始化...")
end
function config.onUpdate(dt)
print(string.format("帧更新: dt=%.3f秒", dt))
end
function config.onKeyDown(key)
if key == "escape" then
print("按下了 ESC,退出游戏")
return "quit"
elseif key == "space" then
print("按下了空格,跳跃!")
return "jump"
end
end
function config.onShutdown()
print("游戏关闭,清理资源...")
end
return configC 代码 game_engine.c
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <stdio.h>
#include <unistd.h> // usleep
int main() {
lua_State* L = luaL_newstate();
luaL_openlibs(L);
// 加载游戏脚本
if (luaL_dofile(L, "game.lua") != LUA_OK) {
fprintf(stderr, "错误: %s\n", lua_tostring(L, -1));
lua_close(L);
return 1;
}
// ---- 读取配置 ----
lua_getfield(L, -1, "title");
const char* title = lua_tostring(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "width");
lua_getfield(L, -1, "height");
int width = lua_tointeger(L, -2);
int height = lua_tointeger(L, -1);
lua_pop(L, 2);
printf("游戏: %s (%dx%d)\n", title, width, height);
// ---- 调用 onInit ----
lua_getfield(L, -1, "onInit");
lua_pcall(L, 0, 0, 0);
// ---- 模拟游戏循环 (1秒) ----
lua_getfield(L, -1, "fps");
int fps = lua_tointeger(L, -1);
lua_pop(L, 1);
double dt = 1.0 / fps;
for (int frame = 0; frame < 3; frame++) {
lua_getfield(L, -1, "onUpdate");
lua_pushnumber(L, dt);
lua_pcall(L, 1, 0, 0);
usleep(1000000 / fps); // 模拟帧间隔
}
// ---- 模拟按键 ----
lua_getfield(L, -1, "onKeyDown");
lua_pushstring(L, "space");
lua_pcall(L, 1, 1, 0);
const char* action = lua_tostring(L, -1);
printf("按键结果: %s\n", action);
lua_pop(L, 1);
// ---- 调用 onShutdown ----
lua_getfield(L, -1, "onShutdown");
lua_pcall(L, 0, 0, 0);
lua_close(L);
printf("游戏结束\n");
return 0;
}编译运行
gcc -o game_engine game_engine.c -llua -lm -ldl
./game_engine第3节:C++ 嵌入 Lua 完整示例
示例4:C++ 封装 Lua 调用
现代 C++ 封装 LuaContext.hpp
#ifndef LUA_CONTEXT_HPP
#define LUA_CONTEXT_HPP
#include <lua.hpp> // C++ 包装头文件(使用 extern "C")
#include <string>
#include <functional>
#include <stdexcept>
class LuaContext {
private:
lua_State* L;
public:
LuaContext() {
L = luaL_newstate();
luaL_openlibs(L);
}
~LuaContext() {
if (L) lua_close(L);
}
// 禁止拷贝
LuaContext(const LuaContext&) = delete;
LuaContext& operator=(const LuaContext&) = delete;
lua_State* getState() { return L; }
// 执行脚本文件
void doFile(const std::string& filename) {
if (luaL_dofile(L, filename.c_str()) != LUA_OK) {
std::string err = lua_tostring(L, -1);
lua_pop(L, 1);
throw std::runtime_error("Lua错误: " + err);
}
}
// 执行脚本字符串
void doString(const std::string& code) {
if (luaL_dostring(L, code.c_str()) != LUA_OK) {
std::string err = lua_tostring(L, -1);
lua_pop(L, 1);
throw std::runtime_error("Lua错误: " + err);
}
}
// 注册 C++ 函数到 Lua
void registerFunction(const std::string& name, lua_CFunction func) {
lua_register(L, name.c_str(), func);
}
// 注册 C++ lambda 函数到 Lua
// (需要模板处理,见下方完整示例)
};
#endifC++ 完整应用 cpp_main.cpp
#include <lua.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <stdexcept>
// ---------- Lua 状态管理器 ----------
class LuaState {
private:
lua_State* L;
public:
LuaState() : L(luaL_newstate()) {
luaL_openlibs(L);
}
~LuaState() { if (L) lua_close(L); }
LuaState(const LuaState&) = delete;
LuaState& operator=(const LuaState&) = delete;
lua_State* get() { return L; }
void doFile(const std::string& file) {
if (luaL_dofile(L, file.c_str()) != LUA_OK) {
std::string err = lua_tostring(L, -1);
lua_pop(L, 1);
throw std::runtime_error(err);
}
}
};
// ---------- 向量类 ----------
struct Vector3 {
double x, y, z;
Vector3(double x = 0, double y = 0, double z = 0) : x(x), y(y), z(z) {}
double length() const { return std::sqrt(x*x + y*y + z*z); }
void print() const {
std::cout << "Vector3(" << x << ", " << y << ", " << z << ") length=" << length() << std::endl;
}
};
// ---------- C++ 暴露给 Lua 的函数 ----------
// 函数1: 计算向量长度 —— C++ 接收 Lua 的表,计算并返回
static int cpp_vectorLength(lua_State* L) {
luaL_checktype(L, 1, LUA_TTABLE);
lua_getfield(L, 1, "x");
lua_getfield(L, 1, "y");
lua_getfield(L, 1, "z");
double x = lua_tonumber(L, -3);
double y = lua_tonumber(L, -2);
double z = lua_tonumber(L, -1);
lua_pop(L, 3);
double len = std::sqrt(x*x + y*y + z*z);
lua_pushnumber(L, len);
return 1;
}
// 函数2: C++ 创建并返回 Lua 表
static int cpp_createVector(lua_State* L) {
double x = luaL_optnumber(L, 1, 0.0);
double y = luaL_optnumber(L, 2, 0.0);
double z = luaL_optnumber(L, 3, 0.0);
lua_newtable(L); // 创建表
lua_pushnumber(L, x); // 值
lua_setfield(L, -2, "x"); // 设置为 t.x = x
lua_pushnumber(L, y);
lua_setfield(L, -2, "y");
lua_pushnumber(L, z);
lua_setfield(L, -2, "z");
return 1; // 返回表
}
// 函数3: C++ 获取 Lua 全局变量并修改
static int cpp_modifyGlobal(lua_State* L) {
const char* varName = luaL_checkstring(L, 1);
double delta = luaL_checknumber(L, 2);
lua_getglobal(L, varName);
if (lua_isnumber(L, -1)) {
double val = lua_tonumber(L, -1);
lua_pop(L, 1);
lua_pushnumber(L, val + delta);
lua_setglobal(L, varName);
lua_pushboolean(L, 1);
return 1;
}
lua_pushboolean(L, 0);
return 1;
}
// ---------- 主程序 ----------
int main() {
try {
LuaState lua;
// 注册 C++ 函数到 Lua
lua_register(lua.get(), "vec_length", cpp_vectorLength);
lua_register(lua.get(), "vec_create", cpp_createVector);
lua_register(lua.get(), "modify_global", cpp_modifyGlobal);
// 执行 Lua 代码
const char* script = R"(
-- Lua 调用 C++ 函数创建向量
local v = vec_create(3, 4, 0)
print("创建的向量:", v.x, v.y, v.z)
-- Lua 调用 C++ 函数计算长度
local len = vec_length({x=6, y=8, z=0})
print("向量长度:", len)
-- Lua 设置全局变量
score = 100
print("修改前 score:", score)
-- C++ 修改 Lua 全局变量
local ok = modify_global("score", 50)
print("修改结果:", ok)
print("修改后 score:", score)
-- Lua 原生计算
local function dot(a, b)
return a.x * b.x + a.y * b.y + a.z * b.z
end
local v1 = {x=1, y=2, z=3}
local v2 = {x=4, y=5, z=6}
print("点积:", dot(v1, v2))
)";
luaL_dostring(lua.get(), script);
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << std::endl;
return 1;
}
return 0;
}编译
g++ -std=c++17 -o cpp_main cpp_main.cpp -llua -lm -ldl
./cpp_main输出:
创建的向量: 3 4 0
向量长度: 10.0
修改前 score: 100
修改结果: true
修改后 score: 150
点积: 32
示例5:C++ 玩家系统 — C++ 数据 + Lua 行为
一个更贴近实际的例子:C++ 管理玩家数据,Lua 定义 AI 行为。
C++ 代码 player_ai.cpp
#include <lua.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <cmath>
#include <memory>
// ---------- C++ 玩家类 ----------
class Player {
public:
std::string name;
double x, y;
double hp;
double maxHp;
Player(std::string name, double x, double y, double hp)
: name(std::move(name)), x(x), y(y), hp(hp), maxHp(hp) {}
void move(double dx, double dy) {
x += dx;
y += dy;
}
void takeDamage(double dmg) {
hp = std::max(0.0, hp - dmg);
}
void heal(double amount) {
hp = std::min(maxHp, hp + amount);
}
bool isAlive() const { return hp > 0; }
double distanceTo(double tx, double ty) const {
return std::sqrt((tx - x) * (tx - x) + (ty - y) * (ty - y));
}
};
// ---------- 将 C++ Player 暴露给 Lua ----------
// 宏简化操作: 从栈中获取 Player*
#define PLAYER(L) (*static_cast<Player**>(luaL_checkudata(L, 1, "Player")))
static int player_move(lua_State* L) {
auto& p = *PLAYER(L);
double dx = luaL_checknumber(L, 2);
double dy = luaL_checknumber(L, 3);
p.move(dx, dy);
return 0;
}
static int player_getHp(lua_State* L) {
auto& p = *PLAYER(L);
lua_pushnumber(L, p.hp);
return 1;
}
static int player_takeDamage(lua_State* L) {
auto& p = *PLAYER(L);
double dmg = luaL_checknumber(L, 2);
p.takeDamage(dmg);
lua_pushboolean(L, p.isAlive());
return 1;
}
static int player_distanceTo(lua_State* L) {
auto& p = *PLAYER(L);
double tx = luaL_checknumber(L, 2);
double ty = luaL_checknumber(L, 3);
lua_pushnumber(L, p.distanceTo(tx, ty));
return 1;
}
static int player_getName(lua_State* L) {
auto& p = *PLAYER(L);
lua_pushstring(L, p.name.c_str());
return 1;
}
// 元方法索引表
static const luaL_Reg player_methods[] = {
{"move", player_move},
{"getHp", player_getHp},
{"takeDamage", player_takeDamage},
{"distanceTo", player_distanceTo},
{"getName", player_getName},
{NULL, NULL}
};
// 创建 Player userdata
static int player_new(lua_State* L) {
std::string name = luaL_checkstring(L, 1);
double x = luaL_checknumber(L, 2);
double y = luaL_checknumber(L, 3);
double hp = luaL_checknumber(L, 4);
auto* pp = static_cast<Player**>(
lua_newuserdata(L, sizeof(Player*))
);
*pp = new Player(name, x, y, hp);
// 设置元表
luaL_getmetatable(L, "Player");
lua_setmetatable(L, -2);
return 1;
}
void registerPlayer(lua_State* L) {
// 创建 Player 元表
luaL_newmetatable(L, "Player");
// __index = 方法表
lua_pushstring(L, "__index");
lua_newtable(L);
luaL_setfuncs(L, player_methods, 0);
lua_settable(L, -3);
// __gc 析构函数
lua_pushstring(L, "__gc");
lua_pushcfunction(L, [](lua_State* L) -> int {
auto* pp = static_cast<Player**>(luaL_checkudata(L, 1, "Player"));
delete *pp;
return 0;
});
lua_settable(L, -3);
// __tostring
lua_pushstring(L, "__tostring");
lua_pushcfunction(L, [](lua_State* L) -> int {
auto& p = *PLAYER(L);
lua_pushfstring(L, "Player(%s, hp=%.0f, pos=(%.0f,%.0f))",
p.name.c_str(), p.hp, p.x, p.y);
return 1;
});
lua_settable(L, -3);
lua_pop(L, 1);
// 注册全局构造函数 Player.new()
lua_register(L, "Player", player_new);
}
// ---------- 主程序 ----------
int main() {
lua_State* L = luaL_newstate();
luaL_openlibs(L);
// 注册 Player 类
registerPlayer(L);
// 注册辅助函数
lua_register(L, "RandomRange", [](lua_State* L) -> int {
double min = luaL_checknumber(L, 1);
double max = luaL_checknumber(L, 2);
lua_pushnumber(L, min + (rand() / (double)RAND_MAX) * (max - min));
return 1;
});
srand(time(nullptr));
// Lua AI 脚本
const char* script = R"(
-- 创建两个玩家
local hero = Player("勇士", 0, 0, 200)
local enemy = Player("巨魔", 50, 0, 150)
print("战斗开始!")
print(tostring(hero))
print(tostring(enemy))
-- 战斗循环
local function battle()
while hero:getHp() > 0 and enemy:getHp() > 0 do
-- 英雄攻击
local dist = hero:distanceTo(enemy.x, enemy.y)
if dist <= 10 then
local dmg = RandomRange(15, 25)
print(string.format("%s 攻击 %s,造成 %.0f 伤害",
hero:getName(), enemy:getName(), dmg))
enemy:takeDamage(dmg)
else
hero:move(5, 0) -- 靠近敌人
print(hero:getName() .. " 靠近敌人")
end
-- 敌人反击
if enemy:getHp() > 0 then
local dmg = RandomRange(10, 20)
print(string.format("%s 反击 %s,造成 %.0f 伤害",
enemy:getName(), hero:getName(), dmg))
hero:takeDamage(dmg)
end
end
-- 判断胜负
if hero:getHp() > 0 then
print(hero:getName() .. " 获得胜利!剩余血量: " .. hero:getHp())
else
print(enemy:getName() .. " 获得胜利!剩余血量: " .. enemy:getHp())
end
end
battle()
)";
if (luaL_dostring(L, script) != LUA_OK) {
std::cerr << "Lua错误: " << lua_tostring(L, -1) << std::endl;
}
lua_close(L);
return 0;
}编译运行
g++ -std=c++17 -o player_ai player_ai.cpp -llua -lm -ldl
./player_ai第4节:C/C++ API 速查表
压栈(Push)
| C API | 说明 |
|---|---|
lua_pushnil(L) | 压入 nil |
lua_pushboolean(L, b) | 压入布尔值 |
lua_pushnumber(L, n) | 压入浮点数 |
lua_pushinteger(L, n) | 压入整数 |
lua_pushstring(L, s) | 压入字符串 |
lua_pushcfunction(L, f) | 压入 C 函数 |
lua_newtable(L) | 压入新表 |
lua_newuserdata(L, sz) | 压入用户数据(sz 字节) |
取值(Get / Check)
| C API | 说明 |
|---|---|
lua_toboolean(L, idx) | 获取布尔值 |
lua_tonumber(L, idx) | 获取浮点数 |
lua_tointeger(L, idx) | 获取整数 |
lua_tostring(L, idx) | 获取字符串(返回的指针可能失效) |
lua_touserdata(L, idx) | 获取用户数据 |
luaL_checknumber(L, idx) | 检查并获取数字(类型不符则报错) |
luaL_checkstring(L, idx) | 检查并获取字符串 |
表操作
| C API | 说明 |
|---|---|
lua_getfield(L, idx, key) | t[k] 压入栈 |
lua_setfield(L, idx, key) | t[k] = v |
lua_gettable(L, idx) | t[k](k 在栈顶) |
lua_settable(L, idx) | t[k] = v(k 在栈顶-1, v 在栈顶) |
lua_geti(L, idx, n) | t[n] 压入栈 |
lua_seti(L, idx, n) | t[n] = v |
lua_rawget(L, idx) | 不触发元方法的 gettable |
lua_rawset(L, idx) | 不触发元方法的 settable |
全局变量
| C API | 说明 |
|---|---|
lua_getglobal(L, name) | 获取全局变量压入栈 |
lua_setglobal(L, name) | 设置全局变量(从栈顶取值) |
调用
| C API | 说明 |
|---|---|
lua_call(L, nargs, nresults) | 直接调用(不保护) |
lua_pcall(L, nargs, nresults, errfunc) | 保护调用 |
luaL_dofile(L, file) | 执行 Lua 文件 |
luaL_dostring(L, str) | 执行 Lua 字符串 |
模块
| C API | 说明 |
|---|---|
luaL_newlib(L, funcs) | 创建模块表 |
luaL_setfuncs(L, funcs, nup) | 批量注册函数 |
luaL_requiref(L, name, fn, glb) | 注册 require 可用模块 |
第5节:编译配置
Makefile 模板
CC = gcc
CXX = g++
LUA_CFLAGS = $(shell pkg-config --cflags lua 2>/dev/null || echo "-I/usr/include/lua5.4")
LUA_LIBS = $(shell pkg-config --libs lua 2>/dev/null || echo "-llua -lm -ldl")
all: c_app cpp_app
c_app: c_app.c
$(CC) -o c_app c_app.c $(LUA_CFLAGS) $(LUA_LIBS)
cpp_app: cpp_app.cpp
$(CXX) -std=c++17 -o cpp_app cpp_app.cpp $(LUA_CFLAGS) $(LUA_LIBS)
clean:
rm -f c_app cpp_appCMakeLists.txt 模板
cmake_minimum_required(VERSION 3.10)
project(LuaIntegration)
find_package(Lua REQUIRED)
add_executable(c_app c_app.c)
target_link_libraries(c_app ${LUA_LIBRARIES})
target_include_directories(c_app PRIVATE ${LUA_INCLUDE_DIR})
add_executable(cpp_app cpp_app.cpp)
target_link_libraries(cpp_app ${LUA_LIBRARIES})
target_include_directories(cpp_app PRIVATE ${LUA_INCLUDE_DIR})
set_target_properties(cpp_app PROPERTIES CXX_STANDARD 17)下一步:05-Neovim示例.md — 使用 Lua 打造高效 Neovim 开发环境
相关知识点
- C 语言教程 — C 语法与指针基础(C API 前置)