作成日:2015/12/25
C言語分からない人間(僕です)が「分からない人間の視点からNScripter用プラグイン(dll)の作成開始までの手順を説明する」解説ページです。
個別具体的にどんなプラグインを作るという話ではなく、「スタート地点どこなの?」という話です。
NScripterでいう00.txtに*define/game/*startだけが書いてある状態を整えるまでのチャートです。

プラグイン便利ですよね? NScripterから読み込むとなんか機能が増えるパワーアップ用パーツですね。
『Atelier de Muguet 制作記録』の「DLL制作-コンパイル環境編」にこんな一文が書いてあります。
NScripterプラグインを作ろうと思うからには、ある程度の知識があるものと思いますので、態々記事を書くのは無駄かも知れませんが、
(『Atelier de Muguet 制作記録』は要するに君影草工房さんです。いわゆる三大Nスクプラグインの聖地ですよね。)

しかし僕はC言語なんて分かりません。なんか文字や数字ごときに山ほど種類がある怖い土地であると聞いたことはあります。
大昔に本体同梱のサンプルフォルダを開いたことはありますが意味は分かりませんでした。
したがって、できることなら輪廻の果てに解脱するまで触れずに済ませたいと思っていました。

そしてある日、突然NScripter(とNSLua)の標準機能で実現できない処理を行う必要に迫られました。
既存のプラグインで賄えないか考えてはみたもののダメそうです。
ではどうしましょうか。プラグイン自作は不可避でしょうか?


  • 0:そもそも我々はプラグインを作らねばならないのか
  • 1:公式のサンプル…cppファイルって何?
  • 2:公式のサンプルを最小構成化する@
  • 3:公式のサンプルを最小構成化するA
  • 4:dllを作れる環境の準備(Visual Studioのインストールと設定、幸運を祈る)

  • 本体同梱のサンプルを開く
    そもそもプラグイン作成以外の道はないのか
    第一に、NScripterにせよLuaにせよテキストファイルの出入力は命令一つで可能です。
    Nスクのcsvopen系列はテキストファイルならなんでも読み込めます。暗号化機能もあります。
    Luaのio.open()系列はなんでも読み込めます。コピー/ペーストする分には画像であろうとなんだろうと余裕です。
    つまり、テキストファイルを吐いたり読んだりすることで済む処理ならその方が手軽です。
    しかしテキスト読み書きでは賄えない処理、アクセスできない情報は多々あります。

    第二に、shell命令とかwinexec命令、またはLuaのos.execute()はどうでしょうか?
    「そもそもこいつらがどんな命令なのかよくわからない(ブラウザを開けることは知っている)」
    という人は少なくないと思いますが、僕もそうです。Windowsに直接処理投げる命令たちですよね。

    shell命令とwinexec命令については永字八法の記事およびその補足記事が参考になります。 2008年の記事なので最新バージョンとは事情が一部異なります(小数点以下の計算、再帰的処理、可変長配列などは現在ならLuaで賄えます)が、
    要するに「テキストファイル越しに外部exeへ情報渡そうぜ!」というアプローチです。
    「外部exe」は何らかのプログラミング言語やツールであることが想定されます。
    手元にそうした「NScripter以外の実行環境」を使える環境に存在するなら、
    そしてテキストファイルを介する処理速度(甘く見て0.01秒は見込んだ方が無難です)が障害にならない用途なら、
    そちらに処理を投げる手は大いにあると思われます。

    そうした手段が常に存在するとは限りません。つまりプラグインを自作する必要に迫られました。
    仕方なく本体のサンプルフォルダを覗きましたがやはり意味が分からなかったとします。さあどうしましょうか?
    公式のサンプルをとりあえず開く
    2.96までの環境では「プラグイン」フォルダの中身がサンプルです。現在なら本体と別配布の旧ドキュメントに入っています。
    NScripter最終版(3.03)においては「NSLUAについて→NSDでフォントのDLL→main.cpp」がサンプルです。
    最終版同梱のサンプルの方が相対的にはシンプルですが、とりあえず覗くだけ両方覗いてみましょう。
    cppファイル? これは単なるテキストファイルです。
    「その実はテキストファイルだけど、拡張子のみ独自(=関連付けアプリを分類する目的)」はWindowsの常套手段です。 luaファイルも同じでしたね。右クリックしてメモ帳から開いてください。

    開いたものの、内容はさっぱり理解できなかったとします。どれが必須処理かなんて見当も付きませんでした。

    そもそも、NScripter本体に同梱されたサンプルは長年「画像処理」「エフェクト」を前提とした解説になっているようです。
    歴史的な経緯・実際上の需要に基づく構成だと思いますが、今知りたいのは個別具体的な処理以前の次元であるとします。
    ※今のうちに容量に10GBくらい余裕のある環境を用意し、  マイクロソフト公式からVisual Studio 2015(超すごいテキストエディタ兼dll作成ソフトと思って構いません)を  ダウンロードしておくとスムーズです。目玉が飛び出る値段ですが個人開発者向けの無料版があります。
    公式のサンプルの最小構成を考える@
    いわゆる*define/game/*start状態はどこ?という話です。
    2.96以前のサンプルなら
    
    extern "C" {
    __declspec(dllexport) BOOL NScrPlugInMain (HINSTANCE hinstance,HWND hwnd,char *param,int *ret_int,char *ret_str);
    //プラグイン本体。hinstanceはNScripterのインスタンス。hwndはNScripterのウィンドウハンドル。
    //paramはexec_dll命令に渡したパラメータ文字列。
    //ゲームその他の処理を作るときは、このウィンドウの子ウィンドウとして
    //ぴったりクライアント領域の大きさの独自のウィンドウを上から貼り付け、処理してください。
    //時々NScrDoEventsを実行してNScripterのメッセージ処理をしてください。
    };
    
    こんなの、
    3.03同梱サンプルなら
    
    //----------------------------
    //エクスポートされる関数
    //----------------------------
    extern "C" {
    	__declspec(dllexport) void Font(unsigned char *bits,int w,int h,const char *param);
    }
    こんなのが書いてあったと思います。
    
    旧サンプルの「プラグイン本体」が
      exturn "C"{ 〜 }
    全体を指すのか
      __declspec(dllexport) ○○
    を指すのか分かりませんが、ここから呼び出されている命令群が「本体」のようです。
    見比べてみましょう。
    
    __declspec(dllexport) BOOL NScrPlugInMain 
    __declspec(dllexport) void Font
    
    __declspec(dllexport) ○○
    ○○が「関数の名前(dll内部で使っている命令の名前)」のようです。
    NScripterでいう命令呼び出し、Luaでいう関数の実行を行っているようです。
    
    BOOL voidは「型」(NScripterでいう文字or数字orラベルorID)を指します。
    なんとC言語はNScripterやLuaと異なりその場で型を宣言するようです。
    しかも新旧サンプルで違う型が書いてありますが、見なかったことにして進みます。
    
    名前以降(=渡している値?を見比べましょう。
    	(HINSTANCE hinstance,HWND hwnd,char *param,int *ret_int,char *ret_str);
    	(unsigned char *bits,int w,int h,const char *param);
    カンマ区切りで色んな値が渡されています(引数)が、落ち着いて比較しましょう。
    旧サンプルにしか書いていない値は
     HINSTANCE hinstance
     HWND hwnd
     int *ret_int
     char *ret_str
    の4つ、
    新サンプルにしか書いていない値は
     unsigned char *bits
     int w
     int h
    の3つ、
     char *param / const char *param
    のみはなんとなく共通しています。
    
    さて、どれが我々に関係ある値でしょうか?
    
    「int」「char」は「数字」「文字」と読み替えます。
     前者は数字変数、後者は文字変数のようなものだと思いましょう。
     unsignedとかconstとかひっ付いているのはとりあえず無視しましょう。
    
    「HINSTANCE」「HWND」は無視して大丈夫です。
     「hinstanceはNScripterのインスタンス。hwndはNScripterのウィンドウハンドル」
     と言われてもインスタンスとウインドウハンドルの意味が分かりませんね。
     共に「現在実行されているNScripter.exeのWindowsから見た名前」に関わる値です。
     つまり今は必要ないな!
    
    今や分かりました。
    旧サンプルでは「なんかNスクの名前、*paramという文字列、*ret_intという数字、*ret_strという文字列」
    新サンプルでは「*bitsという文字列、wという数字、hという数字、*paramという文字列」
    がプラグインに渡されているようです。
    
    ネタばらしをすると、*paramはNスクのexec_dllでプラグイン名以降に渡せる任意の文字列です。
    そしてプラグイン内で*ret_intと*ret_strに代入した値をNスクのgetret命令で受け取ることができます。
    
    
    旧サンプルは「渡された*paramという文字(があるかもしれない)を元に何か処理をして、getret用の文字数字を用意する」
    という処理がコアであることになります。「何か処理をして」はC言語側でいじらざるを得ませんが、ひとまず置いておきましょう。
    
    一方の新サンプルですが、以下のような例であったことを思い出してください。
    
     >指定した文字列を出力するDLLのサンプルをdeffont.dllとして用意しました。
     >パラメータには、"文字幅,文字高さ,文字列"を指定してください。半角全角どちらも表示できます。
     >表示文字列中の半角の%はDLLへの命令に使う予定です。現在、文字色変更を実装しています。
     >半角の%そのものを表示したい場合は、%%としてください。
     >文字列中に%#色とすることで色を変えることが出来ます。色指定はHTMLと同じ要領で。
     >なお、呼び出すたびに色は白に戻ります。最初から色を変えたい場合は文字列の頭で指定してください。
    
     >(DLL設定の例)
     >fontproc=NSDDLL("deffont.dll","Font") -- system.luaの直下で読み込み時に実行
     >(使い方の例)
     >NSDCall(0,"*200,48",fontproc,"24,24,あいうえお%#FF8888かきくけこ")
    
    Lua関数が2つ書いてありますね。今は関係なさそうですが説明は見てみましょう。
     NSLuaリファレンスを参照すると、NSDDLLの説明は以下のようになっています。
     >NSD系命令のテクスチャ作成時に呼び出すDLL関数を設定します。
     >Luaファイル読み込み時に一度だけ実行してください。
     >戻り値の番号はNSDCall関数にて引数に指定します。
    
     NSDCall(テクスチャ番号 , ファイル名 , プラグイン内部の関数名 (, あれば引数として渡す文字列))も
      同じくNSD系命令用の関数で、「外部dllを使ってテクスチャの中身をバイト単位で設定する」というものでした。
    
     「NSD系命令」は「既存の描画と混ぜて使えない代わりに超すごいblt」のような存在です。
     つまり新サンプルは「NSD系列の描画としてdllから文字を表示する例」であることが分かります。
    
     さらにNSDCallの説明を見ると、
     なお、引数paramがない場合は省略できます(この場合、DLLにはNULLが送られます)。
     呼び出されるDLL関数の型は次の通りにしてください。
     void Func (unsigned char *ptr,int w,int h,const char *param);
     とあります。新サンプルのコアと内容が一致しますね。
     新旧サンプルで関数?の型が異なっていた理由も分かりました。
    
    
     「新サンプルはテクスチャ(NSD系列)に纏わるNSLua関数の設定/使用例。それらを使用する予定がなければ旧サンプルをひな型にする」
     ことになります。
     なお新サンプルのdllである「deffont.dll」には、「deffontd.dll」という機能追加版がNScripterようのアップローダーに存在します。
     使用目的ならそちらを使うと良いでしょう。
    
    
    公式のサンプルの最小構成を考えるA
    で、肝心の最小構成は?という話になります。
    
    量の少ない新サンプルから見ましょう。
    
    #include #include extern "C" { __declspec(dllexport) void Font(unsigned char *bits,int w,int h,const char *param); } BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved); BOOL APIENTRY DllMain( HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved) { return TRUE; } //ここが呼び出される関数。テクスチャを処理する void Font(unsigned char *bits,int w,int h,const char *param) { return }
     >#include  は「この機能使うから用意しといてくれ」を意味すると思ってください。  Visual Studioからファイルを開けば具体的に何が必要か/何が不足しているか警告を出してくれますが、今はおまじないです。  >extern "C" {  > __declspec(dllexport) void Font(unsigned char *bits,int w,int h,const char *param);  >}  ここから  >void Font(unsigned char *bits,int w,int h,const char *param) {  > return  >}  が呼び出されています。これが今回プラグインの本体でしたね。  そして間に書いてある  >BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved);  >BOOL APIENTRY DllMain( HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved) {  > return TRUE;  >}  ですがごめんわかんない。(ついにボロが出た)  まあ新サンプルはテクスチャいじり前提=画像生成周りの知識がある人前提=C言語くらい分かってる人前提だと思うので…… 旧サンプルはエフェクト/レイヤー/汎用と3つ並んでいます。  前2つを作る人=C言語分かる人だと思うので、汎用のサンプルから最小構成を考えます。  開くなりなんかいっぱい書いてありますが、「最小構成」にはほとんど不要です。
    #include BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved){ return TRUE; } extern "C" { __declspec(dllexport) BOOL NScrPlugInMain (HINSTANCE hinstance,HWND hwnd,char *param,int *ret_int,char *ret_str); }; //ここが呼び出される関数 BOOL NScrPlugInMain (HINSTANCE hinstance,HWND hwnd,char *param,int *ret_int,char *ret_str) { //返す文字列のセット C言語のcharは操作がなんか死ぬほど不便で面倒くさいです //strcpy (ret_str,param); //strcpy (ret_str,"あいうえお"); //返す数字のセット //*ret_int=999; return FALSE; }
     >BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved){  > return TRUE;  >}  は削っても動いているような気がしますがやっぱりごめんわかんない。  旧サンプルは「受け取った文字(あれば)に基づき何らかの処理をする」「(必要なら)getretで受け取る戻り値を代入しておく」  という操作が想定されたものになります。  最小構成が見えましたね。 具体的な処理はググりながら頑張らざるを得ませんが……。
    環境整備、Visual Studioのインストールと設定
    まずVisual Studioをインストールします。このページを書いている時点ではVisual Studio 2015あたりが最新版です。  年月と共にバージョンは変わると思います。ググってください。 無料版があります、個人開発者など条件に合う人間向けには有料版相当の機能を持った無料版も提供されています。  無料版でもものすごい機能です、おまけに10GBくらいあります、覚悟してください。 10GBってやばくないですか?インストールできましたか? 『Atelier de Muguet 制作記録』の「DLL制作-コンパイル環境編」 に書いてある通りに設定しましょう。記事で使われているのは2008ですが、2015時点では同じ設定手順でちゃんと動きました。 なぜそのように設定する必要があるかはわかりません! プラグインとして完成したら画面上の方にある「dubug」を「release」に変更すると実行速度上がります。 あとはVisual Studioの指示(エラーメッセージ)に従えばいいと思います。僕にはわかりません。 とりあえず「数字変数にも型がいっぱいある(扱える最大値・最小値が違う)」「charはめんどくさい」を覚悟してGoogle片手に頑張りましょう。 僕はできれば二度と触りたくありません。 というか「dll」を生成可能な言語ならC言語以外でも構わないようです。 いわゆるプラグイン三大聖地のひとつであるsenzogawaさんはD言語を好んで使っていたようです。 Cより人間的な言語の選択肢は色々あるに違いありません。 これを読んだあなた。どうかプラグインを作ってください。それだけが私の望みです。