最終更新日 2024-09-25

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;
  }
};