Lua 与C/C++ 交互系列: Lua调用C/C++函数(4-2)

1、本文继续讲解Lua调用C/C++函数,本文的重点是通过metatable来实现Lua Code面向对象调用注册的C函数。本文中涉及的Environment 伪索引,userdata 以及GC 垃圾回收器的内容,都是简单的讲解。不作为本文的重点,这些内容都将在以后的章节中继续讲解。

2、本文涉及的到主要知识点补充说明。

2.1 void *lua_newuserdata (lua_State *L, size_t size); 函数说明

This function allocates a new block of memory with the given size, pushes onto the stack a new full userdata with the block address, and returns this address.

Userdata represent C values in Lua.

A full userdata represents a block of memory.

It is an object (like a table): you must create it, it can have its own metatable, and you can detect when it is being collected.

A full userdata is only equal to itself (under raw equality).

When Lua collects a full userdata with a gc metamethod, Lua calls the metamethod and marks the userdata as finalized. When this userdata is collected again then Lua frees its corresponding memory.

lua_newuserdata()函数分配指定大小的新内存,然后把full userdata的内存地址压入虚拟栈,同时,lua_newuserdata()函数返回内存的地址。

在Lua C API中userdata用于拓展Lua的数据结构,可以在Lua中表示的自定义数据。

full userdata表示一块内存。userdata是一个对象(类型于Table):当刚创建userdata时,userdata可以拥有自己的metatable,还可以确定userdata何时被GC垃圾收集器当做垃圾收集。

full userdata仅仅和自己相等。

当GC垃圾收集器通过__gc元方法回收userdata内存时,Lua 调用__gc元方法并且标记userdata被终止。当GC再一次收集userdata时,Lua将释放userdata.

2.2  LUALIB_API int luaL_newmetatable (lua_State *L, const char *tname) 源代码分析

LUALIB_API int luaL_newmetatable (lua_State *L, const char *tname)
{ //在LUA_REGISTRYINDEX伪索引上面获取tname的Table数据类型
  lua_getfield(L, LUA_REGISTRYINDEX, tname);  /* get registry.name */
  //如果LUA_REGISTRYINDEX伪索引上面已经存在值,则返回0
  if (!lua_isnil(L, -1))  /* name already in use? */
    return 0;  /* leave previous value on top, but return 0 */
  lua_pop(L, 1);
  //否则创建新Table
  lua_newtable(L);  /* create metatable */
  lua_pushvalue(L, -1);
  //在注册表中设置  tname=Table
  lua_setfield(L, LUA_REGISTRYINDEX, tname);  /* registry.name = metatable */
  return 1;
}

2.3  #define luaL_getmetatable(L,n) (lua_getfield(L, LUA_REGISTRYINDEX, (n))) 在注册表中获取指定name的值

2.4 LUALIB_API void *luaL_checkudata (lua_State *L, int ud, const char *tname) 判断userdata的类型

LUALIB_API void *luaL_checkudata (lua_State *L, int ud, const char *tname)
{
  //转换成Void* 类型指针
  void *p = lua_touserdata(L, ud);
  if (p != NULL) {  /* value is a userdata? */
    //获取metatable
    if (lua_getmetatable(L, ud)) {  /* does it have a metatable? */
      //在注册表中获取指定名称的metatable
      lua_getfield(L, LUA_REGISTRYINDEX, tname);  /* get correct metatable */
      //判断两个metatable是否相等,如果相等就返回内存地址
      if (lua_rawequal(L, -1, -2)) {  /* does it have the correct mt? */
        lua_pop(L, 2);  /* remove both metatables */
        return p;
      }
    }
  }
  luaL_typerror(L, ud, tname);  /* else error */
  return NULL;  /* to avoid warnings */
}

3. 本文主要代码

extern "C"
{
	#include <lua.h>
	#include <lualib.h>
	#include <lauxlib.h>
}
#include <stdio.h>
#define CnExampleStr "example"
//在C语言中的结构体,绑定到Lua语言中
typedef struct
{
  int Val;
  int Open;
} ExampleType, * ExamplePtrType;
static ExamplePtrType LclExamplePtrGet(lua_State *L,int StkPos)
{
  ExamplePtrType ExamplePtr = (ExamplePtrType)luaL_checkudata(L, StkPos, CnExampleStr);
  if (! ExamplePtr->Open)
    luaL_error(L, "attempt to use a closed " CnExampleStr);
  return ExamplePtr;
}

static int LclExampleStr(lua_State *L)
{
  ExamplePtrType  ExamplePtr = (ExamplePtrType)luaL_checkudata(L, 1, CnExampleStr);
  if (ExamplePtr->Open)
    lua_pushfstring(L, CnExampleStr " (%d)", ExamplePtr->Val);
  else lua_pushfstring(L, CnExampleStr " (%d, closed)", ExamplePtr->Val);
  return 1;
}
//获取c结构体数据
static int LclExampleGet(lua_State *L)
{
  ExamplePtrType ExamplePtr = LclExamplePtrGet(L, 1);
  lua_pushnumber(L, ExamplePtr->Val);
  printf("Retrieving value of " CnExampleStr " (%d)\n", ExamplePtr->Val);
  return 1;
}
//设置c结构体数据
static int LclExampleSet(lua_State *L)
{ 

  ExamplePtrType ExamplePtr = LclExamplePtrGet(L, 1);
  int  Val = luaL_checkint(L, 2);
  printf("Setting value of " CnExampleStr " from %d to %d\n",
    ExamplePtr->Val, Val);
  lua_pushnumber(L, ExamplePtr->Val);
  ExamplePtr->Val = Val;
  return 1;
}
//关闭结构体
static int LclExampleClose(lua_State *L)
{
  ExamplePtrType ExamplePtr = LclExamplePtrGet(L, 1);
  printf("Closing " CnExampleStr " (%d) explicitly\n", ExamplePtr->Val);
  ExamplePtr->Open = 0;
  return 0;
}
//设置GC函数
static int LclExampleGc(lua_State *L)
{
  ExamplePtrType ExamplePtr = (ExamplePtrType)luaL_checkudata(L, 1, CnExampleStr);
  if (ExamplePtr->Open) {
    printf("Collecting and closing " CnExampleStr " (%d)\n",
      ExamplePtr->Val);
    ExamplePtr->Open = 0;
  }
  else printf("Collecting " CnExampleStr " (%d), already closed\n",
    ExamplePtr->Val);
  return 0;
}
//通过Lua Code构造C语言结构体
static int LclExampleOpen(lua_State *L)
{
  //接受LuaCode传递参数
  int Val = luaL_checkint(L, 1);
  //申请由lua GC管理下的内存
  <span style="color:#ff6666;">ExamplePtrType  ExamplePtr =(ExamplePtrType)lua_newuserdata(L, sizeof(ExampleType));</span>
  printf("Opening " CnExampleStr " (%d)\n", Val);
  //设置数据
  ExamplePtr->Val = Val;
  ExamplePtr->Open = 1;
  ////在注册表中查询注册的C函数
<span style="color:#ff6666;">  luaL_getmetatable(L, CnExampleStr);</span>
  //设置userdata的CnExampleStr的metatable
<span style="color:#ff6666;">  lua_setmetatable(L, -2);</span>
  return 1;
}
//通过面向对象调用注册的C函数
int luaopen_ud_example(lua_State *L)
{
  //metatable的元方法
  static const luaL_reg MetaMap[] =
 {
    {"close", LclExampleOpen},
    {"get", LclExampleGet},
    {"set", LclExampleSet},
    {"__tostring", LclExampleStr},
    {"__gc", LclExampleGc},
    {NULL, NULL}
  };
  //注册模块的方法
  static const luaL_reg Map[] =
 {
    {"open", LclExampleOpen},
    {NULL, NULL}
  };
  // 创建一个命名的metatable在注册表.用来作为识别用户自定义数据的唯一ID和定义数据类型的行为<span style="color:#ff0000;">①</span>
  luaL_newmetatable(L, CnExampleStr);
  // 复制并压入metatable到虚拟栈上面<span style="color:#ff0000;">②</span>
  lua_pushvalue(L, -1);
  // 设置metatable的__index 方法为metatable<span style="color:#ff0000;">③</span>
<span style="color:#ff0000;">  lua_setfield(L, -2, "__index");</span>
  // 把所有的metatable注册到栈顶的metatable中<span style="color:#ff0000;">④</span>
<span style="color:#ff6666;">  luaL_register(L, NULL, MetaMap);</span>
  luaL_register(L, "ud_example", Map);
  return 1;
}
//通过模块名称,方法名称来调用注册的C函数
void luaopen_register(lua_State *L)
{
 static const luaL_reg regMethod[] =
 {
    {"open", LclExampleOpen},
    {"close", LclExampleClose},
    {"get", LclExampleGet},
    {"set", LclExampleSet},
    {"tostring", LclExampleStr},
    {NULL, NULL}
  };
  //创建一个metatable用于标识userdata唯一性
  luaL_newmetatable(L, CnExampleStr);
  luaL_register(L,"ud_example0",regMethod);
}
int main(int argc, char **argv)
{
	lua_State* L = lua_open();
	luaL_openlibs(L);
	//注册c函数模块
	luaopen_ud_example(L);
	//注册c struct新的数据类型
	luaopen_register(L);
	luaL_dofile(L, "sample_6.lua");
	lua_close(L);
	 return 1;
}

Lua Code文件代码:

print("sample_6.lua")

local ud1 =ud_example0.open(1)
print(ud1)
print(ud_example0.tostring(ud1))

local a =ud_example0.get(ud1)
print("a:"..a)

local b =ud_example0.set(ud1,2)
print("b="..b)
print("==================")
--以下通过面向对象形式调用注册的C函数
local HndA = ud_example.open(1)
local HndB = ud_example.open(2)
do -- local block
  local HndC = ud_example.open(3)
  io.write(tostring(HndA), ", ", tostring(HndB), ", ", tostring(HndC), "\n")
  HndA:set(4)
  HndA:set(1)
  HndA:close()
  io.write("End of local block\n")
end
collectgarbage("collect")
io.write("End of script\n")

在设置userdata的metatable时,虚拟栈发生的逻辑变化,在下图中表示

metatable

metatable

metatable

metatable.__index =metatable

metatable={__index=metatable,close=LclExampleOpen,get=LclExampleGet,set=LclExampleSet,__tostring=LclExampleStr,__gc=LclExampleGc}

调用LclExampleOpen方法时,设置对象的元方法为在注册表已经注册的CnExampleStr元方法。

这样就可以利用元方法来实现面向对象调用了。利用面向对象调用注册的C函数时通过metatable元函数和__index重新定义userdata的行为来实现。

在本系列中会不断涉及到对虚拟栈的操作,下一章我们将对虚拟栈的操作做一个小结。

时间: 2024-08-02 07:01:16

Lua 与C/C++ 交互系列: Lua调用C/C++函数(4-2)的相关文章

Lua 与C/C++ 交互系列: Lua调用C/C++函数(4-1)

1.本文将继续讲解在Lua Code中调用注册的C函数.偶在学习本文知识点时,由于知识点的遗漏,在这个上面浪费了大量时间和精力.一直都没有没明白,Lua 通过面向对象的方式是如果调用注册的C函数.在Programming In Lua一书,有对这个方面的讲解.但是当时看书,就是不理解.因为在前面的章节中,有一个重要的知识点被遗漏.在Lua 元方法中,有两个特别重要的.__index 和__newindex被我遗漏.这两个元方法特别重要,对于定义和拓展Lua的机制,基本依靠这两个. 2.本文首先由

Lua 与C/C++ 交互系列: Lua调用C/C++函数(3)

1.Lua API中提供注册C函数关键在lua_pushcclouse()函数.该函数可以在Lua Code中定义C函数. 但是Lua 提供了几个常用的宏定义,用于注册C函数. 这几个宏定义为: /#define lua_pushcfunction(L,f) lua_pushcclosure(L, (f), 0) #define lua_register(L,n,f) (lua_pushcfunction(L, (f)), lua_setglobal(L, (n))) LUALIB_API vo

Lua 与C/C++ 交互系列: Lua调用C/C++函数(2).

1.本文将使用void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n); 来讲解在Lua Code中注册C函数,其他注册方式将在下一篇文章中讲解. When a C function is created, it is possible to associate some values with it, thus creating a C closure ; these values are then accessible to

Lua 与C/C++ 交互系列: Lua调用C/C++函数(1).

在学习Lua和C/C++过程中,在这个过程花费了不少时间.总是在几个地方卡住.Programming  In Lua 和Lua 5.1 Reference Manual  为主要学习资料. 但是Lua 5.1 Reference Manual 在演化过程中,把对虚拟栈管理部分分散在不同的章节里面.Lua 5.0 Reference Manual 版本却有这一个章节.然后,后来在lua 源代码中找到了对虚拟栈管理东东.还有在Lua的不同版本中,对注册C函数也有不同的演化.比如Function lu

Lua 与C/C++ 交互系列:Light userdata翻译

利用零碎的时间,先把以后用的知识点提前准备好.最近比较忙,正在准备一篇绑定C++对象到Lua中.但是,不想轻易下手,希望做足准备. 这篇翻译来自于lua-users.org   ,原文地址. Light User Data Light userdata, like heavy userdata, are a form of userdata, which is one of the basic data types in Lua .Light userdata are characterized

Lua 与C/C++ 交互系列:注册枚举enum到Lua Code中

在Lua Code中注册C/C++的枚举非常容易,就像注册全局变量一样.我们使用枚举名称作为命名空间,来避免注册的枚举发生冲突.注册的枚举存储在全局环境(线程环境)中. 当在Lua Code中访问枚举时,通过名称来访问对应的值. sample_9.cpp   c++代码如下: //在Lua Code中注册的enum,为了避免冲突,以名称作为enumTable来存储 enum Week { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday

Lua 与C/C++ 交互系列:动态注册枚举enum到Lua Code中,在运行时在Lua Code中获取内省信息

在Lua 5.1 Reference Manual  对于Lua 值和类型的介绍.Lua是一个动态语言,在Lua中变量仅仅有值而没有类型.所以在Lua中的变量不需要声明.所以的值本身包含类型. 其实Lua 包含一种运行时类型识别,通过type()函数,可以在运行时获取值的类型. 信息来自: Lua 5.1 Reference Manual  Values and Types Lua is a dynamically typed language. This means that variable

Lua 与C/C++ 交互系列:注冊枚举enum到Lua Code中

在Lua Code中注冊C/C++的枚举很easy,就像注冊全局变量一样.我们使用枚举名称作为命名空间,来避免注冊的枚举发生冲突.注冊的枚举存储在全局环境(线程环境)中. 当在Lua Code中訪问枚举时,通过名称来訪问相应的值. sample_9.cpp   c++代码例如以下: //在Lua Code中注冊的enum,为了避免冲突,以名称作为enumTable来存储 enum Week { Monday, Tuesday, Wednesday, Thursday, Friday, Satur

lua入门之二:c/c++ 调用lua及多个函数返回值的获取

当 Lua 调用 C 函数的时候,使用和 C 调用 Lua 同样类型的栈来交互. C 函数从栈中获取她的參数.调用结束后将返回结果放到栈中.为了区分返回结果和栈中的其它的值,每一个 C 函数还会返回结果的个数(the  function  returns  (in  C)  the  number  of  results  it  is leaving on the stack.). // luacallcpp.cpp : 定义控制台应用程序的入口点. // #include "stdafx.