アルバムソフトを開発していると、色々と疑問が生じ、Microsoft DOCで調べていると、ますます疑問が生じる。
更に調べて解決方法を確認すると、実験したくなるので、時間がかかる。まぁ、このプロセスがプログラミングの楽しみだと思っています。
今日はアルバムソフトに、単一画像表示の際の画像の回転機能をつけました。この次は画像サイズの変更とファイル書き換え、かな?


ps. 初めてスマホから書き込みましたが、ハッシュタグはつけられないのね?残念。

ps2. やはり気になってPCから修正しました。スマホからは編集ソフトを入れないとできないので、面倒だね。

ps3. どうでもよいのですが、これは自慢作の東京ラーメン。ちょっと凝っているでしょ?これを作り始めてから外では食べなくなった。


 

前回、GDI+を活用した*jpg等イメージファイルを纏めて管理するアルバムソフトの開発に起因するバグ騒動について書きました。

【バグは続くよ、何処までも】アルバムソフト-バグとの闘い

今回はその後日譚です。

 

前回言及したバグは、

(1)「Microsoft Windows 10が、ファイル名に禁止している『\ / : * ? " < >』の禁則文字が、ファイルのプロパティダイアログの「ファイル作成日時」をコピーすると違法に(禁止されているものが、何ら警告もなく、容認されて)ファイル名に混入」する現象、と

(2)FileListダイアログを使った際にフルパスファイル名から正しくファイル名のみを抽出できない現象

についてでした。

以下にその後の対応と結果について記します。

 

1.ファイル名禁則文字混入事件

最初にこの問題に気が付いた自作料理の写真については、全てマニュアル(注)で禁則文字を削除し、再度アルバムソフトを使うと正常に扱えることが確認されました。

注:具体的な手順としては、Explorerの右クリックによるポップアップメニューの「名前の変更」を使って、表示されていない '?' (ASCII 0x3F) を選択-見えない文字ですが、ちゃんと選択できます-して削除キーを押しました。

 

次に同様の現象がjpgファイルのみならず、その保存フォールダー名にも表れている例(以下「軽井沢写真」)で、GDI+のImageクラスオブジェクトのインスタンスをファイル名コンストラクターで生成する際に、エラー表示なしにプログラムが落ちることを確認しました。

 

現象が特定され、再現実験で原因と責任の所在がMicrosoft Windows 10にあることが確認できたので、当方の原因対処は行わず、再発防止措置として「混入している'?'」を駆除するツールを作ることとしました。

 

本日このツール(禁則文字が混入して汚染しているので"FileNameCleaner"となずけました)を作成し、実行試験にかけましたが、

パス、ファイル名が違法状態だとWIN32APIが正常に動作せず、プログラムによる対処は不可能

という結論に至りました。具体的には、

(1)特定のフォールダーを選択し、その名称に'?' 等(他の禁則文字も含めました)があれば、それを除いたファイル名に書き換える。

(2)次にその「浄化した」フォールダー内の全ファイルに同様の措置を実施する。

というツールです。

試験で実行すると、

このようにフォールダー名を表示して6つの'?' があることを教えてくれます。しかし、「はい」を押すと、

フォールダー(ディレクトリー)の名称変更に失敗します。名称変更は最初フォールダー、ファイル共に適用できるWin32APIのMoveFile関数を使い、次にC++のランタイムのrename関数も使いましたが、いずれも討ち死にでした。

何故か?それは変更対象ソースのパス、ファイル名が違法なので、関数が受け付けてくれないからです。

簡単な関数では対応できないということで、WEBで色々と調べましたが妙策はなく、「新しくフォールダーを作る、①すべてのファイルをコピーする、②違法な最初のフォールダーとファイルを削除する」という面倒な方法も、矢張り①、②で違法パス、ファイル名を使わなければならないので関数はエラーとなる、という悲しい現実に直面しました。

因みにフォールダーを手作業で修復し、このツールを実行継続すると、ファイルで失敗する以前の問題としてフォールダー内のファイル検出関数(FindFirstFile、FindNextFile)の引数であるパスに問題があるために抽出に失敗します。

結果こういうことになりました。(プロパティダイアログからコピーした日付の'?'にご注目あれ。)

しかし、OS自体はこの'?'があっても正常に処理をしているのに、そのAPIはエラーを出すってどうなっているのか?と思います。日付の数字をあるコードで囲み、それが最終的に'?'に変換されて残ってしまうのでしょうか?'?'があっても処理ができるという裏関数があるなら、公開してほしかったです。いずれにしても、

ファイルを読み込んで即落ちする現象があったら、プロパティダイアログ等OSからコピーしてきたものであっても、禁則文字混入コンタミの可能性がある

事を疑ってください。

 

2.ファイル名首切り「ソ」問題事件

こちらは私に非がある問題ですが、内容は簡単で既に恒久的対処が終わっています。

冒頭↑の写真の通り、FileListダイアログを開くとイメージファイルから切り出された「日付から始まる料理のファイル名」が並ぶはずだったのですが、一部のファイルが「ーメンン」とか「ース」とかから始まっています。松田優作ではないですが、

「なんじゃぁ、こりゃ???」

となりました。(BCCSkeltonを使って20年、このような問題はなかったので。)

しかし、直ぐにこれが全角カタカナの「ソ」(2バイトコード)が悪さをしていること、恐らく第2バイトを'\'と間違えて誤処理をしているであろうことが推測され、即検証に入りました。

Shift-JIS(Wiki)

Shift_JISのダメ文字

 

やはり、「ソーメン」や「ソース」の「」(Shift-JISコードの0x835C)はバイトで見ると第2バイトが''\'(0x5C)'と同一です。
この為、FileListのGetFileName関数が効率性の為に文字列のお尻からチェックしていたため(注1)、「ソ」の第2バイト0x5Cを'\'と誤認して、例えば次の例では

例:"C:\Users\ysama\ピクチャ\料理\2019年8月7日ーメンと厚揚げ、水菜チキンサラダ.jpg"
                             0x83 0x5C('\')                   ←cp(チェックポインター)
"ーメンと厚揚げ、水菜チキンサラダ.jpg"をファイル名と認識してしまいます。

それでは、ポインターが'\'を指した際に、その一つ前のバイトが「Shift-JISで使う第1バイト(0x81-0x9Fまたは0xE0-0xEF)」なら無視して進める、というアルゴリズムで書き直せばよいのでは、と思いました。が、「ソ」は思惑通り通過しましたが、「料理」の「理\(0x97 0x9D 0x5C)」と「ピクチャ」の「ャ\(0x83 0x83 0x5C)」が「絶妙」にヒットして「Shift-JISコード」とみなされ、今度は

                                 0x83 0x83 0x5C

例:"C:\Users\ysama\ピクチャ\理\2019年8月7日ーメンと厚揚げ、水菜チキンサラダ.jpg"
                   0x97 0x9D 0x5C   0x83 0x5C('\')                   ←cp(チェックポインター)
ファイル名を"ャ\料理\2019年8月7日ソーメンと厚揚げ、水菜チキンサラダ.jpg"と認識してしまいました。

ということで、「お尻から見てゆくアルゴリズムには限界があり、解決しない」と、正統派の頭から見てゆくアルゴリズムに変更しました。しかし、結果は"ャ\料理\2019年8月7日ソーメンと厚揚げ、水菜チキンサラダ.jpg"と変わりありません。

最初「何故????」と当惑しましたが、「Shift-JISで使う第1バイト(0x81-0x9Fまたは0xE0-0xEF)」がcharだと負数になる為、条件式が正しく機能していないことが分かり、「char → unsigned char」へ変更し、C++11のBCC102で耐えられるようにキャスト変換等調整し、最終形としました。(注2

注1:問題のある旧アルゴリズム
////////////////////////////////////
//ユーザー定義関数
//フルパス名からファイル名だけを取得
////////////////////////////////////
char* CFILELIST::GetFileName(char* pathname) {

    static char fn[MAX_PATH];    //データをスタックに置かない為
    lstrcpy(fn, pathname);        //引数の文字列のコピーを作る
    //cpをpassnameの終端(NULL)のひとつ前に進める
    char* cp = fn + lstrlen(fn) - 1;
    if(*cp == '\"' && *fn == '\"')
        *cp = NULL;    //「""」で囲まれていれば外す
    //文字列の後ろから最初の'\'を(保険でfnの先頭迄)探す
    while(*cp != '\\' && cp > fn) {
        --cp;
    }

    //*cpが「\」、「"」(fn)の何れでも一つ進める("ファイル名"の場合はそのまま)
    if(*cp == '\\' || *cp == '\"')
        cp++;
    return cp;                //ファイル名の先頭アドレスを返す
}

注2:前からチェックして行き、Shift-JISコードはスキップするように変更
////////////////////////////////////
//ユーザー定義関数
//フルパス名からファイル名だけを取得
////////////////////////////////////
char* CFILELIST::GetFileName(char* pathname) {

    static char fn[MAX_PATH];    //データをスタックに置かない為
    lstrcpy(fn, pathname);        //引数の文字列のコピーを作る
    //cpをpassnameの終端(NULL)のひとつ前に進める
    unsigned char* sp = (unsigned char*)fn;
    unsigned char* ep = (unsigned char*)fn + lstrlen(fn) - 1;
    char* last = 0;                //最後の'\'を指すポインター
    if(*sp == '\"' && *ep == '\"')
        *ep = NULL;    //「""」で囲まれていれば外す(NULLを指す)
    //文字列の'\'を探す
    while(sp < ep) {
        if(*sp == '\\')
        last = (char*)sp;        //spを進めるのでlastに記録
        //Shift-JISの第1バイト(0x81-0x9Fまたは0xE0-0xEF)なら2バイト進める
        if((*sp >= 0x81 && *sp <= 0x9F) || (*sp >= 0xE0 && *sp <= 0xEF))
            sp += 2;
        else
            sp++;                //spを進める
    }
    return ++last;                //ファイル名の先頭アドレスを返す
}

また、これを教訓にBCCSkeltonのCARGクラスも「先頭からチェック」アルゴリズムに変更しました。(ファイル日付:2022年3月20日のものが修正版)CARGクラスを使っている旧いプログラムでShift-JISのダメ文字問題があれば、再コンパイルをすれば解決できます
なお、この修正版は問題となった文字列を使ってテストし、正常に動作したことを報告させていただきます。
<テストプログラム>
CARG arg("C:\\Users\\ysama\\ピクチャ\\料理\\2019年8月7日ソーメンと厚揚げ、水菜チキンサラダ.jpg");
MessageBox(m_hWnd, arg.Drive(), "Drive", MB_OK);        //"C:"と表示
MessageBox(m_hWnd, arg.Path(), "Path", MB_OK);        //"C:\\Users\\ysama\\ピクチャ\\料理"と表示
MessageBox(m_hWnd, arg.FileName(), "FileName", MB_OK);    //"2019年8月7日ソーメンと厚揚げ、水菜チキンサラダ.jpg"と表示
MessageBox(m_hWnd, arg.Ext(), "Ext", MB_OK);        //"jpg"と表示

 

以上を持ちまして、今回のバグ騒動のピリオッドとさせていただきます。

 

プログラマーの宿命、それは「テロとの闘い」ではなく、「バグとの闘い」です。

前回次のように書きました。

2.悪い話

開発していたアルバムソフトですが、ほぼ想定仕様通りの動作ができるようになったので、本格実験を行うべく50数枚の料理写真をアルバムファイルにまとめて開いてみました...ら、プログラムが落ちます。????

サンプルの写真では正常動作するのに何故落ちるのか?写真の枚数を減らしてみた....ら、正常に動作します。????んん?やや動きがのろいな、という印象があります。

で、結論は出ていませんが(注)、現段階の推定原因を「CPHOTO、CALBUMで読み込みイメージをメモリーに保存するので、容量の大きなイメージファイルだと一挙にメモリー落ちするのではないか?」と考えています。

注:なにせ、エラーメッセージも表示せず、ファイルを読み込むとプログラムが落ちるんです。

 

ということで、開発に使ったサンプルの画像と自分の料理の画像を比較してみました。

 

<サンプル画像例>

Cat.jpg 320 x 240 42KB

<本番読込画像例>

2018年9月1日チリコンカーニ③.JPG 2448 x 3264 3.27MB

 

う~ん、やっぱりこれかも。しかし、今後どうするか?

(1)重いイメージファイルに耐えられるように、イメージは毎回表示したら捨ててメモリーに残さない。

(2)巨大イメージファイルを現実的な640x480程度のサイズに縮小するソフトを開発する。

(3)ばからしいから、スマホに任せて、開発をここで中断する。

 

確かに当時の状況から、またCPHOTOクラスのメンバー変数にImageインスタンスを持たせ大量にメモリーを割り当てていたのでこのように考えたのも郁子(むべ)なるかな、と思われます。そして↑を書いた後、先ず(1)を実行し、その後(2)のユーティリティを作ろうかな、と考えました。

 

CALBUMで動的配列を作るCPHOTOクラスはそのメンバー変数に、

char* m_FName;        //写真(イメージ)ファイルパス、名

Image* m_Image;        //GDI+ Imageクラスインスタンスのポインター

char* m_Comment;    //写真につけるコメント

を持たせ、アルバムファイル(*.alb)を読んだり、イメージをドラッグアンドドロップで追加する度にアルバム(CALBUM)が写真(CPHOTO)をC++の「CPHOTO* m_Photo[n] = new (CPHOTO)(コンストラクター);」で動的に追加し、追加された写真インスタンスに"new"で動的にフルパスファイル名を、それを使ってイメージインスタンスを、更に有ればコメントを同様に生成させる、というものでした。しかしこの方法は前に書いた通り数MBクラスのイメージを常時メモリーに駐在させることになり、リソースの負担が大きいことから、(速度は犠牲にしても)改善案ではイメージをCPHOTOクラスから排除し、

char* m_FName;        //写真(イメージ)ファイルパス、名

Image* m_Image;        //GDI+ Imageクラスインスタンスのポインター    //削除

char* m_Comment;    //写真につけるコメント

CALBUMクラスの処理でイメージ描画が必要なときに、CALBUMクラスのメンバー変数のm_Imageポインター変数にCPHOTOインスタンスのm_FNameのデータを使ってインスタンスを生成、描画が終わったら"delete m_Image;"でさっさとメモリーを解放し、極力リソースを使わないようにしました。

 

これで問題は解決する筈、でした。

 

しかし、無情にも56枚の写真を読み込んだアルバムソフトはエラーも出さずにすぐに落ちます。(;´д`)トホホ でも、

 

待てよ?!

 

なんで(または何を読み込んだ時に)落ちるのか?が分かっていなかったよな?と思い直し、アルバムソフトのOnOpen("*.alb"ファイルを読みこむ)関数が使うReadAlbum関数にMessageBoxトラップをかけて調べました。すると...

なんだぁ?この日付廻りにみられる「?2022??1??17?日」は?ということで、Dumpを使って調べてみると0x3F、または'?'という「反則」()があるではないですか?

注:昔MS-DOS時代に*と並び使われた1文字ワイルドカードであった為に「禁則」となった文字コード。

こんな文字を使った覚えが無いので、よく思い出すと、私は料理の写真を撮った後PCに記録する際に「ファイル名をプロパティダイアログの『作成日時』をコピーしてその後に料理名を追加していました」ので、これはその際に混入したのではないか、と考えられます。

また、処理としてこの'?'は、先ずmbstowcs関数でwchar_tのワイド文字ファイル名に置き換えられ、ImageクラスのコンストラクターImage(wchar_t*, bool)に引数として使われます。この際に「エラー落ち」するのではないか、と「新容疑者」を特定しました。

 

この推論を実証すべく、56枚の写真のファイル名を調べて、↑の通り'?'が混入している(不思議と全ファイルではないのです)ファイルを特定し、これらのファイル名から'?'を除去して「56枚の数MBの料理写真」をアルバムソフトに読ませてみると、あら不思議、正常に動作します。(少なくとも今のところは。)

 

再現実験をすべく、再度'?'を入れた写真をテスト用に用意しようとすると、

と受け入れてくれません。では、どうして↑のようなファイル名が可能になったのでしょう?謎?謎?謎?です。

 

では、ということで去年9月に軽井沢に行った時の写真(同様にプロパティダイアログから日付をコピペした)をチェックすると矢張り'?'が入っています。

"C:\Users\<パス>\ピクチャ\?2021??9??23?日軽井沢\?2021??9??23?日軽井沢①.jpg"
"C:\Users\<パス>\ピクチャ\?2021??9??23?日軽井沢\?2021??9??23?日軽井沢②.jpg"
"C:\Users\<パス>\ピクチャ\?2021??9??23?日軽井沢\?2021??9??23?日軽井沢③.jpg"
"C:\Users\<パス>\ピクチャ\?2021??9??23?日軽井沢\?2021??9??23?日軽井沢④.jpg"

矢張りこいつらをドラッグアンドドロップしても、.albファイルとして読み込んでもプログラムは落ちます。では具体的にどの処理で落ちているのかを確認する意味で、次のようにトラップをかけます。

 

【CALBUM.hより抜粋】

MessageBox(m_hWnd, "mbstowcsの前", "エラー", MB_OK | MB_ICONERROR);
    //現在選択されている写真のファイル名をワイド文字にする
    mbstowcs(m_WFName, m_Photo[m_Selected]->m_FName, MAX_PATH);
MessageBox(m_hWnd, "m_Imageコンストラクターの前", "エラー", MB_OK | MB_ICONERROR);
    //現在選択されている写真のファイル名でイメージクラスインスタンスを生成
    
m_Image = new (Image)(m_WFName);

すると案の定、このように↑の赤字部分(Image::Image(wchar_t, bool)コンストラクター)で落ちていることが確認できました。しかし、MSNで調べても、

"Return value

None(コンストラクターなので戻り値がありません)

Remarks

You can construct Image::Image objects based on files of a variety of types including BMP, Graphics Interchange Format (GIF), JPEG, PNG, TIFF, and EMF."(JPGやGIF等いろんなファイルでImageインスタンスを構築できます、と言っています)

参考になりそうなことは書いていませんね。
 
まぁ、結論として今回のバグの原因は、
(1)Windowsが「ファイル名には次の文字は使えません: \ / : * ? " < >」と禁止している'?'が、(プロパティダイアログの「作成日時」のコピーから)違法にファイル名に侵入した。⇒禁則を犯したのはMicrosoft Windowsであり、私ではない!!!
(2)また、Windowsは'?'が混入しているファイル名をエラーで排除することなく、許容した。⇒手書きで入力しようとするとツールチップが出て受け付けないのに違法混入を許容したのはMicrosoft Windowsである!!!
(3)従って、私のアルバムソフトで解決するべき問題ではなく、仕様違反を許容したWindowsのバグとして私のプログラムではこの問題に対する対処を行わない
こととにしますが、何か?(プログラム落ち、が発生した際に写真の枚数を減らして正常に動いたのは、それらのファイルに'?'が無かったからでした。その結果、私の最初の推論が大きく間違ってしまいました。しかし、Imageインスタンスを減らすことはプログラミングの理にかなっていたことだし、まぁ、良しとします。といっても同様の問題がまた発生すると思われるので、「1フォールダー内のファイル名を調べて、'?'があれば除去して文字を詰め、renameするツール」を作るつもりです。)
 
しかし、偉そうなことを言っている私も今回のバグ取りの最中に(今回初めて経験する問題なのですが)次の問題(バグ、といってよいでしょう)と、遭遇しました。
 
【新たなバグ問題】
詳しくは次回解説します。
 

お元気で.....もないかな。

 

1.良い話

私は20年前からBCC55でBCC Developerを使っていたので、このエディターが気に入っていますが、BCC102になってからMAKEベースのビルドができなくなって遠ざかっていました。しかし、BatchGoodを作ってこれを使ってバッチファイルを作ると「ツール」「環境設定」「プログラム」を使ってバッチファイルが登録できる(注)ので、また使い始めるようになりました。

注:この外部プログラムを実行できる機能は本来"*.exe"ファイルを想定しているので、登録用のファイルを開くダイアログではバッチファイルは表示されませんが、エディットコントロールに"*.bat"と入力すればちゃんと登録できます。

 

2.悪い話

開発していたアルバムソフトですが、ほぼ想定仕様通りの動作ができるようになったので、本格実験を行うべく50数枚の料理写真をアルバムファイルにまとめて開いてみました...ら、プログラムが落ちます。????

サンプルの写真では正常動作するのに何故落ちるのか?写真の枚数を減らしてみた....ら、正常に動作します。????んん?やや動きがのろいな、という印象があります。

で、結論は出ていませんが(注)、現段階の推定原因を「CPHOTO、CALBUMで読み込みイメージをメモリーに保存するので、容量の大きなイメージファイルだと一挙にメモリー落ちするのではないか?」と考えています。

注:なにせ、エラーメッセージも表示せず、ファイルを読み込むとプログラムが落ちるんです。

 

ということで、開発に使ったサンプルの画像と自分の料理の画像を比較してみました。

 

<サンプル画像例>

Cat.jpg 320 x 240 42KB

<本番読込画像例>

2018年9月1日チリコンカーニ③.JPG 2448 x 3264 3.27MB

 

う~ん、やっぱりこれかも。しかし、今後どうするか?

(1)重いイメージファイルに耐えられるように、イメージは毎回表示したら捨ててメモリーに残さない。

(2)巨大イメージファイルを現実的な640x480程度のサイズに縮小するソフトを開発する。

(3)ばからしいから、スマホに任せて、開発をここで中断する。

 

どっ、しようかなぁ?

 

あまり芳しくなかったですね。久々にWindows 10をクラッシュさせるバグを出し、OSからMicrosoftへ2回ほど異常報告がチクられました。(ヤレヤレ。)

 

そのバグ退治に難儀をして、途中で嫌気がさして投げ出してブログを更新しませんでした。すみません。

 

前にも書きましたが、デバッガーのないBCC102のデバッグ環境は最悪で、私の作法は「(ここら辺に悪さをする奴がいるだろうと思われるところの前後に)MessageBoxのトラップを仕掛け」て異常発生個所を特定してゆきます。今回の場合、異常終了の状態からメインプログラムの処理((プロジェクト名)Proc.hの部分)部分を想定しましたが、それがクラス定義のヘッダーファイルに及び、そこいら中トラップだらけ状態で嫌気がさした、という訳です。

 

結局エラーの原因は、CALBUMというクラスの仕様変更を行い、似たようなイメージ表示用のメンバー関数のうち一つはゼロ初期化されたポインター配列変数m_Photo[]がゼロのままか否かで未生成かどうかをチェックするようにしましたが、もう一つの関数は仕様変更前のm_PhotoクラスのImageオブジェクト変数m_Imageが生成されているか否かのチェックのままであったために「(m_Photo[n]が未生成の場合)ゼロポインターが参照するm_Image変数を取ってこい」という命令となり、当然のことながらシステム領域に違法侵入することとなった次第です。(以下は該当部分-ご参考迄)

//イメージの表示(x, yからw幅、h高さの矩形に原寸表示比率で表示)
bool CALBUM::ShowImg(int x, int y, int w, int h) {

    if(!m_Photo[m_Selected])    //エラー対応
        return FALSE;

//イメージの表示(第1引数TRUE左上、FALSE中央、第2引数TRUE原寸、FALSEサイズ調整)
bool CALBUM::ShowImg(bool lefttop, bool asis) {

    if(!m_Photo[m_Selected]->m_Image)    //エラー対応 (解説:こいつが悪さをしていました)
        return FALSE;

 

エラーを回避する為の条件文がエラーの原因だった、ということで洒落にもなりません。しかし、このようにCやC++言語は、いかに高度な機構を取り入れたといっても、アセンブラーに近い「低級」言語の性格を持っているのでプログラマーとしては「怖くて」楽しいともいえると思います。いずれにしても一歩前進となりました。

 

さて、話がそれましたが、現在開発しているのは「PC内に分散、散らかった状態になっている種々のイメージファイルを纏めるアルバムソフト」でして、

 

「現実のアルバムのように、空白状態の冊子(CALBUMクラスをベースとしたメインウィンドウ)に写真(GDI+に基づくCPHOTOクラスのイメージ)を加えていって一冊のアルバムとなり、一つのアルバム(*.albファイル)を開くと、見開きに写真が並び(サムネイル表示)、その一つを取ってみると(単一イメージ表示へ切り替え)大きく表示され、裏を見ると備考を読むことができる(単一表示モードでテキストデータをダイアログ表示する)という基本仕様」

 

にしています。このようなソフトを思いついたのは、5年ほど前から自分で作った料理の写真を残しているのですが、それにコメントを付けて一つにまとめたかったからです。「Win10に標準添付のMicrosoftフォト等、イメージを表示するソフトは色々とあるけど、異なる種類のファイルを纏めてアルバムのように管理するソフトってないから」と思っていたのですが、Microsoftフォトをよくよく見たら「+追加先」というのが左上にあり、クリックすると「新しいアルバム」とあったので、このようなアルバム処理ができるんだと今日初めて知りました

 

そりゃ、Microsoftだもん、当ったり前だよねっ!

 

 

それでは前回のクイズの私なりの回答を紹介します。ポインターとメモリー割り当てを使ってC++っぽい感じがするのではないでしょうか?(勿論、関数、クラスを問わず、これ以外にも多くの回答があり得ます。皆さんも考えてみてください。一つの例として、フィルター文字列のデータ文字列をそのまま記録用にメモリーを割り当ててコピーして、各拡張子部分("*.ext;")を"\0.ext\0"に加工して、配列のポインターを割り当てる、という方法もあると思います。)

 

私の回答例は、緑字のテストプログラム付きで、そのままコンパイルできるようにしています。何をやっているのかはかなり詳しくコメントに書きましたのでわかると思います。

また、最後のサンプルを外したもの(青字部分)をCEXTCHK.hとしてBCCSkeltonに追加します。BCC55はC++11に合致していないのでメンバー変数の初期化はコンストラクターで行わなければなりませんが、BCC102はC++11を満たしているので不要です。その差異を紫字部分で示しますので参照してください。コードはC++11に適合していなくてもコンパイルできるようにしてあります。

 

//////////////////////////////////////////////////////////
// OPENFILENAME構造体のlpstrFilterを用いた拡張子チェック
// Extension Check Class using OPENFILENAME's lpstrFilter
// OPENFILENAME's lpstrFilterの構造
// (表示文字列\0)
// (拡張子データ文字列\0)
// ......
// (表示文字列\0)
// (拡張子データ文字列\0)\0←最終NULL
// なお、拡張子データ文字列は"*.ext1;*.ext2;*ext3...\0"と
// いう書式により複数の拡張子を対象とすることができる
//////////////////////////////////////////////////////////

#include    <windows.h>    //これは単体コンパイルの為に入れているので、CEXTCHK.hでは外しています。

class CEXTCHK
{
public:
    //メンバー変数

    int m_Count;                    //拡張子数
    char** m_Ext;                    //拡張子配列
//C++11以降ではメンバー変数宣言時に初期化可能
//    int m_Count = 0;                //拡張子数
//    char** m_Ext = 0;                //拡張子配列

    //メンバー関数
//C++11以降では初期化コンストラクターは不要
    CEXTCHK();                        //コンストラクター

    ~CEXTCHK();                        //デストラクター
    void Init();                    //全てのメンバー変数を初期化する
    int GetExt(char*, char**&);        //'.'付小文字拡張子配列を返す
    void ConvertUC();                //'.'付大文字拡張子配列を返す(必ずGetExt関数の後に使う)
    bool CheckExt(char*, char*);    //フルパスファイル名の拡張子チェック
};


//C++11以降では初期化コンストラクターは不要
//コンストラクター
CEXTCHK::CEXTCHK() {

    m_Count = 0;
    m_Ext = 0;
}


//デストラクター
CEXTCHK::~CEXTCHK() {

    Init();
}

//全てのメンバー変数を初期化する
void CEXTCHK::Init() {
    if(m_Count) {                            //拡張子配列があれば
        for(int i = 0; i < m_Count; i++)    //すべての
            delete [] m_Ext[i];                //拡張子文字列配列を解放する
    }
    delete [] m_Ext;                        //最後にポインター配列を解放する
    m_Count = 0;                            //拡張子数
    m_Ext = 0;                                //拡張子配列
}

//'.'付小文字拡張子配列を返す
int CEXTCHK::GetExt(char* src, char**& pext) {

    if(m_Count)                                //既に使われた場合
        Init();                                //再初期化する
    char *cp1, *cp2, *cp3;                    //処理用文字列ポインター
    cp1 = src;                                //ソース文字列をセット
    while(*cp1) {                            //cp1が二重のNULL(\0\0)を指すまで
        //最初の表示用文字列をスキップする
        cp1 += strlen(cp1) + 1;                //cp1を次のNULL(\0)の次まで進める
        //cp1~3はデータ文字列(*.ext1;*.ext2;*.ext3;\0)を指している
        cp3 = cp2 = cp1;
        //ここからはcp2を先頭に置き、cp3を前に進める
        while(*cp3) {                        //cp3を次のNULLまで進める
            //大文字は小文字に変換する
            if(*cp3 >= 'A' && *cp3 <= 'Z')
                *cp3 += 'a' - 'A';
            //';'で区切られた複数の拡張子設定の場合
            if(*cp3 == ';') {                //';'の場合もNULLと同様の処理を行う
                //文字列配列ポインターextを作り、m_Extのデータを退避させる
                char** ext = m_Ext;
                //要素数が一つ多い配列を作る
                m_Ext = new char*[m_Count + 1];
                //退避した配列の文字列ポインターデータを新しい配列に移す
                for(int i = 0; i < m_Count; i++)
                    m_Ext[i] = ext[i];
                //旧m_Extの配列用メモリーを解放
                delete [] ext;
                //追加した配列にアスタリスク以降(cp2 + 1)NULLまでをコピーする
                m_Ext[m_Count] = new char[cp3 - cp2];                //正しくは「(cp3 - cp2 - 1) + 1」
                strncpy(m_Ext[m_Count], cp2 + 1, cp3 - cp2 - 1);    //"(cp2)->*.ext(cp3)->;"
                m_Ext[m_Count][cp3 - cp2 - 1] = NULL;                //m_Ext[m_Count]の指す文字列の最後にNULLを置く
                //配列の追加が終わったのでカウンターを増加させる
                m_Count++;
                //cp3、cp2を';'の次に進める
                cp3++;
                cp2 = cp3;
            }
            else
                cp3++;
        }
        //文字列ポインター配列を作り、m_Extのデータを退避させる
        char** ext = m_Ext;
        //要素数が一つ多い配列を作る
        m_Ext = new char*[m_Count + 1];
        //退避した配列の文字列ポインターデータを新しい配列に移す
        for(int i = 0; i < m_Count; i++)
            m_Ext[i] = ext[i];
        //旧m_Extの配列用メモリーを解放
        delete [] ext;
        //追加した配列にアスタリスク以降(cp2 + 1)NULLまでをコピーする
        m_Ext[m_Count] = new char[strlen(cp2)];
        strcpy(m_Ext[m_Count], cp2 + 1);
        //配列の追加が終わったのでカウンターを増加させる
        m_Count++;
        //cp1を次の'\0'の次まで進める(表示文字列を指す)
        cp1 = cp3 + 1;
    }
    //第2引数に文字列配列のポインタ―を渡す
    pext = m_Ext;
    //戻り値で文字列配列の要素数を返す
    return m_Count;
}

//'.'付大文字拡張子配列を返す(必ずGetExt関数の後に使う)
void CEXTCHK::ConvertUC() {

    for(int i = 0; i < m_Count; i++) {
        for(char* cp = m_Ext[i]; *cp; cp++)
            if(*cp >= 'a' && *cp <= 'z')
                *cp -= 'a' - 'A';
    }
}

//フルパスファイル名の拡張子チェック 
bool CEXTCHK::CheckExt(char* filename, char* flt) {

    char** ext;
    int n = GetExt(flt, ext);
    //小文字チェック
    for(int i = 0; i < n; i++) {
        if(strstr(filename, ext[i])) {
            return TRUE;
        }
    }
    ConvertUC();
    //大文字チェック
    for(int i = 0; i < n; i++) {
        if(strstr(filename, ext[i])) {
            return TRUE;
        }
    }
    return FALSE;
}


////////// Sample Program/////////
#include    <stdio.h>
#include    <conio.h>
#include    <stdlib>
#include    <iostream>

/////////////////
//サンプルデータ
/////////////////
char* g_Flt = "ビデオファイル(*.avi;*.mp4;*.mov)\0*.avi;*.mp4;*.mov\0オーディオファイル(*.mp3;*.wav;*.mid)\0*.mp3;*.wav;*.mid\0\0";
char* filename = "C:\\Users\\ysama\\Programing\\Windows Program\\DirectShow\\Debug\VideoData\\Butterfly.mp4";
/////////////////
///////////////////////////
//Testコンソールプログラム
///////////////////////////
int main(int argc, char** argv) {

    CEXTCHK ec;

    char** ext;
    int n = ec.GetExt(g_Flt, ext);
    for(int i = 0; i < n; i++)
        std::cout << ext[i]  << std::endl;
    fa.ConvertUC();
    for(int i = 0; i < n; i++)
        std::cout << ext[i]  << std::endl;
    getch();
    std::cout << "<<<Source file ---> "  << filename << ">>>"  << std::endl;
    if(ec.CheckExt(filename, g_Flt))
        std::cout << "Found!"  << std::endl;
    else
        std::cout << "Not found!"  << std::endl;
    getch();
    return 0;
}

 

昨日の今日でネタ晴らししちゃうのもせっかちすぎるので、今日はつれづれなるままにファイルの拡張子の話などを。

 

ファイル拡張子」と最初に出会ったのは、初めてコンピューター(シャープのポケコン、確かSHARP PC-1500/1501)に触った27歳の時。やはり"*.bas"で、その後の「7万円」のMSXでも、「20万円」のMZ-2500でもbasic言語プログラムは(中間言語ファイルでも)"*.bas"でしたね。実際当時は"(ファイル名 ascii 8文字).(拡張子 ascii 3文字)"が「法」でしたから。オペレーティングシステムもまだMS-DOSが新興物でCP/Mが「本家」でしたが、実行ファイルが"*.com"、機械語ファイルが"*.obj"や"*.lib"など、今でお見かける名前ですね。16bit系でも、当時は実行ファイルは64KB以内の*.comが主流で、リロケート可能な"*.exe"のプログラムは80年代に入ってから一般的になってきたと記憶しています。

 

MS-DOSが興隆して、Windows 3.1(16bit)に至る過程でも、依然"(ファイル名 ascii 8文字).(拡張子 ascii 3文字)"は健在で、Windows 3.1で長いファイル名を付けたり、表示したりできましたが、それらは「別名(Alias)」で、実際にシステムで使うのはショートバージョンでした。また、拡張子はアプリケーションベースで多様化してきました。(例:*.doc、*.xls-これを「一覧」というのはちょっと大げさですね。、暇な人はこれも眺めてください。)

 

参考までに、以下に私が米国にいた時(1991 - 1996)に入手した、16bit Windows 3.1ベースのMIDIデータ作曲、編集ソフト Session.exeの画像を紹介します。

中央にあるのが当時の「ファイルを開く」ダイアログで、"Music Data"というフォールダーが、システム的には”MUSICD~1""Sound Vision.mid"というファイルが"SOUNDV~1.MID"という ascii 8文字に代えられているのが分かります。この"(ファイル名 ascii 8文字).(拡張子 ascii 3文字)"という呪縛が解き放たれて、現在の「スペース文字も許す長文ファイル名(拡張子も3文字以上可)」となったのはWindows 95以降になります。(なお、このSessionはとてもよくできたソフトで、MIDIデータを18トラック迄楽譜化し、ミキサーシーケンサーで処理できる優れもので、現在もきちんと完動します。良いソフトウェアは旧さを感じさせませんね。)

 

プログラミング的には、フルパスファイル名を扱う場合の注意点として、

(1)ドライブ

(2)パス

(3)ファイル名

(4)拡張子

に分解する場合に「スペース文字も許す長文ファイル名(拡張子も3文字以上可)」が許される為、空白文字が含まれる場合にはフルパスが""で囲まなければなりません。

種々のライブラリー(MicrosoftのMFC、EmbarcaderoのVCL)がそのような場合に役に立つクラスを用意しています。(注)

注:MFCのCPathクラスやDelphiのTPathクラスなど。(WEBで調べるとSysUtilsクラスだと思うのですが、そこの解説を見るとこんなで、C++ Builderがどうなっているのかよく分かりません。)

因みにBCCSkeltonでもCARGクラスの以下メソッドが使えます。

    char* Drive();

    char* Path();

    char* FileName();

    char* Ext();

 

とりとめのない話でしたが、最後にもう一つ。拡張子の関連話題で、

「何故Microsoftはファイルパスにバックスラッシュ(日系コードでは"\")を使うのか?」

その答えはこの面白い記事に出ていました。

 

GDI+を使ったアルバムソフトを開発しようとしていて、私のGDI+環境でデコードできる静止画ファイルの制限を拡張子で行うことが必要と考えた時に、前のFileListとDirectShowで「内心忸怩たるところがあった点」に思いが至りました。

 

それは...以下に実物で示します。

<FileListProc.h>
    if( strstr(FileName, ".avi") || strstr(FileName, ".AVI") ||
        strstr(FileName, ".mp4") || strstr(FileName, ".MP4") ||
        strstr(FileName, ".mov") || strstr(FileName, ".MOV") ||
        strstr(FileName, ".mp3") || strstr(FileName, ".MP3") ||
        strstr(FileName, ".wav") || strstr(FileName, ".WAV") ||
        strstr(FileName, ".mid") || strstr(FileName, ".MID")) {
<DirectShowProc.h>
    if( strstr(arg.v(1), ".avi") || strstr(arg.v(1), ".AVI") ||
        strstr(arg.v(1), ".mp4") || strstr(arg.v(1), ".MP4") ||
        strstr(arg.v(1), ".mov") || strstr(arg.v(1), ".MOV") ||
        strstr(arg.v(1), ".mp3") || strstr(arg.v(1), ".MP3") ||
        strstr(arg.v(1), ".wav") || strstr(arg.v(1), ".WAV") ||
        strstr(arg.v(1), ".mid") || strstr(arg.v(1), ".MID")) {

    if( strstr(FileName, ".avi") || strstr(FileName, ".AVI") ||
        strstr(FileName, ".mp4") || strstr(FileName, ".MP4") ||
        strstr(FileName, ".mov") || strstr(FileName, ".MOV") ||
        strstr(FileName, ".mp3") || strstr(FileName, ".MP3") ||
        strstr(FileName, ".wav") || strstr(FileName, ".WAV") ||
        strstr(FileName, ".mid") || strstr(FileName, ".MID")) {

 

う~ん、いかにもダサいっ!

ダサいのみならず、FileListについては汎用サービスとしてアルバムソフトでも使おうと思ったのですが、Drag & Dropで拡張子が固定化されており、アプリでいちいちソースを書き換えるのみならず、同じファイル名の違うDLLがいくつもあるのは不味、(面倒くさいけど、また面倒くさいことはやりたくないけど)さすがにこれ(FileList)が汎用的なサービスを提供するにはまずいでしょう、とFileListの汎用化の為にこの「ファイル拡張子チェック」を書き換えることを決断しました。

 

では、どうやるか?やりたいことを小さい処理に分割して具体仕様にしてみましょう。

 

【プログラム仕様】

1.独自に変数等で設定するのはロスなので、アプリでファイルの読み書きで使うOPENFILENAME構造体のlpstrFilter情報を使う。

2.「引数で与えられる文字列であるlpstrFilter情報」「(表示文字列1\0)(拡張子データ文字列1\0)(表示文字列2\0)(拡張子データ文字列2\0)...(表示文字列n\0)(拡張子データ文字列n\0)\0」というデータ構造を取る)を分解して、拡張子データ文字列「*.ex1;*.ex2;*.ex3; ... ;*.exn\0」というデータ構造を取る)から↑のような「ピリオッド付き拡張子文字列(例:".mp4")の配列」を作成する。

3.得られたピリオッド付き拡張子配列を基に、ループで「引数のフルパスファイル名」の中にピリオッド付拡張子があるか否かをstrstr関数でチェックする。

4.更にこのループチェックは小文字のみならず大文字でも行う

5.結果はbool(TRUE-見つかった or FALSE-見つからない)で返す

 

【読者へのQuiz】

「拡張子チェック」の関数またはクラスオブジェクトのメソッドをC++言語で作ってください。

 

次回に私のソリューションを紹介するとともに、SampleBCCSkeltonのFileListとDirectShowのファイル起動、ドラッグアンドドロップのコードを書き換えてアップロードする予定です。

 

【私のソリューション-参考】

私は関数で長々書いて、他のプログラムに張り付けるよりもクラスで定義してBCCForm and BCCSkeltonに追加することにしました。

クラス名は"CEXTCHK"で"CEXTCHK.h"にいれて次のアップロードから追加します。

<使用例>

////////////////////////////////////

//ここでは外部変数として定義しています。

////////////////////////////////////

CEXTCHK g_ExtChk;
char* g_FileName;

const char* g_Flt = "ビデオファイル(*.avi;*.mp4;*.mov)\0*.avi;*.mp4;*.mov\0オーディオファイル(*.mp3;*.wav;*.mid)\0*.mp3;*.wav;*.mid\0\0";

    if(g_ExtChk.CheckExt(g_FileName, g_Flt)) {

        //処理...
    }

 

既にイメージの描画についてはDrawImageに30ものローバーロード関数があり、例として

例:m_pGraphics->DrawImage(m_Image[m_Selected], x, y, w, h);    //GraphicsクラスのインスタンスのDrawImageメソッドの引数にImageクラスのインスタンスへのポインターを入れ、x,y座標に幅(w)、高さ(h)指定で表示する。

も挙げました。

 

ここで注意したいのは、「x,y座標に幅(w)、高さ(h)指定で表示する」の点です。

 

イメージファイルの画像は横長のみならず、縦長もあり、また加工された画像では極端に横長、縦長もあります。これらのオリジナル画像を表示する際には、

例:m_pGraphics->DrawImage(m_Image[m_Selected], x, y, m_Image[m_Selected]->GetWidth(), m_Image[m_Selected]->GetHeight());    //GraphicsクラスのインスタンスのDrawImageメソッドの引数にImageクラスのインスタンスへのポインターを入れ、x,y座標に原寸大で表示する。

とすればよいのですが、2000年代のガラケーの写真ならまだしも、現在のデジカメやスマホでとった画像はピクセルが膨大で原寸大だとなにがなんだかわからないような大きさになります。(一度やってみてくださいね。)

従って矢張り原寸大ではなく、幅、高さ指定(ピクセル数)で表示せざるを得ないのですが、幅と高さの比率がオリジナルイメージと異なると画像自体横長や縦長になってしまいます。

これはDrawImageのみならず、大きな画像のサムネイルを取得するGetThumbnailImage関数でも同じです。(実はこのGetThumbnailImage関数はそこのところをうまくやっているのではないか、と考え、↓の画像のように実験しましたが、矢張り内容はDrawImageと何ら変わりないことが分かりました。)

 

:以下の3つのコードとその実行結果を示す。

1-GetWidth、GetHeightメソッドを使い、原寸大のイメージを座標(0, 0)に表示させる。

MyGraphics.DrawImage(imageP, 0, 0, imageP->GetWidth(), imageP->GetHeight());
2-上記1のイメージの下(従っていのイメージの高さをy座標にしている」)にシステムサイズ(注)で表示。

注:物理サイズのこと。Imageクラスは「ピクセル幅の値と横方向の解像度 (ドット/インチ) の値を格納します。 イメージの物理的な幅 (インチ単位) は、ピクセル幅を水平方向の解像度で割ったものです。 たとえば、ピクセル幅が360で水平方向の解像度が72ドット/インチのイメージの物理的な幅は5インチです。 同様の解説は、ピクセルの高さと物理的な高さに適用されます。」(MSDN)

MyGraphics.DrawImage(imageP, 0, imageP->GetHeight());    //幅、高さを指定していない
3-サムネイル用のポインターを使い、GetThunmbnailImage関数で128x128のサムネイルを作って右(1のイメージの幅がx座標)に表示。(オリジナルイメージの縦横比が変えられてしまう。)

pThumbnail = imageP->GetThumbnailImage(128, 128, NULL, NULL);

MyGraphics.DrawImage(pThumbnail, imageP->GetWidth(), 0, pThumbnail->GetWidth(), pThumbnail->GetHeight());
 

 

このような実験を行いながら、プログラム開発を行っています。

さて、前回の方針(注)にそって学習を進めましょう。

注:「ということで(何が「ということ」なのか、分かりませんが)、「GDI+をカバーする」なんて大口はたたかず、GDI+で拡張された種々のイメージファイルの読み書きのみを先ずは当たろうか、ということで考えています。」

 

定石1:GDI+の開始の際のお作法

GDI+を使うには、その為のDLLをアプロードし、その為の定義が入ったヘッダーが必要です。また、それらは専用ネームスペースにあるので使えるようにします。

//GDI+のインクルード
#include <gdiplus.h>
#include <wchar.h>
#pragma comment(lib,"gdiplus.lib")

using namespace Gdiplus;
次にGDI+の初期化に関わる専用の変数と関数を呼びます。

GdiplusStartupInput m_gdiSI;    //GDI+のスタートアップインプット
ULONG_PTR m_gdiToken;            //GDI+のトークン
//GDI+の開始
GdiplusStartup(&m_gdiToken, &m_gdiSI, NULL);
これでGDI+が使えるようになります。なお、新し系のサービスではワイド文字を使っているので"wchar.h"を入れておくのが良いでしょう。またその際には必ずワイド文字-マルチバイト文字変換があるのでロケール設定関数も必要です。

//ワイド文字使用のためロケールの初期化(日本語)
setlocale(LC_CTYPE, "JPN");
 

定石2:GDI+の終了の際のお作法

C++では入り口にお作法(例:コンストラクター)があれば、必ず出口にもお作法(例:デストラクター)があります。GDI+の場合は以下の通りです。

//GDI+の終了
GdiplusShutdown(m_gdiToken);
 

ここまではすべてのGDI+の利用に共通です。掘り下げて学びたい方は↓をご覧ください。

学習用-GDI+ (復習用-Windows GDI

なお、20ものヘッダーファイルリストがありますが、BCC102のインクルードファイルで見ると全てgdiplus.hで済まされそうです。(エラーが出たらその時に考えましょう。)

 

定石3:GDI+のイメージ描画のお作法

今回やるイメージファイルからのイメージの読みこみ、ウィンドウへの表示には、次の二つのクラスがかかわってきます。(というか、老人には荷が重いので、今回はこれだけしか使わないつもりです。)また、GDI+はC++で書かれているようで同一名のメソッド(関数のオーバーロード)がおおく(注)、いちいち確認して進めることが肝要です。

注:例えばGraphicsクラスのイメージを表示するDrawImageメソッドだけで30ものオーバーロード関数があることは前回書きました。

 

Graphics
The Graphics class provides methods for drawing lines, curves, figures, images, and text. A Graphics object stores attributes of the display device and attributes of the items to be drawn.(線、曲線、図形、像や文章を描画するメソッドを規定するクラスで、その実体(インスタンス)は描画デバイスと描画対象の属性を保有します。)

これを使えるようにするにはコンストラクターが必要です。これもオーバーロード関数がいくつかありますが、次が簡単でしょう。

Graphics(HDC)

Graphics(HWND,BOOL)

第2引数のboolは色調整を行うか否かの指示で既定値はFALSEです。Graphics(hWnd)と省略して書く事が出来ます。

Graphics* m_pGraphics = new Graphics(m_hDC);

 

Image
The Image class provides methods for loading and saving raster images (bitmaps) and vector images (metafiles).(ラスターイメージ(ビットマップ)とベクターイメージ(メタファイル)の読み込み、書き込みメソッドを規定するクラスです。)(注)

注:「なんだ、ビットマップだけなんだ」と思わないでください。データとして保有するのはビットマップ形式ですが、読み込み(デコード)と書き込み(エンコード)には多彩な種類があります。これは末尾に確認するコードを載せておきます。また、ビットマップとメタファイル(よくワードやエクセルの画像などで出てきますよね?)については次を参考にしてください。

ベクター画像とラスター画像の違いとは?

コンストラクターは大きく、ファイルからとストリームからの二つあります。

Image(WCHAR*,BOOL)

Image(IStream*,BOOL)

初心者の私は勿論前者のファイルからのものを使います。(ここでwcharが出てきましたね。)

Image* m_Image[m_NoI] = new (Image)(m_WFileName);    //m_WFileNameはワイド文字のフルパス名です。

 

このGraphicsクラスのインスタンスとImageクラスのインスタンスを使った画像表示は次のようになります。(これも↑で書いたように30のオーバーロード関数があります。)

例:m_pGraphics->DrawImage(m_Image[m_Selected], x, y, w, h);    //GraphicsクラスのインスタンスのDrawImageメソッドの引数にImageクラスのインスタンスへのポインターを入れ、x,y座標に幅(w)、高さ(h)指定で表示する。

 

ここまでくれば、GDI+によるファイルの読み込み、表示ができるようになりますね。

 

<ご参考-ご自身のPCで読み書きできるイメージファイルの列挙(MSDNの例を改造)>

#include <windows.h>
#include <gdiplus.h>
#include <stdio.h>
#include <conio.h>

using namespace Gdiplus;

INT main() {

    // Initialize GDI+.
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    //Variables to use
    UINT  num;                              // number of image encoders
    UINT  size;                             // size, in bytes, of the image encoder array
    ImageCodecInfo* pImageCodecInfo;    //ImageCodeInfo
    //<<< Encoder List>>>
    // How many encoders are there?
    // How big (in bytes) is the array of all ImageCodecInfo objects?
    GetImageEncodersSize(&num, &size);
    // Create a buffer large enough to hold the array of ImageCodecInfo
    // objects that will be returned by GetImageEncoders.
    pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    // GetImageEncoders creates an array of ImageCodecInfo objects
    // and copies that array into a previously allocated buffer. 
    // The third argument, imageCodecInfo, is a pointer to that buffer. 
    GetImageEncoders(num, size, pImageCodecInfo);
    // Display the graphics file format (MimeType)
    // for each ImageCodecInfo object.
    printf("--------------------\r\nEncoder list of GDI+\r\n--------------------\r\n");
    for(UINT j = 0; j < num; ++j) { 
        wprintf(L"%s\n", pImageCodecInfo[j].MimeType);    
    }
    free(pImageCodecInfo);

    //<<< Decoder List>>>
    // How many decoders are there?
    // How big (in bytes) is the array of all ImageCodecInfo objects?
    GetImageDecodersSize(&num, &size);
    // Create a buffer large enough to hold the array of ImageCodecInfo
    // objects that will be returned by GetImageDecoders.
    pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    // GetImageDecoders creates an array of ImageCodecInfo objects
    // and copies that array into a previously allocated buffer. 
    // The third argument, imageCodecInfo, is a pointer to that buffer. 
    GetImageDecoders(num, size, pImageCodecInfo);
    // Display the graphics file format (MimeType)
    // for each ImageCodecInfo object.
    printf("--------------------\r\nDecoder list of GDI+\r\n--------------------\r\n");
    for(UINT j = 0; j < num; ++j) { 
        wprintf(L"%s\n", pImageCodecInfo[j].MimeType);    
    }
    free(pImageCodecInfo);

    //Finalizing
    GdiplusShutdown(gdiplusToken);
    getch();
    return 0;
}

 

<私のPCの出力結果>

--------------------
Encoder list of GDI+
--------------------
image/bmp
image/jpeg
image/gif
image/tiff
image/png
--------------------
Decoder list of GDI+
--------------------
image/bmp
image/jpeg
image/gif
image/x-emf
image/x-wmf
image/tiff
image/png
image/x-icon