さて、前回の最後に「『このMaze_DLL.dllとTestMaze.exeを機能改良する例』としてもう一回程度お付き合い下さい」と書いたので、追加仕様を実装しようとして結構苦戦しました。
すったもんだの挙句、何とか完成したので、変更点についてコメント、解説してゆきます。

【変更点-ファイル:Maze_DLL.cs】
<追加>
using System.IO;        //StreamReader/Writerを使う為
using System.Text;     //Encoderを使う為
//解説:これはファイルを扱う為に必須でした。

<追加>
/////////////////
//迷路関連データ
/////////////////
//迷路データの中断ファイル

const string mzFileName = "Maze.tmp";
//解説:C++の"define"による定数定義の代わりに、C#では変更できないconst変数を使います。

//迷路探索者の進行方向
public int mzWhereToGo;        //上-0、右-1、下-2、左-3

//解説:最初「名無し(private)」にして、初期設定を1(右)にしていましたが、中断再開時には異なることもあることから、publicに変更し、初期値はコンストラクターで明示的に決定するようにしました。しかし、このメンバーに直接アクセスしたらコンパイラーに怒られたので別途アクセスメソッドを設けたため、privateのままでよかったように思います。

<追加>
////////////////////////////////////////////////////
//コンストラクタ(引数がない場合の初期値は41 x 41)
////////////////////////////////////////////////////
//迷路情報を初期化

if(!IniMaze())    //中断用設定ファイルがあればそれを使う
{    //なければオリジナル通りの設定となる
    //迷路サイズの設定
    mzWidth = width;
    mzHeight = height;
    MazeData = new int[mzWidth, mzHeight];
    //迷路探索者の初期位置
    mzWhereIam = new Cell();
    mzWhereIam.X = 1;
    mzWhereIam.Y = 1;
    //迷路出口の初期位置
    mzExit = new Cell();
    mzExit.X = mzWidth - 2;
    mzExit.Y = mzHeight - 2;
    //迷路内の進行方向
    mzWhereToGo = 1;    //初期値は右(1)
}
//迷路作成用Cellリスト(解説:上記の条件式から外した)
StartCells = new List<Cell>();

//解説:IniMazeというプログラム中断時のパラメーターを記録したファイル(中断ファイル)を読みだして設定するメソッドを追加したので、それで設定できない場合のみオリジナル仕様の設定(↑)を行い、進行方向の初期設定も行うことにしました。なお、いずれもStartCellsリストは作るのでこれは外出ししました。

<追加>
///////////////////////////
//Mazeの進行方向を取得する
///////////////////////////

public int GetDir()
{
    return mzWhereToGo;
}
//解説:もともとは常に白紙で、右向きで発進する仕様だったのですが、中断ファイルで右以外の方向もあり得るようになったので、↑で「別途アクセスメソッドを設けた」と書いたように、Mazeコントロールを使うアプリが進行方向のデータをMazeから取得できるようにこのメソッドを追加しました。

<追加>
///////////////
//迷路生成処理
///////////////

public int[,] GenerateMaze()
{
    //既に壁データがあれば戻る(解説:中断ファイルを読み込んだ場合は、新たな迷路は作らない為)
    if(MazeData[0, 0] == Wall)
        return null;
//解説:オリジナル仕様では毎回Mazeコントロール(クラスインスタンス)が生成されるたびに一回限りの迷路を作っていたのですが、中断する場合は新規作成する必要がないので、「(常に存在する)左上の壁部分があれば何もしない」で戻る仕様にしました。

<追加>
/////////////////////////////
//Maze.tmpファイルを読み込み
//迷路を初期化する
/////////////////////////////

private bool IniMaze()
{
    string data = "";
    //ファイル(UTF-8:Encoding.UTF8)を読む
    try
    {
        using(StreamReader file = new StreamReader(mzFileName, Encoding.UTF8))
        {
            data = file.ReadToEnd();
            // ファイルを閉じる
            file.Close();
        }
    }
    catch(Exception e)    //FileNotFoundException e
    {
        //Debug用-引数のeを使わないので、コンパイラーから警告が出されます。
        //MessageBox.Show("エラーコード:" + e.ToString(), "初期化ファイル読み込みエラー", MessageBoxButtons.OK, MessageBoxIcon.Error);

        return false;
    }
    //文字列の行分割
    string[] lines = data.Split(new[]{"\r\n","\n","\r"},StringSplitOptions.None);
    //第1行(迷路の幅と高さ)の語分割
    string[] widthheight = lines[0].Split(',');
    if(!Int32.TryParse(widthheight[0], out mzWidth))
    {
        MessageBox.Show("mzWidthの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return false;
    }
    if(!Int32.TryParse(widthheight[1], out mzHeight))
    {
        MessageBox.Show("mzHeightの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return false;
    }
    //迷路の作成
    MazeData = new int[mzWidth, mzHeight];
    //第2行(UNIT、mzIs3D、mzWhereToGo)の語分割
    string[] others = lines[1].Split(',');
    if(!Int32.TryParse(others[0], out UNIT))
    {
        MessageBox.Show("UNITの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return false;
    }
    if(!Boolean.TryParse(others[1], out mzIs3D))
    {
        MessageBox.Show("mzIs3Dの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return false;
    }
    if(!Int32.TryParse(others[2], out mzWhereToGo))
    {
        MessageBox.Show("mzWhereToGoの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return false;
    }
    //第3行(迷路探索者の位置)の語分割
    string[] where = lines[2].Split(',');
    mzWhereIam = new Cell();
    int x, y;    //TryParse用変数
    if(Int32.TryParse(where[0], out x))
        mzWhereIam.X = x;
    else
    {
        MessageBox.Show("mzWhereIam.Xの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return false;
    }
    if(Int32.TryParse(where[1], out y))
        mzWhereIam.Y = y;
    else
    {
        MessageBox.Show("mzWhereIam.Xの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return false;
    }
    //第4行(迷路出口の位置)の語分割
    string[] exitpos = lines[3].Split(',');
    mzExit = new Cell();
    if(Int32.TryParse(exitpos[0], out x))
        mzExit.X = x;
    else
    {
        MessageBox.Show("mzExit.Xの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return false;
    }
    if(Int32.TryParse(exitpos[1], out y))
        mzExit.Y = y;
    else
    {
        MessageBox.Show("mzExit.Yの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return false;
    }
    //第5行以降(迷路データ)の語分割
    for(int i = 0; i < mzHeight; i++)
    {
        string[] dt = lines[4 + i].Split(',');
        for(int j = 0; j < mzWidth; j++)
        {
            if(!Int32.TryParse(dt[j], out MazeData[i, j]))
            {
                MessageBox.Show("MazeDataの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }
        }
    }
    return true;
}
/////////////////////////////
//Maze.tmpファイルを書き込み
//中断する処理
/////////////////////////////

public void Abort()
{
    //第1行(迷路の幅と高さ)
    string data = mzWidth.ToString() + "," + mzHeight.ToString() + Environment.NewLine;
    //第2行(UNIT、mzIs3D、mzWhereToGo)
    data += UNIT.ToString() + "," + mzIs3D.ToString() + "," + mzWhereToGo.ToString() + Environment.NewLine;
    //第3行(迷路探索者の位置)
    data += mzWhereIam.X.ToString() + "," + mzWhereIam.Y.ToString() + Environment.NewLine;
    //第4行(迷路出口の位置)
    data += mzExit.X.ToString() + "," + mzExit.Y.ToString() + Environment.NewLine;
    //第5行以降(迷路データ)
    for(int i = 0; i < mzHeight; i++)
    {
        int j = 0;
        while(j < mzWidth - 1)
        {
            data += MazeData[i, j].ToString() + ",";
            j++;
        }
        data += MazeData[i, j].ToString() + Environment.NewLine;
    }
    //ファイル(UTF-8:Encoding.UTF8)を書く
    using(StreamWriter file = new StreamWriter(mzFileName, false, Encoding.UTF8))
    {
        //ファイルを書く
        file.Write(data);
        // ファイルを閉じる
        file.Close();
    }
}
/////////////////////////////
//Maze.tmpファイルを削除する
/////////////////////////////

public void DeNovo()
{
    //ファイルの削除
    File.Delete(mzFileName);
}

//解説:この3つの中断ファイル操作メソッドは「中断ファイルを読む(IniMaze)」、「中断ファイルを作成する(Abort)」、「中断ファイルを削除する(DeNovo-注)」動作をします。やっている内容は、極めてプリミティブな「変数数値の文字列化/文字列の数値化とファイル読み込み/書き込み」だけで、(ファイルを扱う場合はエラーが生じることがあるのでusingやtry~catch~finallyを使うこと位が注意点で)使用されているメソッドも特殊なものはありませんので、コメントを読んでいただければわかると思います。

注:ラテン語で「新たに」「再び」という意味です。

なお、ファイルが書き込む内容は、
 第1行:迷路幅、迷路高さ
 第2行:UNIT値、表示方法(2D/3D)、進行方向
 第3行:迷路探索者の位置
 第4行:出口の位置
 第5行~:迷路配列(MazeData)のWall(1)とPath(0)のデータ
です。以下の例を参照してください。(勿論暗号化したり、バイナリーで見えないように書いてもよいのですが、遊びのサンプルなので、これをいじくってチートできるようにテキストにしていること、前に書いた通りです。)
【初期状態のMaze.tmpの例】
41,41
16,True,1
1,1
39,39
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1
1,0,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1,1,0,1,0,1
1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1
1,1,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1
1,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,0,0,1
1,0,1,1,1,0,1,1,1,0,1,1,1,0,1,0,1,1,1,0,1,0,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,1,1,0,1
1,0,1,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,1
1,0,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1,0,1,1,1,0,1,0,1
1,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,1,0,1
1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,0,1
1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,1
1,0,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,1,1,0,1,1,1,0,1,0,1,1,1,1,1,1,1,0,1,0,1,1,1,0,1
1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,1
1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,0,1,0,1,1,1,1,1,1,1,0,1,1,1
1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1
1,0,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1
1,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,1
1,1,1,1,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,0,1,0,1
1,0,0,0,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,1,0,1,0,1,0,1,0,1
1,0,1,0,1,1,1,0,1,0,1,0,1,1,1,1,1,1,1,0,1,1,1,0,1,0,1,1,1,0,1,0,1,0,1,0,1,0,1,0,1
1,0,1,0,0,0,0,0,1,0,1,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,1
1,0,1,1,1,1,1,1,1,0,1,0,1,0,1,1,1,0,1,1,1,1,1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1
1,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0,0,0,0,0,1,0,0,0,1
1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,1,1,0,1,0,1,0,1,0,1,1,1,1,1,0,1,0,1,1,1
1,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,1,0,1,0,1,0,1,0,0,0,1,0,1,0,0,0,1
1,0,1,0,1,0,1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,1,1,1
1,0,0,0,1,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,1,0,1,0,1,0,1,0,0,0,0,0,1
1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,0,1,0,1,1,1,1,1,1,1,0,1,0,1,1,1,0,1,1,1,1,1,0,1
1,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,1
1,0,1,0,1,0,1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1
1,0,1,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,1,0,1
1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,0,1,0,1,1,1,0,1,0,1,1,1,0,1
1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,1,0,1,0,0,0,0,0,1,0,0,0,1,0,1
1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,1,1,1,1,1,1,0,1,1,1,0,1
1,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1
1,0,1,1,1,1,1,1,1,0,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,1,1,0,1,0,1,1,1
1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,0,1,0,1,0,1
1,1,1,1,1,0,1,0,1,1,1,1,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,0,1
1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,1
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1


【変更点-ファイル:TestMaze.cs】
Maze_DLLの変更により、アプリケーション(以下アプリ)も変更が必要です。

<追加>
//迷路の初期化

private void InitMaze()
{
    if(maze != null)
        maze.Dispose();
    maze = new Maze();                //初期値の41 x 41の迷路で、660 x 660のサイズとなる
    maze.GenerateMaze();            //迷路を作る
    maze.Display(Is3D, WhereToGo = maze.GetDir());    //Mazeコントロールの進行方向を継承して、迷路を表示する

//解説:Mazeコントロールから進行方向の値を承継します。Maze.tmpファイルがない場合は初期値(右-1)となります。
    maze.Location = new Point(10, 10);
    maze.Anchor = (AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Bottom | AnchorStyles.Right);
    this.Controls.Add(maze);
}
//解説:先ず、オリジナルでは進行方向の初期値が右だったので、アプリの進行方向データも初期値を右にしていましたが、今回の変更により、毎回変わる可能性があるので、コントロールの初期値を取得する(WhereToGo = maze.GetDir();)仕様にしました。

<追加>
//終了処理

protected override void OnFormClosing(FormClosingEventArgs e)
{
    base.OnFormClosing(e);
    DialogResult dr = MessageBox.Show("終了しますか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
    if(dr == DialogResult.Yes)
    {
        dr = MessageBox.Show("中断して、後で再開する予定ですか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
        if(dr == DialogResult.Yes)
            maze.Abort();    //中断ファイルを作成する
        else
            maze.DeNovo();    //中断ファイルを削除する
    }
    else
    {
        dr = MessageBox.Show("新しい迷路にしますか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
        if(dr == DialogResult.Yes)
        {
            e.Cancel = true;
            maze.DeNovo();    //中断ファイルを削除する
            InitMaze();
        }
        else
            e.Cancel = true;
    }
}

//解説:最初は「はい(Yes)、いいえ(No)、中断(Abort)」のボタンを使って「三択」にしようと思っていたのですが、C#ではないのと、今までは「単純に今やっている迷路探索を続ける」選択肢が無かったので、「四択」にしました。
最初に「終了するか、否か」聞いてきますので、これに「はい」を押すと、次に「中断後再開するか、否か」着てきます。

 実行終了(1)-中断すると中断ファイルが作られ、次のプログラム実行で読み込まれる。
 実行終了(2)-完全終了すると存在する中断ファイルも削除され、次のプログラム実行では新規迷路となる。
終了しない(「いいえ」)を押すと、そのまま継続するか、新規迷路にするか聞いてきます。

 実行継続(1)-中断ファイルを削除して、Mazeコントロールを更新するので、新規迷路でプログラムを継続することなる。
 実行終了(2)-何もしないので現在の迷路を継続する。


以上でした。自分で動かしていると、41 x 41の迷路でも結構時間がかかるので、途中で中断せざるを得ない局面がままあったことから、この改造を行いました。まぁ、仕様を変更してこっちのプログラムを組みなおすと、あっちも手を入れなくてはならない、というように、DLL、アプリ共に見直しが必要で結構手がかかることが分かりました。(爆)

ではでは、

これにて一件落着。