Lua Userdata

37482 ワード

(一)簡単な例でuserdataの使い方を見てみましょう.
CのLuaライブラリを書き、LuaがCの配列にアクセスできるようにし、userdataで実現します.
(1)VSにDLLプロジェクトを新設し、luaライブラリのディレクトリ、リンクライブラリを設定する.
(2)新しいソースファイルmain.cppを作成します.コードは以下の通りです.
#include <stdio.h>
#include <string.h>

extern "C" 
{  
    #include <lua.h>  
    #include <lauxlib.h>  
    #include <lualib.h>  
} 

typedef struct NumArray
{
    int size;
    double values[1];
}NumArray;

// lua  :newarray(size)
extern "C" int newarray(lua_State* L)
{
    int n = luaL_checkint(L, 1);
    size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double);
    NumArray* a = (NumArray*)lua_newuserdata(L, nbytes);
    a->size = n;
    return 1;    //    userdata   
}

// lua  :setarray(userdata, index, value)
extern "C" int setarray(lua_State* L)
{
    NumArray* a = (NumArray*)lua_touserdata(L, 1);
    int index = luaL_checkint(L, 2);
    double value = luaL_checknumber(L, 3);

    luaL_argcheck(L, a != NULL, 1, "array excepted");
    luaL_argcheck(L, index >= 1 && index <= a->size, 2, "index out of range");

    a->values[index - 1] = value;
    return 0;
}

// lua  :getarray(userdata, index)
extern "C" int getarray(lua_State* L)
{
    NumArray* a = (NumArray*)lua_touserdata(L, 1);
    int index = luaL_checkint(L, 2);

    luaL_argcheck(L, a != NULL, 1, "array excepted");
    luaL_argcheck(L, index >= 1 && index <= a->size, 2, "index out of range");

    lua_pushnumber(L, a->values[index - 1]);
    return 1;
}

// lua  :getsize(userdata)
extern "C" int getsize(lua_State* L)
{
    NumArray* a = (NumArray*)lua_touserdata(L, 1);
    luaL_argcheck(L, a != NULL, 1, "array excepted");
    lua_pushnumber(L, a->size);
    return 1;
}

static const struct luaL_reg arraylib[] = 
{
    {"new", newarray},
    {"set", setarray},
    {"get", getarray},
    {"size", getsize},
    {NULL, NULL}
};

extern "C" __declspec(dllexport)
int luaopen_array(lua_State* L)
{
    const char* libName = "array";
    luaL_register(L, libName, arraylib);
    return 1;
}

(3)array.dllという名前のファイルをコンパイルして生成し、array.dllをluaforwindowsのclibsサブディレクトリの下に置く.このディレクトリの下にはluaのために書かれたcライブラリがあるか、ローカルに登録されたLua環境変数のあるディレクトリの下に置く.
(4)luaテスト:
require "array"

a = array.new(100)
print(array.size(a))

for i = 1, 100 do
    array.set(a, i, i)
end

print(array.get(a, 10))

上のコードのキー関数:
  void *lua_newuserdata (lua_State *L, size_t size);新規full userdata.(1)指定されたサイズのメモリを割り当てる;(2)このfull userdataをスタックに押す;(3)このメモリブロックのアドレスをホストプログラムに返し、ホストプログラムはこのメモリを自由に使用することができる.
  void luaL_argcheck (lua_State *L,int cond,int arg,const char *extramsg);条件が満たされているかどうかを確認します.
(二)metatableによるuserdataの識別によるコードのセキュリティの向上
上記のCライブラリには欠陥があります.例えば、例のsetarrayの最初のパラメータが、他の関連のないuserdataではなく、私たちが望んでいる配列userdataであることをどのように確保しますか.userdataはluaタイプで、ホスト言語のさまざまなカスタムタイプオブジェクトを表すために使用できます.特定のタイプを区別するために、配列に対してmetatableを個別に作成し、配列userdataを作成するたびにmetatableとの関連付けを設定します.配列にアクセスするたびに、正しいmetatableがあるかどうかをチェックします.すなわち,異なるmetatableを用いて異なるタイプのuserdataをタグする.Luaコードはuserdataのmetatableを変えることができないので、Luaは私たちのコードを偽造しません.
そこで、上記の例をいくつか改善し、配列userdataにタイプ識別を追加します.Cライブラリコードは以下の通りです.
#include <stdio.h>
#include <string.h>

extern "C" 
{  
    #include <lua.h>  
    #include <lauxlib.h>  
    #include <lualib.h>  
} 

typedef struct NumArray
{
    int size;
    double values[1];
}NumArray;

// lua  :newarray(size)
extern "C" int newarray(lua_State* L)
{
    int n = luaL_checkint(L, 1);
    size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double);
    NumArray* a = (NumArray*)lua_newuserdata(L, nbytes);

    //         metatable,       userdata
    luaL_getmetatable(L, "LuaBook.array");
    lua_setmetatable(L, -2);

    a->size = n;
    return 1;    //    userdata   
}

//     ,    userdata metatable   LuaBook.array(       LuaBook.array   userdat)
static NumArray* checkarray(lua_State* L)
{
    void* ud = luaL_checkudata(L, 1, "LuaBook.array");
    luaL_argcheck(L, ud != NULL, 1, "array expcected");
    return (NumArray*)ud;
}

//
static double* getelem(lua_State* L)
{
    NumArray* a = checkarray(L);
    int index = luaL_checkint(L, 2);
    luaL_argcheck(L, index >= 1 && index <= a->size, 2, "index out of range");
    return &a->values[index - 1];
}

// lua  :setarray(userdata, index, value)
extern "C" int setarray(lua_State* L)
{
    double newvalue = luaL_checknumber(L, 3);
    *getelem(L) = newvalue;
    return 0;
}

// lua  :getarray(userdata, index)
extern "C" int getarray(lua_State* L)
{
    lua_pushnumber(L, *getelem(L));
    return 1;
}

// lua  :getsize(userdata)
extern "C" int getsize(lua_State* L)
{
    NumArray* a = checkarray(L);
    lua_pushnumber(L, a->size);
    return 1;
}

static const struct luaL_reg arraylib[] = 
{
    {"new", newarray},
    {"set", setarray},
    {"get", getarray},
    {"size", getsize},
    {NULL, NULL}
};

extern "C" __declspec(dllexport)
int luaopen_array(lua_State* L)
{
    //     userdata     metatable
    luaL_newmetatable(L, "LuaBook.array");

    const char* libName = "array";
    luaL_register(L, libName, arraylib);
    return 1;
}

上のコードのキー関数:
  void luaL_newmetatable (lua_State *L, const char *tname);userdataで使用可能なmetatableを作成します.registryにtnmeキー値が既にある場合、関数は0を返します.そうでない場合は、[tname,metatable]を作成し、registryを入れて1を返します.どちらの場合も、tnameに対応する値がスタックに入ると言います.スタック+1
  void *luaL_checkudata (lua_State *L, int index, const char *tname);スタックで指定した場所のオブジェクトが、指定した名前のmetatable(registryのキーtnameに対応する値)のusertataであるかどうかを確認します.はい、userdataアドレスを返します.そうでない場合はNULLを返します.
  void luaL_getmetatable (lua_State *L, const char *tname);registryのtnameに対応するmetatableを取得し、スタックに組み込みます.注意lua_getmetatable関数.
  void luaL_setmetatable (lua_State *L, const char *tname);スタックトップオブジェクトのmetatableをregistryテーブルのキーtnameに対応する値に設定します.注意lua_setmetatable関数.
  int lua_getmetatable (lua_State *L, int index);index対応tableのmetatableを取得し、スタックに組み込みます.tableにmetatableがない場合は0を返し、スタックは変更されません.
  void lua_setmetatable (lua_State *L, int index);スタックトップのtableをスタックから出てindexに設定した値をmetatableとします.スタック-1
(三)上のコードをオブジェクト向けに改造する
タイプはオブジェクトのuserdataです.オブジェクトのインスタンスを操作するには、次の構文を使用します.
require "array"

a = array.new(100)

print(getmetatable(a))

print(a:size())

for i = 1, 100 do
    a:set(i, i)
end

print(a:get(10))

考え方は大体以下の通りである.
(1)arrayテーブルには、配列オブジェクトを生成するためのnewメソッドという1つのメソッドしか含まれていない.
(2)配列userdataはmetatableを持ってタイプ識別に用いる.
(3)userdataのmetatable定義_index,では,配列のメソッドにアクセスするたびに__がトリガーされる.indexというmetamethod(userdataにとって、アクセスされるたびにメタメソッドが呼び出されます.userdataにはkeyがないからです).
(4)metatable._indexは、このテーブルmetatable自体として設定される(_indexは、関数またはテーブルとすることができ、ここでは後者を用いる).
(5)metatableは残りのすべての配列操作関数を含む.
では、userdataのメソッドが呼び出されるたびに、例えばa:size()はa.size(a)に等しくなり、userdataの名前が__とトリガーされる.indexのmetamethod,metatableの_indexはそれ自体であり、metatableテーブルにはsizeドメインがあるので、metatableのsize(a)関数を呼び出すとokになります.
#include <stdio.h>
#include <string.h>

extern "C" 
{  
    #include <lua.h>  
    #include <lauxlib.h>  
    #include <lualib.h>  
} 

typedef struct NumArray
{
    int size;
    double values[1];
}NumArray;

// lua  :newarray(size)
extern "C" int newarray(lua_State* L)
{
    int n = luaL_checkint(L, 1);
    size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double);
    NumArray* a = (NumArray*)lua_newuserdata(L, nbytes);

    //         metatable,       userdata
    luaL_getmetatable(L, "LuaBook.array");
    lua_setmetatable(L, -2);

    a->size = n;
    return 1;    //    userdata   
}

//     ,    userdata metatable   LuaBook.array(       LuaBook.array   userdat)
static NumArray* checkarray(lua_State* L)
{
    void* ud = luaL_checkudata(L, 1, "LuaBook.array");
    luaL_argcheck(L, ud != NULL, 1, "array expcected");
    return (NumArray*)ud;
}

//
static double* getelem(lua_State* L)
{
    NumArray* a = checkarray(L);
    int index = luaL_checkint(L, 2);
    luaL_argcheck(L, index >= 1 && index <= a->size, 2, "index out of range");
    return &a->values[index - 1];
}

// lua  :setarray(userdata, index, value)
extern "C" int setarray(lua_State* L)
{
    double newvalue = luaL_checknumber(L, 3);
    *getelem(L) = newvalue;
    return 0;
}

// lua  :getarray(userdata, index)
extern "C" int getarray(lua_State* L)
{
    lua_pushnumber(L, *getelem(L));
    return 1;
}

// lua  :getsize(userdata)
extern "C" int getsize(lua_State* L)
{
    NumArray* a = checkarray(L);
    lua_pushnumber(L, a->size);
    return 1;
}

// metatable tostring   
int array2string(lua_State* L)
{
    NumArray* a = checkarray(L);
    lua_pushfstring(L, "array(%d)", a->size);
    return 1;
}

//         new  
static const struct luaL_reg arraylib_f[] = 
{
    {"new", newarray},
    {NULL, NULL}
};

//        metatable  
static const struct luaL_reg arraylib_m[] = 
{
    {"__tostring", array2string},
    {"set", setarray},
    {"get", getarray},
    {"size", getsize},
    {NULL, NULL}
};

extern "C" __declspec(dllexport)
int luaopen_array(lua_State* L)
{
    //     userdata     metatable
    luaL_newmetatable(L, "LuaBook.array");

    //   metatable __index metatable  
    lua_pushstring(L, "__index");
    lua_pushvalue(L, -2);
    lua_settable(L, -3);
    //   metatable   
    luaL_register(L, NULL, arraylib_m);

    //   array ,    new  
    luaL_register(L, "array", arraylib_f);
    return 1;
}

(四)配列の下付き文字でアクセスする
次の表の操作をサポートする構文を実装してuserdataにアクセスするには、次のようにします.
require "array"

a = array.new(100)
a[10] = 3
print(a[10])

luaでは、以下のコードで直接実装できます.
local metaarray = getmetatable(newarray(1))
metaarray.__index = array.get
metaarray.__newindex = array.set

Cに対応する実現方式は以下の通りである.
static const struct luaL_reg arraylib[] = 
{
    {"new", newarray},
    {"set", setarray},
    {"get", getarray},
    {"size", getsize},
    {NULL, NULL}
};

extern "C" __declspec(dllexport)
int luaopen_array(lua_State* L)
{
    //     userdata     metatable
    luaL_newmetatable(L, "LuaBook.array");
    luaL_register(L,"array",arraylib);

    //     metatable   ,array       
    // metatable.__index = array.get
    lua_pushliteral(L, "__index");
    lua_pushliteral(L, "get");
    lua_gettable(L, 2);
    lua_settable(L, 1);

    // metatable.__index = array.set
    lua_pushliteral(L, "__newindex");
    lua_pushliteral(L, "set");
    lua_gettable(L, 2);
    lua_settable(L, 1);

    return 0;
}

metatableの__をindexをarrayのgetメソッドに設定し、_newindexはsetメソッドにすればよい.a[i]を読み込むとトリガーされます_index、オブジェクト自体とパラメータを同時に__に渡しますindex対応の関数は、a[i]を書くときに原理が一致します.
(五)light userdata
light userdataはfull userdataとは異なり、(1)full userdataはLuaのCオブジェクトを表し、light userdataはCポインタの値(つまりvoid*タイプの値)を表します.値であるため、彼らを作成することはできません(同様に、数値を作成することはできません).(2)ただ1つのポインタで、数字のようにmetatablesがなく、light userdataはゴミ収集器を必要とせず、彼女を管理する.(3)異なるタイプのオブジェクトを表すために使用でき、Luaではlight userdataを使用してCオブジェクトを表す.
値であるため、同じCアドレスを指すlight userdataはすべて等しい.
  void lua_pushlightuserdata (lua_State *L, void *p);ライトuserdataをスタックに追加します.
(六)userdata関連リソース解放
Lua以_gcメタメソッドの方法はfinalizersを提供する.このメタメソッドはuserdataタイプの値にのみ有効です.userdataが収集されると、userdataには__がある.gcドメインでは、Luaはこのドメインの値(関数であるべき)を呼び出します.userdataをこの関数のパラメータとして呼び出します.この関数は、ファイル記述子、ウィンドウハンドルなど、userdataに関連するすべてのリソースを解放する責任を負います.