ScenarioModで.NET FrameWorkの混在

  • ~入門編~

    ScenarioModは、元々は天翔記やTSMod.dllと同じ、C/C++ ネイティブで「インラインアセンブラ+ Win32 SDK」で書かれていました。

    これらは、「極めて高速にキビキビ動作する」「CPU1命令を変更するような細かな処理が可能」という便利な一方で、
    Windows GUI ツール寄りの機能の作成には向かない、という弱点を持ちます。

    現在も、ScenarioModの中核の99パーセントはネイティブですが、
    .NET FrameWork隆盛の時代に合わせ、混成のCLR(共通言語ランタイム)(C++/CLI)となりました、

    このようにして、ScenarioModは現在では.NETを利用できるため、WindowsのGUI関連の記述もある程度書きやすくなっています。

    では、具体的に見ていきましょう。

  • 確認のためのメニューの追加

    まず、.NET FrameWork うんぬんの前に、確認作業等をしやすくするため、独自の起動メニューを用意します。
    ScenarioModに元々存在する、On_起動時()メソッドと、On_アプリケーションメニュー選択時メソッドを利用すれば、実現できます。

    .NET FrameWork の機能を試す前段階として、天翔記のアプリケーションメニューに独自のメニューを追加しましょう。

    #include "カスタム駆動関数.h"
    
    
    int iフォーム起動用メニューID = -1;
    
    void カスタム::On_起動時() {
        iフォーム起動用メニューID = アプリケーション::メニューアイテム追加("フォーム起動用");
    }
    
    // 下の方のメソッド
    void カスタム::On_アプリケーションメニュー選択時(int メニュー番号) {
        if ( メニュー番号 == iフォーム起動用メニューID ) {
            デバッグ出力 << "フォーム起動!!" << endl;
        }
    }
    

  • Windowsフォームを表示してみよう!

    C++/CLI のうち、CLIを使う部分は、C#に近い書き方となります。

    以下は、ScenarioModでフォームを表示する、最もミニマムなサンプルとなりますので、
    基本的には、このパターンに立ち返れば良いでしょう。

    慣れてくれば、.NET FramWorkのサンプルが多いC#で検索し、
    それをC++/CLIで書いたらこうだな
    とすぐに変換できるようになります。

    さて、.NET FrameWork のクラスで作成した変数は、直接グローバル変数には出来ません。
    C#やJavaを知ってる方なら納得できるでしょう。

    そこで、どこかのクラスのstatic 変数に値(実際には参照)を持っておきます。

    天翔記が終わるまでにフォームを閉じることを忘れないようにしましょう。

    例題が冗長になるため、処理をしていませんが、
    実際には、「MemoryEditorFormが複数起動しない」ように制御する必要があります。

    #include "カスタム駆動関数.h"
    
    using namespace System; // .NET FrameWork
    using namespace System::Windows::Forms;
    
    // メモリエディタフォームクラス。Windowの.NETのFormクラスを継承する。
    ref class MemoryEditorForm : public Form {
    
    public:
        MemoryEditorForm() {
            this->Width = 500;
            this->Height = 300;
            this->Text = "MemoryEditor";
        }
    };
    
    // MemoryEditorForm型のグローバル変数。C++/CLIのクラス型のオブジェクトはグローバル変数に出来ないのだ。このためScenarioModからグローバル変数的に扱えるようにするため。
    ref class GlobalVariable {
    public:
        static MemoryEditorForm^ form;
    };
    
    
    int iフォーム起動用メニューID = -1;
    
    void カスタム::On_起動時() {
        iフォーム起動用メニューID = アプリケーション::メニューアイテム追加("フォーム起動用");
    }
    
    void カスタム::On_終了時() {
        // 天翔記終了時には、閉じる必要がある。
        // 閉じないと、「天翔記終わったのにぼくちゃんどのメモリに存在するの? ねぇねぇ?」といった困ったことになり不正終了間違いなしとなる。
        GlobalVariable::form->Close();
    }
    
    
    
    void カスタム::On_アプリケーションメニュー選択時(int メニュー番号) {
        if ( メニュー番号 == iフォーム起動用メニューID ) {
            デバッグ出力 << "フォーム起動!!" << endl;
            // メモリエディタ型のオブジェクトを作成。 .NET型のオグジェクトは原則このように「gcnew」というガベージコレクト付きのnewをする。
            MemoryEditorForm^ mef = gcnew MemoryEditorForm();
            // 他のScenarioModのメソッドから見れるように、グローバル変数に入れる。
            GlobalVariable::form = mef; // GlobalVariableに入れておく。
            // フォーム起動(モーダレス)
            mef->Show();
    
        }
    }
    


  • ボタンを追加して、イベントハンドラを知る

    ここから先は、「MemoryEditorForm」のクラスの中だけが変更対象となりますので、そこだけのソースを示します。

    イベントハンドラとは、「マウスを押した時」とか「テキストが編集された時」など、「なになになった時」、
    に実行する処理のことです。


    以下では、ボタンをクリックした時に、ModDebuggerに文字列を出力しています。

    このMemoryEditorFormに、Buttonを1つ乗せ、イベントハンドラを結びつける形が、C++/CLIやC#の骨格です!!
    このパターンさえ覚えておけば、「複雑な画像描画を伴わないもの」であれば、
    かなり込み入ったツールであっても作成できると考えてよいでしょう。

    ref class MemoryEditorForm : public Form {
    private:
      Button^ button;
    public:
      MemoryEditorForm() {
        this->Width = 500;
        this->Height = 300;
        this->Text = "MemoryEditor";
    
    
        button = gcnew Button();
        button->Text = "ボタン";
    
        // ボタンが押された時、というイベントハンドラ。
        // ボタンがクリックされた時に実行するメソッドを登録する。
        button->Click += gcnew EventHandler(this, &MemoryEditorForm::button_OnClick);
    
        this->Controls->Add(button);
      }
    
    private:
      void button_OnClick(Object^ sender, EventArgs^ e) {
        デバッグ出力 << "クリックした!!" << endl;
      }
    };