備忘録　AviUtlでDLLスクリプトを書く方法

Visual Stduio 2019を使って、AviUtlで使えるDLLをつくる方法を備忘録として残します。

- [準備](#準備)
- [プロジェクトの設定](#プロジェクトの設定)
- [コーディング](#コーディング)
 - [インクルード](#インクルード)
 - [関数](#関数)
 - [後始末](#後始末)
- [ビルド](#ビルド)
- [AviUtlで使う](#AviUtlで使う)

## 準備
Visual Studio 2019 は、あらかじめ用意しておきましょう。
[ここ](https://sourceforge.net/projects/luabinaries/files/5.1.4/Windows%20Libraries/)から``lua5_1_4_Win32_dll8_lib.zip``をダウンロードして、解凍しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618500/42799f44-19de-224a-7467-6bb6b7cda89a.png)

それができたら、「新しいプロジェクトの作成」からC++の「空のプロジェクト」を選択します。
プロジェクトができたら、ソリューションエクスプローラーからソースファイルを右クリックして、cppファイルを作っておきます。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618500/ae23d14d-e80a-edbb-21ba-652d612943fa.png)

## プロジェクトの設定
プロジェクト名を右クリックして、プロパティを開きます。

### 全般
``構成の種類``を``ダイナミックライブラリ(.dll)``に変更
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618500/9b7c474e-15fa-eed6-0b83-5f7a72b6c9cd.png)

### 詳細
``ターゲットファイルの拡張子``を``.dll``に変更
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618500/995efb32-9c8e-e6db-1a17-7db65636dae9.png)

### C/C++
#### 全般
`追加のインクルードディレクトリ`に、先程ダウンロードしたLuaの中にある`include`フォルダを指定します。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618500/b4059691-7ead-547f-bb19-51f656ed1e5b.png)

### リンカー
#### 全般
`追加のライブラリディレクトリ`に``lua51.lib``のある場所を指定します。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618500/a2e56540-54d0-0d53-eb83-bccd17013cd2.png)

#### 入力
``追加の依存ファイル``に``lua51.lib``を追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618500/e267c76d-df86-5f91-4692-cf6aedee3db6.png)

## コーディング
今回は`test_module`という名前のプロジェクトだと仮定して、ソースコードを書いていきます。
以下が最低限のソースファイルです。

```C++:Source.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;
	}
}
```

### インクルード
インクルードは、`lua.hpp`を指定します。

```C++
#include <lua.hpp>
```

### 関数
LuaとC++では、関数を直接呼び出したりすることはできません。
そのため、`lua_State*`という引数を用いて、データをやり取りします。

```C++
int test_func(lua_Sate* L){
	//ここにコード

	return 0;
}
```
後述しますが、最後の`return`には、Luaに渡す返り値の数を指定します。


#### 引数と返り値のやり取り
前述したとおり、LuaとC++は直接関数を呼び出すことはできません。
そのため、スタックという概念を使用して、データをやり取りしています。

スタックについては、[Wikipedia](#https://ja.wikipedia.org/wiki/%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF)でも参照してください。
簡単に言うと、__後入れ先出し__のデータ構造のことを言います。
スタックにデータを入れることを__プッシュ__、スタックに積まれたデータを取り出すことを__ポップ__といいます。
ちなみに、スタックのインデックス（場所）は、__下から1、2、3__ または __上から-1、-2、-3__と数えます
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618500/b88743ad-1b90-b99f-7ca1-68b642963fe3.png)


以下の`sum`関数を例にしてみます。

```C++
int sum(lua_Sate* L){
   int num1 = lua_tonumber(L, 1);    //スタックの一番下から値を取得
   int num2 = lua_tonumber(L, 2);    //スタックの下から2番目の値を取得

   lua_pushinteger( L, num1+num2 );    //スタックに計算結果をプッシュ
   return 1;    //スタックからLuaに渡す返り値の数
}
```

このsum関数をAviUtl上で呼び出すと、

```Lua:@test.anm
require("test_module")
local num1, num2 = 1, 2
local ans = test_module.sum(num1, num2) --sum関数を呼び出し

debug_print(ans) --ansを表示
```
結果は`3`になります。


なにが起こっているのかと言うと、
① AviUtl（Lua）側から`test_module.dll`の`sum`関数を呼び出し
② スタックに1と2が積まれる
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618500/8dfd0a6c-513d-cbe1-1389-d593eed0ccc0.png)
③ スタックから値を取得し、`test_module`内の変数に代入
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618500/ae814159-c235-9331-362f-aac2c2395063.png)
④ num1とnum2を計算し、結果の値をスタックにプッシュ
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618500/384294c0-3f7d-50da-58c8-da5a67b0453a.png)
⑤ スタックの1番上の値を、AviUtl（Lua）に返す
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618500/36457b02-5190-4e21-3c20-9fde44ec69e6.png)


具体的には、
 `lua_to〇〇(lua_State*, int index)`：　スタックの`index`番の値を取得
 `lua_push〇〇(lua_State*, value)`：　　スタックの一番上に`value`をプッシュ
という関数で、これらのことを行っています。

最後の`return 1;`は、Luaに渡す返り値の数を指定します。
（正確には、スタックから値を読み込むときに、上から何番目まで読み込むかを指定しています）

#### Luaから渡された引数の判別
渡された引数を判別するには、`lua_type(lua_State* , int index)`関数を使います。
これは、スタックの`index`番にある値の型を返してくれる関数です。
返り値は`int`ですが、`define`されているので、以下の定数も使用できます。

| Lua上での型 | lua_typeの返り値の型 |
|:-----------:|:-------------------:|
| nil | LUA_TNIL |
| number  | LUA_TNUMBER  |
| boolean | LUA_TBOOLEAN |
| string | LUA_TSTRING |
| table | LUA_TTABLE |
| function | LUA_TFUNCTION |
| userdata | LUA_TUSERDATA |
| thread | LUA_TTHREAD |
| lightuserdata | LUA_TLIGHTUSERDATA |

以下が引数の型判別のサンプルコードです。

```C++
int num;

if (lua_type(L, 1) == LUA_TNUMBER) {
	num = lua_tointeger(L, 1);   //引数が数字だった場合
}
else {
	return 0;   //引数が数字じゃなかった場合
}
```

#### 返り値をテーブルにする
DLLからLuaへ、テーブルで返り値を渡したいときには、少し特殊な処理が必要になります。
当然、``std::vector``を直接返したりすることはできないので、スタックの操作が必要になります。

##### キーが数字のテーブル
まず、`table[1]`のように、数値をインデックスとするテーブルについてですが、ここでは、``lua_settable``関数を使用します。

`lua_settable(lua_State* L, int index)`は、スタックの一番上の値を`index`にあるテーブルに代入します。
このときに使われるテーブルのインデックスは、スタックの上から2番目の値が使われます。
空のテーブルは、`lua_newtable(lua_State* L)`で作成できます。

```C++
int getTable(lua_State* L) {
	lua_newtable(L);  //スタックに空のテーブルを積む

	// table[1] = 10
	lua_pushinteger(L, 1);   //スタックにテーブルの番号を積む
	lua_pushinteger(L, 10);    //スタックに、テーブルへ入れたい値を積む
	lua_settable(L, -3);     //空のテーブルに値を入れる

	// table[2] = 20
	lua_pushinteger(L, 2);
	lua_pushinteger(L, 20);
	lua_settable(L, -3);

	// table[5] = 50
	lua_pushinteger(L, 5);
	lua_pushinteger(L, 50);
	lua_settable(L, -3);

	return 1;    //スタックの1番下（=テーブル）を返す
}
```

```Lua:@test.anm
require("test_module")
local table = test_module.getTable() --テーブルを取得

debug_print( table[1] ) --10が表示される
debug_print( table[2] ) --20が表示される
debug_print( table[5] ) --50が表示される
```
このようにすると、CからLuaへテーブルを渡すことができます。
なお、`lua_settable`を使用すると、使った値はスタック上から消滅します。

##### キーが文字列のテーブル
``table["color"]``または``table.color``のような、インデックスに文字列を使用したテーブルの場合は、``lua_setfield``関数を使用します。

``lua_setfield(lua_State* L, int index, const char* key)``は、スタックの一番上にある値を`index`にあるテーブルに代入します。
``lua_settable``と似ていますが、キーには文字列`key`しか使えません。

```C++
int getTable(lua_State* L) {

	lua_newtable(L);  //スタックに空のテーブルを積む

	lua_pushinteger(L, 1);    //スタックに、テーブルへ入れたい値を積む
	lua_setfield(L, -2, "x");     //空のテーブルに値を入れる

	lua_pushinteger(L, 2);
	lua_setfield(L, -2, "y");

	lua_pushstring(L, "0x000000");
	lua_setfield(L, -2, "color");

	return 1;
}
```

```Lua:@test.anm
require("test_module")
local table = test_module.getTable() --テーブルを取得

debug_print( table["x"] ) --1が表示される
debug_print( table.y ) --2が表示される
debug_print( table["color"] ) --"0x000000"が表示される
```

### 後始末
```C++
static luaL_Reg functions[] = {
	{"test_func", test_func},   //Luaから呼び出したい関数を記載
	{ nullptr, nullptr }  //リストの最後は nullptr にする
};
```
前半の`static luaL_Reg functions[] = {...};`の部分は、LuaからDLLの関数を呼び出すときのリストです。
`{"関数名", 関数}`というように指定して、リストの最後には`{nullptr, nullptr}`を挿入します。
これをしないと、Luaから関数が認識されません。

```C++
extern "C" {  //C言語で処理
	__declspec(dllexport) int luaopen_test_module(lua_State* L) {
		luaL_register(L, "test_module", functions);  //関数リストを登録
		return 1;
	}
}
```
後半の`extern "C" {...}`の部分は、Luaから呼び出せるように関数をエクスポートしています。
`luaopen_〇〇`の部分や、`luaL_register(L, "〇〇", functions);`の「〇〇」部分は、
自分のプロジェクトの名前に変更してください。

## ビルド
最後に、``ビルド``タブから`ソリューションをビルド`をクリックすると、ターゲットフォルダにDLLを含め、いろいろなファイルが生成されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/618500/24aa5c37-e607-1187-08e3-2b58f13ebb74.png)
この中の``〇〇(プロジェクト名).dll``を、AviUtlの``script``フォルダにぶち込みましょう。

## AviUtlで使う
AviUtlでDLLを使用するには、``require``関数を使います。
DLLの関数を呼び出すには、「DLLの名前.関数()」という形にします。

```Lua
require("test_module")  --dll読み込み
test_module.function()   --関数呼び出し
```

注意点として、DLLを一度読み込むと、AviUtlのキャッシュを削除しても再読み込みがされません。
DLLをビルドし直したら、AviUtlは再起動しましょう。

# 参考
[Luaプログラミング入門  第６章 C言語との連携](https://densan-labs.net/tech/lua/chapter6.html)（[Densanlabs](https://densan-labs.net/index.html)）
[AviUtlのスクリプト用にDLLつくろう](https://qiita.com/SEED264/items/b57927e1dadb044cf614)（@SEED264）
[C と lua の連携方法メモ](https://qiita.com/miuk/items/82e5566ea01313a8b1af)（@miuk）
[LuaのC++組み込み方自分用まとめ](https://qiita.com/hiz_/items/8739c46ddd2563a5603f)（@hiz_）
[AviUtlスクリプト Wiki](https://aviutlscript.wiki.fc2.com/)