注意:翌9月4日に↓にあるCALBUMの修正部分を再修正し、すぐにアップしなおしました。DLされた方はお気を付けください。なお、その理由詳細は末尾参照。
既に旧い話(6月1日付ブログ)になりますが、Album(写真閲覧ソフト)が突如(A bolt out of the blue)として「落ちる」という現象に見舞われ、
【またまた、謎】Can you hit the bolt back?
というブログを書き、MicrosoftのGDIPLUS.DLLがおかしいんじゃないの?等と匂わせています。(スミマセン、Microsoftさん!)
実は(お恥ずかしい話ですが)このAlbumの不具合は覚えていて、「いつか fix せにゃあかんのぅ」とは思っていたんですが、原因のヒントが思いつかず、モチベーションが下がり、「先送り、先送り」で触らずにいました。その結果...原因調査をして↑のブログを書いたことも忘れてしまい、再度全く同じ様にトラップをかけて原因を探って行って、本日目出度く「ShowImg(int x, int y, int w, int h)関数で、m_Image->GetWidth()とGetHeight()関数がゼロを返すので、Division by zeroエラーが生じている」ということを「発見」しました。
【CALBUM抜粋】
//イメージの表示(x, yからw幅、h高さの矩形に原寸表示比率で表示)
bool CALBUM::ShowImg(int x, int y, int w, int h) {
//エラー対応
if(!m_Photo[m_Selected])
return FALSE;
//現在選択されている写真のファイル名をワイド文字にする
mbstowcs(m_WFName, m_Photo[m_Selected]->m_FName, MAX_PATH);
//現在選択されている写真のファイル名でイメージクラスインスタンスを生成
m_Image = new (Image)(m_WFName);
//m_Imageインスタンスの生成に失敗した場合(解説:これが成否判定の積り)
if(!m_Image)
return FALSE;
//画像を回転させる-RotateFlipType:Rotate<A>Flip<B> <A:None, 90, 180, 270>, <B:X, Y, XY>
m_Image->RotateFlip(m_RFType);
//イメージの原寸を取得
int imgw = m_Image->GetWidth(); //imgwに0が代入される
int imgh = m_Image->GetHeight(); //imghに0が代入される
//w、h内で原寸の縦横対比のイメージを中央に配置
double asp1, asp2;
asp1 = (double)imgh / (double)imgw; //ここでDiv by zeroエラーが発生する
asp2 = (double)h / (double)w;
if(asp1 > asp2) {
imgh = h;
imgw = h / asp1;
}
else {
imgw = w;
imgh = w * asp1;
}
x += (w - imgw) / 2;
y += (h - imgh) / 2;
//m_Imageインスタンスを表示
m_pGraphics->DrawImage(m_Image, x, y, imgw, imgh);
//m_Imageインスタンスを解放
delete m_Image;
m_Image = 0;
//再描画を要求
InvalidateRect(m_hWnd, NULL, TRUE);
return TRUE;
}
「そんなことってあるんかいっ!」ということで、原因をググって行って↑の自分の過去ブログを「再発見」した次第。
ほんにおそろしい忘却力、ボケ力(ヂカラ)!
まぁ、それはそれとして、今回解決まで進展したのは、前回と同じように色々と調べ、色々と試した際に2020年の英文の質疑応答サイトで回答者が「『全く同じファイルパス』-というのは間違いだね。ファイルアクセスの仕方が実質的に違うんだよ。("the very same file paths" - You aren't. You are using a substantially different way to access file system objects.)」という文章を見て、「FileListのファイルパスが間違っていて、イメージファイルが読み込めないという可能性」が頭をよぎりました。
当時のコードはCALBUMクラスのメンバー変数m_Image(注)というImageオブジェクトへのポインターにワイド文字のファイル名からオブジェクトを生成し、その成否判定も行って(いる積り↑で)いました。
注:m_Imageは画像を表示させるときだけポインターとして使い、使わない場合は常時0を代入している。
しかし、この視点で「コンストラクターって値を返さないよなぁ」と思いながら調べなおすと、矢張りImage::Image(WCHAR, bool = FALSE)というコンストラクターは、
Return value
None
と書かれています。
と、いうことはよ?ファイルが読み込まれていなくても(ゼロ)メモリー領域が確保されて、それをm_Imageが指すので「Non 0」になり、その幅と高さを求めると(当然)「0」が返る.....そいこと?
もう言わずもがなですが、(その時、「あっ、そうだ。OneDriveの設定からイメージデータの入っていたフォールダー名「ピクチャ」を"Pictures"フォールダーに入れ替えたんだった」という記憶が...)「落ちる」FileListのファイルパスを見ると「...\ピクチャ\...」となっていたので、一括変換で"Pictures"にすると、
お目出度うございま―す。きちんと表示されましたぁ。
では、次にどのようにして「ファイルのNot foundエラーを検知させるのか?」ということで、ImageクラスのFromFileメソッドとImage::GetLastStatus()を使って戻り値がOk (0) でない場合にエラーとし、特によくありそうな FileNotFound (10) の時はメッセージボックスで表示させようか、とか考えたのですが、実験してみるとなかなかFileNotFoundを返さないんですね。
そんなこんなで、(イメージファイルが読み込めなかった場合、イメージサイズが幅高さともにゼロになることを逆手に取り)現実的な対応として「(幅か高さがゼロなら)『ファイルが読み込めなかった』というエラーメッセージを出して、『絵なし』表示にすることにしました。これで少なくとも「突然落ちる」ことはなくなりました。
【ShowImg関数の修正部】
.
.
.
//現在選択されている写真のファイル名でイメージクラスインスタンスを生成
m_Image = new (Image)(m_WFName, FALSE);
//画像を回転させる-RotateFlipType:Rotate<A>Flip<B> <A:None, 90, 180, 270>, <B:X, Y, XY>
m_Image->RotateFlip(m_RFType);
//イメージの原寸を取得
int imgw = m_Image->GetWidth();
int imgh = m_Image->GetHeight();
//m_Imageインスタンスの生成に失敗した場合imgw、imghはゼロになる
if(!imgw || !imgh) {
MessageBox(m_hWnd, "ファイルが読み込めませんでした。\nファイルパスを見直してください。",
"エラー", MB_OK | MB_ICONERROR);
return FALSE;
}
.
.
.
遅ればせながら、やっと夏休みの宿題が出せました。
【翌9月4日追記-CALBUM再修正】
翌朝も1時半くらいに目が覚めてしまい、ぼんやりスマホをいじってこのブログの確認をしていたら、途端に目が覚めました。
「ファイルが読み込めずにエラー処理しているのに、作ったイメージオブジェクト(サイズはゼロだが、メモリーリークはメモリーリーク)を消していないぞ?」
また、アホなことに、引数が違う別のShowImageのオーバーロード関数も同じ問題があるのに未処理でした。
ということで、次のようにして両ShowImg関数に入れて再修正を行いました。ゴメンナサイ。
//m_Imageインスタンスの生成に失敗した場合imgw、imghはゼロになる
if(!imgw || !imgh) {
MessageBox(m_hWnd, "ファイルが読み込めませんでした。\nファイルパスを見直してください。",
"エラー", MB_OK | MB_ICONERROR);
//m_Imageインスタンスを解放
delete m_Image;
m_Image = 0;
return FALSE;
}