1. ホーム
  2. スクリプト・コラム
  3. ルア

Luaチュートリアル(XXI)。C言語関数の書き方のコツ

2022-02-12 20:17:21

1. 配列の操作。

    Luaでは、"array"はtableの別名に過ぎず、tableの特別な使い方を指しており、パフォーマンス上の理由から、LuaのC APIには、次のような配列操作のための特別な関数が用意されています。

コピーコード コードは以下の通りです。

    void lua_rawgeti(lua_State* L, int index, int key);
    void lua_rawseti(lua_State* L, int index, int key);

    上記の2つの関数は、それぞれ配列の要素の値を読み出し、設定するために使用されます。index パラメータはスタック内の操作対象テーブルの位置を示し,key はテーブル内の要素のインデックス値を示す.両関数とも原始的な操作であるため,メタテーブルを含むテーブルアクセスよりも高速に動作する.一般に、配列として使用されるテーブルでは、メタテーブルを使用することはほとんどない。

    以下のコード例と主要なコメントをご覧ください。

コピーコード コードは以下の通りです。

#include <stdio.h>
#include <string.h>
#include <lua.hpp>
#include <luxlib.h>
#include <lualib.h>

extern "C" int mapFunc(lua_State* L)
{
    // Check that the first argument passed in the Lua call code must be a table. otherwise an error will be raised.
    luaL_checktype(L,1,LUA_TTABLE);
    luaL_checktype(L,2,LUA_TFUNCTION);
    //Get the number of fields in the table, i.e. the number of elements in the array.
    int n = lua_objlen(L,1);
    //The starting index of arrays in Lua is conventionally 1, not 0 in C.
    for (int i = 1; i <= n; ++i) {
        lua_pushvalue(L,2); //Press a copy of the function (second argument) of the Lua argument onto the stack.
        lua_rawgeti(L,1,i); //push into table[i]
        lua_call(L,1,1); //calls function(table[i]) and presses the result of the function onto the stack.
        lua_rawseti(L,1,i); //table[i] = function return value, while popping the return value off the stack.
    }

    // No results returned to Lua code.
    return 0;
}

 2. 文字列の操作。

    C関数がLuaから文字列の引数を受け取る場合、「アクセス時に文字列をスタックからポップしない」「文字列を変更しない」という2つのルールに従わなければなりません。LuaのC APIには、Luaの文字列を操作するために、主に以下の2つの関数が用意されています。

コピーコード コードは以下の通りです。

    void lua_pushlstring(lua_State *L, const char *s, size_t l);
    const char* lua_pushfstring(lua_State* L, const char* fmt, ...) ;

    最初のAPIは、指定された長さの部分文字列をスタックに押し付けながらインターセプトするために使用されます。2番目のAPIは、Cライブラリのsprintf関数に類似しており、整形された文字列をスタックに押下するものである。sprintf の書式指定子とは異なり、%%(文字 %)、%s(文字列)、%d(整数)、%f(Lua の数値)、%c(文字)しかサポートしていません。それ以外の幅や精度などのオプションはサポートされていません。

コピーコード コードは以下の通りです。

#include <stdio.h>
#include <string.h>
#include <lua.hpp>
#include <luxlib.h>
#include <lualib.h>

extern "C" int splitFunc(lua_State* L)
{
    const char* s = luaL_checkstring(L,1);
    const char* sep = luaL_checkstring(L,2); //separator
    const char* e;
    int i = 1;
    lua_newtable(L); //result table
    while ((e = strchr(s,*sep)) ! = NULL) {
        lua_pushlstring(L,s,e - s); //push in the substring.
        // Set the substring just pushed in to table, and assign the specified index value.
        lua_rawseti(L,-2,i++);      
        s = e + 1;
    }
    //Press in the last substring
    lua_pushstring(L,s);
    lua_rawseti(L,-2,i);
    return 1; //return table.
}

 Lua API には lua_concat という関数があり、これは ". " 演算子と同様の機能で、スタックの一番上にある n 個の値を連結(とポップ)し、連結結果をプレスインするものです。プロトタイプは
    void lua_concat(lua_State *L, int n);
    パラメータ n はスタックに連結される文字列の数を示す。この関数は、meta メソッドを呼び出します。ただし、この関数は連結する文字列の数が少ない場合にうまく動作し、その逆はパフォーマンス上の問題があることに注意する必要があります。このため、Lua APIでは、次のコード例に示すように、特にこのパフォーマンスの問題に対処する別の関数群を用意しています。

コピーコード コードは以下の通りです。

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

extern "C" int strUpperFunc(lua_State* L)
{
    size_t len;
    luaL_Buffer b;
    // Check if the first argument is a string, and return a pointer to the string and its length.
    const char* s = luaL_checklstring(L,1,&len);
    //Initialize Lua's internal Buffer.
    luaL_buffinit(L,&b);
    //append the processed characters in turn (luaL_addchar) to Lua's internal Buffer.
    for (int i = 0; i < len; ++i)
        luaL_addchar(&b,toupper(s[i]));
    //Press this Buffer and its contents onto the stack.
    luaL_pushresult(&b);
    return 1;
}

  バッファリング機構を使用する最初のステップは、変数luaL_Bufferを宣言し、luaL_buffinitで初期化することです。初期化されると、luaL_addcharによってバッファに文字を入れることができます。この関数に加え、Luaのヘルパー・ライブラリには、文字列を直接追加する関数が用意されています。

コピーコード コードは以下の通りです。

    void luaL_addlstring(luaL_Buffer* b, const char* s, size_t len);
    void luaL_addstring(luaL_Buffer* b, const char* s);

    最後に luaL_pushresult はバッファを更新し、最終的な文字列をスタックの一番上に残します。これらの関数では、バッファの割り当てを気にする必要はありません。しかし、追加処理中に、バッファはいくつかの中間結果をスタックに置くことになります。Lua APIは、より一般的な、スタックの先頭からバッファに文字列や数値を追加する関数も提供しており、そのプロトタイプは以下のとおりです。
コピーコード コードは以下の通りです。

    void luaL_addvalue(luaL_Buffer* b);

    3. C関数で状態を保存する。
    Lua APIでは、ローカルでない変数を保存する方法として、レジストリ、環境、upvalueの3つが用意されています。
    1). レジストリです。
    レジストリは、Cコードからのみアクセス可能なグローバルなテーブルです。通常、複数のモジュール間で共有されるデータを保持するために使用されます。LUA_REGISTRYINDEX インデックス値を通してレジストリにアクセスすることができます。

コピーコード コードは以下の通りです。

 #include <stdio.h>
#include <string.h>
#include <lua.hpp>
#include <luxlib.h>
#include <lualib.h>

void registryTestFunc(lua_State* L)
{
    lua_pushstring(L,"Hello");
    lua_setfield(L,LUA_REGISTRYINDEX,"key1");
    lua_getfield(L,LUA_REGISTRYINDEX,"key1");
    printf("%s\n",lua_tostring(L,-1));
}

int main()
{
    lua_State* L = luaL_newstate();
    registryTestFunc(L);
    lua_close(L);
    return 0;
}

 2). 環境。
    モジュールのプライベートなデータ、つまりモジュール内の関数間で共有する必要があるデータを保存する必要がある場合は、環境を使用する必要があります。LUA_ENVIRONINDEX インデックス値を通じて環境にアクセスすることができます。

コピーコード コードは以下の通りです。

 #include <lua.hpp>
#include <luxlib.h>
#include <lualib.h>

// function to set environment data within the module
extern "C" int setValue(lua_State* L)
{
    lua_pushstring(L,"Hello");
    lua_setfield(L,LUA_ENVIRONINDEX,"key1");
    return 0;
}

// function within the module to get the environment data
extern "C" int getValue(lua_State* L)
{
    lua_getfield(L,LUA_ENVIRONINDEX,"key1");
    printf("%s\n",lua_tostring(L,-1));
    return 0;
}

static luaL_Reg myfuncs[] = {
    {"setValue", setValue},
    {"getValue", getValue},
    {NULL, NULL}
};


extern "C" __declspec(dllexport)
int luaopen_testenv(lua_State* L)
{
    lua_newtable(L); //create a new table for the environment
    lua_replace(L,LUA_ENVIRONINDEX); //replace the new table just created and pressed onto the stack with the current module's environment table.
    luaL_register(L,"testenv",myfuncs);
    return 1;
}

Luaのテストコードは以下の通りです。

コピーコード コードは以下の通りです。

 require "testenv"
 print(testenv.setValue())
 print(testenv.getValue())
 -- The output is: Hello

    3). upvalue.
    upvalueは特定の関数に関連付けられており、単純に関数内の静的変数として理解することができます。
コピーコード コードは以下の通りです。

#include <lua.hpp>
#include <luxlib.h>
#include <lualib.h>

extern "C" int counter(lua_State* L)
{
    //Get the value of the first upvalue.
    int val = lua_tointeger(L,lua_upvalueindex(1));
    //Press the resulting result onto the stack.
    lua_pushinteger(L,++val);
    // Assign a copy of the data at the top of the stack for later replacement operations.
    lua_pushvalue(L,-1);
    //This function replaces the data at the top of the stack with the value in upvalue(1). Also pops the top-of-stack data.
    lua_replace(L,lua_upvalueindex(1));
    // The data pressed in lua_pushinteger(L,++value) remains on the stack and is returned to Lua.
    return 1;
}

extern "C" int newCounter(lua_State* L)
{
    //Press in an upvalue with an initial value of 0. This function must be called before lua_pushcclosure.
    lua_pushinteger(L,0);
    // Push in the closure function, with argument 1 indicating the number of upvalues for the closure function. The function returns the value and the closure function is always at the top of the stack.
    lua_pushcclosure(L,counter,1);
    return 1;
}

static luaL_Reg myfuncs[] = {
    {"counter", counter},
    {"newCounter", newCounter},
    {NULL, NULL}
};


extern "C" __declspec(dllexport)
int luaopen_testupvalue(lua_State* L)
{
    luaL_register(L,"testupvalue",myfuncs);
    return 1;
}

    Luaのテストコードは以下の通りです。

コピーコード コードは以下の通りです。

require "testupvalue"

func = testupvalue.newCounter();
print(func());
print(func());
print(func());

func = testupvalue.newCounter();
print(func());
print(func());
print(func());

--[[ The output is.
1
2
3
1
2
3
--]]