• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

C/C++ Lua Parsing Engine

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

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, "[email protected]"
//上面的代码中,因为只有3个变量,所以第四个值被丢弃

函数也可以返回多个值
function getUserInfo(id)
    print(id)
    return "haoel", 37, "[email protected]", "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://< 

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap