目录

lua底层原理浅析

table底层原理浅析

Lua表的C语言定义

原理和实现

userdata

lua和C#的交互机制(更新中)

基本介绍

Lua 与 C/C++ 的数据交互

Lua 调用 C/C++ 函数

C/C++ 调用 Lua 函数

基元类型传递

对象类型传递

Lua 调用 C#

总结


网上有很多文章写和C#的交互原理,但大多比较复杂,截止到目前为止笔者也只是了解了其中的一部分,目前还在边学边做笔记的阶段,所以说是浅析,后续会做更深入的研究。

lua底层原理浅析

Lua的底层实现是基于C语言,这使得它非常轻量级且高效,同时具有很好的跨平台特性。

  1. 虚拟机(VM)

    • Lua使用基于寄存器的虚拟机来执行编译后的字节码。这与基于堆栈的虚拟机相比,可以减少指令数量和执行时间。
    • Lua的字节码是平台无关的,这意味着在一台机器上编译的Lua代码可以在任何其他平台上运行,只要那里有Lua虚拟机。
  2. 解释器和编译器

    • Lua源代码首先被一个解释器读取,解释器是用C语言编写的。
    • 源代码被解析成抽象语法树(AST),然后转换成中间表示,最终编译为字节码。
  3. 内存管理

    • Lua使用自动垃圾回收机制来管理内存。它主要使用标记-清扫(mark-and-sweep)算法来回收不再使用的内存。

垃圾回收部分具体可以看我的另外一篇文章

Lua的垃圾回收机制详解-CSDN博客

table底层原理浅析

Lua的表(table)是其最强大的特性之一,它们是动态的关联数组,可以用作普通数组、字典、对象等。在C语言中,Lua的表是通过一种复杂的数据结构实现的,这种结构使得表既能高效地存储和访问序列元素,也能高效地处理散列键值对。

Lua表的C语言定义

在Lua的C源码中,表是通过struct定义的,主要是Table结构。这个结构包括指向数组部分和散列部分的指针,以及这些部分的大小等信息。核心结构大致如下(简化版本,用于说明):

typedef struct lua_Table {
  CommonHeader;
  lu_byte flags;  /* 一些标志位 */
  lu_byte lsizenode;  /* 散列表部分的大小 */
  unsigned int sizearray;  /* 数组部分的大小 */
  TValue *array;  /* 数组部分的指针 */
  Node *node;    /* 散列表部分的指针 */
  Node *lastfree;  /* 散列表的一个自由位置 */
  struct Table *metatable;  /* 元表 */
  GCObject *gclist;
} Table;

原理和实现

Lua表的实现基于以下原理:

  1. 双重结构

    • Lua表由两部分组成:一个数组(Array)部分和一个散列(Hash)部分。
    • 数组部分用于存储以数字为键的元素,而散列部分用于存储其他类型的键值对。
  2. 动态调整

    • 当你向表中添加或移除元素时,Lua会动态调整这两部分的大小和存储方式,以保持操作的高效性。
    • 例如,如果你主要使用数字键,Lua会倾向于扩展数组部分,而减少散列部分。
  3. 散列算法

    • 对于非数字键,Lua使用散列算法来快速定位和存储键值对。
    • Lua的散列算法旨在减少冲突并快速处理查找、插入和删除操作。
  4. 数组和散列表的动态重分配

    • Lua会根据表的使用情况动态地重分配内部数组和散列结构,以优化内存使用和访问速度。
  5. 元表支持

    • Lua表还可以有一个元表(metatable),用于定义该表的特殊行为,比如操作符重载或自定义访问方式。

userdata

在Lua编程语言中,userdata 是一种特殊的数据类型,用于表示任何由应用程序或者C语言代码创建的数据对象。userdata 提供了一种将C中的数据和对象暴露给Lua代码的方式,同时保持类型安全和内存管理的控制。

userdata 在Lua中主要有两种形式:

  1. 全用户数据(Full userdata)

    • 这是一个指向C数据的指针,Lua只负责存储和传递这个指针,不尝试理解或操作其指向的数据。
    • 它是一个黑盒,Lua不知道其内部结构,只是简单地通过指针来引用它。
    • Lua负责管理这些对象的内存生命周期,通常是通过垃圾回收机制。
  2. 轻量用户数据(Light userdata)

    • 这也是一个指针,但Lua不管理其指向的内存。
    • 轻量用户数据相当于一个裸指针,其生命周期由外部代码控制。
    • 它适用于表示轻量级的、生命周期由应用程序控制的对象。

userdata 的主要用途包括:

  • 将C中的对象传递到Lua代码,使得Lua代码可以间接地操作这些对象。
  • 在C和Lua之间共享数据,尤其是当需要在Lua脚本中操作C语言中创建的复杂数据结构时。
  • 实现C和Lua之间的接口调用,尤其是在嵌入Lua到C/C++应用程序时。

使用 userdata 时,通常会配合元表(metatable)来提供对象的方法和属性,这样Lua代码就可以像操作普通的Lua对象那样操作C中的数据。这是实现面向对象编程风格的一种常用手段,特别是在Lua绑定到C/C++库的上下文中。

lua和C#的交互机制(更新中)

参考文章:

C#与XLua交互原理 - 知乎 (zhihu.com)

基本介绍

  • Lua 虚拟机:Lua 是由 C/C++ 实现的,因此它可以直接与宿主程序(如Unity)进行通信。
  • C# 与 Lua 交互:C# 通过 P/Invoke(平台调用)方式调用 Lua 虚拟机函数。这意味着 C# 可以通过 C/C++ 层与 Lua 进行数据交互。
  • xLua 中的 P/Invoke:xLua 提供的 P/Invoke 调用接口主要在 LuaDLL.cs 文件中。

Lua 与 C/C++ 的数据交互

  • 虚拟栈:Lua 提供了一个虚拟栈用于数据交换。所有类型的数据交换都通过这个栈完成。
  • 栈索引:Lua 有两种索引方式操作虚拟栈 — 正数索引(1表示栈底)和反向索引(-1表示栈顶)。

Lua 调用 C/C++ 函数

  1. 包装函数:将C++函数包装成可供Lua调用的格式,通常是接收一个Lua状态机指针的静态方法。
  2. 注册函数:在Lua环境中注册这些包装好的函数。
  3. 调用过程:Lua调用时,通过Lua栈获取参数,执行函数,然后将结果压栈返回。

C/C++ 调用 Lua 函数

  1. 获取函数:使用 lua_getglobal 获取Lua函数,并将其压入栈。
  2. 压栈参数:将函数的参数压入栈。
  3. 执行函数:调用 lua_pcall 执行函数。
  4. 处理结果:如果无误,从Lua虚拟栈中取出结果。

基元类型传递

  • 直接通过 C API 传递,如 lua_pushboolean, lua_pushnumber 等。

对象类型传递

  • 过程:C# 对象在Lua中通过表(table)模拟,传递的是索引,同时需要将C#类型信息注册到Lua。
  • userdata:C# 对象在 Lua 中对应的是一个 userdata,用于保持与C#对象的联系。
  • 元表:为 userdata 设置的元表包含了对象的类型信息,如成员方法、属性等。

Lua 调用 C#

  • Lua 通过调用 C# 包装好的静态方法来实现调用,这些方法转换Lua的调用为C#函数的调用。
  • 使用元表信息来确定要调用的C#方法和属性。
  • 函数通过Lua的栈接收参数,参数按顺序入栈。

总结

在 Unity 中,xLua 框架通过 C API 层实现 C# 和 Lua 的交互。Lua 和 C# 之间的调用主要是通过虚拟栈来传递数据和参数。C# 对象在Lua中通过 userdata 表示,而 C# 与 Lua 的函数调用则是通过预先包装好的静态方法来实现。这个过程涉及多个层面的数据转换和类型匹配,但最终实现了两种语言间高效的互操作性。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐