「GraphMaker」の続編かと思わせておいて、肩透かしを食らわせてすみません。

実はGraphMakerを作っていて、「べき乗」をあらわすための「上付き文字」や「ルート記号」を書きたかったのですが、それらを入れた文字列、

 

    WCHAR str[] = L"y = ax + b\r\ny = ax² + bx + c\r\ny = ax³ + bx² + cx + d\r\ny = +-√(ax² - b)";
 

を書くことはできるのですが、それを普通のテキストファイル(Shift JIS)でセーブすると文字化けを起こして保存できません。

 

    WCHAR str[] = L"y = ax + b\r\ny = ax? + bx + c\r\ny = ax? + bx? + cx + d\r\ny = +-√(ax? - b)";

 

しかし、今までbcc32c.exeにはShift JIS(要すればANSI)コードしか食わせたことがなく、果たして、

「Windowsのネイティブな文字コード(WCHAR)であるUTF-16や(今やネットの標準である)UTF-8の形式で保存したテキストファイルをコンパイラーが正しく処理できるのか?」

という疑問が生じました。(正しく読み込めなければ、キーワードやトークンであればシンタックスエラー等になり、コンパイルできなくなることが予想されます。)

 

When doubt, check it!

 

の方針に基づき、ウィンドウベースでユニコード記号を使った簡単なサンプルプログラムを作り、

 

/////////////
// Test.cpp
/////////////
#define        STRICT
#include    <windows.h>
#include    <wchar.h>
#include    <string.h>

////////////////
// WinMain関数
////////////////
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPWSTR lpCmdLine, int nCmdShow) {

    WCHAR str[] = L"y = ax + b\r\ny = ax
² + bx + c\r\ny = ax³ + bx² + cx + d\r\ny = +-(ax² - b)";
    MessageBoxW(0, str, L"Unicode 文字列表示テスト", MB_OK | MB_ICONINFORMATION);

    return 0L;
}

 

bcc32c.exeに喰わせてみました。その結果、

【Shift JIS】

当たり前ですが文字化けします。(ルート記号はSJISも使えるのか、パスしますね。)

 

【UTF-8】

やたーっ!!ちゃんとコンパイルできました!なので、Unicode記号を使った文字列を有するプログラムはUTF-8形式でセーブすればbcc32c.exeでコンパイル可能です。しかし、...

 

【UTF-16】

UTF-16は失敗しました。膨大な量の「~」文字が表示され、一見1、7、9、11、13行に不正文字を使用したように見えますが、それらは↑のソースにある通りコメント記号部分等ですし、5つのエラーで「7つのエラー」と表示されるのもおかしいです。まず、bcc32s.exeはUTF-16ファイルは使えない、と考えた方がよいでしょう。

 

尚、GraphMakerを作成するときは、このようなテストを行っておらず、Shift JISファイルで開発したので、ユニコードの文字化け対策として「文字列をコードで入力」しました。

例:    WCHAR expStr2[21] = {0x0079, 0x0020, 0x003D, 0x0020, 0x0061, 0x0078, 0x00B2, 0x0020, 0x002B, 0x0020, 0x0062, 0x0078, 0x0020, 0x002B, 0x0020, 0x0063, 0x0000};    // WCHAR expStr2[21] = "y = ax² + bx + c";

 

ps. 因みに、「ご本家MicrosoftのC#コンパイラーはどうかな?」と思い、簡単なフォーム(ウィンドウ)を表示するプログラム(Form.cs)をUTF-8とUTF-16形式で保存してコンパイルしましたが、流石Microsoft、何の苦も無くコンパイルできました。

 

前回、【GraphMaker】ボチボチと説明してゆきますで仕様の概要みたいなものを示しました。これからはやや子細に開発の様子を説明してゆきます。

 

BCCFormから始まったBCCSkelton(Unicode版がECCSkelton)プログラミングは、まずBCCFormによるリソースの作成が出発点です。が、今回はダイアログベースのアプリで、ツールバーやTBEditorで作る16 x 15()のビットマップを使っていないので、唯一のイメージリソースであるプログラムアイコンの作成プロセスを紹介します。

:今では普通に16x16とか32や64までありますが、20年前の昔、MSN(Microsoft Network)にはツールバーに張り付けるビットマップは「16 x 15」と書かれており、TBEditorはこれを忠実に守ったものです。なお、TBEditorはその開発で初めてImageListコントロールを知り、CIMGLISTクラスを作って初めて実用化したツールプログラムです。特にイメージをクリックしてドラッグしてゆくのは楽しい光景です。

 

まずアイコン(*.icoファイル)は身近なものであり、よくわかっているつもりでしょうが、実は*.icoファイルにはいくつものビットマップ等の画像が含まれております。例としてフリーゲーム、「魔女の家」のアイコンをサンプルプログラムのIconViewerで示します。

このiconファイルにはサイズ(16 x 16~48 x 48迄)、色表示(画素の表示可能色数)の画像(例ではBitmap)データが入っています。その理由は、アイコンを使用するコンピューター環境で使用できる最適な画像が選択されるようにアイコンファイルが設計されているからです。(といっても、現在は昔のようなモノクロ 640 x 480 画素のPCとかはもう無いでしょうが...)

いつもはよくBCCForm and BCCSkeltonの統一icon)を使うのですが、今回は関数グラフをイメージしたものを作ってみました。

:このアイコンは最初にBCCFormを開発した際に使用した統一環境であるBCC Developerに敬意を表し、BCC DeveloperとともにBCC55のフリー開発環境開発の末席を汚して参加したいという気持ちから、BCC Developerの開発者、三浦様のご了解をいただき、以降BCCForm and BCCSkelton関連ソフトで使用させていただいております。

 

まず、アイコン用の画像を一つ以上ビットマップで用意します。今回は同じくサンプルプログラムのEZImageを使って16 x 16と32 x 32の 24bit 色のビットマップを用意しました。

 

そしてそれら二つのビットマップをIconViewerの「ビットマップの追加(ボタン)」で読み込ませて、"GraphMaker.ico"というアイコンファイルを作ります。

 

これでプログラムアイコンの完成です。次回はメインとなるダイアログを作りましょう。

 

前回、(他愛もないのですが)ダイアログベースの中坊の数学に出てくる関数グラフを表示するようなCPICBOXクラスコントロールのデモンストレーションプログラム()を企画し、スケルトンを作成してみました。そしてその基本仕様として、

(1)「式の種類」コンボボックスで好きなグラフ(といっても一、二次方程式や円程度ですが)を選択すると

(2)項の定数a, b, c, d用エデイットボックスをEnableWindowでいじり、入力を可能にさせ、

(3)グラフ表示ボタンを押すと描画(項の値がおかしいまたは実数解が無いとエラー)

(4)グラフは重層化描画させて、背景色、前景色、消去は右クリックのポップアップメニューで指定する。

を上げました。

:名付けて、GraphMakerとします。

 

本日、どうもGraphMakerが完成したみたいなので(この後も若干手直しが入るかもしれないことを考え)年末までゆるり()と解説してみたいと考えております。

:この「ゆるり」は、年末までの2Wの尺に合わせて、20年前に開発したBCCFormでのダイアログレイアウトから、SkeltonWizardの生成コード、それを基にしたEmbarcadero C++コンパイラ―用のECCSkeltonを利用(注の注)する為にBCC2ECCによる変換コード等を解説することを考えております。

注の注:↑の「式の種類」を見ていただきたいのですが、式には上付きのべき乗の「2」やルート記号などが使われておりますが、これらはUnicode文字です。Unicode用のですが、Shift JISコードでソースコードを書くECCSkeltonでどのようにUnicode文字を文字列として記載するのか、等「苦労話」もお聞きください。

 

MSCompAssを作ってから、ここのところC#の学習、ResWriterとResReaderの作成でC#に嵌り、本題のBCCSkeltonやUnicode版のECCSkeltonに触っていませんでした。では、久々になんか作ろうか?と思ったのですが、矢張りネタ切れです。

 

そんな時に昔見ていたActiveBasic(注)関連サイトを再訪し、昔の暇プロが何を作っていたかを見てヒントを得ようとしました。

注:元々はNECのN88互換Basicだったのですが、インタープリターでもWindowsを扱えるようになり、(当時のVisual Studioが10万程度するような高額な開発環境に対する挑戦として)Win32プログラミングが可能なコンパイラーに発展しました。しかし、台頭するネット技術にMSが変化するにつれ、フリーソフトの開発リソース不足なのかついて行けなくなり、現在はほぼ「シャッター商店街」化していますね。悲しい限りです。

 

矢張り、余り参考になるようなものはなかったのですが、「電子計算機」なら計算をさせようと思い、中坊の数学程度のグラフ描画をピクチャーボックス(注)などを使って描いてはどうかな、等とお気楽に考え始めました。

注:BCBやC#にもありますが、描画専門のコントロールのことですね。BCCSkeltonにも習作でCPICBOXというクラスがあり、今回はそれを使います。

まぁ、今年もあと少し。最後の小ネタはECCSkeltonで作る関数グラフも悪くはないですね。

 

スケルトンはBCCFormでちゃちゃっと出来て、SkeltonWizardを掛けてダイアログベースのスケルトンプログラムを作り、更に秋に作ったBCC2ECCを掛けてECCSkeltonプログラムに変換してEmbarcadero C++コンパイラーにかけると↑のような助ロトンができました。今考えているのは、

(1)「式の種類」コンボボックスで好きなグラフ(といっても一、二次方程式や円程度ですが)を選択すると

(2)項の定数a, b, c, d用エデイットボックスをEnableWindowでいじり、入力を可能にさせ、

(3)グラフ表示ボタンを押すと描画(項の値がおかしいまたは実数解が無いとエラー)

(4)グラフは重層化描画させて、背景色、前景色、消去は右クリックのポップアップメニューで指定する。

というような仕様です。

 

まぁ、年内にこだわらず(注)、ボチボチやります。

注:高齢化社会のご多分に漏れず、(私自身の母親は来年100を迎えますが、先に)女房の親が施設に入居することになり、夫婦で老骨に無知を打って施設探しに奔走しております。

 

ps. なお、久々に開発は32bit Win 10マシンで行っています。このブログ記事も同様です。(私と同じように)まだまだ現役?

 

DataGridというコントロールを知っていますか?

 

MS Excelを挙げるまでもなく2次元データ配列を視覚的に扱うコントロールとして昔から使われてきたようですが、この記事を見るとMSの(.NetFrameWork 4.8)の仕様が決まったのは結構新しいのかも?(知らんけど...)

 

いずれにせよ、気になるコントロールですが、ボタンやエディットボックス等のWin32 SKDで簡単にプログラミングできるような玉ではない、と思っていましたので、「使うならC#かな?」と考えていたら、

 

こんなもの(Win32 SDK Data Grid View Made Easy)

 

を発見。

 

中身が知りたいのでCode Project(注)に加入して、ソースとサンプルを共にダウンロードしましたが、その評価が↑のタイトルです。こういうマニアックな方は大好きですし、尊敬しますね。(なお、ご利用できるか、どういう制限があるかはCode Projectに加入して契約条件を確認してください。(注2))

注:様々な言語でプログラムを共有するサイトのようでご参考になるでしょう。ただし、入って5分もたつとパスワードが漏出したとしてWin 11から警告が出てパスワードを更新したことも報告させていただきます。

注2:後でよんだらThe Code Project Open License (CPOL)でした。

Windows 10や11でC#とVBのコンパイラーが同梱されていることをしり、その利用ソフトとしてBCCSkeltonでMSCompAssを作製した序にC#の学習(VBは懲りたので今後の予定なし)を始めました。手始めに「猫でもわかるプログラミング」の「C#コンソール編 | フォーム編」を受講し、後半は自主学習としてVisual Studio無しでもリソースが利用できるツールを卒業制作に宛てました。

 

本日「猫でも」の全編を一応カバーし、卒業製作であるResWriterとResReader(注)

もできたので、C#の初級クラスを「自主卒業」します。

注:ResWriterの評点は100点でよいでしょう。しかし、ResReaderは*.resourcesファイルのPinnedBufferMemoryStream型リソースが読めないので80点ぐらいなのですが、もともとGDI+デサポートされていてクラスがないイメージファイル(jpeg, png, tiff等)はイメージの「型」が読めないのでビットデータをもらってもすべてチェックしなければならないというハードルもあり、まぁ、「今日のところは大目に見てやろう」でよいのではないかと自分を慰めています。

 

今後もC#は弄ることがあると思う(注)ので、その際は(BCCForm and BCCSkeltonのサポートではないですが)【C#関連】としてトピックに使わせてください。

注:何度か書きましたが、何せWindowsの機能を目一杯簡単に使えて、ガーベージコレクション等高級言語であるにもかかわらず「C/C++の文法に親しんだ人に使いやすいVisualBasicの機能」を持つC#は、コンパイラー(csc.exe)の軽快さ、使い勝手の良さと相俟って「もう一度使ってみたい言語」です。Visual Studioは何せでかく、スペースを取り、どう使うかが巨大すぎてわかりづらいのでインストールするつもりはありませんが、「素人が小ぶりのプログラム(それも64bitプログラム!)をササっと書く」には便利な言語であり、また使うと思います。

 

なお、BCCFormandBCCSkeltonパッケージのMSCompAssの所に、ResWriterとResReader、およびいくつかのサンプルを同梱してアップしましたので、参考になると思います。

 

昨晩は飲みながらWEB検索して、ResourceReaderのGetResourceDataメソッドを使い、

 

    //ResourceSetでは対応できないので、ResourceReaderのGetResourceDataメソッドを使ってみたらすんなり決まった!
    ResourceReader rr = new ResourceReader(ResFileName);
    string resType = "";
    byte[] resData;
    rr.GetResourceData(m_Name, out resType, out resData);
    resType = String.Format("Resource Type: {0} and the Size : {1}", resType, resData.Length);
    MessageBox.Show(resType, "PinnedBufferMemoryStream 情報", MessageBoxButtons.OK,

                    MessageBoxIcon.Information);

 

何とかByte[](配列)データを取得することができました、...(サイズがちょうどjpegファイルの大きさだったので...)と思ったのですが、今朝取得したByte[]データをMemoryStreamへ落とし、このように

 

    MemoryStream mstream = new MemoryStream(resData);
    Image img = Image.FromStream(mstream);
    ImgShow imgdlg = new ImgShow(img);
    imgdlg.ShowDialog();   
//2023年06月30日追記-ShowDialog()で開いた場合、必ずDispose()してください。
    //代替案
    //ImageConverter imgconv = new ImageConverter();
    //Image img = (Image)imgconv.ConvertFrom(resData);

 

ImageのFromStreamで読み込んで表示しようとしたら、...

 

    「使用されたパラメーターが有効ではありません。

  (場所 System.Drawing.Image.FromStream(Stream stream, Boolean useEmbeddedColorManagement,

   Boolean validateImageData)」

 

というきつーいお達し。(ガックシ...)
 

↑の代替案等試してみましたがすべてダメ。

因みにメッセージボックスの表示は↓のとおり。

 

このコード自体が間違っているのか、ということで読み込めるBitmapリソースで試すと、

 

と、Type情報とTypeCodeがなく、サイズだけ、ということが分かりました。

 

矢張りPinnedBufferMemoryStreamからデータを読み取るのは簡単ではなく、当分(私がC#のexpertになるまでは)メッセージボックスに、

 

MessageBox.Show("PinnedBufferMemoryStreamリソースは保護されており、アクセスも他の形にキャストもできません。", "保存不能", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);

 

と書いて対応するしかないようです。

 

昨日書いたブログの課題で少し進展がありました。

--- Quotation Begin ---

Metafile→現在どうやって保存するかもNo idea状態です。これはMicrosoftも推していないようで、(あまり使われないので)頑張って*.wmfに復元する意味はないかも?

MemoryStream→コンパイルはできるのですが、復元されていないようです。

PinnedBufferMemoryStream→こいつがどういうクラスなのかをこれから調べる予定これで追記します

--- Quotation End ---

 

Metafileですが、さすがにMicrosoftもクラスを作っているのでImageクラスで読めるだろう、と考え成功。いったんImageクラスで読み込めれば形式を指定してSaveメソッドで保存できます。

 

MemoryStreamは前回バイト配列に落とし込んで保存しようとして失敗しましたが、WEBでもっと簡単で安全な方法があることが分かり成功。

 

なお、Stringリソースについては「一行の文字列のファイルを書いても仕方ないんじゃね?」ということもあり、すべてのStringリソースを"*.restext"ファイルに落とす処理も書き加えました。

 

【「保存」メニューで「保存」ダイアログを出してOKボタンを押した際の処理の一部】

    protected void OnOK_Click(object sender, EventArgs e)
    {
        if(String.IsNullOrEmpty(txbPath.Text))
        {
            MessageBox.Show("保存ファイル名が指定されていません。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }
        if(FileType)    //*.resourcesファイル
        {
            //ResourceSetを作成する
            ResourceSet rs = new ResourceSet(ResFileName);
            if(m_Type == "Bitmap")
            {
                //Bitmapリソースの保存(保存確認◎)
                Image img = (Bitmap)rs.GetObject(m_Name);
                img.Save(txbPath.Text, ImageFormat.Bmp);
            }
            else if(m_Type == "Icon")
            {
                //Iconリソースの保存(保存確認◎)
                Image img = ((Icon)rs.GetObject(m_Name)).ToBitmap();
                img.Save(txbPath.Text, ImageFormat.Icon);
            }
            else if(m_Type == "String")
            {
                //すべての文字列リソースを取得ファイル出力する選択
                DialogResult dr = MessageBox.Show("指定出力ファイルを\".restext\"に変更し、すべての文字列リソースをまとめて出力しますか?", "restextファイル出力確認", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
                if(dr == DialogResult.Yes)
                {
                    m_Name =
m_Type = "StrResCollection";    //これをフラグとしてメインフォームで処理を行う
                    m_Data = txbPath.Text;    //その為に保存ファイルパス、名を返す
                }
                else

                {
                    //Textリソースの保存(保存確認◎)
                    using (StreamWriter sw = new StreamWriter(txbPath.Text))
                    {
                        sw.WriteLine(m_Data);
                        sw.Close();
                    }
                }
            }
            else if(m_Type == "Byte[]")
            {
                //Byte[]リソースの保存(保存確認◎)
                Byte[] bytes = (Byte[])rs.GetObject(m_Name);
                File.WriteAllBytes (txbPath.Text, bytes);
            }
            else if(m_Type == "Metafile")
            {
                Metafile metaFile = (Metafile)rs.GetObject(m_Name);
                Image img = (Metafile)metaFile;
                img.Save(txbPath.Text, ImageFormat.Wmf);   
//結局こんなに簡単でした(汗;)。
                /*
                Metafile metaFile = (Metafile)rsx.GetObject(m_Name);
                //GDI+ では、EMF および EMF+ 形式でメタファイルを記録できますが、WMF 形式では記録できません。
                //https://learn.microsoft.com/ja-jp/dotnet/desktop/winforms/advanced/metafiles-in-gdi?view=netframeworkdesktop-4.8
                Save メソッドを使用してグラフィック イメージを Windows メタファイル形式 (WMF) または拡張メタファイル形式 (EMF) 
                ファイルとして保存すると、結果のファイルは代わりにポータブル ネットワーク グラフィックス (PNG) ファイルとして保存
                されます。 この動作は、.NET Frameworkの GDI+ コンポーネントに、ファイルを .wmf または .emf ファイルとして保存する
                ために使用できるエンコーダーがないために発生します。
                */

            }
            else if(m_Type == "MemoryStream")
            {

                /* Case 1 MemoryStreamリソースの保存(保存確認×-jpgファイル使用)
                MemoryStream mStream = new MemoryStream();
                mStream = (MemoryStream)rsx.GetObject(m_Name);
                using (FileStream file = new FileStream(txbPath.Text, FileMode.Create, FileAccess.Write))    //FileMode.Createは既存のファイルがあれば上書きする
                {
                    Byte[] bytes = new Byte[mStream.Length];
                    mStream.Read(bytes, 0, (int)mStream.Length);
                    file.Write(bytes, 0, bytes.Length);
                    mStream.Close();
                    file.Close();
                } */
                // Case 2 MemoryStreamリソースの保存(保存確認◎-jpgファイル使用)
                using (FileStream file = new FileStream(txbPath.Text, FileMode.Create, FileAccess.Write))    //FileMode.Createは既存のファイルがあれば上書きする
                {
                    MemoryStream mStream = (MemoryStream)rs.GetObject(m_Name);
                    mStream.WriteTo(file);
                }   
//これもこんなに簡単でした。Using構文は簡単で助かります。
/*
                // Case 3 MemoryStreamリソースの保存
                MemoryStream mStream = (MemoryStream)rsx.GetObject(m_Name);
                FileStream file = new FileStream(txbPath.Text, FileMode.Create, FileAccess.Write);
                mStream.WriteTo(file);
                file.Close();
                mStream.Close();
                // Case 4 MemoryStreamリソースの保存
                MemoryStream mStream = (MemoryStream)rsx.GetObject(m_Name);
                File.WriteAllBytes(txbPath.Text, mStream.ToArray()); 
*/   
//↑でダメならこれらを試すつもりでした。
            }
//>>>> 同日追記 <<<<

            else if(m_Type == "PinnedBufferMemoryStream")
            {
                /* http://www.dotnetframework.org/default.aspx/4@0/4@0/DEVDIV_TFS/Dev10/Releases/RTMRel/ndp/clr/src/BCL/System/IO/PinnedBufferMemoryStream@cs/1305376/PinnedBufferMemoryStream@cs
                    ↑によればunmanagedMemoryStreamから派生させたクラスなので、↓で行けるかと思ったのですが「
'System.IO.PinnedBufferMemoryStream' はアクセスできない保護レベルになっています」エラーが出ます。
                using (FileStream file = new FileStream(txbPath.Text, FileMode.Create, FileAccess.Write))    //FileMode.Createは既存のファイルがあれば上書きする
                {
                    PinnedBufferMemoryStream pbms  = (PinnedBufferMemoryStream)rs.GetObject(m_Name);
                    pbms.WriteTo(file);
                }

                仕方がないので、無理やりMemoryStreamで読む実験をします。
                
using (FileStream file = new FileStream(txbPath.Text, FileMode.Create, FileAccess.Write))    //FileMode.Createは既存のファイルがあれば上書きする
                {
                    MemoryStream mStream = (MemoryStream)rs.GetObject(m_Name);
                    mStream.WriteTo(file);
                }

                矢張り「System.InvalidCastException: 型 'System.IO.PinnedBufferMemoryStream' のオブジェクトを型 'System.IO.MemoryStream' にキャストできません」エラーが出ます。
                
PinnedBufferMemoryStream pbms  = (PinnedBufferMemoryStream)rs.GetObject(m_Name);
                File.WriteAllBytes(txbPath.Text, pbms.ToArray()); 

                これもダメ。「PinnedBufferMemoryStream」は保護されており、使えません。
                */
/*だったのですが、矢張りねがしつこいので恨みがましくWEBを見ていて、ResourceReaderクラスのGetResourceDataメソッドに気が付きました。(これはバイトデータに落としてセーブしてくれる。)「とはいってもやはり保護クラスだからエラーが出てだめだよねぇ」と始める前から諦め気分でコンパイルしたら、

なーんと、通っちゃいましたぁ!!!

ということでコードを。

*/

                //ResourceSetでは対応できないので、ResourceReaderのGetResourceDataメソッドを使ってみたらすんなり決まった!(以下が追記部分のソリューション)
                ResourceReader rr = new ResourceReader(ResFileName);
                string resType;
                byte[] resData;
                rr.GetResourceData(m_Name, out resType, out resData);
                resType = String.Format("Resource Type: {0} and the Size : {1}", resType, resData.Length);
                
MessageBox.Show(resType, "PinnedBufferMemoryStream 情報", MessageBoxButtons.OK, MessageBoxIcon.Information);    //動作確認用のMessageBoxです。
            }
            else
                MessageBox.Show("違法なイメージリソースです", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
            rs.Dispose();
        }

        else            //*.resxファイル
        {
            //ResXResourceSetを作成する
            ResXResourceSet rsx = new ResXResourceSet(ResFileName);
    ・・・・・以下resourcesと同等なので省略・・・・・

        }

        Close();

    }

 

この「PinnedBufferMemoryStream」が何かというと、UnmanagedMemoryStreamから派生させたbyte[](Byte構造体の配列)で、"unsafe"でかかれたメモリーを固定したストリームであり「勝手にいじらせたくない」という感がありありとします。この型はjpeg等クラスのないイメージファイルを *.resources ファイルに書き込むと規定されるのですが、同じjpeg画像を"MemoryStream"で処理した記憶もあるので調べてみると、*.resx ファイルの際には「MemoryStream」型になっていることが分かりました。

いずれにしても「『保護された』型なのでアクセスが不能」「『保護された』型を変えるためにキャストすることも不能」なので全く手が出せない状態です。(

注:和文のサイトではこの「PinnedBufferMemoryStream」について触れている記事が全く見当たりません。また、英文記事でもこのMicrosoftの2007年のコード以外に何も見つけることができませんでした。

 

結論:PinnedBufferMemoryStream型のイメージファイルは断念し、ここまででResReaderを完結させることにしました。「勇気ある撤退」としてお許しを。→一旦は撤退しましたが、矢張り諦めきれずに、最後のソリューションを見つめました!今日は酒も美味いし、夜もよく眠れるでしょう!

バンザーイ!!!(すでに飲んでいますが...何か?)

 

さて、ResWriterはしっかりと働いてくれて満足しています。(注)

注:タスクマネージャーで見てみると、私のWin 11 PCのMSCompAss(csc.exe)でコンパイルされた実行ファイルはちゃんと64bitプログラムなんですね。(もちろん MSCompAss はbcc32c.exeでコンパイルしているので32bitプログラムです。)

 

残りのResReaderの方は*.resourcesファイル、*.resxファイルを読んでリソースを列挙するまでは完了し、列挙されたリソースを復元する「保存」メニューだけ残っています。しかしこれが難物です。

 

「保存」の際にリソースを読む基盤はResourceSet、RexXResourceSetを使っていますがこれらがビミョーに仕様が違うんですね。ResourceSetはストリームからの読み込みやDisposeができてもRexXResourceSetではできないので困惑しました。(注)

注:Disposeの方は「using() {}(C++のtry-catch-finallyと同等)」処理でDisposeしてくれる、というのでこれでやっています。

そんなこんなで色々と試していたら、リソースの列挙で出てくるValue.GetType().Name(リソースでいえば、Bitmap, Icon, Metafile, String, Byte[], MemoryStream, PinnedMemoryStreamを今まで経験しています)がC#のオブジェクトの型(クラスや構造体)であることに気が付きました。では、ResReaderで読まれたオブジェクトの型で書き込んで復元しようかと現在奮闘中ですが、まだ完成には至っていません。以下は最後の保存のところのコードの一部です。(C++とは似て非なるものであることがよくわかります。)

 

【「保存」メニューで「保存」ダイアログを出してOKボタンを押した際の処理の一部】

    protected void OnOK_Click(object sender, EventArgs e)
    {
        if(String.IsNullOrEmpty(txbPath.Text))
        {
            MessageBox.Show("保存ファイル名が指定されていません。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }
        if(FileType)    //*.resourcesファイル
        {
            //ResourceSetを作成する
            ResourceSet rs = new ResourceSet(ResFileName);
            if(m_Type == "Bitmap")
            {
                //Bitmapリソースの保存(保存確認

                Image img = (Bitmap)rs.GetObject(m_Name);
                img.Save(txbPath.Text, ImageFormat.Bmp);
            }
            else if(m_Type == "Icon")
            {
                //Iconリソースの保存(保存確認

                Image img = ((Icon)rs.GetObject(m_Name)).ToBitmap();
                img.Save(txbPath.Text, ImageFormat.Icon);
            }
            else if(m_Type == "String")
            {
                //Textリソースの保存(保存確認

                using (StreamWriter sw = new StreamWriter(txbPath.Text))
                {
                    sw.WriteLine(m_Data);
                    sw.Close();
                }
            }
            else if(m_Type == "Byte[]")
            {
                //Byte[]リソースの保存(保存確認

                Byte[] bytes = (Byte[])rs.GetObject(m_Name);
                File.WriteAllBytes (txbPath.Text, bytes);
            }
            else if(m_Type == "Metafile")
            {
                Metafile metaFile = (Metafile)rs.GetObject(m_Name);
                //GDI+ では、EMF および EMF+ 形式でメタファイルを記録できますが、WMF 形式では記録できません。
                //https://learn.microsoft.com/ja-jp/dotnet/desktop/winforms/advanced/metafiles-in-gdi?view=netframeworkdesktop-4.8
                /* Save メソッドを使用してグラフィック イメージを Windows メタファイル形式 (WMF) または拡張メタファイル形式 (EMF) 
                ファイルとして保存すると、結果のファイルは代わりにポータブル ネットワーク グラフィックス (PNG) ファイルとして保存
                されます。 この動作は、.NET Frameworkの GDI+ コンポーネントに、ファイルを .wmf または .emf ファイルとして保存する
                ために使用できるエンコーダーがないために発生します。*/
            }
            else if(m_Type == "MemoryStream")
            {
                //MemoryStreamリソースの保存(保存確認
×-jpgファイル使用)
                MemoryStream mStream = new MemoryStream();
                mStream = (MemoryStream)rs.GetObject(m_Name);
                using (FileStream file = new FileStream(txbPath.Text, FileMode.Create, FileAccess.Write))
                {
                    byte[] bytes = new byte[mStream.Length];
                    mStream.Read(bytes, 0, (int)mStream.Length);
                    file.Write(bytes, 0, bytes.Length);
                    mStream.Close();
                    file.Close();
                }
            }
            else if(m_Type == "PinnedBufferMemoryStream")
            {
                //http://www.dotnetframework.org/default.aspx/4@0/4@0/DEVDIV_TFS/Dev10/Releases/RTMRel/ndp/clr/src/BCL/System/IO/PinnedBufferMemoryStream@cs/1305376/PinnedBufferMemoryStream@cs
                
            }
            else
                MessageBox.Show("違法なイメージリソースです", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
            rs.Dispose();
        }
        else            //*.resxファイル
        {
            //ResXResourceSetを作成する
            ResXResourceSet rsx = new ResXResourceSet(ResFileName);

    ・・・・・以下resourcesと同等なので省略・・・・・

        }

        Close();

    }

 

上記のとおり、Bitmap、Icon、String、Byte[]は成功していますが、

Metafile→現在どうやって保存するかもNo idea状態です。これはMicrosoftも推していないようで、(あまり使われないので)頑張って*.wmfに復元する意味はないかも?

MemoryStream→コンパイルはできるのですが、復元されていないようです。

PinnedBufferMemoryStream→こいつがどういうクラスなのかをこれから調べる予定。

 

まぁ、(垢抜けたコードではないですが)老後の頭の体操としては楽しみの一つになっていますので、じっくりやりましょう。

 

昨日、

【C#学習】ResWriterの完成

(1)『イメージ(画像)』リソース

Bitmapリソースは今まで通りとし、とつぃ扱いがやや特殊なIconリソースはIconクラスを使ってストリーム出力し、その他の画像データ形式はImageクラスのImageFormatを"RawFormat"にしてストリーム出力するようにして問題を解決しました。

と書いていたのですが、今朝また少しいじってしまいました。

 

というのは、矢張りC#のGraphic、Image等のクラスはその基盤がGDI+のようですが、Imageクラスで取り扱える画像種類はImageFormatで定義され、これら種類のうちC#のオブジェクトとしてクラスを定義しているのはBitmapとIconだけだと思っていたのですが、なんとWmf(Windows MetaFile)形式もMetafileというクラスがあり、オブジェクトタイプとして認識されることが分かりました。ということで、Bitmap、Iconと同じくMetafileも(まぁ、使う人もいないだろうし、Microsoft Docsでも表立って現れないオワコンだと思うけど)追加しました。

ResWriterでCat.wmfというファイルをwmfCatという名前でImageとしてリソースに登録すると、

確かにBitmapやIconと同様に"Metafile"タイプと認識されていますね。(その他のjpeg等の画像形式は本日再チェックしましたが、C#での定義はまずないと再確認しています。今度こそ本当に完成、だと思います、と祈ります。)