AviUtlのスクリプト用にDLLつくろう

はろーはろー、星野なたねだよ
解説するって言って1年近く経っているのでいい加減書くことにしたよ
今回はとりあえずVisual Studio 2017 Communityを使うよ

# 目次
- [準備](#準備)
    - [プロジェクトの作成](#プロジェクトの作成)
    - [諸々の設定](#諸々の設定)
- [コーディング](#コーディング)
    - [引数の処理](#引数の処理)
    - [画像処理](#画像処理)
    - [返り値について](#返り値について)
    - [関数のリストの作成](#関数のリストの作成)
    - [関数の登録とエクスポート](#関数の登録とエクスポート)
- [スクリプト側の処理](#スクリプト側の処理)
- [おわり](#おわり)

# 準備
とりあえずVisual Studio 2017 Community(2017じゃなくてもいい)を入れてない場合はさくっと入れましょう。
C++を使うので当然C++をインストール項目から外すと話が始まりません。
また、[Luaのバイナリ配布元](https://sourceforge.net/projects/luabinaries/files/5.1.4/Windows%20Libraries/)から`lua5_1_4_Win32_dll8_lib.zip`をダウンロードし、好きな場所に解凍しといてください。
## プロジェクトの作成
`New Project`から`Visual C++`>`Empty Project`で空のプロジェクトを作りましょう。
今回は`AUL_DLL_Sample`で作りましたが、各自好きに決めてください。
![Create_Project.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142463/8d8366b5-7e7a-8379-ef2d-60ed218abbe4.png "Create project")
プロジェクトができたら、設定の前にC++のソースファイルを作成しましょう(ソースファイルがない状態でプロパティを開いてもC++関連の設定はできないので)。
ファイル名は問いません。
![Create_Source.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142463/f601984a-c048-6800-5f1a-c940a58329b7.png "Create source")

## 諸々の設定
Solution Explorerでプロジェクトを右クリックし、プロパティを開きます。
左上のConfiguration(構成)でDebugとRelease、またはその両方に対して設定ができるので、最適化やデバッグに関する設定等、必要に応じて対象を切り替えながら設定していきましょう。
### General(全般)
![Setting_General.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142463/4be0d00c-fbeb-d154-6666-7cfc56a1bf0c.png "General settings")
`Output Directory(出力ディレクトリ)`でDLLの出力先を指定します。スクリプトのフォルダを指定しておくと、DLLに加えていろいろ生成されちゃうのはあるけどコピーの手間が省けていいと思います。
`Target Name(ターゲット名)`は出力ファイルの拡張子抜きの名前です。プロジェクト名以外にしたい場合は変更しましょう。
`Target Extension(ターゲットの拡張子)`は拡張子名です。`.dll`に変更してください。
`Configuration Type(構成の種類)`は実際にバイナリをどの形式にするかを指定します。`Dynamic Library (.dll)`に変更してください。
### C++
#### General(全般)
`Additional Include Directories(追加のインクルード ディレクトリ)`は、`#include`で読み込むヘッダのパスを指定します。今回はLua以外は特に使わないので[先ほど](#準備)導入したバイナリパッケージに含まれているincludeディレクトリを指定します。
#### Optimization(最適化)
`Optimization(最適化)`で最適化の指定をします。目的にもよるけど`Ox`でいいんじゃないでしょうか。`Ox`や`O2`を選ぶ際は、後述の`Basic Runtine Checks`を変更する必要があります。
最適化がかかると処理内容に変更が加わることもあるので、Debug構成では目的に応じて`/Od`にして最適化を切っておく方が良いです。
#### Code Generation(コード生成)
`Basic Runtime Checks(基本ランタイム チェック)`は、ランタイムのエラーチェックの指定です。内容としては[こんな感じ](https://qiita.com/sawada/items/5c22393af2bfb55a11ac)ですが、個人的にはDefaultでいいなと思います。最適化オプションと競合しがちだし。
ただ、Debug構成では`/RTC1`あたりでチェックを入れておくとよさそうです。
`Runtime Library(ランタイム ライブラリ)`でランタイムライブラリの指定をします。MTでいいんじゃないでしょうか。
Debug構成では、`/MT`なら`/MTd`、`/MD`なら`/MDd`が存在するのでそちらを選んでおきましょう。
### Linker(リンカー)
#### General(全般)
`Additional Library Directories(追加のライブラリ ディレクトリ)`で追加のライブラリのディレクトリを指定します。先ほどのパッケージの`lua51.lib`があるディレクトリを指定します。
#### Input(入力)
`Additional Dependencies(追加の依存ファイル)`で追加のライブラリを指定します。今回は`lua51.lib`を指定します。
OpenCVなどのようにReleaseやDebugビルドでバイナリが違う場合は、構成毎に設定してください。
#### Debugging(デバッグ)
`Generate Debug Info(デバッグ情報の生成)`でデバッグ情報を生成するか選びます。ReleaseのだけNoにしときゃいいんじゃないでしょうか。

# コーディング
ではコードディングに入りましょう。
今回の想定として、Lua側で`AUL_DLL_Sample.mult_color(data, w, h, red, green, blue)`と呼べる関数としましょう。
それぞれの引数で受け取った数を画像に乗算する単純なものです。
data,、w,、hはそれぞれ`obj.getpixeldata()`の返り値で受け取れる画像データと画像データの幅、高さです。
まずは

```cpp
#include <lua.hpp>
```
で、Luaのヘッダ群をincludeします。これがないと始まりません。
次にLua側に登録できる形式の関数を作りましょう。
Lua側から呼べるCの関数は、`lua_State`のポインタを受け取り、`int`を返す形式です。とりあえずもっとも単純な形として、

```cpp
int mult_color(lua_State *L) {
    return 0;
}
```
としましょう。
## 引数の処理
Luaから呼び出す関数では、`lua_State`を介して呼出し側からの引数を受け取ります。
Luaとの値のやり取りは基本的にスタックで行われます。今回受け取った引数(のRGBだけの場合)を簡単な図に表すとこのような感じで、積み木のように下から積みあがります。
![Stack_Image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142463/d5bfc192-186b-20bf-f6ef-741fd157d5cf.png "Stack image")
まずは関数に最初に渡された画像データとサイズを受け取ってみましょう。
画像データのようなC側で扱うためのデータはUserdataと呼ばれ、`lua_touserdata`関数でスタックから取り出すことができます
AUL側から受け取る画像データはピクセルのデータの`w*h`個の一次元配列になっており、1pixelのデータはARGB各色1byteずつの計4byteとなっています。内部での配列は以下の図のように上位バイトから順にARGBの順に格納されているようです
![Pixel_Data.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142463/6771077f-0b38-64d6-be79-ff3866231dee.png "Pixel data")
[一般的](https://aviutlscript.wiki.fc2.com/wiki/obj.getpixeldata%2C%20obj.putpixeldata#c72a5185)にはビットシフトで取り出すべきですが、面倒なので同じ内部配置の構造体を作ってそれの配列という解釈にします(かなり特殊な環境とかでバイトオーダー絡みの問題置きそうで怖いけど)

```cpp
struct Pixel_RGBA {
    unsigned char b;
    unsigned char g;
    unsigned char r;
    unsigned char a;
};
```
画像サイズ等の整数値は`lua_tointeger`関数で取り出せます。
それでは取り出しましょう。
Luaの関数は、第一引数に`lua_State*`を受け取ります。
`lua_to`系の関数は、第二引数にスタック上のインデックス値を受け取ります。
`lua_touserdata`の返り値は`void*`型なので、`Pixel_RGBA*`にキャストしましょう。

```cpp
Pixel_RGBA *pixels = reinterpret_cast<Pixel_RGBA*>(lua_touserdata(L, 1));
int w = static_cast<int>(lua_tointeger(L, 2));
int h = static_cast<int>(lua_tointeger(L, 3));
```
続いて、残りの3つの引数も受け取りましょう。
これらは整数ではなく浮動小数点数なので、`lua_tonumber`で受け取ります。
返り値の`lua_Number`は`double`です。

```cpp
double rm = lua_tonumber(L, 4);
double gm = lua_tonumber(L, 5);
double bm = lua_tonumber(L, 6);
```
## 画像処理
いよいよ画像データの処理に入ります。
画像データは左上を(0, 0)として、(0, y)~(w-1, y)の横方向のスライスがh個連続している形になっています。
特定の座標に対応する配列のインデックスは`x + w * y`で計算できます。
新しいデータを作ってもいいのですが、今回は画像のサイズを変更することも特にないのでそのまま受け取ったデータを変更します。


```cpp
for (int y = 0; y < h; y++) {
    for (int x = 0; x < w; x++) {
        int index = x + w * y;
        pixels[index].r *= rm;
        pixels[index].g *= gm;
        pixels[index].b *= bm;
    }
}
```
xとyの二重でforループを行い、全ての座標のデータに操作を行います。
座標に依存する処理等を行う際は、一つのループで行うよりこちらの方がずっと簡単です。
座標に対応する配列要素の各色に受け取った係数を乗算しているだけです。
## 返り値について
Luaから呼び出す関数はintを返すことになっていますが、このintは実際にLua側に渡す返り値の数となっています。
CからLuaへは返り値もスタックを介して行われます。Lua側へ返したい値をスタックに積み、積んだ数を関数の返り値として返すことで、Lua側がスタックからその数だけ値を受け取るわけです。
今回は特に値は返さないので0を返します。

```cpp
return 0;
```
## 関数のリストの作成
先ほど作った関数を、Luaに登録する関数のリストに追加しましょう。
リストは`luaL_Reg`型の配列で、`luaL_Reg`型の中身は`const char`配列の関数名と、`lua_CFunction`型の関数ポインタです。
[コーディング](#コーディング)のはじめに述べた

> Lua側から呼べるCの関数は、`lua_State`のポインタを受け取り、`int`を返す形式です。

は、この`lua_CFunction`の形で登録するためです。
内容はこのようになります。

```cpp
static luaL_Reg functions[] = {
    { "mult_color", mult_color },
    { nullptr, nullptr }
};
```
リストの最後の要素は両方`NULL`か`nullptr`にしておいてください。
## 関数の登録とエクスポート
最後に、関数のリストをLuaからモジュールとして呼べるように登録する関数を作り、それをDLLから呼べるようにエクスポートします。
コードはこのようになります。

```cpp
extern "C" {
__declspec(dllexport) int luaopen_AUL_DLL_Sample(lua_State *L) {
    luaL_register(L, "AUL_DLL_Sample", functions);
    return 1;
}
}
```
`__declspec(dllexport)`を頭につけることによって、DLLの外部からでも`luaopen_AUL_DLL_Sample`関数が見えるようにエクスポートします。
関数名は、`luaopen_`に拡張子を除いたDLL名を合わせたものにしてください。詳しい規則は[ここ](http://milkpot.sakura.ne.jp/lua/lua51_manual_ja.html#pdf-require)を見るといいかもしれません。
関数は`lua_State*`を引数に取り、`int`を返します。
この関数内で`luaL_register`関数を呼び出すことで、関数群をモジュールとして登録します。第二引数はモジュール名、第三引数は実際に登録するリストです。
複数のモジュールを登録したい場合は、その数だけ`luaL_register`を呼び出してください。
全体を`extern "C"`で囲んでいる理由は、Lua側が`luaopen_*`関数をDLLに埋め込まれるシンボル情報から探す際、Cの関数として宣言しないと見つけることができないからです。
Cの関数のシンボルは単純に関数名のみですが、C++は関数がオーバーロードできるという都合上、シンボルの衝突を避けるために関数名に引数や返り値の型などの情報を加えるマングリングという処理を行ったものをシンボルとして使うからです。詳しく知りたい人は[ここ](https://ja.wikipedia.org/wiki/%E5%90%8D%E5%89%8D%E4%BF%AE%E9%A3%BE#C++%E3%81%AE%E5%A0%B4%E5%90%88)や[ここ](https://www.wagavulin.jp/entry/2017/02/09/215036)でも読んでみるといいんじゃないでしょうか。
# スクリプト側の処理
DLLを作っても、呼び出すスクリプトがなければ使うことはできません。
DLLはスクリプトのファイルと同じディレクトリにあると仮定し、`require`でDLLからモジュールを読み込みます。

```lua
require("AUL_DLL_Sample")
```
`obj.getpixeldata`関数でDLL関数に渡すデータを受け取ります。引数は不要です。

```lua
data, w, h = obj.getpixeldata()
```
`data`に画像データ、`w`と`h`に画像の幅、高さがそれぞれ入ります。
データの準備ができたら、引数を渡して関数を呼び出します。RGBの係数をそれぞれ`obj.track0~2`から受け取ると仮定して、

```lua
AUL_DLL_Sample.mult_color(data, w, h, obj.track0, obj.track1, obj.track2)
```
となります。
処理が終わったら、データを`obj.putpixeldata`関数で反映させます。

```lua
obj.putpixeldata(data)
```
これでこのスクリプトを呼び出せば、晴れて自作DLLのエフェクトがかかることになります。
# おわり
つかれた
解説クッッッッッッッッッッッッッッッッッッッッッソめんどくさかった
でもまぁまだいろいろ書けること自体はたくさんあるので、いつか気が向いたら書いてみようかなとは思います

気に入ったらついったで褒めてくれてもいいんだよ

多分かなり説明がへたくそで奇怪だったであろう文をここまで読んでくれてありがとうなのでした
次は期待しない程度に待っててね

あと今回のサンプルコードだよ
https://github.com/SEED264/AUL_DLL_Sample