cocos2dx lua 与 C/C++ 交互入门

目录
  • 中间栈
    • 1.中间件栈
      • C/C++访问Lua数据和函数
      • Lua访问C/C++数据和函数
  • 集成Lua
    • 集成Lua环境到C_Cpp项目中
  • C/C++ 调用Lua
    • C/C++调用Lua之标准Lua读取类
    • C/C++调用Lua中的变量
    • C/C++调用Lua中的函数
    • C/C++调用Lua之遍历和获取Table的值
  • Lua 调用 C/C++
    • 1.Lua调用C/C++函数
      • 深入理解编写C函数上
      • 深入理解编写C函数下
    • 2.Lua调用C/C++之标准C模块
  • 待续

中间栈

lua(https://www.lua.org/)作为一种轻量级的脚本语言, 以其简单的语法结构, 方便的c++集成能力, 高效的执行效率收到广大游戏开发者的热爱, 也是cocos2d-x官方首次引入的脚本语言.

作为一种脚本语言, lua是在一个运行时环境(State)里执行的, 这个运行时环境保存了脚本运行所需的内存空间, 创建的全局变量, 加载的库文件等. 在这个运行时环境里还有一个栈空间(Stack), 其作用就是在lua和c语言进行数据传递和函数调用. lua原生实现了很多c api对栈空间进行操作, 让开发者能够方便地实现lua脚本代码与c编译代码的双向通信.

Lua的栈是一个线性数组,栈中的每一项元素的类型都是TValue,它是Lua表示内部数据的数据结构。栈的最大空间在luaconf.h中给出:#define LUAI_MAXSTACK 1000000。

栈包括:基指针、栈顶指针、栈大小。图中的stack->top表示栈顶指针。其实在Lua中栈的很多空间都留作它用,如call info也使用一部分的栈。

1.中间件栈

背后其实是lua和c api的互相调用, 所有c++的功能都要通过一层c函数的包装, 这点是要牢记在心的, 这也正是lua-binding的核心.

cocos2d-x提供的lua-bingding工具使用libclang分析c++源码, 提取语法树, 将c++的类成员函数封装为c函数, 然后根据参数类型自动调用lua c api, 实现对栈空间的操作, 将c++的数据传递给lua. lua脚本加载编译好的c++库, 就可以自由调用c++里面的类对象和成员函数了; c++的代码则可以直接使用lua c api, 执行一段lua脚本, 并通过栈空间获取返回结果.

  • 执行过程如下:
    • 1)C程序读取Lua脚本
    • 2)lualib库解析脚本,并保存解析结果。
    • 3)通过Lua API,将解析结果中的某一个值放入栈中。
    • 4)C程序通过Lua提供的API到栈中取得数据。
经过上面的过程C语言程序和Lua脚本就能相互传值了。

开始撸代码 ——————————- 华丽的分割线 ——————————-

创建一个Lua管理类,

用利用栈的原理,使用C++简单访问Lua

.h文件
#ifndef LuaInterface01_h
#define LuaInterface01_h

#include <stdio.h>
#include "lua.hpp"

class LuaInterface01 {

public:
static LuaInterface01 *shareInterface();

private:
static LuaInterface01 *i;
void init();

};

#endif /* LuaInterface01_hpp */
.cpp文件
#include "LuaInterface01.h"
#include "cocos2d.h"
#include <string>

USING_NS_CC;
using namespace std;

LuaInterface01 *LuaInterface01::i = NULL;

LuaInterface01 *LuaInterface01::shareInterface() {
if (!i) {
i = new LuaInterface01;
i -> init();
}
return i;
}

void LuaInterface01::init() {
// 创建一个Lua状态指针
lua_State *luaState = luaL_newstate();
luaL_openlibs(luaState);
string path = CCFileUtils::sharedFileUtils()->fullPathForFilename("Interface01.lua");
luaL_dofile(luaState, path.c_str());
lua_pcall(luaState, 0, 0, 0); // Hello iCocos LuaInterface01!
}
新建一个.lua的访问类文件,并且输入
print("Hello iCocos LuaInterface01!")
控制答应结果:
Hello iCocos LuaInterface01!

下面用简单的Lua与C/C++互调,验证中间件栈, 如果想了解更多或者更详细的实现Lua与C/C++,请略过本小结,滑到第三部分,或者通过目录寻找需要的内容!

C/C++访问Lua数据和函数
创建一个Lua管理类,
.h文件
#ifndef LuaInterface02_h
#define LuaInterface02_h

#include <stdio.h>
#include "lua.hpp"

#include "cocos2d.h"
USING_NS_CC;

class LuaInterface02 {

public:
LuaInterface02();
~LuaInterface02();
static LuaInterface02 *shareInterface();
void init();

void readVariable();
void loadFunstion();

private:
static LuaInterface02 *i;
lua_State *m_pLuaState;

};


#endif /* LuaInterface02_h */
.cpp文件
#include "LuaInterface02.h"
#include <string>

using namespace std;

LuaInterface02 *LuaInterface02::i = NULL;

LuaInterface02::LuaInterface02() {

}
LuaInterface02::~LuaInterface02() {
if (m_pLuaState) {
lua_close(m_pLuaState);
m_pLuaState = NULL;
}
}

LuaInterface02 *LuaInterface02::shareInterface() {
if (!i) {
i = new LuaInterface02;
i -> init();
}
return i;
}

void LuaInterface02::init() {
m_pLuaState = luaL_newstate();
luaL_openlibs(m_pLuaState);
}

void LuaInterface02::readVariable() {
string path = CCFileUtils::sharedFileUtils()->fullPathForFilename("Interface02.lua");
luaL_dofile(m_pLuaState, path.c_str());
int err_relt = lua_pcall(m_pLuaState, 0, 0, 0);

CCAssert(err_relt, "读取Lua文件错误");

lua_getglobal(m_pLuaState, "username");
char *rlt = (char*)lua_tostring(m_pLuaState, -1);  //lua_tonumber,lua_toboolean
CCLOG("rlt is : %s", rlt);
lua_pop(m_pLuaState, 1);
}

void LuaInterface02::loadFunstion() {
string path = CCFileUtils::sharedFileUtils()->fullPathForFilename("Interface02.lua");
luaL_dofile(m_pLuaState, path.c_str());
int err_relt = lua_pcall(m_pLuaState, 0, 0, -1);

CCAssert(err_relt != -1, "读取Lua文件错误");

lua_getglobal(m_pLuaState, "getResult");
lua_pushnumber(m_pLuaState, 12);
err_relt = lua_pcall(m_pLuaState, 1, 2, -1); // 1: 一个参数, 2:两个参数 , -1:当出错的时候的返回值
CCAssert(err_relt != -1, "访问函数报错");

char *rlt = (char*)lua_tostring(m_pLuaState, -1);  //lua_tonumber,lua_toboolean
CCLOG("rlt is : %s", rlt);
char *rlt2 = (char*)lua_tostring(m_pLuaState, -2);  //lua_tonumber,lua_toboolean
CCLOG("rlt2 is : %s", rlt2);
}
新建一个.lua的访问类文件,并且输入
username = "iCocos"

function getResult(x)

print("Enter Result")
print("x:"..x)

return "OK","Function"

end
控制答应结果:
rlt is : iCocos
Enter Result
x:12
rlt is : Function
rlt2 is : OK
Lua访问C/C++数据和函数
LuaInterface02.h中增加函数
// 调用C++
static int l_show(lua_State * L) {
lua_pushstring(L, "String from C/C++");
return 1;
}
然后在init()中输入执行方法
// 调用C++
lua_pushcfunction(m_pLuaState, l_show);
lua_setglobal(m_pLuaState, "show");
然后在.lua的访问类文件,并且输入
print(show())
控制答应结果:
String from C/C++
总结

简单点讲就是维护了一个堆栈,需要交互的数据通过入栈,出栈操作来传递数据。而具体脚本语言的实现机制,一般来说都会有一个编译模块、一个虚拟机(执行)模块、一套类型实现及数据管理模块,通常还会有一个供外部操作的接口,如lua c api,这个接口让嵌入方得以操作脚本状态(如访问变量、调用函数、管理内存),实现交互。

最后,总结一下,所有程序最终都是以机器码的形式被硬件CPU执行,从这个角度去看,不同语言的代码并没有本质区别,最终都是被编译器编译从二进制机器码,而所谓交互,就是在处理共享数据而已。

  • 注意
  • Lua与C/C++或C/C++语言之前那通信过程中,实际上更多的时候是对栈顶的元素进行操作
  • 从栈顶往下看索引是-1-2-3…
  • 从栈底往上看索引是1,2,3…
  • 无论从Lua调C/C++,还是C/C++调Lua,参数值都是最先被压入到栈顶的,最后结果值才会被压入到栈顶

集成Lua

集成Lua环境到C/C++项目中

下载并获取到Lua源代码(src文件夹就是lua源码)https://www.lua.org/

在lua中文件类型有三种
  • .c c文件
  • .h 头文件
  • .o 可执行文件
开始接入
  • 接入Lua到C/C++的时候需要删除
  • lua.c
  • luac.c
  • MakeFile

新建一个Xcode项目,拷贝src文件夹到项目中就可以了

最后

在llex.c中trydecpoint, 修改里面decpoint=“getlocaldecpoint”

decpoint=’.’

如果不改会导致编译报错

验证

新建一个C++文件,在头文件中输入

#include <stdio.h>
#include "lua.hpp"

#include "cocos2d.h"
USING_NS_CC;

class TestInportLua {
public:
void test() {
lua_State *state = luaL_newstate();
luaL_openlibs(state);

luaL_dofile(state, "res/test.lua");
}
}
#endif /* TestInportLua_h */

C/C++ 调用Lua

C/C++调用Lua之标准Lua读取类

创建一个Lua管理类,
.h文件
#ifndef LuaInterface03_hpp
#define LuaInterface03_hpp

#include "cocos2d.h"
#include "lua.hpp"
#include <string>

USING_NS_CC;
using namespace std;

class LuaInterface03 {

public:
LuaInterface03();
~LuaInterface03();

static LuaInterface03 *shareInterface();

void readVailable();
void loadFunciton();

private:
static LuaInterface03 *i;
lua_State *m_pLuaState;
void init();

};

#endif /* LuaInterface03_hpp */
.cpp文件
#include "LuaInterface03.h"

LuaInterface03 *LuaInterface03::i = NULL;

LuaInterface03::LuaInterface03(){}
LuaInterface03::~LuaInterface03(){
lua_close(m_pLuaState);
m_pLuaState = NULL;
}

void LuaInterface03::init() {
// 创建一个新的状态
m_pLuaState = luaL_newstate();
// 打开所有系统提供的库
luaL_openlibs(m_pLuaState);

// 完整路径
string path = CCFileUtils::sharedFileUtils()->fullPathForFilename("LuaInterface03.lua");

// 读取文件
luaL_dofile(m_pLuaState, path.c_str());
// 启动调用文件
lua_pcall(m_pLuaState, 0, 0, -1);
}

LuaInterface03 *LuaInterface03::shareInterface() {
if (!i) {
i = new LuaInterface03;
i -> init();
}
return i;
}
新建一个.lua的访问类文件,并且输入
print("=======> I am Lua Config")
控制答应结果:
=======> I am Lua Config

C/C++调用Lua中的变量

增加方法和对应的实现,用来获取变量
void configWindowContentSize();


void LuaInterface03::configWindowContentSize() {
lua_getglobal(m_pLuaState, "width");
// 是否能转换成number
if (lua_isnumber(m_pLuaState, -1)) {
int width = (int)lua_tonumber(m_pLuaState, -1);
CCLOG("width: %i", width);
}
lua_getglobal(m_pLuaState, "height");
if (lua_isnumber(m_pLuaState, -1)) {
int height = (int)lua_tonumber(m_pLuaState, -1);
CCLOG("height: %i", height);
}
}
Lua中增加变量
width = 320
height = 640
控制答应结果:
width: 320
height: 640

C/C++调用Lua中的函数

增加方法和对应的实现,用来获取变量
void callLuaFunctionContent();

void LuaInterface03::callLuaFunctionContent() {
lua_getglobal(m_pLuaState, "test1");
int err_relt = lua_pcall(m_pLuaState, 0, 0, -1);
CCAssert(err_relt != -1, "访问函数报错");

lua_getglobal(m_pLuaState, "test2");
lua_pushstring(m_pLuaState, "test2 调用");
err_relt = lua_pcall(m_pLuaState, 1, 0, -1); // 1: 一个参数, 2:两个参数 , -1:当出错的时候的返回值
CCAssert(err_relt != -1, "访问函数报错");

lua_getglobal(m_pLuaState, "test3");
err_relt = lua_pcall(m_pLuaState, 0, 0, -1);
CCAssert(err_relt != -1, "访问函数报错");
char* rlt = (char*)lua_tostring(m_pLuaState, -1);
CCLOG("rlt is %s",rlt);

lua_getglobal(m_pLuaState, "test4");
lua_pushnumber(m_pLuaState, 10);
err_relt = lua_pcall(m_pLuaState, 1, 2, -1); // 1: 一个参数, 2:两个参数 , -1:当出错的时候的返回值
CCAssert(err_relt != -1, "访问函数报错");

int rlt2 = (int)lua_tonumber(m_pLuaState, -1);
CCLOG("rlt is %i",rlt2);

lua_getglobal(m_pLuaState, "test5");
lua_pushnumber(m_pLuaState, 100);
err_relt = lua_pcall(m_pLuaState, 1, 2, -1); // 1: 一个参数, 2:两个参数 , -1:当出错的时候的返回值
CCAssert(err_relt != -1, "访问函数报错");

int rlt3 = (int)lua_tonumber(m_pLuaState, -1);
int rlt4 = (int)lua_tonumber(m_pLuaState, -1);
CCLOG("rlt is %i",rlt3);
CCLOG("rlt is %i",rlt4);
}
Lua中增加变量
function test1()
print("call lua function")
end

function test2(var)
print("call lua function", var)
end

function test3()
return "I am Lua test3"
end

function test4(var)
var = var + 10
return var
end

function test5(var)
t = 10
var = var + 10
return t, var
end
控制答应结果:
call lua function
call lua function test1 调用

C/C++调用Lua之遍历和获取Table的值

增加方法
char* getField(lua_State *L, const char* key);

char* getField(lua_State *L, const char* key) {
char *rlt = NULL;
lua_pushstring(L, key);
lua_gettable(L, -2);
if (lua_isstring(L, -1)) {
rlt = (char*)lua_tostring(L, -1);
lua_pop(L, 1);
return rlt;
}
return "error";
}
初始化方法(void LuaInterface03::init())中增加如下代码
lua_getglobal(m_pLuaState, "application");
if (lua_isnumber(m_pLuaState, -1)) {
char *width = getField(m_pLuaState, "width");
char *height = getField(m_pLuaState, "height");
CCLOG("width:%s", width);
CCLOG("height:%s", height);
int nWidth = atoi(width);
int nHeight = atoi(height);
}
Lua中增加Table
application = {
width = 320,
height = 640,
}

Lua 调用 C/C++

1.Lua调用C/C++函数

创建一个Lua管理类,
.h文件
#ifndef LuaInterface04_hpp
#define LuaInterface04_hpp

#include <stdio.h>

#include "cocos2d.h"
#include "lua.hpp"
#include <string>

USING_NS_CC;
using namespace std;

class LuaInterface04 {

public:
LuaInterface04();
~LuaInterface04();

static LuaInterface04 *shareInterface();

static int l_getMyName(lua_State * L)

private:
static LuaInterface04 *i;
lua_State *m_pLuaState;
void init();

};

#endif /* LuaInterface04_hpp */
.cpp文件
#include "LuaInterface04.h"

LuaInterface04 *LuaInterface04::i = NULL;

LuaInterface04::LuaInterface04(){}

LuaInterface04::LuaInterface04(){
if (m_pLuaState) {
lua_close(m_pLuaState);
m_pLuaState = NULL;
}
}

// 调用C++
static int l_getMyName(lua_State * L) {
lua_pushstring(L, "String from C/C++");
return 1;
}


void LuaInterface04::init() {
// 创建一个新的状态
m_pLuaState = luaL_newstate();
// 打开所有系统提供的库
luaL_openlibs(m_pLuaState);

// 调用C++
lua_pushcfunction(m_pLuaState, l_getMyName);
lua_setglobal(m_pLuaState, "getMyName");

string path = CCFileUtils::sharedFileUtils()->fullPathForFilename("Interface04.lua");
luaL_dofile(m_pLuaState, path.c_str());
lua_pcall(m_pLuaState, 0, 0, -1);
}

LuaInterface04 *LuaInterface04::shareInterface() {
if (!i) {
i = new LuaInterface04;
i -> init();
}
return i;
}
新建一个.lua的访问类文件,并且输入
print("Begin")
print(getMyName())
print("End")
控制答应结果:
Begin
String from C/C++
End
深入理解编写C函数上
增加register_my_function方法
.h
static int l_showName(lua_State *L);

static int l_showYoueName(lua_State *L);

static void register_my_function(lua_State *L);
.cpp
static int l_showName(lua_State *L) {
const char * value1 = luaL_checkstring(L, -1);
const char * value2 = luaL_checkstring(L, -2);
const char * value3 = luaL_checkstring(L, -3);

CCLOG("value1 is :%s", value1);
CCLOG("value2 is :%s", value2);
CCLOG("value3 is :%s", value3);
//lua_pushstring(L, value1);
return 1;
}

static int l_showYoueName(lua_State *L) {
const char * value = luaL_checkstring(L, -1);
lua_pushstring(L, value);
return 1;
}

static void register_my_function(lua_State *L) {
lua_pushcfunction(L, LuaInterface04::l_getMyName);
lua_setglobal(L, "getMyName");

lua_pushcfunction(L, LuaInterface04::l_getMyName);
lua_setglobal(L, "showName");

lua_pushcfunction(L, LuaInterface04::l_getMyName);
lua_setglobal(L, "show");
}
在init中输入如下替换原始的调用方法
// 调用C++
//lua_pushcfunction(m_pLuaState, l_getMyName);
//lua_setglobal(m_pLuaState, "getMyName");
register_my_function(m_pLuaState);
新建一个.lua的访问类文件,并且输入
showName("Hello iCocos")
show("Hello iCocos", "Hello Lua")
深入理解编写C函数下
增加register_my_function方法
.h
static int lua_showreturn1(lua_State * L);

static int lua_showreturn2(lua_State * L);

static int lua_showreturn3(lua_State * L);

static int lua_showtable(lua_State * L);

static int lua_showtable2(lua_State * L);
.cpp
//print(lua_showreturn1())
static int lua_showreturn1(lua_State * L) {
CCLOG("I am no return");
return 0;
}

//print(lua_showreturn2())
static int lua_showreturn2(lua_State * L) {
CCLOG("I am one values");
lua_pushstring(L, "I am one values");
return 1;
}


//print(lua_showreturn3())
static int lua_showreturn3(lua_State * L) {
CCLOG("I am two values");
lua_pushstring(L, "I am value one");
lua_pushstring(L, "I am value two");
return 1;
}

//print(lua_showtable())
static int lua_showtable(lua_State * L) {
CCLOG("I am a table");
lua_newtable(L);
char str[20] = {0};
for (int i = 1; i <= 10; i++) {
lua_pushnumber(L, i); // 压入key
sprintf(str, "numert is : %i", i);
lua_pushstring(L, str); // 压入value
lua_settable(L, -3); // 将前面的key和value保存到table中
}
return 1;
}

//print(lua_showtable2())
static int lua_showtable2(lua_State * L) {
CCLOG("I am a table2");
lua_newtable(L);
char str[20] = {0};
int looper = 1;
while (looper <= 10) {
sprintf(str, "key%i", looper);
lua_pushstring(L, str); // 压入value
looper++;
lua_settable(L, -3); // 将前面的key和value保存到table中
}
return 1;
}
在init中输入如下替换原始的调用方法
// 调用C++
//lua_pushcfunction(m_pLuaState, l_getMyName);
//lua_setglobal(m_pLuaState, "getMyName");

lua_pushcfunction(L, LuaInterface04::lua_showreturn1);
lua_setglobal(L, "showreturn1");

lua_pushcfunction(L, LuaInterface04::lua_showreturn2);
lua_setglobal(L, "showreturn2");

lua_pushcfunction(L, LuaInterface04::lua_showreturn3);
lua_setglobal(L, "showreturn3");

lua_pushcfunction(L, LuaInterface04::lua_showtable);
lua_setglobal(L, "getMyTable");

lua_pushcfunction(L, LuaInterface04::lua_showtable2);
lua_setglobal(L, "getMyTable2");
新建一个.lua的访问类文件,并且输入
print(showreturn1())

print(showreturn2())

print(showreturn3())

local t = getMyTable()
for k, v in pairs(t) do
print(k, v)
end

local t2 = getMyTable2()
for k, v in pairs(t2) do
print(k, v)
end

2.Lua调用C/C++之标准C模块

在lua中init.c
static int showResult1(lua_State * L) {
lua_pushstring(L, "I am showResult1");
return 1;
}

static int showResult2(lua_State * L) {
const char *value = luaL_checkstring(L, -1);
//string rel = strcat("iCocos", value);
lua_pushstring(L, value);
return 1;
}

static const luaL_Reg myLibs[] = {
{"result1", showResult1},
{"result2", showResult2},
{NULL, NULL},
};

int luaopen_my_lib(lua_State * L) {
//luaL_newLib(L, myLibs);
luaL_register(L, "my_lib", myLibs);
return 1;
}
在static const luaL_Reg loadedlibs[] 里面增加
{"my_lib", luaopen_my_lib},
在lua中引入并调用方法
--- 已入包
local my_lib = require "my_lib"
--- 调用方法
print(my_lib.result1())
print(my_lib.result2("I am showResult2"))

待续

  • 后续的学习和开发,会相继介绍和分析一下totua,luajit,手动和自动绑定,第三个引入(支付,分析,分析,统计),插件引入(sqlite,json,zlib)
坚持原创技术分享,您的支持将鼓励我继续创作!