1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-06-04

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

準備

Visual Studio 2019 は、あらかじめ用意しておきましょう。
ここからlua5_1_4_Win32_dll8_lib.zipをダウンロードして、解凍しておきます。
image.png

それができたら、「新しいプロジェクトの作成」からC++の「空のプロジェクト」を選択します。
プロジェクトができたら、ソリューションエクスプローラーからソースファイルを右クリックして、cppファイルを作っておきます。
キャプチャ.PNG

プロジェクトの設定

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

全般

構成の種類ダイナミックライブラリ(.dll)に変更
image.png

詳細

ターゲットファイルの拡張子.dllに変更
image.png

C/C++

全般

追加のインクルードディレクトリに、先程ダウンロードしたLuaの中にあるincludeフォルダを指定します。
キャプチャ.PNG

リンカー

全般

追加のライブラリディレクトリlua51.libのある場所を指定します。
キャプチャ.PNG

入力

追加の依存ファイルlua51.libを追加します。
image.png

コーディング

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

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を指定します。

#include <lua.hpp>

関数

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

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

	return 0;
}

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

引数と返り値のやり取り

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

スタックについては、Wikipediaでも参照してください。
簡単に言うと、後入れ先出し__のデータ構造のことを言います。
スタックにデータを入れることを__プッシュ
、スタックに積まれたデータを取り出すことを__ポップ__といいます。
ちなみに、スタックのインデックス(場所)は、下から1、2、3 または __上から-1、-2、-3__と数えます
キャプチャ.PNG

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

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上で呼び出すと、

@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.dllsum関数を呼び出し
② スタックに1と2が積まれる
キャプチャ.PNG
③ スタックから値を取得し、test_module内の変数に代入
キャプチャ.PNG
④ num1とnum2を計算し、結果の値をスタックにプッシュ
キャプチャ.PNG
⑤ スタックの1番上の値を、AviUtl(Lua)に返す
キャプチャ.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

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

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)で作成できます。

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番下(=テーブル)を返す
}
@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しか使えません。

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;
}
@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"が表示される

後始末

static luaL_Reg functions[] = {
	{"test_func", test_func},   //Luaから呼び出したい関数を記載
	{ nullptr, nullptr }  //リストの最後は nullptr にする
};

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

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
この中の〇〇(プロジェクト名).dllを、AviUtlのscriptフォルダにぶち込みましょう。

AviUtlで使う

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

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

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

参考

Luaプログラミング入門 第6章 C言語との連携Densanlabs
AviUtlのスクリプト用にDLLつくろう@SEED264
C と lua の連携方法メモ@miuk
LuaのC++組み込み方自分用まとめ@hiz_
AviUtlスクリプト Wiki

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?