C/C++ Lua Parsing Engine

catalog

复制代码
1. Lua语言简介
2. 使用 Lua 编写可嵌入式脚本
3. VS2010编译Lua
4. 嵌入和扩展: C/C++中执行Lua脚本
5. 将C++函数导出到Lua引擎中: 在Lua脚本中执行C++函数
6. 将C函数导出到Lua引擎中: 在Lua脚本中执行C函数
7. C++ Function Library For Lua
8. Lua、Python嵌入式语言引擎的优缺点对比
复制代码

 

1. Lua语言简介

0x1: 运行

Lua是类C的,所以,他是大小写字符敏感的,同时,Lua脚本的语句的分号是可选的(和GO语言类似)
可以像python一样,在命令行上运行lua命令后进入lua的shell中执行语句

也可以把脚本存成一个文件,用如下命令行来运行

0x2: 语法

1. 注释

-- 两个减号是行注释
 
--[[
 这是块注释
 这是块注释
 --]]

2. 变量

Lua的数字只有double型,64bits,可以以如下的方式表示数字

复制代码
num = 1024
num = 3.0
num = 3.1416
num = 314.16e-2
num = 0.31416E1
num = 0xff
num = 0x56
复制代码

布尔类型只有nil和false是 false,数字0啊,''空字符串('\0')都是true
另外,需要注意的是:lua中的变量如果没有特殊说明,全是全局变量,那怕是语句块或是函数里。变量前加local关键字的是局部变量 

theGlobalVar = 50
local theLocalVar = "local variable"

3. 字符串

字符串你可以用单引号,也可以用双引号,还支持C类型的转义,比如

复制代码
1. '\a': 响铃
2. '\b': 退格
3. '\f': 表单
4. '\n': 换行
5. '\r': 回车
6. '\t': 横向制表
7. '\v': 纵向制表
8. '\\': 反斜杠
9. '\"': 双引号
10. '\'': 单引号
复制代码

下面的四种方式定义了完全相同的字符串(其中的两个中括号可以用于定义有换行的字符串)

a = 'alo\n123"'
a = "alo\n123\""
a = '\97lo\10\04923"'
a = [[alo
123"]]

C语言中的NULL在Lua中是nil,比如你访问一个没有声明过的变量,就是nil,比如下面的v的值就是nil

v = UndefinedVariable

4. 控制语句

值得注意的是,Lua没有++或是+=这样的操作

复制代码
1. while循环
sum = 0
num = 1
while num <= 100 do
    sum = sum + num
    num = num + 1
end
print("sum =",sum)

2. if-else分支
if age == 40 and sex == "Male" then
    print("男人四十一枝花")
elseif age > 60 and sex ~= "Female" then
    print("old man without country!")
elseif age < 20 then
    io.write("too young, too naive!\n")
else
    local age = io.read()
    print("Your age is "..age)
end
/*
上面的语句不但展示了if-else语句,也展示了
1) ~="是不等于,而不是!=
2) io库的分别从stdin和stdout读写的read和write函数
3) 字符串的拼接操作符".."
*/
 
3. for循环
从1加到100 
sum = 0
for i = 1, 100 do
    sum = sum + i
end
 
从1到100的奇数和 
sum = 0
for i = 1, 100, 2 do
    sum = sum + i
end 

从100到1的偶数和 
sum = 0
for i = 100, 1, -2 do
    sum = sum + i
end

4. until循环 
sum = 2
repeat
   sum = sum ^ 2 --幂操作
   print(sum)
until sum >1000
复制代码

5. 函数

复制代码
1. 递归 
function fib(n)
  if n < 2 then return 1 end
  return fib(n - 2) + fib(n - 1)
end

2. 闭包
示例一 
function newCounter()
    local i = 0
    return function()     -- anonymous function
        i = i + 1
        return i
    end
end
 
c1 = newCounter()
print(c1())  --> 1
print(c1())  --> 2

示例二 
function myPower(x)
    return function(y) return y^x end
end
 
power2 = myPower(2)
power3 = myPower(3)
 
print(power2(4)) --4的2次方
print(power3(5)) --5的3次方


3. 函数的返回值
和Go语言一样,可以一条语句上赋多个值,如
name, age, bGay = "haoel", 37, false, "haoel@hotmail.com"
//上面的代码中,因为只有3个变量,所以第四个值被丢弃

函数也可以返回多个值
function getUserInfo(id)
    print(id)
    return "haoel", 37, "haoel@hotmail.com", "http://coolshell.cn"
end
 
name, age, email, website, bGay = getUserInfo()
//上面的示例中,因为没有传id,所以函数中的id输出为nil,因为没有返回bGay,所以bGay也是nil
复制代码

6. 局部函数

下面的两个函数是一样的

function foo(x) return x^2 end
foo = function(x) return x^2 end

7. Table

所谓Table其实就是一个Key Value的数据结构,它很像Javascript中的Object,或是PHP中的数组,在别的语言里叫Dict或Map

haoel = {name="ChenHao", age=37, handsome=True}

下面是table的CRUD操作

haoel.website="http://coolshell.cn/"
local age = haoel.age
haoel.handsome = false
haoel.name=nil

看上去像C/C++中的结构体,但是name,age, handsome, website都是key。我们还可以像下面这样写义Table

t = {[20]=100, ['name']="ChenHao", [3.14]="PI"}
//我们可以这样访问: t[20],t["name"], t[3.14] 

数组的定义更加灵活

arr = {10,20,30,40,50}
其等价于
arr = {[1]=10, [2]=20, [3]=30, [4]=40, [5]=50}

也可以定义成不同的类型的数组,比如

arr = {"string", 100, "haoel", function() print("coolshell.cn") end}
//其中的函数可以这样调用: arr[4]() 

Lua的下标不是从0开始的,是从1开始的

for i=1, #arr do
    print(arr[i])
end
//上面的程序中:#arr的意思就是arr的长度

我们知道,Lua中的变量,如果没有local关键字,全都是全局变量,Lua也是用Table来管理全局变量的,Lua把这些全局变量放在了一个叫"_G"的Table里 
我们可以用如下的方式来访问一个全局变量(假设我们这个全局变量名叫globalVar)

_G.globalVar
_G["globalVar"]

也可以通过下面的方式来遍历一个Table

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

8. MetaTable 和 MetaMethod

MetaTable和MetaMethod是Lua中的重要的语法,MetaTable主要是用来做一些类似于C++重载操作符式的功能
比如,我们有两个分数

fraction_a = {numerator=2, denominator=3}
fraction_b = {numerator=4, denominator=7}

想实现分数间的相加:2/3 + 4/7,我们如果要执行: fraction_a + fraction_b,会报错的 
所以,我们可以动用MetaTable,如下所示

复制代码
fraction_op={}
function fraction_op.__add(f1, f2)
    ret = {}
    ret.numerator = f1.numerator * f2.denominator + f2.numerator * f1.denominator
    ret.denominator = f1.denominator * f2.denominator
    return ret
end
复制代码

为之前定义的两个table设置MetaTable: (其中的setmetatble是库函数)

setmetatable(fraction_a, fraction_op)
setmetatable(fraction_b, fraction_op)

接下来就可以正常进行分数运算了

fraction_s = fraction_a + fraction_b
//调用的是fraction_op.__add()函数

0x3: 模块

我们可以直接使用require(“model_name”)来载入别的lua文件,文件的后缀是.lua。载入的时候就直接执行那个文件了(和PHP的逻辑是一样的)

复制代码
1. require函数,载入同样的lua文件时,只有第一次的时候会去执行,后面的相同的都不执行了,相当于PHP中的require_once
2. 如果要让每一次文件都会执行的话,你可以使用dofile("hello")函数,相当于PHP中的require
3. 如果要 载入后不执行,等需要的时候执行时,你可以使用 loadfile()函数,如下所示
/*
local hello = loadfile("hello")
... ...
... ...
hello()

loadfile("hello")后,文件并不执行,我们把文件赋给一个变量hello,当hello()时,才真的执行 
*/
复制代码

mymod.lua

复制代码
local HaosModel = {}
 
local function getname()
    return "Hao Chen"
end
 
function HaosModel.Greeting()
    print("Hello, My name is "..getname())
end
 
return HaosModel
复制代码

file.lua

local hao_model = require("mymod")
hao_model.Greeting()

Relevant Link:

http://coolshell.cn/articles/10739.html

 

2. 使用 Lua 编写可嵌入式脚本

0x1: Lua新特性

与其他脚本语言一样,Lua也有自己的一些特性

复制代码
1. Lua类型: 在 Lua 中,值可以有类型,但是变量的类型都是动态决定的。nil、布尔型、数字 和 字符串 类型的工作方式与我们期望的一样
    1) Nil: 是值为 nil 的一种特殊类型,用来表示没有值 
    2) 布尔型的值可以是 truefalse 常量(Nil 也可以表示 false,任何非 nil 的值都表示 true)
    3) Lua 中所有的数字都是双精度的 
    4) 字符串是定长字符数组(因此,要在一个字符串后面附加上字符,必须对其进行拷贝)
    5) 表、函数 和线程类型都是引用。每个都可以赋值给一个变量,作为参数传递,或作为返回值从函数中返回。例如,下面是一个存储函数的例子 
/*
-- example of an anonymous function
-- returned as a value
-- see http://www.tecgraf.puc-rio.br/~lhf/ftp/doc/hopl.pdf
function add(x)
  return function (y) return (x + y) end
end
f = add(2)
print(type(f), f(10))
function  12
*/

2. Lua线程: 线程是通过调用内嵌函数 coroutine.create(f) 创建的一个协同例程 (co-routine),其中 f 是一个 Lua 函数。线程不会在创建时启动;相反,它是在创建之后使用 coroutine.resume(t) 启动的,其中 t 就是一个线程。每个协同例程都必须使用 coroutine.yield() 偶尔获得其他协同例程的处理器 

3. 赋值语句: Lua允许使用多种赋值语句,可以先对表达式进行求值,然后再进行赋值。例如,下面的语句 
/*
i = 3
a = {1, 3, 5, 7, 9}
i, a[i], a[i+1], b = i+1, a[i+1], a[i]
print (i, a[3], a[4], b, I)

会生成 4 7 5 nil nil。如果变量列表的个数大于值列表的个数,那么多出的变量都被赋值为 nil;因此,b 就是 nil。如果值的个数多于变量的个数,那么多出的值部分就会简单地丢弃。在 Lua 中,变量名是大小写敏感的,这可以解释为什么 I 的值是 nil
*/

4. 块(Chunk): 块可以是任何 Lua 语句序列。块可以保存到文件中,或者保存到 Lua 程序中的字符串中。每个块都是作为一个匿名函数体来执行的。因此,块可以定义局部变量和返回值。

5. 更酷的东西: Lua 具有一个标记-清理垃圾收集器。在 Lua 5.1 中,垃圾收集器是以增量方式工作的。Lua 具有完整的词法闭包。Lua 具有可靠的尾部调用语义 
在所有的工程任务中,要在编译性语言和解释性语言之间作出选择,就意味着要在这种环境中对每种语言的优缺点、权重和折中进行评测,并接受所带来的风险 
复制代码

0x2: Lua 提供了高级抽象,却又没失去与硬件的关联

对高性能代码和高级编程的需要进行平衡是 Lua(一种可嵌入式脚本语言)要解决的问题。在需要时我们可以使用编译后的代码来实现底层的功能,然后调用 Lua 脚本来操作复杂的数据。由于 Lua 脚本是与编译代码独立的,因此我们可以单独修改这些脚本。使用 Lua,开发周期就非常类似于

编码 -> 编译 -> 运行 -> 编写脚本 -> 编写脚本 -> 编写脚本 

即Lua对编程开发人员来说是脚本语言,同时它对底层解析引擎中会被进行编译、链接实现高性能和硬件绑定

0x3: Lua堆栈

要理解Lua和C++交互,首先要理解Lua堆栈,简单来说,Lua和C/C++语言通信的主要方法是一个无处不在的虚拟栈。栈的特点是先进后出。在Lua中,Lua堆栈就是一个struct,堆栈索引的方式可是是正数也可以是负数,区别是:正数索引1永远表示栈底,负数索引-1永远表示栈顶

Relevant Link:

http://www.ibm.com/developerworks/cn/linux/l-lua.html
http://www.cocos.com/doc/tutorial/show?id=1474

 

3. VS2010编译Lua

0x1: 从源代码编译Lua解释器binary

0x2: 从源代码编译Lua静态Lib库: 用于将Lua嵌入到宿主程序中

1. 下载lua源代码: http://www.lua.org/download.html
2. 选择新建 Win32 console project,在wizard界面选择 static Library;不选择Precomplied Header
3. 往工程中添加代码 Add Existing Item,将所有头文件源文件加入project
4. 点击"属性-c/c++-高级-编译为",选择"编译为C++代码(/TP)"(这样才能是CPP调用C文件,才能不会出现链接lib错误) 
5. release/debug编译
6. 得到CompileLuaStaticLib.lib

0x3: 将Lua嵌入到宿主程序中

复制代码
1. 在解决方案中添加一个 Win32 console project,项目名称命名为CompileLuaBinary,后面wizard界面中的选项取消预编译头
2. 添加对头文件的include directory
Configuration Properties -> C/C++-> General -> Additional Include Directories 
添加: D:\学习资料\Lua\lua-5.3.1\src

3. 源文件加入#pragma comment(lib,"CompileLuaStaticLib.lib")或者
Configuration Properties -> Linker-> Input -> Additional Dependencies
加入: CompileLuaStaticLib.lib

4. Configuration Properties -> Linker-> General -> Additional Libary Include Directories 
加入: C:\Users\zhenghan.zh\Documents\Visual Studio 2010\Projects\CompileLuaStaticLib\Release

5. 编写宿主程序代码,对Lua脚本进行进行解释执行 
复制代码

CompileLuaBinary.cpp

复制代码
#pragma comment(lib,"CompileLuaStaticLib.lib") 

#include "stdafx.h"
#include <stdio.h>  
#include <luaconf.h>  
#include <lua.h>   
#include <lualib.h>  
#include <lauxlib.h>   

int _tmain(int argc, _TCHAR* argv[])
{
    lua_State* L = luaL_newstate();  
    luaL_openlibs(L);  
    luaL_dofile(L, "file.lua");  
    lua_close(L);  

    return 0;  
} 
复制代码

Relevant Link:

http://www.cnblogs.com/dyllove98/p/3162930.html
http://www.tuicool.com/articles/Bj2eQ3
http://blog.csdn.net/berdy/article/details/7925040
http://blog.csdn.net/x_iya/article/details/8644180
http://blog.csdn.net/appletreesujie/article/details/12065369

 

4. 嵌入和扩展: C/C++中执行Lua脚本

Lua除了语法简单并且具有功能强大的表结构(Table)之外,Lua 的强大功能使其可以与宿主语言混合使用。由于 Lua 与宿主语言的关系非常密切,因此 Lua 脚本可以对宿主语言的功能进行扩充。但是这种融合是双赢的:宿主语言同时也可以对 Lua 进行扩充。举例来说,C 函数可以调用 Lua 函数,反之亦然 
Lua 与宿主语言之间的这种共生关系的核心是宿主语言是一个虚拟堆栈。虚拟堆栈与实际堆栈类似,是一种后进先出(LIFO)的数据结构,可以用来临时存储函数参数和函数结果。要从 Lua 中调用宿主语言的函数(反之亦然),调用者会将一些值压入堆栈中,并调用目标函数;被调用的函数会弹出这些参数(当然要对类型和每个参数的值进行验证),对数据进行处理,然后将结果放入堆栈中。当控制返回给调用程序时,调用程序就可以从堆栈中提取出返回值 
实际上在 Lua 中使用的所有的 C 应用程序编程接口(API)都是通过堆栈来进行操作的。堆栈可以保存 Lua 的值,不过值的类型必须是调用程序和被调用者都知道的,特别是向堆栈中压入的值和从堆栈中弹出的值更是如此

0x1: 一个简单的 Lua 解释器

复制代码
#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <string.h>

int main (void) 
{
    char buff[256];
    int error;
    lua_State *L = lua_open();   /* opens Lua */
    luaL_openlibs(L);
    luaopen_base(L);             /* opens the basic library */
    luaopen_table(L);            /* opens the table library */
    luaopen_io(L);               /* opens the I/O library */
    luaopen_string(L);           /* opens the string lib. */
    luaopen_math(L);             /* opens the math lib. */
    
    while (fgets(buff, sizeof(buff), stdin) != NULL) 
    { 
        error = luaL_loadbuffer(L, buff, strlen(buff), "line")  
        || lua_pcall(L, 0, 0, 0);
        if (error) 
        { 
            fprintf(stderr, "%s", lua_tostring(L, -1));
            lua_pop(L, 1);  /* pop error message from the stack */
        }
    }
    
    lua_close(L);
}

//gcc parseLuaInC.c -o parseLuaInC -llua

复制代码

运行的时候会出现PANIC: unprotected error in call to Lua API (no calling environment),原因是在Lua5.1中不能直接调用luaopen_*函数,解决办法是调用luaL_openlibs()

复制代码
#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <string.h>

int main (void) 
{
    char buff[256];
    int error;
    lua_State *L = lua_open();   /* opens Lua */ 
    luaL_openlibs(L);
    //luaopen_base(L);             /* opens the basic library */
    //luaopen_table(L);            /* opens the table library */
    //luaopen_io(L);               /* opens the I/O library */
    //luaopen_string(L);           /* opens the string lib. */
    //luaopen_math(L);             /* opens the math lib. */
    
    while (fgets(buff, sizeof(buff), stdin) != NULL) 
    { 
        error = luaL_loadbuffer(L, buff, strlen(buff), "line")  
        || lua_pcall(L, 0, 0, 0);
        if (error) 
        { 
            fprintf(stderr, "%s", lua_tostring(L, -1));
            lua_pop(L, 1);  /* pop error message from the stack */
        }
    }
    
    lua_close(L);
}

//gcc parseLuaInC.c -o parseLuaInC -llua
复制代码

传输是通过堆栈进行的。从 C 中调用任何 Lua 函数与这段代码类似:使用 lua_getglobal() 来获得函数,将参数压入堆栈,调用 lua_pcall(),然后处理结果。如果 Lua 函数返回 n 个值,那么第一个值的位置在堆栈的 -n 处,最后一个值在堆栈中的位置是 -1。
反之,在 Lua 中调用 C 函数也与之类似。如果您的操作系统支持动态加载,那么 Lua 可以根据需要来动态加载并调用函数。(在必须使用静态加载的操作系统中,可以对 Lua 引擎进行扩充,此时调用 C 函数时需要重新编译 Lua)

0x2: 使用 Lua脚本引擎

Lua 脚本引擎本身是由 C 语言写成的,在 C 或 C++ 中使用 Lua 脚本也相当简单

复制代码
基本的初始化步骤如下 
1. 使用 lua_newstate() 创建一个新的 Lua 状态机 
2. 若有必要,调用 luaL_openlibs() 函数加载 Lua 的标准库

一旦初始化了 Lua 脚本引擎,你可以通过如下步骤执行一段 Lua 脚本
1. 使用 luaL_loadfile 加载一段 Lua 程序或脚本到 Lua 执行引擎中
2. 调用 lua_pcall 函数执行已加载的脚本
复制代码

如果想在应用程序中加载Lua脚本并执行其中的函数,你必须执行被加载的Lua程序块(chunk)。刚刚加载的程序块只是编译后存放于Lua的脚本引擎中,并没有被执行。只有在程序块被执行后,Lua中的全局变量和函数才会被创建,在这之前这些任何全局变量和函数对于应用程序来说都不可用。作为Lua引擎的环境由应用程序提供给Lua脚本引擎的任何全局变量和函数也不可用。应用程序必须首先创建变量和函数,并使用函数lua_setglobal()让它们可用 

0x3: C/C++解析执行Lua脚本文件

file.lua

str = "I am so cool"  
tbl = {name = "shun", id = 20114442}  
function add(a,b)  
    return a + b  
end

loadLuafileInC.cpp

复制代码
#include <iostream>  
#include <string.h>  
using namespace std;  
   
extern "C"  
{  
    #include "lua.h"  
    #include "lauxlib.h"  
    #include "lualib.h"  
}  
int main()  
{  
    //1.创建Lua状态  
    lua_State *L = luaL_newstate();  
    if (L == NULL)  
    {  
        return 0;  
    }  
   
    //2.加载Lua文件  
    int bRet = luaL_loadfile(L,"file.lua");  
    if(bRet)  
    {  
        cout << "load file error" << endl;  
        return 0;  
    }  
   
    //3.运行Lua文件  
    bRet = lua_pcall(L,0,0,0);  
    if(bRet)  
    {  
        cout << "pcall error" << endl;  
        return 0;  
    }  
   
    //4.读取变量  
    lua_getglobal(L,"str");  
    string str = lua_tostring(L,-1);  
    cout<<"str = "<<str.c_str()<<endl;        //str = I am so cool~  
   
    //5.读取table  
    lua_getglobal(L,"tbl");   
    lua_getfield(L,-1,"name");  
    str = lua_tostring(L,-1);  
    cout << "tbl:name = " << str.c_str() << endl; //tbl:name = shun  
   
    //6.读取函数  
    lua_getglobal(L, "add");        // 获取函数,压入栈中  
    lua_pushnumber(L, 10);          // 压入第一个参数  
    lua_pushnumber(L, 20);          // 压入第二个参数  
    int iRet= lua_pcall(L, 2, 1, 0);// 调用函数,调用完成以后,会将返回值压入栈中,2表示参数个数,1表示返回结果个数。  
    if (iRet)                       // 调用出错  
    {  
        const char *pErrorMsg = lua_tostring(L, -1);  
        cout << pErrorMsg << endl;  
        lua_close(L);  
        return 0;  
    }  
    if (lua_isnumber(L, -1))        //取值输出  
    {  
        double fValue = lua_tonumber(L, -1);  
        cout << "Result is " << fValue << endl;  
    }  
   
    //至此,栈中的情况是:  
    //=================== 栈顶 ===================   
    //  索引  类型      值  
    //   4   int:      30   
    //   3   string:   shun   
    //   2   table:     tbl  
    //   1   string:    I am so cool~  
    //=================== 栈底 ===================   
   
    //7.关闭state  
    lua_close(L);  
    return 0;  
}

//g++ loadLuafileInC.cpp -o loadLuafileInC -llua
复制代码

需要注意的是:堆栈操作是基于栈顶的,就是说它只会去操作栈顶的,为了更好地C/C++中和Lua交互都是虚拟栈进行的,我们来看几个Lua C API的逻辑流程

1. lua_getglobal(L,"var"): 会执行两步操作
    1) 将var放入栈中
    2) 由Lua去寻找变量var的值,并将变量var的值返回栈顶(替换var)

2. lua_getfield(L,-1,"name"): 等价于lua_pushstring(L,"name") + lua_gettable(L,-2)

0x4: C/C++中调用Lua函数过程

函数调用流程是

1. 先将函数入栈
2. 参数入栈
3. 然后用lua_pcall调用函数
//此时栈顶为参数,栈底为函数

函数调用结束返回的栈过程大致会是

复制代码
1. 参数出栈
2. 保存参数
3. 参数出栈
4. 保存参数
..
5. 函数出栈
6. 函数返回结果入栈
复制代码

Relevant Link:

http://lua-users.org/wiki/CallingLuaFromCpp
http://gearx.googlecode.com/svn-history/r8/trunk/vc_lua_error.txt
http://www.ibm.com/developerworks/cn/linux/l-lua.html

 

5. 将C++函数导出到Lua引擎中: 在Lua脚本中执行C++函数

在讨论在Lua中调用C/C++ API之前,我们需要明白,Lua解释器本身是一个极其精简的语法解析、编译、执行容器,它本身是不具备任何的扩展能力的(所有脚本语言都是类似的思想),如果需要扩展解析器的API能力,只有从解析器源代码编译、静态库引入编译、DLL动态引入的方式将扩展功能API的二进制代码引入到Lua解析器的代码空间中,才可以在Lua脚本中使用扩展功能。是否拥有扩展功能,和是否是原生Lua解释器还是将Lua解释器内嵌到宿主程序中没有本质的关系
不考虑架构优劣问题,从纯技术层面考虑Lua中调用C++的方法,有如下几种

0x1: 方法一: 将扩展模块源代码写入Lua源码中

这是成本最高,定制化最差的一种方式,在Lua中调用C/C++,我们可以将函数写lua.c中,然后重新编译Lua文件,类似于在Mysql的源代码中直接加入UDF函数
我们可以在lua.c中加入我们自己的函数。函数要遵循规范(可在lua.h中查看)如下

typedef int (*lua_CFunction) (lua_State *L);

所有的函数必须接收一个lua_State作为参数,同时返回一个整数值。因为这个函数使用Lua栈作为参数,所以它可以从栈里面读取任意数量和任意类型的参数。而这个函数的返回值则表示函数返回时有多少返回值被压入Lua栈(因为Lua的函数是可以返回多个值的)

在lua.c中加入如下函数

复制代码
// This is my function  
static int getTwoVar(lua_State *L)  
{  
    // 向函数栈中压入2个值  
    lua_pushnumber(L, 10);  
    lua_pushstring(L,"hello");  
   
    return 2;  
}  
 
在pmain函数中,luaL_openlibs函数后加入以下代码:
//注册函数  
lua_pushcfunction(L, getTwoVar); //将函数放入栈中  
lua_setglobal(L, "getTwoVar");   //设置lua全局变量getTwoVar
复制代码

然后需要在lua.h中加入注册函数

lua_register(L,"getTwoVar",getTwoVar);

0x2: 方法二: 使用静态依赖的方式

将Lua解释引擎嵌入到宿主程序中(引入Lua静态库和src源代码),同时在宿主程序中使用C++编写扩展函数,并注册到Lua的全局域中
avg.lua

avg, sum = average(10, 20, 30, 40, 50)  
print("The average is ", avg)  
print("The sum is ", sum)

test.cpp

复制代码
#include <stdio.h>  
extern "C" {  
#include "lua.h"  
#include "lualib.h"  
#include "lauxlib.h"  
}  
   
/* 指向Lua解释器的指针 */  
lua_State* L;  
static int average(lua_State *L)  
{  
    /* 得到参数个数 */  
    int n = lua_gettop(L);  
    double sum = 0;  
    int i;  
   
    /* 循环求参数之和 */  
    for (i = 1; i <= n; i++)  
    {  
        /* 求和 */  
        sum += lua_tonumber(L, i);  
    }  
    /* 压入平均值 */  
    lua_pushnumber(L, sum / n);  
    /* 压入和 */  
    lua_pushnumber(L, sum);  
    /* 返回返回值的个数 */  
    return 2;  
}  
   
int main ( int argc, char *argv[] )  
{  
    /* 初始化Lua */  
    L = lua_open();  
   
    /* 载入Lua基本库 */  
    luaL_openlibs(L);  
    /* 注册函数 */  
    lua_register(L, "average", average);  
    /* 运行脚本 */  
    luaL_dofile(L, "avg.lua");  
    /* 清除Lua */  
    lua_close(L);  
   
    /* 暂停 */  
    printf( "Press enter to exit…" );  
    getchar();  
    return 0;  
}
复制代码

我们在C++中写一个模块函数,将函数注册到Lua解释器中,然后由C++去执行我们的Lua文件,然后在Lua中调用刚刚注册的函数

0x3: 方法三: 使用dll动态链接的方式

新建一个dll工程,工程名为mLualib(因此最后导出的dll也为mLualib.dll)
.h

复制代码
#pragma once  
extern "C" {  
#include "lua.h"  
#include "lualib.h"  
#include "lauxlib.h"  
}  
   
#ifdef LUA_EXPORTS  
#define LUA_API __declspec(dllexport)  
#else  
#define LUA_API __declspec(dllimport)  
#endif  
   
extern "C" LUA_API int luaopen_mLualib(lua_State *L);//定义导出函数
复制代码

.cpp

复制代码
#include "mLualib.h"  
static int averageFunc(lua_State *L)  
{  
    int n = lua_gettop(L);  
    double sum = 0;  
    int i;  
   
    /* 循环求参数之和 */  
    for (i = 1; i <= n; i++)  
        sum += lua_tonumber(L, i);  
   
    lua_pushnumber(L, sum / n);     //压入平均值  
    lua_pushnumber(L, sum);         //压入和  
   
    return 2;                       //返回两个结果  
}  
   
static int sayHelloFunc(lua_State* L)  
{  
    printf("hello world!");  
    return 0;  
}  
   
static const struct luaL_Reg myLib[] =   
{  
    {"average", averageFunc},  
    {"sayHello", sayHelloFunc},  
    {NULL, NULL}       //数组中最后一对必须是{NULL, NULL},用来表示结束      
};  
   
int luaopen_mLualib(lua_State *L)  
{  
    luaL_register(L, "ss", myLib);  
    return 1;       // 把myLib表压入了栈中,所以就需要返回1  
}
复制代码

在lua中我们这样子来调用(调用之前记得把dll文件复制到lua文件目录下)

require "mLualib"  
local ave,sum = ss.average(1,2,3,4,5)//参数对应堆栈中的数据  
print(ave,sum)  -- 3 15  
ss.sayHello()   -- hello world!

Lua和C++是通过一个虚拟栈来交互的
C++调用Lua实际上是:由C++先把数据放入栈中,由Lua去栈中取数据,然后返回数据对应的值到栈顶,再由栈顶返回C++
Lua调C++也一样:先编写自己的C模块,然后注册函数到Lua解释器中,然后由Lua去调用这个模块的函数

Relevant Link:

http://www.cocos.com/doc/tutorial/show?id=1474

 

6. 将C函数导出到Lua引擎中: 在Lua脚本中执行C函数

为了创建一个要在Lua脚本中使用的C辅助函数,C应用程序就必需提供该函数体(the function body)并使用适当的Lua引擎函数让该新函数变为在Lua引擎中可用。在应用程序里将一个函数提供给在Lua引擎中进行使用所需的函数调用要将若干值压入Lua的虚拟堆栈之中,然后调用lua_setglobal()函数,就可以把应用程序中的函数作为全局函数提供给在Lua脚本引擎中使用

lua_pushcclosure (lua, concatMultiWideStrings, 0);
lua_setglobal (lua, "wcscat");

Lua为函数提供闭包概念。当一个Lua脚本引擎调用应用函数时,一个闭包允许一个应用指定一个或多个提供给应用函数的值。这些值可以被应用函数更新,这个例子使用的一个特性是通过一个与应用函数关联的计数器增量提供一个惟一值
要导出的C函数的源代码应该具有如下所示的形式

复制代码
// concatenate multiple wide strings
// const wchar_t *wcscat(wchar_t *wcharSt1, const wchar_t *wcharSt2, const wchar_t *wcharSt3, ...)
static int concatMultiWideStrings (lua_State *lua)
{
    // 使用lua_State *这个参数提供给该函数相关的session环境
    int  nPushCount = 0;
    int  nArgIndex = 1;
    //Lua脚本引擎提供了位于Lua虚拟堆栈之中的参数的个数信息
    int argc = lua_gettop(lua);
    wchar_t  tempBuffer[2048];

    if (argc > 0) 
    {
        wchar_t    *pWideString = &tempBuffer[0];
        size_t     iLen = 1;

        while (nArgIndex <= argc) 
        {
            //使用Lua引擎提供的lua_type()函数判断出参数的数据类型,从而可以跳过那些不是正确类型的参数
            if (lua_type(lua, nArgIndex) == LUA_TSTRING) 
            {
                const wchar_t *msgX = (wchar_t *) lua_tostring (lua, nArgIndex);
                while (*msgX) {*pWideString++ = *msgX++; iLen++; }
            }
            nArgIndex++;
        }
        *pWideString = 0;  // final zero terminator
        lua_pushlstring (lua, (char *)(&tempBuffer), iLen * sizeof(wchar_t));
        nPushCount++;
    }

    return nPushCount;
}
复制代码

Relevant Link:

http://www.oschina.net/translate/extending-a-cplusplus-application-with-lua-5-2 
http://www.aliog.com/27362.html 

 

7. C++ Function Library For Lua

0x1: luaforwindows

Lua for Windows is a 'batteries included environment' for the Lua scripting language on Windows.
Lua for Windows (LfW) combines Lua binaries, Lua libraries with a Lua-capable editor in a single install package for the Microsoft Windows operating system. LfW contains everything you need to write, run and debug Lua scripts on Windows. A wide variety of libraries and examples are included that are ready to use with Microsoft Windows. LfW runs on Windows 2000 and newer versions of Windows. Lua and its associated libraries are also available for other operating systems, so most scripts will be automatically cross-platform.

0x2: Sweet Lua

Sweet Lua is a C++ to Lua binding library.

1. Features

复制代码
1. Bind C++ functions as Lua functions and closures with out of order parameters.
2. Bind C++ objects as Lua tables with lifetime controlled by either C++ or Lua.
3. Coroutines.
4. Type safety.
5. Error handling.
6. Convert C++ iterator sequences to Lua iterators.
7. STL vector, list, set, and map integration.
8. Boost Filesystem integration.
复制代码

2. Usage

Sweet Lua is a C++ library for providing Lua bindings to C++ variables, functions, and objects. It provides classes to represent the Lua virtual machine (Lua), a table or object (LuaObject), and a coroutine or thread (LuaThread).
The Lua class represents the Lua global environment and virtual machine. It provides functions to execute Lua scripts from files and memory and to define Lua bindings to C++ variables, functions, and objects.
The LuaObject class represents an object in the Lua virtual machine. It is typically used to create prototypes or metatables in Lua that have no equivalent C++ object to associate with non-intrusively. When a LuaObject is constructed a corresponding table is created in the Lua global environment. Both the C++ application and the Lua virtual machine can then set variables on and call member functions of that table.
The LuaThread class represents a Lua coroutine. It provides functions to start and resume execution in a separate yieldable thread in the Lua virtual machine. The calls can yield and return control from the virtual machine back to the program allowing asynchronous behaviour to be abstracted behind an interface that scripts can treat as synchronous.

0x3: luabind

luabind is a library, inspired by and similar to Boost.Python, that helps you create bindings between C++ and Lua. It has the ability to expose functions and classes, written in C++, to Lua.

复制代码
// --------------------------------------------------------------------------------------------- //
// LuaBind example
//
// This example project for Microsoft(tm) Visual C++ 2010(tm) users
// compiles out-of-the-box. Precompiled binaries can be found in the
// "References" directory in case you wish to use them for other
// projects. Pay attention to the runtime library (multithreaded dll)
// setting and the different library files being used for debug and release
// builds when you do so!
// --------------------------------------------------------------------------------------------- //

// Include the lua headers (the extern "C" is a requirement because we're
// using C++ and lua has been compiled as C code)
extern "C" {
  #include "lua.h"
  #include "lualib.h"
  #include "lauxlib.h"
}

// This is the only header we need to include for LuaBind to work
#include "luabind/luabind.hpp"

// Some other C++ headers we're most likely going to use
#include <iostream>
#include <string>

// We don't want to write std:: every time we're displaying some debug output
using namespace std;

// --------------------------------------------------------------------------------------------- //

// Put your testing code here...
void print_hello(int number) 
{
    cout << "hello world " << number << endl;
}

int main() 
{
    // Create a new lua state
    lua_State *myLuaState = luaL_newstate();

    // Connect LuaBind to this lua state
    luabind::open(myLuaState);

    // Add our function to the state's global scope
    luabind::module(myLuaState) [
    luabind::def("print_hello", print_hello)
    ];

    // Now call our function in a lua script
    luaL_dostring(
    myLuaState,
    "print_hello(123)\n"
    );

    lua_close(myLuaState);
}
复制代码

0x4: 方案优缺点

复制代码
缺点
1. Lua解释器本身十分精简小巧,交互不包含任何的高级操作,如果希望通过Lua扩展宿主程序的能力,实现某些高级操作,就需要在宿主程序中用C/C++实现大量的底层操作,并导出给Lua使用(lua_pushcclosure、lua_setglobal)

优点
1. C/C++ Library For Lua已经有很多开源的代码库(类似Python的代码库),可以直接使用这些开源代码库强化Lua的API能力
复制代码

0x5: 需要支持的API种类

1. 文件系统IO操作
2. 系统命令执行
3. 加解密算法函数
4. 字符串处理、JSON、XML处理
5. 进程管控相关
6. 网络相关

Relevant Link:

http://simion.com/info/api.html
liblutok
https://github.com/rjpcomputing/luaforwindows
http://www.sweetsoftware.co.nz/lua_overview.html
http://sourceforge.net/projects/luabind/
http://blog.nuclex-games.com/tutorials/cxx/luabind-introduction/

 

8. Lua、Python嵌入式语言引擎的优缺点对比

0x1: 选择Lua的理由

1. Lua解释器很小,而且很高效,用C/C++扩展很方便
2. Python不管是嵌入式解释引擎还是外部扩展库都相对较为庞大,虽然可裁减

0x2: 选择Python的理由

1. 调试会更方便
2. 用Python可以减少很多的工作,Python已经哟哟大量现成的、稳定的代码库(.py)和扩展库(.so),可以直接在脚本中通过import引入使用
3. lua很多功能函数都缺少,同时文档资料太少

Relevant Link:

http://www.360doc.com/content/15/1120/09/15307239_514478941.shtml

 转自:http://www.cnblogs.com/LittleHann/p/4972459.html

Copyright (c) 2015 LittleHann All rights reserved

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值