http://chenlq.net/dev/cpp-why/51-half-performance-half-of-the-flexible-cc-and-lua-hybrid-programming-how-to-call-c-functions-in-the-lua-script.html
Q:
在前一篇文章[50]一半是性能,一半是灵活——C++和Lua混合编程(如何在C++代码中调用Lua脚本的函数?)中,我们介绍了如何在C++程序中调用Lua脚本的函数来提高程序的灵活性,那么,反过来,我们是不是也可以在Lua脚本程序化中调用C++的函数,来提高程序的性能呢?如果可以,如何进行?
A:
回答当然是肯定的。我们这个系列的题目就是“一半是性能,一半是灵活”,也就是当C++需要灵活的时候,我们就可以在C++中调用Lua的函数来提高灵活性,反过来,当Lua需要性能的时候,我们自然也可以在Lua中调用C++函数来提高其性能。事物总是这样相辅相成,互相帮助。
我们还是来看一个实际的例子。在[50]一半是性能,一半是灵活——C++和Lua混合编程(如何在C++代码中调用Lua脚本的函数?)中,我们在C++程序中调用了Lua函数来定义游戏逻辑(判断某个点是否在矩形场景内),以此来提高程序的灵活性。现在,需求发生了变化(这个世界上,唯一不变的就是需求总是在变化),我们不再是判断某个点(x,y)是否在某个矩形场景(0,0,100,100)内,而是判断点是否在某个圆形场景(x=50,y=50,r=50)内。好在我们已经在C++程序中调用了Lua函数inscene()来进行逻辑判断,我们只需要简单地修改这个函数,就可以实现新的游戏逻辑:
--判断点(x,y)是否在圆形场景内
function inscene(x,y)
--用lua的数学函数,计算点(x,y)到场景中心(50,50)的距离
local len1 = math.pow(math.abs(x-50),2)
local len2 = math.pow(math.abs(y-50),2)
local dis = math.sqrt(len1+len2)
--判断距离是否小于半径
return dis < 50
end
经过这样的简单修改,我们的C++程序就可以拥有新的游戏逻辑,从而轻松地满足了新的需求,这就是Lua脚本程序给C++程序带来的灵活性。
然而,问题又来了(一个旧的矛盾解决了,预示着一个新的矛盾的到来)。为了判断点是否在圆形场景之内,我们使用了Lua的数学函数计算了点到圆心的距离,可是,数学计算(或者说大量高负荷的计算 )是Lua这种脚本语言的弱项,而这恰恰是C/C++这种编译型语言的强项。一个很自然的想法就是在Lua脚本程序中调用C++函数,将大量计算的工作交给C++程序去完成,以此来提高Lua脚本程序的性能。
废话了这么多,才引入正题。要想在Lua脚本程序中调用C++函数,第一步自然是编写完成计算工作的C++函数。在这个例子中,最大的计算量集中在计算点到圆心的距离,所以我们编写一个C++函数来完成这个计算工作:
// 计算点到圆心的距离
static int distance(lua_State* L)
{
int pos[4] = {0};
// 用lua_tonumber()获取
// 从Lua传递过来的点(pos[0],pos[1])和
// 圆心(pos[2],pos[3])的坐标
for(int i = 0; i < 4; ++i)
{
// 获取从Lua传递过来的参数数据
// 这里需要注意的是,这些数据是以1开始的
// 所以我们这里用i+1作为序号
// 如果序号是0,得到的是参数数据的个数
pos[i] = luaL_checkint(L, i+1);
}
// 用C++的数学函数计算点到圆心的距离
int a = pow(abs(pos[0]-pos[2]),2);
int b = pow(abs(pos[1]-pos[3]),2);
double dis = sqrt(a + b);
// 将计算得到的距离压入栈顶作为返回结果
lua_pushnumber(L,dis);
return 1; // 返回返回值的个数
}
这里我们可以看到,整个过程其实很简单,无非就是将参数类型设置为lua_State*,然后用luaL_checkint()获得从Lua传递过来的点和圆心的坐标,接着就利用这些坐标,通过C++的数学函数计算两点之间的距离(这个自然是比Lua的计算要更加高效),最后通过lua_pushnumber()将结果数据传递给Lua程序,并最终返回结果数据的个数。
完成C++函数的实现后,为了让Lua脚本程序能够调用这个函数,我们还必须用lua_regiser()将这个函数注册,让Lua脚本可以找到这个函数:
// C++程序执行Lua脚本
int main()
{
// Lua解释器指针
lua_State* L = nullptr;
// 创建Lua解释器
L = luaL_newstate();
if(nullptr != L)
{
luaL_openlibs(L);// 打开Lua库
lua_register(L, "distance", distance);// 注册lua基本库,绑定之
// 执行Lua脚本snake.lua
luaL_dofile(L, "snake.lua");
//...
}
return 0;
}
最后,自然就是在Lua脚本程序中调用这个函数,用C++来高效地计算距离:
--通过调用C++的distance函数,
--判断点(x,y)是否在场景内
function inscene(x,y)
--调用C++的distance函数,
--计算点(x,y)到场景中心(50,50)的距离
local dis = distance(x,y,50,50);
return dis < 50
end
通过这样的混和编程,我们在C++程序中调用了Lua函数,增加了程序的灵活性,在Lua程序中调用了C++函数,提高了程序的性能。两相结合,各取所长,这就是混合编程的意义所在。
最后,补充一下这个例子完整的C++代码:
// 引入Lua需要的头文件
// 如果是C语言代码,extern "C"可以省略
extern "C"
{
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
};
#include <iostream>
#include <cmath>
using namespace std;
// 计算点到圆心的距离
static int distance(lua_State* L)
{
int pos[4] = {0};
// 用lua_tonumber()获取
// 从Lua传递过来的点(pos[0],pos[1])和
// 圆心(pos[2],pos[3])的坐标
for(int i = 0; i < 4; ++i)
{
// 获取从Lua传递过来的参数数据
// 这里需要注意的是,这些数据是以1开始的
// 所以我们这里用i+1作为序号
// 如果序号是0,得到的是参数数据的个数
pos[i] = luaL_checkint(L, i+1);
}
// 用C++的数学函数计算点到圆心的距离
int a = pow(abs(pos[0]-pos[2]),2);
int b = pow(abs(pos[1]-pos[3]),2);
double dis = sqrt(a + b);
// 将计算得到的距离压入栈顶作为返回结果
lua_pushnumber(L,dis);
return 1; // 返回返回值的个数
}
// 判断x,y是否在场均内
bool inscene(lua_State* L,int x,int y)
{
bool isin = false; // 初始值,未在场景内
if(nullptr != L)
{
// 根据函数名获得Lua中的函数
lua_getglobal(L, "inscene");
lua_pushnumber(L, x); // 参数一入栈
lua_pushnumber(L, y); // 参数二入栈
// 调用Lua中的inscene函数,
// 这里的2,1表示两个输入参数,一个返回值
lua_call(L, 2, 1);
// 获得bool类型的返回值
isin = lua_toboolean(L, -1);
lua_pop(L, 1);// 将返回值出栈,恢复栈中的元素
}
return isin;
}
// C++程序执行Lua脚本
int main()
{
// Lua解释器指针
lua_State* L = nullptr;
// 创建Lua解释器
L = luaL_newstate();
if(nullptr != L)
{
luaL_openlibs(L);// 打开Lua库
lua_register(L, "distance", distance);// 注册lua基本库,绑定之
// 执行Lua脚本snake.lua
luaL_dofile(L, "snake.lua");
int x = 120;
int y = 20;
// 调用Lua脚本中的函数
bool isin = inscene(L, x, y);
// 输出结果
if(isin)
std::cout<<x<<","<<y<<" is in the scene."<<std::endl;
lua_close(L);// 关闭Lua
}
return 0;
Q:
在前一篇文章[50]一半是性能,一半是灵活——C++和Lua混合编程(如何在C++代码中调用Lua脚本的函数?)中,我们介绍了如何在C++程序中调用Lua脚本的函数来提高程序的灵活性,那么,反过来,我们是不是也可以在Lua脚本程序化中调用C++的函数,来提高程序的性能呢?如果可以,如何进行?
A:
回答当然是肯定的。我们这个系列的题目就是“一半是性能,一半是灵活”,也就是当C++需要灵活的时候,我们就可以在C++中调用Lua的函数来提高灵活性,反过来,当Lua需要性能的时候,我们自然也可以在Lua中调用C++函数来提高其性能。事物总是这样相辅相成,互相帮助。
我们还是来看一个实际的例子。在[50]一半是性能,一半是灵活——C++和Lua混合编程(如何在C++代码中调用Lua脚本的函数?)中,我们在C++程序中调用了Lua函数来定义游戏逻辑(判断某个点是否在矩形场景内),以此来提高程序的灵活性。现在,需求发生了变化(这个世界上,唯一不变的就是需求总是在变化),我们不再是判断某个点(x,y)是否在某个矩形场景(0,0,100,100)内,而是判断点是否在某个圆形场景(x=50,y=50,r=50)内。好在我们已经在C++程序中调用了Lua函数inscene()来进行逻辑判断,我们只需要简单地修改这个函数,就可以实现新的游戏逻辑:
--判断点(x,y)是否在圆形场景内
function inscene(x,y)
--用lua的数学函数,计算点(x,y)到场景中心(50,50)的距离
local len1 = math.pow(math.abs(x-50),2)
local len2 = math.pow(math.abs(y-50),2)
local dis = math.sqrt(len1+len2)
--判断距离是否小于半径
return dis < 50
end
经过这样的简单修改,我们的C++程序就可以拥有新的游戏逻辑,从而轻松地满足了新的需求,这就是Lua脚本程序给C++程序带来的灵活性。
然而,问题又来了(一个旧的矛盾解决了,预示着一个新的矛盾的到来)。为了判断点是否在圆形场景之内,我们使用了Lua的数学函数计算了点到圆心的距离,可是,数学计算(或者说大量高负荷的计算 )是Lua这种脚本语言的弱项,而这恰恰是C/C++这种编译型语言的强项。一个很自然的想法就是在Lua脚本程序中调用C++函数,将大量计算的工作交给C++程序去完成,以此来提高Lua脚本程序的性能。
废话了这么多,才引入正题。要想在Lua脚本程序中调用C++函数,第一步自然是编写完成计算工作的C++函数。在这个例子中,最大的计算量集中在计算点到圆心的距离,所以我们编写一个C++函数来完成这个计算工作:
// 计算点到圆心的距离
static int distance(lua_State* L)
{
int pos[4] = {0};
// 用lua_tonumber()获取
// 从Lua传递过来的点(pos[0],pos[1])和
// 圆心(pos[2],pos[3])的坐标
for(int i = 0; i < 4; ++i)
{
// 获取从Lua传递过来的参数数据
// 这里需要注意的是,这些数据是以1开始的
// 所以我们这里用i+1作为序号
// 如果序号是0,得到的是参数数据的个数
pos[i] = luaL_checkint(L, i+1);
}
// 用C++的数学函数计算点到圆心的距离
int a = pow(abs(pos[0]-pos[2]),2);
int b = pow(abs(pos[1]-pos[3]),2);
double dis = sqrt(a + b);
// 将计算得到的距离压入栈顶作为返回结果
lua_pushnumber(L,dis);
return 1; // 返回返回值的个数
}
这里我们可以看到,整个过程其实很简单,无非就是将参数类型设置为lua_State*,然后用luaL_checkint()获得从Lua传递过来的点和圆心的坐标,接着就利用这些坐标,通过C++的数学函数计算两点之间的距离(这个自然是比Lua的计算要更加高效),最后通过lua_pushnumber()将结果数据传递给Lua程序,并最终返回结果数据的个数。
完成C++函数的实现后,为了让Lua脚本程序能够调用这个函数,我们还必须用lua_regiser()将这个函数注册,让Lua脚本可以找到这个函数:
// C++程序执行Lua脚本
int main()
{
// Lua解释器指针
lua_State* L = nullptr;
// 创建Lua解释器
L = luaL_newstate();
if(nullptr != L)
{
luaL_openlibs(L);// 打开Lua库
lua_register(L, "distance", distance);// 注册lua基本库,绑定之
// 执行Lua脚本snake.lua
luaL_dofile(L, "snake.lua");
//...
}
return 0;
}
最后,自然就是在Lua脚本程序中调用这个函数,用C++来高效地计算距离:
--通过调用C++的distance函数,
--判断点(x,y)是否在场景内
function inscene(x,y)
--调用C++的distance函数,
--计算点(x,y)到场景中心(50,50)的距离
local dis = distance(x,y,50,50);
return dis < 50
end
通过这样的混和编程,我们在C++程序中调用了Lua函数,增加了程序的灵活性,在Lua程序中调用了C++函数,提高了程序的性能。两相结合,各取所长,这就是混合编程的意义所在。
最后,补充一下这个例子完整的C++代码:
// 引入Lua需要的头文件
// 如果是C语言代码,extern "C"可以省略
extern "C"
{
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
};
#include <iostream>
#include <cmath>
using namespace std;
// 计算点到圆心的距离
static int distance(lua_State* L)
{
int pos[4] = {0};
// 用lua_tonumber()获取
// 从Lua传递过来的点(pos[0],pos[1])和
// 圆心(pos[2],pos[3])的坐标
for(int i = 0; i < 4; ++i)
{
// 获取从Lua传递过来的参数数据
// 这里需要注意的是,这些数据是以1开始的
// 所以我们这里用i+1作为序号
// 如果序号是0,得到的是参数数据的个数
pos[i] = luaL_checkint(L, i+1);
}
// 用C++的数学函数计算点到圆心的距离
int a = pow(abs(pos[0]-pos[2]),2);
int b = pow(abs(pos[1]-pos[3]),2);
double dis = sqrt(a + b);
// 将计算得到的距离压入栈顶作为返回结果
lua_pushnumber(L,dis);
return 1; // 返回返回值的个数
}
// 判断x,y是否在场均内
bool inscene(lua_State* L,int x,int y)
{
bool isin = false; // 初始值,未在场景内
if(nullptr != L)
{
// 根据函数名获得Lua中的函数
lua_getglobal(L, "inscene");
lua_pushnumber(L, x); // 参数一入栈
lua_pushnumber(L, y); // 参数二入栈
// 调用Lua中的inscene函数,
// 这里的2,1表示两个输入参数,一个返回值
lua_call(L, 2, 1);
// 获得bool类型的返回值
isin = lua_toboolean(L, -1);
lua_pop(L, 1);// 将返回值出栈,恢复栈中的元素
}
return isin;
}
// C++程序执行Lua脚本
int main()
{
// Lua解释器指针
lua_State* L = nullptr;
// 创建Lua解释器
L = luaL_newstate();
if(nullptr != L)
{
luaL_openlibs(L);// 打开Lua库
lua_register(L, "distance", distance);// 注册lua基本库,绑定之
// 执行Lua脚本snake.lua
luaL_dofile(L, "snake.lua");
int x = 120;
int y = 20;
// 调用Lua脚本中的函数
bool isin = inscene(L, x, y);
// 输出结果
if(isin)
std::cout<<x<<","<<y<<" is in the scene."<<std::endl;
lua_close(L);// 关闭Lua
}
return 0;