前言

这篇博客,我估计写不好。一方面是内容挺绕,一方面是我没有看过书,是照葫芦画瓢写代码。

前置要求:

  • Lua调用C代码
  • lua中表与元表
  • Lua操作C语言用户自定义类型数据Userdata

上面第三个链接的内容是,C语言自定义数据类型,在Lua中使用userdata表示。Lua调用C中的函数,操作userdata。这样写也挺好,但是分割了userdata和函数。

我们知道,lua有元表。那能否将C中的函数,注册到lua的元表中,再将元表绑定到userdata上呢?

注:本文完整代码见仓库demo2

  • 《Lua程序设计》第四版 第三十一章 C语言中的用户自定义类型,应该是比较详细的介绍(我看了下示例代码,文字没看)。

给userdata绑定元表

回头来看写的代码,逻辑上(非执行顺序上)基本上是这样的思路。

  • 第一步:提供一个函数来创建userdata。(类似与面向对象中的new)
  • 第二步:在lua加载C动态库的时候创建元表,在元表上注册可以操作userdata的方法。(因为这个元表只需要一份,所以在加载动态库的时候创建,合适不过了)
  • 第三步:在第一步创建userdata的时候,将第二步创建的元表附加上。(这样的好处是,不同的userdata对象,用相同的元表。即不同的对象,有相同的方法。)

示例代码如下。

#define LUA_BUILD_AS_DLL
#define LUA_LIB

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <stdio.h>

#define COMPLEX_MT "complex_mt"

 数据结构

typedef struct {
  double real;
  double imag;
} complex;

/// 数据结构的操作方法,存放在元表中

complex add(complex num_a, complex num_b)
{
  complex num_res;
  num_res.real = num_a.real + num_b.real;
  num_res.imag = num_a.imag + num_b.imag;
  return num_res;
}

complex sub(complex num_a, complex num_b)
{
  complex num_res;
  num_res.real = num_a.real - num_b.real;
  num_res.imag = num_a.imag - num_b.imag;
  return num_res;
}

int l_add(lua_State *L)
{
  complex *num_a = (complex *)luaL_checkudata(L,1,COMPLEX_MT);
  complex *num_b = (complex *)luaL_checkudata(L,2,COMPLEX_MT);
  complex num_res = add(*num_a, *num_b);
  complex *res = lua_newuserdata(L, sizeof(complex));
  res->real = num_res.real;
  res->imag = num_res.imag;
  luaL_getmetatable(L,COMPLEX_MT);
  lua_setmetatable(L,-2);
  return 1;
}

int l_sub(lua_State *L)
{
  complex *num_a = (complex *)luaL_checkudata(L,1,COMPLEX_MT);
  complex *num_b = (complex *)luaL_checkudata(L,2,COMPLEX_MT);
  complex num_res = sub(*num_a, *num_b);
  complex *res = lua_newuserdata(L, sizeof(complex));
  res->real = num_res.real;
  res->imag = num_res.imag;
  luaL_getmetatable(L,COMPLEX_MT);
  lua_setmetatable(L,-2);
  return 1;
}

int l_print(lua_State *L)
{
  complex *num = (complex *)luaL_checkudata(L,1,COMPLEX_MT);
  if(num->imag > 0) {
    printf("%f + %f", num->real, num->imag);
  } else if(num->imag == 0) {
    printf("%f", num->real);
  } else {
    printf("%f %f", num->real, num->imag);
  }
  return 0;
}

static const struct luaL_Reg complex_methods [] = {
  {"add", l_add},
  {"sub", l_sub},
  {"print", l_print},
  {NULL, NULL}
};

static void regist_complex_methods(lua_State *L)
{
  // 该库只需要一个complex元表。该元表可以绑定到不同的对象上。
  // 所以在加载库的时候,创建下元表即可
  luaL_newmetatable(L, COMPLEX_MT); //  为用户数据的元表创建一张新表,并添加到注册表中,最终将新表压栈
  luaL_setfuncs(L,complex_methods,0); // 将complex_methods中的方法,注册到栈顶的表中
  lua_pushvalue(L,-1); // 将上面创建的新表,做副本压栈
  lua_setfield(L,-2,"__index"); // COMPLEX_MT.__index = COMPLEX_MT(这样当一个表访问其没有的元素时,到其元表中查找)(同时副本出栈)
  lua_pop(L,1); // 元表出栈,此时该元表已经赋值过方法了。此后想访问该表,通过注册表即可
}

/// 其他函数,非元表中方法

static int l_new(lua_State *L)
{
  double real = lua_tonumber(L, 1);
  double imag = lua_tonumber(L, 2);
  complex *res = lua_newuserdata(L, sizeof(complex)); // 分配一块指定大小的内存块, 把内存块地址作为一个完全用户数据压栈, 并返回这个地址。 宿主程序(lua)可以随意使用这块内存
  res->real = real;
  res->imag = imag;
  luaL_getmetatable(L,COMPLEX_MT); // 从注册表中获取元表
  lua_setmetatable(L,-2); // 给创建的用户数据,绑定上元表,并将表弹出栈
  return 1; // 表示C在虚拟栈中,存放了一个返回值,即上面创建的userdata的地址
}

static const luaL_Reg complex_func[] = {
  {"new", l_new},
  {NULL, NULL}
};

LUALIB_API int luaopen_complex(lua_State *L) {
  luaL_newlib(L, complex_func);
  regist_complex_methods(L);
  return 1;
}

编译代码如下。

cmake_minimum_required(VERSION 3.11)

project(complex C)

# lua path
set(LUA_HEADER_PAHT C:/lua64/include)
set(LUA_LIB_PAHT C:/lua64)
set(LUA_LIB_NAME "lua53")
include_directories(${LUA_HEADER_PAHT})
link_directories(${LUA_LIB_PAHT})

include_directories(${CMAKE_CURRENT_SOURCE_DIR})

set(SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/complex.c)

add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES})

target_link_libraries(${PROJECT_NAME} ${LUA_LIB_NAME})

测试代码如下。

local complex = require "complex"

local num_a = complex.new(1,2)
local num_b = complex.new(3,4)
local num_c = num_a:add(num_b)
num_c:print()

print() -- 换行

local num_d = num_b:sub(num_a)
num_d:print()

输出如下。

C:\lua64\lua.exe .\test.lua
4.000000 + 6.000000
2.000000 + 2.000000