65.9K
CodeProject 正在变化。 阅读更多。
Home

LuaDotNet:.NET 下的 Lua 和 Luabind 的一个轻量级封装

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (8投票s)

2003年8月8日

4分钟阅读

viewsIcon

157702

downloadIcon

1625

将 Lua 脚本引擎添加到您的 .NET 应用程序中。

Sample Image - luanetwrapper.gif

引言

本文介绍了一个 Lua(参见 [1])和 Luabind(参见 [2])的 .NET 轻量级封装:使用它,您可以将一个具有 C、C++ 后端的脚本引擎嵌入到您的 .NET 应用程序中。

如果您不熟悉 Lua 和 Luabind,下面也提供了一个简单的入门示例。

Lua,一个可嵌入的 C 脚本语言

以下是一些摘自 Lua “关于”页面的引述

“Lua 是一种功能强大的轻量级编程语言,旨在扩展 C、C++ 应用程序。”
“Lua 是一个您可以嵌入到应用程序中的语言引擎。这意味着,除了语法和语义之外,Lua 还提供了一个 API,允许应用程序与 Lua 程序交换数据,并允许使用 C 函数扩展 Lua。从这个意义上说,Lua 可以被视为一个用于构建领域特定语言的语言框架。”

通常,在应用程序中使用 Lua 需要遵循以下步骤:

  1. 创建 Lua 状态
  2. 绑定方法到 Lua
  3. 执行 Lua 脚本
  4. 清理并释放状态

创建 Lua 状态

“Lua 库是完全可重入的:它没有全局变量。Lua 解释器的整个状态(全局变量、堆栈等)存储在 `lua_State` 类型的动态分配结构中。必须将指向该状态的指针作为第一个参数传递给库中的每个函数,除了 `lua_open` 之外,它从头开始创建一个 Lua 状态。”

如上所述,`lua_open` 用于分配 Lua 状态。

lua_State* L = lua_open();

绑定方法到 Lua

假设需要绑定以下方法:

void my_print( const char* str )
{
    printf( str );
}

首先,我们需要创建一个 `my_print` 函数的包装器,该包装器接收一个 Lua 状态并返回一个整数,表示它希望返回给 Lua 的值的数量。

int my_print_lua(lua_State *L)
{
   /* get the number of arguments (n) and
    check that the stack contains at least 1*/
   int n = lua_gettop(L);
   if (n<1)
   {
       lua_pushstring(L, "not enough arguments");
       lua_error(L);
   }

   /* try to cast argument to string and call my_print,
    - remember that Lua is dynamically typed
    - we take the first element in the stack
   */
   my_print( lua_checkedstring( L, 1) );

   /* my_print does not return values */   
   return 0;
};

最后,使用以下方法在 Lua 中注册该方法:

lua_register(L, "pr", my_print_lua);

注意: 处理 Lua 堆栈可能会变得繁琐且容易出错。希望 Luabind 的出现能(极大地)简化包装任务。

执行 Lua 脚本

考虑以下脚本:

s = 'this is a test';
pr( s );

正如所见,Lua 语法非常直接。在脚本中,我们看到我们绑定的方法(`pr`)被调用了。要在 C 中执行此脚本,请使用 `lua_dostring`:

const char* str = "s = 'this is a test';pr( s );";
lua_dostring(L, str);

程序将输出:

this is a test

清理并释放状态

完成时,请不要忘记调用 `lua_close` 来释放 Lua 状态。

lua_close(L);

Luabind,简化操作

如前所述,处理 Lua 状态是一项繁琐的工作,我们希望避免。Luabind 使用模板元编程来为我们简化操作。

使用 Luabind,之前的步骤变为:

  1. 创建状态
    using namespace luabind; // luabind namespace
    lua_State* L =lua_open(L);
    luabind::open(L); // init luabind
  2. 绑定方法
    module(L)
    [
        def("pr", &my_print);
    ];

备注

  • 我们不需要编写 `my_print_lua` 包装器,Luabind 为我们完成了这项工作。
  • Luabind 还可以绑定类。
  • 请查看 Luabind 页面以获取更多信息,因为它们的文档做得非常好。

LuaNET,封装器

那么,在 .NET 应用程序中执行 Lua 脚本怎么样?这应该不是一个大问题,只需要编写一个托管的 C++ 封装器。

备注

  • 可以通过在 LuaNET 项目上运行文档工具来构建详细的类文档。
  • 所有类都位于 `Lua` 命名空间下。

State,封装 lua_State

托管类 `State` 封装了 `lua_State` 结构。它处理对 `lua_open` 和 `lua_close` 的调用。

这是一个创建状态、设置一些变量并执行脚本的小示例:

  • C#
    Lua.State L = new Lua.State(); // creating a lua state
    L.set_Global("a",1);          // equivalent to a=1 in Lua
    L.DoString("b=a * 2;")        // executing a Lua script
    Double b = L.get_Global("b").ToDouble(); // retreive the value of b
    

请注意,`get_Global` 返回一个 `LuaObject`,然后通过 `ToDouble` 强制转换为 `double`。

LuaObject,封装 luabind::object

`LuaObject` 使您能够设置和检索 Lua 中的值。支持的值包括:

  • 字符串
  • double, int
  • bool
  • table

由于 Lua 是动态类型的,因此在转换之前需要检查 `LuaObject` 的类型。否则,如果转换失败,将会引发异常。

L.DoString("s='string';");
L.DoString("print('s: ' .. tostring(s));");

LuaObject s=state.get_Global("s");
s.ToString(); // ok
s.ToDouble(); // fails, s is a string

如果对象是 `table`,`LuaObject` 会在 `table` 上实现 `IDictionnary` 接口,而 `TableEnumerator` 会实现 `IDictionnaryEnumerator` 接口。

L.DoString("t={1,'string'};");
LuaObject t=L.get_Global("t");

System.Collections.IDictionaryEnumerator te = t.GetEnumerator();
while( te.MoveNext() )
{
    System.Console.WriteLine("t...(" + te.Key + "," + te.Value +")");
};

加载库

Lua 提供了一组默认 API 来处理字符串、表、I/O、文件等。要加载这些 API,请使用 `DefaultLibrary.Load`。

Lua.Libraries.DefaultLibrary.Load( L );

使用 `LuabindLibrary.Load` 加载 Luabind。

Lua.Libraries.LuabindLibrary.Load( L );

您可以使用与 `DefaultLibrary` 或 `LuabindLibrary` 相同的方法来封装您的 API 并使用 .NET 加载它们。

使用演示

演示项目支持 Lua 5.0 和 Luabind。您需要重新编译所有项目并启动 _LuaNetTest_。

历史

  • 2004/8/8: 初始发布

参考文献

© . All rights reserved.