AviUtlでC++のクラスや構造体を使う

AviUtlではLuaを使用したスクリプティングが可能ですが、Luaの機能を使ってC++のクラスを変数として使用します。

# 準備
まずそもそも、LuaからC++の関数を使用するためにはDLLが必要です。
以前に[こちら](https://qiita.com/shulmj_/items/1a8e146887b825a77785)でも記述したので詳細は省きますが、以下のような手順をとります。
1. Visual Studio 2019でC++のプロジェクトを作成する。
2. `構成の種類`や`ターゲットファイルの拡張子`を`dll`に設定。
3. Lua5.1の中にある`lua51.lib`をライブラリとして指定する。また`lua.hpp`のある場所をインクルードディレクトリに設定する。
4. `文字セット`を`マルチバイト文字セットを使用する`に設定する。
5. C++のソースファイルを作成し、以下のようなコードを書く。
```cpp:main.cpp
#include <lua.hpp>

int test_func(lua_State* L){
    //ここにコード

    return 0;
}

static luaL_Reg functions[] = {
    {"test_func", test_func},
    { nullptr, nullptr }
};

extern "C" {
    __declspec(dllexport) int luaopen_test_module(lua_State* L) {
        luaL_register(L, "test_module", functions);
        return 1;
    }
}
```

# userdataとは
Luaにはユーザーデータ（userdata）という型があり、これはC言語のメモリを扱っています。
Lua標準のライブラリでは、`fs = io.open("hoge.txt", "r")`したときの`fs`がユーザーデータ型です。

## Luaの標準ライブラリから学ぶ
これはLuaの[ソースコード](https://www.lua.org/source/5.1/liolib.c.html#io_open)を見れば一目瞭然です。
```cpp:liolib.c
static int io_open (lua_State *L) {
  const char *filename = luaL_checkstring(L, 1);
  const char *mode = luaL_optstring(L, 2, "r");
  FILE **pf = newfile(L);
  *pf = fopen(filename, mode);
  return (*pf == NULL) ? pushresult(L, 0, filename) : 1;
}
```
このファイルハンドル`fs`は、`fopen`したときの返り値である`FILE*`型であることがわかります。

ここで`newfile(L)`を調べてみると、
```cpp:liolib.c
static FILE **newfile (lua_State *L) {
  FILE **pf = (FILE **)lua_newuserdata(L, sizeof(FILE *));
  *pf = NULL;  /* file handle is currently `closed' */
  luaL_getmetatable(L, LUA_FILEHANDLE);
  lua_setmetatable(L, -2);
  return pf;
}
```
のようになっていることがわかります。
`FILE*`型のポインタを`lua_newuserdata`関数で取得していることがわかります。

# 実際にクラスを作る
今回はC++の[`std::complex`](https://cpprefjp.github.io/reference/complex/complex.html)を使用して、複素数を表現できるクラスを作成します。

## 定義
複素数クラスを得るためのLua関数を`new_complex`関数とします。
先程の`lua_newuserdata`関数を使用するのですが、この関数は指定したサイズのメモリを確保し、それをスタックに乗せるという関数です。
`malloc`に似ていますが、Lua側でメモリの管理が行なえます。
```cpp:main.cpp
#include <complex>

typedef std::complex<double> Complex;

int new_complex(lua_State* L){   //Luaから呼び出す関数
   //引数を取得
   double r = lua_tonumber(L, 1); 
   double i = lua_tonumber(L, 2);
   
   // コンストラクタ
   Complex* p = lua_newuserdata(L, sizeof(Complex));
   auto c = new(p) Complex(r, i);

   return 1;
}
```
## クラスを引数として使う
続いて、作成したクラスをLua関数の引数として使用します。
今回は共役複素数を得る`conj`関数を作成してみます。

userdataは`lua_userdata`関数から取得できます。
```cpp:main.cpp
typedef std::complex<double> Complex;

int complex_conj(lua_State* L){   //Luaから呼び出す関数
   //スタックからuserdataを取得
   Complex* val = reinterpret_cast<Complex*>( lua_touserdata(L,1) );
   
   //返り値に使用するクラスのメモリを確保
   Complex* ret = reinterpret_cast<Complex*>( lua_newuserdata(L, sizeof(Complex)) );
   ret->real(0);   
   ret->imag(0);

   //共役を得る
   *ret = std::conj(*val);

   //スタックにあるuserdataを渡す
   return 1;
}
```

## metatableの活用
Luaにはメタテーブル（metatable）という機能が存在します。
metatableはテーブル型の変数で、未定義の演算を定義したりすることができます。
userdataにもmetatableを設定することができ、クラスから関数を呼び出したり、クラス同士の四則演算を定義するなどできます。

まず、DLLが呼び出された時点でメタテーブルをグローバルに登録しておきましょう。
```cpp:main.cpp
#define METATABLE_NAME "std::complex<double>" //メタテーブルの名前（なんでもいい）
typedef std::complex<double> Complex;

static luaL_Reg meta_functions[] = {
   //メタテーブルに登録する関数
   {nullptr, nullptr}
}

extern "C" {
    __declspec(dllexport) int luaopen_test_module(lua_State* L) {
        luaL_register(L, "test_module", functions);

        //メタテーブルを作成し関数を登録
        luaL_newmetatable(L, METATABLE_NAME); 
        luaL_register(L, NULL, meta_functions);

        //メタテーブルの__indexに関数を登録（complex:conj()のように呼び出せるようになる）
        lua_pushstring(L, "__index");
		lua_newtable(L);
        luaL_register(L, NULL, meta_functions);
		lua_settable(L, -3);
        
        //メタテーブルをスタックから取り除く
        lua_pop(L, 1); 
        
        return 1;
    }
}
```

続いて、Lua用関数側でメタテーブルを複素数クラスに設定していきます。
```cpp:main.cpp
#define METATABLE_NAME "std::complex<double>"
typedef std::complex<double> Complex;

int new_complex(lua_State* L){   //Luaから呼び出す関数
   double r = lua_tonumber(L, 1); 
   double i = lua_tonumber(L, 2);
   
   // コンストラクタ
   Complex* c = lua_newuserdata(L, sizeof(Complex));
   auto c = new(p) Complex(r, i);

   //メタテーブルを設定
   luaL_getmetatable(L, METATABLE_NAME);  //メタテーブルをスタックに積む
   lua_setmetatable(L, -2); //メタテーブルをuserdataに設定

   return 1;
}
```

これでメタテーブルの設定は完了です。

### luaL_checkudataを使用する
`luaL_checkudata`関数を使用すると、指定したメタテーブルをユーザーデータが保持しているかを確認することができます。
保持していれば、そのユーザーデータをスタックに積みます。
この関数のマクロを`complex_check`と設定して、以下のように変更してみました。

```cpp:main.cpp
#define METATABLE_NAME "std::complex<double>"
#define complex_check(L, n) (reinterpret_cast<std::complex<double>*>(luaL_checkudata(L, n, METATABLE_NAME)))

typedef std::complex<double> Complex;

int complex_conj(lua_State* L){   //共役複素数を得る関数
   //引数を取得
   Complex* val = complex_check(L, 1);

   //共役を取得
   auto ret = std::conj(*val)
   
   //返り値用のクラス
   auto p = lua_newuserdata(L, sizeof(Complex));
   auto c = new(p) Complex(ret.real(), ret.imag());

   //メタテーブル設定
   luaL_getmetatable(L, METATABLE_NAME);
   lua_setmetatable(L, -2);

   return 1;
}
```

## metatableを使用した演算の定義
メタテーブルを使用すると、`+`や`*`や`<=`のような記号を使った演算をも定義することができます。
例えば、加算なら`__add`、減算なら`__sub`という名前のキーでメタテーブルに登録します。
今回は掛算を表す`__mul`と、tostring関数を使用したときの出力を定義する`__tostring`関数を作成してみます。
ちなみに、このような`__hogehoge`をメタメソッドと呼びます（[参考](http://milkpot.sakura.ne.jp/lua/lua51_manual_ja.html#2.8)）。

```cpp:main.cpp
#include <iostream>
#include <string>

#define METATABLE_NAME "std::complex<double>"
#define complex_check(L, n) (reinterpret_cast<std::complex<double>*>(luaL_checkudata(L, n, METATABLE_NAME)))

typedef std::complex<double> Complex;

int complex__mul(lua_State* L){   //掛算の定義
   //スタックからuserdataを取得
   Complex* val1 = complex_check(L, 1);
   Complex* val2 = complex_check(L, 2);
   
   //返り値に使用するクラスのメモリを確保
   Complex* ret = reinterpret_cast<Complex*>( lua_newuserdata(L, sizeof(Complex)) );
   ret->real(0);   
   ret->imag(0);

   //メタテーブルを設定
   luaL_getmetatable(L, METATABLE_NAME);
   lua_setmetatable(L, -2);

   //掛算
   *ret = (*val1) * (*val2);

   //スタックにあるuserdataを渡す
   return 1;
}

int complex__tostring(lua_State* L){   //tostringの定義
   //スタックからuserdataを取得
   Complex* val = complex_check(L, 1);

   //文字列を作成
   std::string ret = "(" + std::to_string(val->real()) + "," + std::to_string(val->imag()) + ")";
   lua_pushstring(L, ret.c_str());

   //スタックにある文字列を渡す
   return 1;
}
```

## ガーベッジコレクション
Luaはメモリ管理を、ガーベッジコレクションという仕組みで行います。
`__gc`メソッドを持っていると、メモリ解放時に呼び出してくれます。ここでメモリを開放します。

```main.cpp
int complex__gc(lua_State* L){   //gcの定義
    auto c = reinterpret_cast<Complex*>(lua_touserdata(L, 1));
    std::destroy_at(c);
    return 0;
}
```

## 完成
最終的に完成したソースファイルがこちらです。
```cpp:main.cpp
#include <lua.hpp>
#include <iostream>
#include <string>
#include <complex>

#define METATABLE_NAME "std::complex<double>"
#define complex_check(L, n) (reinterpret_cast<std::complex<double>*>(luaL_checkudata(L, n, METATABLE_NAME)))

typedef std::complex<double> Complex;

int new_complex(lua_State* L){   //複素数クラスの作成
   double r = lua_tonumber(L, 1); 
   double i = lua_tonumber(L, 2);
   
   // コンストラクタ
   Complex* p = lua_newuserdata(L, sizeof(Complex));
   auto c = new(p) Complex(r, i);

   //メタテーブルを設定
   luaL_getmetatable(L, METATABLE_NAME);  //メタテーブルをスタックに積む
   lua_setmetatable(L, -2); //メタテーブルをuserdataに設定

   return 1;
}

int complex_conj(lua_State* L){   //共役複素数を得る関数
   //引数を取得
   Complex* val = complex_check(L, 1);

   //共役を取得
   auto ret = std::conj(*val)
   
   //返り値用のクラス
   auto p = lua_newuserdata(L, sizeof(Complex));
   auto c = new(p) Complex(ret.real(), ret.imag());

   //メタテーブル設定
   luaL_getmetatable(L, METATABLE_NAME);
   lua_setmetatable(L, -2);

   return 1;
}

int complex__mul(lua_State* L){   //掛算を定義するメタメソッド
   //引数を取得
   Complex* val1 = complex_check(L, 1);
   Complex* val2 = complex_check(L, 2);
   
   //メモリ確保＆メンバ初期化
   Complex* ret = reinterpret_cast<Complex*>( lua_newuserdata(L, sizeof(Complex)) );
   ret->real(0);   
   ret->imag(0);

   //メタテーブル設定
   luaL_getmetatable(L, METATABLE_NAME);
   lua_setmetatable(L, -2);

   //掛算を行う
   *ret = (*val1) * (*val2);
   return 1;
}

int complex__tostring(lua_State* L){   //tostringを定義するメタメソッド
   //引数を取得
   Complex* val = complex_check(L, 1);

   //文字列を作成しスタックへプッシュ
   std::string ret = "(" + std::to_string(val->real()) + "," + std::to_string(val->imag()) + ")";
   lua_pushstring(L, ret.c_str());

   return 1;
}

int complex__gc(lua_State* L){   //gcの定義
    auto c = reinterpret_cast<Complex*>(lua_touserdata(L, 1));
    std::destroy_at(c);
    return 0;
}


static luaL_Reg functions[] = {   //関数の登録
    { "new_complex", new_complex },
    { "conj", complex_conj },
    { nullptr, nullptr }
};

static luaL_Reg meta_functions[] = {   //メタテーブル用関数の登録
    { "conj", complex_conj },
    { "__mul", complex__mul },
    { "__tostring", complex__tostring },
    { "__gc", complex__gc },
    { nullptr, nullptr }
};

extern "C" {
    __declspec(dllexport) int luaopen_test_module(lua_State* L) {   //DLLの呼び出し
        //関数を登録
        luaL_register(L, "test_module", functions);

        //メタテーブルを作成
        luaL_newmetatable(L, METATABLE_NAME); 
        luaL_register(L, NULL, meta_functions);
        lua_pushstring(L, "__index");
		lua_newtable(L);
        luaL_register(L, NULL, meta_functions);
		lua_settable(L, -3);
        lua_pop(L, 1);
        
        return 1;
    }
}
```

これをAviUtlのスクリプト制御から呼び出します。
```lua:スクリプト制御
local t = require("test_module")

c1 = t.new_complex(1, 2) --複素数クラスの作成
debug_print( "type: " .. type(c1) )
debug_print( "c1: " .. tostring(c1) )

c2 = c1:conj() --共役複素数の取得
debug_print( "c2: " .. tostring(c2) )

c3 = c1 * c2 --複素数同士の掛算
debug_print( "c3: " .. tostring(c3) )
```
```:コンソール出力
type: userdata
c1: (1.000000,2.000000)
c2: (1.000000,-2.000000)
c3: (5.000000,0.000000)
```

# さいごに
これで複素数クラスを作ることができました。
この方法を使えば、C++のクラスや構造体をLuaの変数として扱うことができます。
三次元ベクトルやファイルハンドル、色の構造体など多種多様な使い方ができるかと思いますので、是非活用してみてください。

# 参考文献
* [Lua 5.1 リファレンスマニュアル](http://milkpot.sakura.ne.jp/lua/lua51_manual_ja.html)
* [userdataとmetatableを用いたLuaの機能拡張 - いちごパック](https://ichigopack.net/lua/c_object.html)
* [Luaプログラミング入門 第６章 C言語との連携 - Densanlabs](https://densan-labs.net/tech/lua/chapter6.html)
* [AviUtlスクリプト Wiki](https://aviutlscript.wiki.fc2.com/)