前々回で棒倒し法のソースをC# 5用にどう改造するか、前回の「無駄話」でエントリーポイントであるMain()の役割、使い方等について書きましたので、今回は棒倒し法迷路プログラムをC# 5用に動かすことが出来るようにします。

 

オリジナルのソースにはMain関数(おっと、「メソッド」だった!)がありません。これはC# 9以降用に省略しているのか、Visual Studioがコンソールプログラム用に自動作成しているのか分かりませんが、いずれにしてもC# 5には必要で、

(1)メインとなるMazeGenerator_Barクラスに入れるか、

(2)他のクラス(または構造体)に入れる

必要があります。

 

私はオリジナルを尊重し、クラス内のメソッドを増やさず、同じ名前空間の中に他の実行用クラスを追加しました。

 

【オリジナルのソース】

using System;

namespace ConsoleApplication1
{

//(解説:→ここに入れることとした。↓の【追加するMainメソッド用クラス】参照。)
    public static class MazeGenerator_Bar
    {
        // 通路・壁情報
        const int Path = 0;
        const int Wall = 1;

        // 棒倒し法による迷路生成
        public static int[,] GenerateMaze(int width, int height)
        {
(以下略)

 

【追加するMainメソッド用クラス】赤字追加、変更

///////////////////////////////////////////////////////
//迷路-棒倒し法
//参照URL:https://algoful.com/Archive/Algorithm/MazeBar
//静的クラス、関数等を止めています。
///////////////////////////////////////////////////////

using System;

namespace MazeSample    //解説:手書きにしたときにVisualStudio風のネーミングを「人間風」に変えました。
{
    public class Application  //解説:クラス名は重複しなければ、なんでも結構です。
    {
        public static void Main() 
//解説:Mainメソッドは常にstaticにしてください。
        {
            //解説:私は棒倒し法も非staticクラスにしているので次の通りにしています。

            //MazeGenerator_Bar mzBar = new MazeGenerator_Bar();
            //mzBar.DebugPrint(mzBar.GenerateMaze(20, 20));
            //解説:オリジナルでは、インスタンス名の代わりにstaticクラス名を使います。

            MazeGenerator_Bar.DebugPrint(MazeGenerator_Bar.GenerateMaze(20, 20));
            //解説:↑はGenerateMazeの戻り値をDebugPrintに渡している、ということです。
            Console.ReadKey();            //解説:コンソールウィンドウ消去防止用のメソッドです。
        }
    }

(以下は前々回通り)

 

さてこれを入れてコンパイルしてみましょう。今度はエラーが出ないと思います。(注)

注:ウェブのテキストがUnicodeで、ソースコードがSJISだと、Unicode→SJIS変換を行わないと余計な"00"が残っていてエラーになることがあります。

 

取り敢えず、棒倒し法は完了です。なお、アルゴリズムの解説はオリジナルソースのサイトで確認してください。(ブラウザーで棒倒し法の実行状況が見れますので、これが一番わかりやすいでしょう。)

 

この話題は「迷路」というよりもプログラム一般の問題なので、無駄話として書きます。(内容の一部は私の推論も含み、モニターやデバッガーで検証したわけではないのでご注意願います。)

 

1.CPU

最初にPCを買うと、PCについて調べ、その構成部品からCPU(Central Processing Unit)という「心臓」を発見します。(私の場合は8bitのIntel 8080互換のZilog 80でした。)そして、(ノイマン式の8bit)CPUは2進数1バイト(8bit:00000000<0>~11111111(255))をメモリーアドレスに沿って(CPUの内部にプログラムカウンターという機構があります)データを読み込み、それをopcode(注)、oprandとして解釈して、実行します。

注:「オプコード、オプランド」ではなく、日本ではOperationの略なので「オペコード、オペランド」と発音しますね。

 

このオペコード、オペランドを「機械語」(今でも使われているIntel 8086系の機械語例)とよび、特にオペコードは人間が直感的にわかるように「ニーモニック(mniemonic-記憶法)」という英数字の短い符号を使います。従って原始的プログラミングではメモリーに直接機械語をモニターやデバッガーで書き込む所、アッセンブラー(ニーモニックのテキストファイルを機械語に変換するソフトウェア)があれば、ニーモニックでプログラミングできます。

機械語(16進数-注): ニーモニック(補足)

89e2         mov dx, sp(スタックポインターspの値をCPUの記憶素子<レジスター>dxに代入する)

b80000        mov ax, 0000(CPUの記憶素子axに0000を代入する)

注:0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 10(10進数の16), ....

 

これで大分人間にやさしくなりますが、それでも一挙手一投足を記述するので相当なコード量になります。ニーモニックの定番処理をさらに「言語」としてまとめたものがC++やC#などの「プログラミング言語」です。

 

以上のことから、「プログラミング言語」で書かれた「プログラム(ある特定の仕事をする目的でメモリーに順序建てて並べられた機械語の集合体)」は「最初の一歩(その先頭アドレス)」が特定されなければならないことが分かります。

 

2.エントリーポイント

エントリーポイントとは、(exe、dll、その他実行ファイルだろうが)特定のプログラムが仕事を開始する「最初の一歩(入口)」のことで、プログラムが読み込まれてメモリーに展開された際に、Windows等「OSからコントロールを付与(注)」されるメモリーアドレスになります。

注:現在のマルチプロセスOSでは「OSからコントロールを受け取る、引き継ぐ」訳ではないので。16bit時代まではアプリケーションの実行により、OSから制御の手が離れたので「暴走」したり、「落ち」たりして、OSもハングアップしました。

 

C、C++のプログラマーだと、

 

  C  → int main(int argc, char** argv)

  C++→int main()等(main関数もオーバーロードがある)

  (ascii)int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)

  (unicode)int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)

BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason,  LPVOID lpvReserved)

 

というような関数を「すべての(Windows)プログラムに」含めるようにMicrosoftに言われています。

 

3.C#の場合

同様に同じC系のC#も、様々なオーバーロードがあるMain関数(注)があり、エントリーポイントになっており、クラスまたは構造体の中で宣言され、必ずstaticでなければならないとされています。

注:概要
  Mainメソッドは実行可能プログラムのエントリ ポイントであり、ここでプログラムの制御を開始および終了します。
  Mainは、クラスまたは構造体の内部で宣言されます。 Mainは
staticとする必要がありますが、publicである必要はありません。 (先ほどの例では、既定のprivate のアクセスを受け取ります)。外側のクラスまたは構造体は、staticである必要はありません。(略)

 

しっかし!

ご本家の代弁者としてMicrosoft Bingに確認を入れると次の通りの回答です。

 

Q:C#でMain関数を省略できますか?

A:「はい、C# 9 以降では、コンソールアプリケーションプロジェクトに Main メソッドを明示的に含める必要はありません。代わりに、最上位レベルのステートメント機能を使用し、記述しなければならないコードを最小限に抑えることができます1。また、C# 4.0 以降では、省略可能な引数を使用することで Main メソッドのシグネチャを簡素化することができます23。
2: C# プログラミング ガイド - C# | Microsoft Docs 1: 最上位レベルのステートメント - Main メソッドを使用しない | Microsoft Docs 3: [C# 入門] メソッドを呼び出す際に引数を省略可能にする | yaspage.com](https://yaspage.com/cs-method-optional/)」

 

なーんと、

Visual Studioがユーザーに代わってMain関数のコードを書いてくれるだけではなく、省略もできるんですね。でも、なんでstaticにする必要があるのでしょうか?

 

Q:何故C#のMain関数はstaticとしなければならないのですか?エントリーポイントのアドレスを固定する必要があるのでしょうか?

A:「C#の`Main`メソッドは、アプリケーションのエントリーポイントであり、プログラムの制御を開始および終了します。`Main`メソッドは、クラスまたは構造体の内部で宣言されます。`Main`メソッドは`static`とする必要がありますが、`public`である必要はありません。外側のクラスまたは構造体は、`static`である必要はありません。
C# 9 以降では、コンソールアプリケーションプロジェクトに `Main` メソッドを明示的に含める必要はありません。代わりに、最上位レベルのステートメント機能を使用し、記述しなければならないコードを最小限に抑えることができます。
また、C# 4.0 以降では、省略可能な引数を使用することで `Main` メソッドのシグネチャを簡素化することができます。

以上の理由から、C#の`Main`メソッドは`static`とする必要があります。

 

答えになっていねーよ!!!(怒)つかえねーな、Bingは!

 

ここからは私の想像ですが、ガーベージコレクション等高級言語機能のあるC#は「殆どOS」と言えるような(多くのDLLで提供される)「共通言語基盤(CLI)」を使ってプログラムが仕事をするのですが、ユーザープログラム(アプリケーション)をロードして呼び出すには矢張り固定エントリーポイントが必要です。C#ではクラスや構造体はプロセスに与えられたヒープ、スタック領域(注-前回巻末参考参照)に作られる(注)ということですが、「プログラムがメモリーにロードされ、起動されて初めてヒープ、スタック領域が決定されるが、プログラムが起動する為にはエントリーポイントから実行されなければならないし、その為にはエントリポイントが固定されていなければならない」のでエントリーポイントはヒープ、スタック領域に置くことはできず、「グローバルメモリーにあるクラス、構造体の定義領域」(そんなものがあるのかどうか、知らんけど)に置かなければならないからではないかと思量されます。

注:C#では、クラスは参照型変数としてスタック内のヒープに、構造体は値型変数としてスタックに置かれる、と解説されています。(総てスタックかと誤解していました...てへへ)

Bingは答えに「問いを含める」という愚挙を行ったので、ChatGPTはどうかな、と同じ質問をしてみました。

 

【ChatGPTの回答】

C#のMain関数がstaticでなければならない理由は、C#言語の仕様とアプリケーションのエントリーポイントとしての役割に関連しています。以下にその理由を説明します:
エントリーポイント:C#プログラムは通常、Main関数をエントリーポイントとして使用します。エントリーポイントは、プログラムが実行されるときに最初に呼び出される関数です。Main関数はプログラムが実行を開始する場所であり、C#コンパイラはこの関数を見つけて実行します。(C#コンパイラーが見つけて実行する、というのは変だが???)
インスタンスを作成せずにアクセス:Main関数は、プログラムが実行される前にクラスのインスタンスを作成しないで呼び出される必要があります。なぜなら、Main関数を呼び出す前にオブジェクトを作成するインスタンスメソッドが必要な場合、どのインスタンスを作成すべきかが不明瞭になります。staticメソッドはインスタンスを作成せずに呼び出すことができます。
クラスレベルのメソッド:Main関数はクラスのメソッドとして定義されるため、staticメソッドとして宣言される必要があります。これは、Main関数がクラスのインスタンスに依存しないことを示し、クラス名を使用して呼び出せることを意味します(これは「クラスインスタンスを作成しなくても呼び出せる」の方が分かりやすいでしょう)
以下は、Main関数の一般的な形式です:

csharp
Copy code
class Program
{
    static void Main(string[] args)
    {
        // プログラムのエントリーポイントとして実行されるコード
    }
}
要するに、C#のMain関数はstaticである必要があるのは、エントリーポイントとしての特別な役割と、クラスのインスタンスを作成しない必要(「しない必要がある」!=「する必要がない」だから、「クラスのインスタンスを作成する必要がない」が正しい)があるからです。

 

まぁ、突っ込みどころはありますが、Bingよりも数段上の回答ではないでしょうか?

 

御託が多かったのですが、早速始めましょう。

 

【以降で利用する開発環境とソース】

1.コンパイラー:Microsoft (R) Visual C# Compiler version 4.8.9032.0 for C# 5

2.エディター :サクラエディター

3.IDE     :MSCompAss Ver 1.6

4.ソース   :Algoful掲載「棒倒し法サンプル」(

:なぜか他のサンプルでは迷路クラスが非静的(「巻末参考」参照)で書かれているのに、棒倒し法だけ静的(static)クラスで書かれています。私はこのサンプルを動的に書き換えています。皆さんも課題として書き換えてみることをお勧めします。

 

using System;

namespace ConsoleApplication1
{
    public
static class MazeGenerator_Bar
    {
       
// 通路・壁情報
        const int Path = 0;
        const int Wall = 1;

       
// 棒倒し法による迷路生成
        public static int[,] GenerateMaze(int width, int height)
        {
           
// 5未満のサイズでは生成できない
            if (height < 5 || width < 5) throw new ArgumentOutOfRangeException();
            if (width % 2 == 0) width++;
            if (height % 2 == 0) height++;

           
// 指定サイズで生成し外周を壁にする
            var maze = new int[width, height];
            for (int x = 0; x < width; x++)
                for (int y = 0; y < height; y++)
                    if (x == 0 || y == 0 || x == width - 1 || y == height - 1)
                        maze[x, y] = Wall;
// 外周はすべて壁
                    else
                        maze[x, y] = Path;  
// 外周以外は通路

            // 棒を立て、倒す
            var rnd = new Random();
            for (int x = 2; x < width - 1; x += 2)
            {
                for (int y = 2; y < height - 1; y += 2)
                {
                    maze[x, y] = Wall;
// 棒を立てる

                    // 倒せるまで繰り返す
                    while (true)
                    {
                       
// 1行目のみ上に倒せる
                        int direction;
                        if (y == 2)
                            direction = rnd.Next(4);
                        else
                            direction = rnd.Next(3);

                       
// 棒を倒す方向を決める
                        int wallX = x;
                        int wallY = y;
                        switch (direction)
                        {
                            case 0:
// 右
                                wallX++;
                                break;
                            case 1:
// 下
                                wallY++;
                                break;
                            case 2:
// 左
                                wallX--;
                                break;
                            case 3:
// 上
                                wallY--;
                                break;
                        }
                       
// 壁じゃない場合のみ倒して終了
                        if (maze[wallX, wallY] != Wall)
                        {
                            maze[wallX, wallY] = Wall;
                            break;
                        }
                    }
                }
            }
            return maze;
        }

       
// デバッグ用メソッド
        public static void DebugPrint(int[,] maze)
        {

            Console.WriteLine($"Width: {maze.GetLength(0)}");
            Console.WriteLine($"Height: {maze.GetLength(1)}");

            for (int y = 0; y < maze.GetLength(1); y++)
            {
                for (int x = 0; x < maze.GetLength(0); x++)
                {
                    Console.Write(maze[x, y] == Wall ? "■" : " ");
                }
                Console.WriteLine();
            }
        }
    }
}

 

これをエディターにコピペし、"*.cs"ファイルに落としてコンパイルすると次のようなエラー(MSCompAssの出力)が出ます。

***** コンパイル開始 *****
Microsoft (R) Visual C# Compiler version 4.8.9032.0
for C# 5
Copyright (C) Microsoft Corporation. All rights reserved.

This compiler is provided as part of the Microsoft (R) .NET Framework, but only supports language versions up to C# 5, which is no longer the latest version. For compilers that support newer versions of the C# programming language, see http://go.microsoft.com/fwlink/?LinkID=533240


boutaoshi.cs(79,31): error CS1056: 文字 '$' は予期されていません。
boutaoshi.cs(80,31): error CS1056: 文字 '$' は予期されていません。

***** コンパイル終了 *****

 

このエラーは、ソースで赤色をつけたConsole.WriteLineメソッドの現在利用できる文字列補間の書式'$'がC# 5ではまだ利用できない(注)為に起こります。これについての説明は以下のサンプルが分かりやすいです。緑色の行は等価です。

注:調べてみたらC#6から利用可能になったようです。

【Microsoft Docsのサンプル】

var name = "Mark";

var date = DateTime.Now; // Composite formatting:

Console.WriteLine("Hello, {0}! Today is {1}, it's {2:HH:mm} now.", name, date.DayOfWeek, date);

// String interpolation:

Console.WriteLine($"Hello, {name}! Today is {date.DayOfWeek}, it's {date:HH:mm} now.");

// Both calls produce the same output that is similar to: // Hello, Mark! Today is Wednesday, it's 19:40 now.

 

従ってDebugPrintメソッドを次のように書き換えればよいことになります。

【C# 5用に書き換えた(注)DebugPrint】

注:赤字部分に対応する所を赤字で書いています。因みに maze.GetLength(0)と(1)は迷路を表す2次元配列mazeの幅(二次元配列の一番目要素)と高さ(二番目要素)を取得するメソッドです。添字が似通っているので誤解しないようにしましょう。

        //デバッグ用メソッド(旧いコンパイラーなので$を使えない)
        public void DebugPrint(int[,] maze) //→動的に書き換えているので、サンプルで動作するには"public static void"にする
        {
            
Console.WriteLine("Width: {0}\nHeight: {1}", maze.GetLength(0), maze.GetLength(1));
            for (int y = 0; y < maze.GetLength(1); y++)
            {
                for (int x = 0; x < maze.GetLength(0); x++)
                {
                    Console.Write(maze[x, y] == Wall ? "〇" : " ");    //オリジナルは"■" : " "で、半角になってしまう
                }
                Console.WriteLine();
            }
        }

 

こう書き換えてコンパイルすると、...あれれ、またエラー

"error CS5001: プログラム 'c:\Users\(略)\Maze\boutaoshi.exe' は、エントリ ポイントに適切な静的 'Main' メソッドを含んでいません"

が出ます。これについては(エントリーポイントとは何か、何故Mainメソッドが静的でなければならないかについても)次回!

 

【巻末参考:「静的」"static"の意味】

私も専門家ではないですが、矢張りこれはわかっていないと不味なので極々簡単に触れておきます。

 

コンピューターがお仕事をするとき、「プログラムの実行単位」としてプロセスがコンピューター(メモリー)内に確保されます。(所謂「マルチタスク」は、OSの管理の下で複数のプロセス(マルチプロセス)を走らせることです。)

この場合、プロセスは自分が使ってよいコンピューター資源(リソース)をOSから供与されますが、メモリーに関して各プロセスは概ね次のような形で使うことが出来ます。

(出典:ヒープとスタックの違いを解説 | プログライフ (proglife.net)

 

C++やC#などのC系の言語では、データ、手続きをまとめたクラスや変数は、そのインスタンス(実体)を宣言すると「スタック(先入れ先出し-FIFO-メモリー管理になります)」領域に動的に確保され、終了時にスタックから解放(要すれば上書き可能となる)されます。この為、都度生成されるインスタンスのメモリーは位置(メモリーアドレス)不定(可変)となります。

 

一方OSによって管理される(位置固定の)グローバル領域にもメモリーは確保(注)することが出来(グローバルメモリー)、その場合にはインスタンスや変数のデータや手続き(関数やメソッド)も位置固定となります。

注:C言語系の"static"修飾子の他、Cのmalloc関数、WinAPIのGlobalAlloc関数なども指定のグローバル領域メモリーを確保することが出来ます。

 

C#(やC++)において、このグローバル変数に置かれたインスタンスや変数のデータや手続き(関数やメソッド)を「静的(static)」とされ、スタックに都度積まれ、解放されるデータや手続きを動的(dynamicですが、そうは呼ばれておらず、「非静的」というようです。昔々のC言語では自動(auto)という修飾子が使われていました。)と言います。

具体的に違いは、「静的」クラス、変数のインスタンスはグローバル領域に一つだけ確保されるのに対して、「非静的」クラスや変数はいくつでも(メモリーが許す限り)スタックに生成させることが出来ます。

 

参考:C#のstaticとは?静的クラス・静的メンバ・静的メソッドの活用法 | コードカキタイ (one-div.com)

もう既にふれていたかもしれませんが、C(++)文法で書けるVisual Basic的高級言語であるMicrosoftのC#は以下のような発展の歴史があります。(Microsoftの.NET Framework→.NET Core→.NETへの発展と足並みをそろえています。)

C#の歴史(ご本家)

C#(Wiki)

 

C# 言語のバージョン管理

↑によれば、おおむね次の通りです。

移行先               Version     C# 言語の既定のバージョン
.NET                 7.x           C# 11
.NET                 6.x           C# 10
.NET                 5.x           C# 9.0
.NET Core         3.x           C# 8.0
.NET Core         2.x           C# 7.3
.NET Standard   2.1          C# 8.0
.NET Standard   2.0          C# 7.3
.NET Standard   1.x          C# 7.3
.NET Framework all          C# 7.3

 

本ブログで使っているC#は、WindowsのC:\Windows\Microsoft.NETのフォールダーに入って標準でついてくる()"C# 5"というもので、大分昔(2012~2013年)の版であることが分かります。

注:コンパイラー(csc.exe)は、

    C:\Windows\Microsoft.NET\Framework または

    C:\Windows\Microsoft.NET\Framework64

のフォールダーに、.NET Frameworkの1.x~4.x迄互換性の為にいくつかついてきますが、 最新のものがこれです。それぞれのバージョンを確認するには"csc.exe /?"というコマンドで実行してください。

<BCCSkeltonサンプルの"Command"で実行した写真>

このコマンドの実行結果の出力に”Microsoft (R) Visual C# Compiler version 4.8.9032.0 for C# 5”と書かれているのが分かると思います。(32bit、64bitともに変わらず。)

 

新しいC#を使いたい場合はVisual Studioをダウンロードすればよいのですが、何せでかいので「コンパクトな開発環境で先ずC#の基礎的なことを勉強したい」という方は「MSCompAss+C# 5」で十分、(本ブログで紹介しているような)アマチュアプログラマーが書ける面白いプログラムが作れます。

 

問題は、Visual Studioが自動的に生成してくれるコードも手作業で作成しなくてはならないことと最新のC#で書ける文法がコンパイラーを通らないことですが、現在の文法のプログラムを旧文法で書けないわけではなく、ご自身で書き直すことでプログラミングを学習することが出来ます。

 

今後は、迷路アルゴリズムのサンプルを公開されている参照サイト)のプログラムを基に、C# 5でコンパイルできるコードに変更する部分を解説してゆきたいと考えています。

注:このAlgofulというサイトには、詳細な知的財産権利用条件が記されていませんが、"© 2023 - Algoful"とありますので、日本の著作権法の適用を求めていることは明確です。コンピュータープログラムの著作権の保護範囲はこのような特殊性がありますので、以降のブログではプログラム本文はこのサイトのものを参照し、ここでは私の著作物である追加、変更部分のみを載せることにします。

 

さて、何にもプログラミングしないと、段々とプログラミングの虫がうずいてきます。

 

じゃぁ、何をしようか、と言っても余り関心があるテーマもないので、昔BCCSkeltonでプログラミングし()、結構好きなテーマである「迷路(Maze)」をネタにして少し連載してみようかな、と考えました。

注:"SampleBCCSkelton"というフォールダーの"Maze"というプロジェクト名のフォールダーにこのサンプルが入っています。これは今回取り上げる参照サイトのアルゴリズムを基に、C++で穴掘り法(後述)を使った迷路作成用のMAZEクラスや、MAZEクラスで使うCELLクラスやスタック(CELLSTACKクラス)を自作して再現したもので、1層だけの迷路を作り、WANDERERというクラスで作った人間(2Dの赤丸)に(「左手法」で)その迷路をさまよってもらって、SDIウィンドウに平面(2D)と立体(3D)で表示するようにしたものです。(いやぁ、60歳前半はまだ若かったんだな。今では「脳体力」がなく、ここまでできないですね。)

 

迷路作成には、「棒倒し法」、「穴掘り法」、「壁伸ばし法」という定番の方法があり、ウェブでも結構紹介しているサイトが色々とありますが、私はその中でもわかりやすく、C++に移植が簡単と思われた、きれいなC#プログラムを書かれていたこのアルゴリズム解説サイトを参照させてもらいました。

 

今回は元々C#で書かれているので、「MSCompAssで簡単にコンパイルして、さてそれから『何を書こうかな?』」と考えるところですが、実際に例示プログラムをコピーしてコンパイルすると幾つもの異なるエラーが出ます

 

神様は優しいので、最初からcsc.exeにエラーを出させて「何故エラー?」というブログネタを提供してくれたのでしょう。(要すれば、この参照サイトに載っているC#の例示プログラムは、Visual Studioで作成されていて、C#のバージョンも新しいもので書かれており、Windows 11に標準でついてくるcsc.exeではいくつものエラー()が出てしまいます。)

注:現時点で確認できたものは、先ずそもそも「Main関数がない」という非情なもの(Visual StudioではIDEが勝手にエントリーポイントとして使うMain関数を自動作成するので、ユーザーは書く必要がない)や、自動実装関連(所謂プロパティの"{ get; set;}"という、あれです)、コンソール出力関連(C#はコンソール出力にUnicode文字を使っていますが、Ascii文字等は自動的に半角になったりしてきれいに表示されません)等ですね。

 

ということで、先ずは参照サイトの例示プログラムを旧いバージョンのcsc.exeで動くようにして、それを使った迷路作成コンポーネント(DLL)で「何か」を考えてみようかな、と思っています。

 

まぁ、ボチボチやりましょか。

 

...だいぶ長い間Embarcadero C++でプログラム開発をしていません。最後にbcc32c.exeを使ってコンパイルしたのは今年の3月(半年以上前)のECCSkeltonを使ったRenamerで、その後は(C#での開発の為の)MSCompAssのバージョンアップだけだし。

 

まぁ、32bitプログラムコンパイルしかできないフリーのコマンドツール(注)では、もうフリーで64bitターゲットが作成できるVisual Studioが手に入る時代にはついていけないのかもしれませんね。事実、私自身がMSCompAssの開発後、64bitプログラムを求めてC#(csc.exe)を使っているくらいですから...

注:64bitコンパイラーは勿論C++ Builderにはあるみたい(bcc64.exe)ですが、それはフリーで入手できません。

 

実際、"Embarcadero C++"でググっても、ほとんどEmbarcaderoのサイトばかりで、昔のbcc55.exeの時代のように「フリーC++コンパイラーを使う為のマニア」は以前にも紹介したこのサイト以外ほとんど()見当たりません。(本サイトも引っ掛かってきませんし...)

注:「じゃ、どんな人が使っているのだろうか?」ということで調べてみたのですが、一橋、東海大の情報関連学科のサイトがあったので覗いてみると、入手・設定方法の説明以外は「お約束」の"Hello, World!"表示コンソールプログラムを載せている程度ですね。もう授業としてC++を学び、演習課題をプログラミングするだけで、趣味で色々なソフトを作ってみる、ということも、前に書いた理由からあまりしなくなったのかもしれませんね

 

まぁ、簡単なゲーム作成が一番手っ取り早いのですが、矢張り同期になりにくいんですね。その理由の一つに「ゲームソフトは使う必然性が低いのですぐに飽きちゃいますね。(ホビードローンと同じです。)」と書いたように、作ったものは「クソゲー」で余り面白くないんですね。本当に面白いのは「あーでもない、こーでもない、と困難にぶち当たりながら、四苦八苦して開発してゆくプロセス」にあるのかもしれませんね。しかし、欲しいツール等はフリーで簡単に手に入るようになってきたので、自分で苦労して開発する必然性もなくなってきている現状が最大の障碍になっているのかもしれません。要すれば...

 

もう人間がプログラミングする時代ではない

 

ということか?

 

ネタ探しの旅の途中で、(セキュリティに問題があった)Internet Explorerで使われていた昔のWebBrowserに代わって現在のEdgeで使われているWebView2コンポーネントのことを知りました。そこで、WebView2コンポーネントを使う簡単なサンプルを見つけてきてMSCompAssでコンパイルするとエラーが出ます。

 

最初はDLL読み込み文は書いたけど、

using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.WinForms;
これらのDLLへのパスが通っていないのではないか、と考えました。

それでExplorerで調べて、WebView2SDKのパスをMSCompAssに与えてコンパイルすると今度は、

fatal error CS0009: '(略)Microsoft.Web.WebView2.Core.dll' メタデータ ファイルを開けませんでした -- '間違ったフォーマットのプログラムを読み込もうとしました。 '

というエラーが出てコンパイルすることが出来ません。

どうもこのコントロール(DLL)は版の管理為、ユーザーサイドで固定版を管理するか、OSにエバーグリーン版を管理させるか、のいずれかの対処を採らなくてはならないようです。(知らんけど)ということでVisual Studioを使わないとちょっと無理かな。

 

そんな訳で又々CharGPT様に相談しました。()でもやっぱり結論は変わらないかな。

 

【ChatGPTの回答】

このエラーメッセージは、Microsoft.Web.WebView2.Core.dll メタデータファイルが正しく読み込めないことを示しています。このエラーを解決するために、以下のステップを試してみてください。

  1. WebView2 SDKのインストール: WebView2を使用する前に、正しいWebView2 SDKをインストールしていることを確認してください。WebView2 SDKは、Microsoftの公式ウェブサイトからダウンロードできます。最新バージョンをダウンロードし、適切にインストールしてください。

  2. プロジェクト設定の確認: プロジェクトの設定を確認し、正しいWebView2のバージョンを対象としていることを確認してください。また、プラットフォームターゲットと.NETのバージョンも適切に設定されていることを確認してください。

  3. NuGetパッケージの更新: ソリューション内のNuGetパッケージをアップデートして、WebView2関連のパッケージが最新であることを確認してください。

  4. ビルドキャッシュのクリア: ビルドキャッシュをクリアしてみてください。ビルドキャッシュを削除するには、プロジェクトの bin および obj フォルダー内の一時ファイルを削除します。その後、プロジェクトを再ビルドしてみてください。

  5. プロジェクトのクリーンと再ビルド: Visual Studioでプロジェクトをクリーンし、その後再ビルドしてみてください。これにより、一時ファイルやキャッシュが削除され、新しいビルドが実行されます。

  6. プラットフォームを確認: プロジェクトが正しいプラットフォームを対象としていることを確認してください。x86とx64のWebView2 SDKの違いに注意してください。

  7. ウイルス対策ソフトウェアのチェック: セキュリティソフトウェアがファイルの読み込みに干渉していないか確認してください。一時的にセキュリティソフトウェアを無効にしてみて、問題が解決するかどうか確認してみてください。

  8. IDEの再起動: Visual Studioや他のIDEを再起動してみてください。時々、IDE自体が問題の原因となることがあります。

これらのステップを試してもエラーが解決しない場合、問題が複雑である可能性があるため、Microsoftの公式サポートや関連するフォーラムでサポートを受けることを検討してください。

 

「あ~、ネタがない」

と言い続けて随分と経ちます。

現時点でこのブログもほぼ2年半。息切れしてくるのも仕方がないのでしょう。そうゆう時には、

「温故知新」(meaning "By lookin' back the things gone, you'll see the new.")

なんでしょうね。

 

(1)30代、8bit時代

当時としては珍しく、PC(MZ-2500)には標準でマウスポートが付いていたので、ROM BasicでAppleのMacintoshをまねたOSっぽい"Filer"、BDS-CではZ80チップ用のアッセンブラー(ZASM)を作ったのを覚えています。

(2)30代、16bit時代

米国でOIBM PC互換機を使っていた時は、仕事が忙しく、業務用のDBをdBase IVで組んだほかには、仕事を現地スタッフに教えるためのソフトをDOS5+QBasicで組みましたね。

(3)40代、16-32bit時代

日本にいるときはPC自作に凝って何も作りませんでした。シンガポールに行った時に、初めてまともなWIndowsソフトをプログラミングすることを覚え、このブログの主題であるBCCForm、BCCSkeltonライブラリーと関連ツールを作っていました。

(4)50代、32bit時代

何もしていません。

(5)60代、32-64bit時代

定年退職後、再度プログラミングを学習し、BCCForm and BCCSkeltonを(Unicode版のECCSkelton含む)更新、そのアプリケーションとして際にロハでついてくるC#、VB用のコンパイラーを利用するためのMSCompAssを作った際についでにC#を学習して、そのアプリを追加し、自分で常用する実用ソフト(注)も初めて作りました。

注:開発系ソフト以外の実用ソフトで一番使うのは、金融機関と一般のIDとパスワードを管理するBkAct、IDList、静止画用のAlbum、動画・音楽のDirectShow、ファイル管理用のRenamer、ReplacerとCardboardBoxですかね。環境ソフト的なものを含め、ゲームソフトは使う必然性が低いのですぐに飽きちゃいますね。(ホビードローンと同じです。)

 

こうしてみると、矢張り

1番-開発系(MS等大手がIDE等開発関連ソフトを無料化するまでは意味があったが、今は...)

2番-実用ツール(既に多数の優秀なフリーソフトが出回っている)

3番-ゲーム等エンタメ(ホビーで作るゲームなどはクソゲーと蔑まれる)

という感じですかね。

 

確かに昔はソート、サーチなどの様々なアルゴリズム系のライブラリーやサブシステムの需要がありましたが、(C#をやって分かったのですが)現在は殆どOSやそのDLLで供給されるので、既に需要はありませんね。(注)現在重宝されるのは「(サイズを問わず)データ」であり、「(自作を含むプログラムが提供する)処理」ではないかもしれません。

注:また、32bit→64bitへの移行が、昔のソフトを淘汰しており、32bit開発システムで作られてモノの寿命はあまりないかもしれません。

 

で、どうする俺?

前にC#でバイオリズムを表示するサンプルを書いてみました。

 

その時は何も考えずに日付入力をドロップダウンリストで作りましたが、年月が経つと新しい日付(例:2024年とか)が入力できないんですよね?(修正)それじゃ不味だろう、ということでこのダイアログを所謂MCコントロール(MonthCalender - MC)を使えるようにしようかと考えました。

修正この時のコードは↓の通りで、これからも使えますが100年以上のリストが出来ちゃいます。

            for(int i = DateTime.Today.Year; i >= 1923; i--)    //100年分リスト
            {
                yyList.Items.Add(i.ToString("D4"));
            }

これを↓のようにすると、これからも「100年リスト」として使えますね。

            for(int i = 0; i < 100; i++)    //100年分リスト
            {
                yyList.Items.Add((DateTime.Today.Year - i).ToString("D4"));
            }

 

しかし、このプログラムだけで使うのももったいないので、汎用の「日付入力ダイアログ」のサンプルを作り、それを使って書き換えられるようにしましょう。

内容はMicrosoft Docsのサンプルを参考にまとめたものですが、青字部分だけをDLLでコンパイルするといつでも呼び出せますよ。呼び出し方、使い方は赤字部分をご覧ください。

 

//////////////////////////////
// 日付入力ダイアログサンプル
// SampleMCDll.cs
//////////////////////////////
using System;
using System.Drawing;
using System.Windows.Forms;

namespace SampleMCDll
{
    public class AppForm : Form
    {
        private TextBox textBox;
        private Button ShowBtn, CloseBtn;
        [STAThread]
        static void Main() 
        {
            Application.Run(new AppForm());
        }

        public AppForm()
        {
            //ウィンドウサイズ、文字列の設定
            this.Size = new Size(320, 120);
            this.Text = "MonthCalendarを使った日付入力ダイアログサンプル";
            textBox = new TextBox();
            textBox.BorderStyle = BorderStyle.FixedSingle;
            textBox.Location = new Point(10, 10);
            textBox.Multiline = false;
            textBox.Size = new Size(this.ClientSize.Width - 20, 20);
            this.Controls.Add(textBox);
            //Showボタン
            ShowBtn = new Button();
            ShowBtn.Location = new Point(ClientSize.Width - ShowBtn.Width - 10, ClientSize.Height - ShowBtn.Height - 10);
            ShowBtn.Text = "Show";
            ShowBtn.AutoSize = true;
            ShowBtn.Click += ShowButton_Click;                            //クリック時ハンドラーを設定する
            this.Controls.Add(ShowBtn);
            //Closeボタン
            CloseBtn = new Button();
            CloseBtn.Location = new Point(10, ClientSize.Height - CloseBtn.Height - 10);
            CloseBtn.Text = "Close";
            CloseBtn.AutoSize = true;
            CloseBtn.Click += CloseButton_Click;                            //クリック時ハンドラーを設定する
            this.Controls.Add(CloseBtn);
        }

        private void ShowButton_Click(object sender, EventArgs e)
        {
            //日付入力ダイアログの生成
            SelectDate sd = new SelectDate(textBox.Text);
            if(sd.ShowDialog() == DialogResult.OK)
            {
                textBox.Text = sd.Date.ToString();
                MessageBox.Show(sd.Date.ToString(), "選択された生年月日", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
            sd.Dispose();

        }

        private void CloseButton_Click(object sender, EventArgs e)
        {
            DialogResult result = MessageBox.Show("終了しますか?", "確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if(result == DialogResult.Yes)
                this.Close();
        }
    }

    /////////////////////
    //日付入力ダイアログ

    //呼び出すときに日付文字列を渡すとその日付のある年を表示します。

    //引数無しで呼ぶと当日の年を表示します。
    /////////////////////
    public class SelectDate : Form
    {
        //日付出力用DateTimeクラスDateプロパティ
        private DateTime _date;
        public DateTime Date
        {
            get
            {
                return _date;
            }
        }
        //ダイアログコントロール
        Button OkBtn, CancelBtn;
        MonthCalendar monthCalendar;

        public SelectDate(string str = "")
         {
            //文字列引数を日付に変換できるか判定(できない場合は今日の日付を設定)
            if(!DateTime.TryParse(str, out _date))
            {
                _date = DateTime.Today;
            }
            // ダイアログボックス用の設定
            this.Text = "日付入力";
            this.MaximizeBox = false;                                //最大化ボタン
            this.MinimizeBox = false;                                //最小化ボタン
            this.FormBorderStyle = FormBorderStyle.FixedDialog;        //境界のスタイル
            this.StartPosition = FormStartPosition.CenterParent;    //親フォームの中央に配置
            this.ClientSize = new Size(556, 468);                    //ウィンドウ表示サイズ、領域、文字列の設定
            //Cancelボタン
            CancelBtn = new Button();
            CancelBtn.Location = new Point(10, ClientSize.Height - CancelBtn.Height - 10);
            CancelBtn.Text = "Cancel";
            CancelBtn.AutoSize = true;
            CancelBtn.Click += CancelButton_Click;                    //クリック時ハンドラーを設定する
            this.Controls.Add(CancelBtn);
            this.CancelButton = CancelBtn;                            //Esc キーで選択できるボタン
            //OKボタン
            OkBtn = new Button();
            OkBtn.Location = new Point(ClientSize.Width - OkBtn.Width - 10, ClientSize.Height - OkBtn.Height - 10);
            OkBtn.Text = "OK";
            OkBtn.AutoSize = true;
            OkBtn.Click += OkButton_Click;                            //クリック時ハンドラーを設定する
            this.Controls.Add(OkBtn);
            this.AcceptButton = OkBtn;                                //Enter キーで選択できるボタン
            //カレンダー
            monthCalendar = new MonthCalendar();
            //位置の設定
            monthCalendar.Location = new Point(10, 10);
            //色の設定
            //monthCalendar.BackColor = Color.GhostWhite;
            monthCalendar.BackColor = Color.Azure;
            //monthCalendar.BackColor = SystemColors.Info;
            //monthCalendar.ForeColor = Color.FromArgb(((Byte)(192)), ((Byte)(0)), ((Byte)(192)));
            monthCalendar.TitleBackColor = Color.DarkBlue;
            //monthCalendar.TitleBackColor = Color.Purple;
            monthCalendar.TitleForeColor = Color.Yellow;
            //monthCalendar.TrailingForeColor = Color.FromArgb(((Byte)(192)), ((Byte)(192)), ((Byte)(0)));
            //月表示を3段4列に設定
            monthCalendar.CalendarDimensions = new Size(4, 3);
            //週を月曜から始まるように設定
            monthCalendar.FirstDayOfWeek = Day.Monday;
            //最大選択日数を30日にする
            monthCalendar.MaxSelectionCount = 30;
            //矢印ボタンによる日付変更を12カ月にする
            monthCalendar.ScrollChange = 12;
            //「本日」を表示にする
            monthCalendar.ShowToday = true;
            //今日の日付に〇をつける
            monthCalendar.ShowTodayCircle = true;
            //週番号をつける
            monthCalendar.ShowWeekNumbers = true;
            //日付選択、変更時のハンドラーを設定する
            monthCalendar.DateSelected += new DateRangeEventHandler(this.monthCalendar_DateSelected);
            monthCalendar.DateChanged += new DateRangeEventHandler(this.monthCalendar_DateChanged);
            //指定日付で設定する
            monthCalendar.SetDate(_date);
            //monthCalendar.SelectionStart = monthCalendar.SelectionEnd = _date;
            this.Controls.Add(monthCalendar);
        }

        private void OkButton_Click(object sender, EventArgs e)
        {
            this.DialogResult = DialogResult.OK;        // Meaning true
            this.Close();
        }

        private void CancelButton_Click(object sender, EventArgs e)
        {
            this.DialogResult = DialogResult.Cancel;    // Meaning true
            this.Close();
        }

        private void monthCalendar_DateSelected(object sender, DateRangeEventArgs e)
        {
            _date = e.Start;
        }

        private void monthCalendar_DateChanged(object sender, DateRangeEventArgs e)
        {
            _date = e.Start;
        }
    }
}

 

前にちょっと触れましたが、今夏は神さんが一大決心をして、3回の眼科入院手術を受けることになりました。

 

2回は横浜にある病院で入院、1回は東京で入院です。(と言っても、第一回目は横浜で手術し一週間入院、週末のみ東京の自宅に戻り、翌週も横浜で入院。第二回目は東京にある病院で手術するのですが、入院病床がないので提携ホテル入院となり、頼み込んで在宅入院にしてもらいました。そして第三回目はまた横浜で一週間入院です。)この間、炊事、洗濯、掃除や、ゴミ出し、買い物等すべての家事を私が行い、横浜入院は自分のことだけやっておればよいのですが、横浜からの帰宅時と東京入院時は(目の見えない)神さんの介護を行うので結構大変でした。

 

今回の手術に至る経緯としては、先ず神さんは結構な近視でずーっと眼鏡、コンタクトのお世話になっていましたが、十年近く前に「目で見た画像が歪む」黄斑上膜変性が生じ、いくつかの病院や専門医の診断、セカンドオピニオンを経て、世田谷の総合病院で右眼の硝子体手術と同時に白内障手術を受けました。が、余り予後の大きな改善が見られず、落胆しておりました。今度は左目もそれらしくなってきたということで、手術後も多数の眼科医に診断や意見を聞いていたところ、左目の処置が単焦点レンズを装着したが視力が出ていない右眼をどうするかの問題に左右されることから慎重な判断を要することとなり、私も色々とwebで情報を採り、何度も夜遅くまで神さんとあーでもない、こーでもない、と論議することとなりました。

 

その結果、手術の内容や神さんの年齢からも今回が最後の処置となること、目的はQOLの向上に尽きることから(多焦点眼内レンズによる白内障手術が2020年3月末をもって先進医療から除外されたので)自由診療となっても左右共に信頼のおける眼科医による最高の治療を受けることにしました。

 

結果的に有形無形で結構な負担とはなりましたが、術後の経過は良好であり、「50年間ずーっと眼鏡を離せなかった生活が、裸眼で送れるようになる」と今後の視力改善に大きな期待を持っております。

 

そんなこんなで、第1回目の7月10日の手術からこの方家事に煩わされ、プログラミング等余暇活動があまりできませんでしたが、昨日の東京での通院で、9月に東京、横浜の2回通院すれば一応今回のプロジェクトが完了する見込みとなりました。

 

この1か月半以上の共同作業で、一段と夫婦の絆が強まったことが一番の収穫ですが、一方プログラミングにはあまり興味がわかなくなったのも事実であり、またもやネタ枯渇状態となっております。

 

まっ、いーか?

 

神さんがハッピーなら俺もハッピーだし。