LoadImageを使用してのbitmap表示不具合?について

[戻る]
HAK [E-Mail] 2009/10/15(木) 14:47:51
visual studio 6.0 sp6 mfc+sdkにてアプリケーションを作成しています。
現在Loadimage関数を使用してBITMAP画像を表示しています。
通常時には問題なく画像データが表示されるのですが、何かのタイミング
(window切り替え等、アプリケーションを連続動作させていると突然発生する)で
読み込みを行ったはずのBITMAP画像では無くWindowsに標準登録してある
「最大化」「最小化」「チェックボックス」といった画像データが読み込まれることが
あります。
この状態になると特定フォルダ内にあるどんなBITMAPを読み込んでも
上記画像データが読み込まれてしまいます。
その後、ソフトを動作させていると突然正常に戻ります。
このような状態を経験された方はいらっしゃらないでしょうか?
よろしくお願いいたします。

読み込み部のソース↓

HBITMAP            hbmp, hbmp_old;
HDC            bmp_dc;
BITMAP            bmp;
char * fn ←読み込みを行うファイル名が格納されています。

if( ( hbmp = (HBITMAP)LoadImage( NULL, fn, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE 
| LR_CREATEDIBSECTION )) == NULL ){
        return FAILUR;
}
bmp_dc = CreateCompatibleDC( hdc );
if( !bmp_dc ){
    return FAILUR;
}
hbmp_old = (HBITMAP)SelectObject( bmp_dc, hbmp );
GetObject(hbmp, sizeof(BITMAP), &bmp);

みい 2009/10/15(木) 16:48:14
変数はグローバルもしくはメンバ変数で宣言して
関数を抜けても保持するようにしていますか?

HAK [E-Mail] 2009/10/15(木) 17:57:17
早速の回答ありがとうございます。

ソース内でグローバルやメンバ変数は

fn:関数呼び出し時にファイル名称が入ったポインタとして使用しています。
  呼び出し元もローカル変数です。

hdc:グローバル変数です。

その他はローカル変数としています。

不具合発生時にデバッガーにて確認した所、fnに格納されているファイル名は
呼び出しを行いたい正しいファイル名となっていました。

ローカルでは都合の悪い箇所が有るのでしょうか?

よろしくお願いいたします。

みい 2009/10/15(木) 19:10:01
hbmp, hbmp_oldはグローバルに移すべきです。
読み込み情報が不定状態になる事になります
(他でその領域を使われたら情報が変わります)。
hbmp_oldをきちんと使用しているかどうかは分かりませんが。
bmpは提示されているコードだけでは判断できません。

HAK [E-Mail] 2009/10/15(木) 20:33:29
通常ローカル変数はサブルーチン内では生存していると思いますが
そういった事もあり得ると認識でよろしいでしょうか?

グローバル変数に変更して試してみます。

今更で申し訳ありませんが、下記に関数全体を記載します。

short    fnc_a( HDC hdc, char *fn, short x, short y )
{
HBITMAP            hbmp, hbmp_old;
HDC            bmp_dc;
BITMAP            bmp;

if( ( hbmp = (HBITMAP)LoadImage( NULL, fn, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE 
| LR_CREATEDIBSECTION )) == NULL ){
        return FAILUR;
}
bmp_dc = CreateCompatibleDC( hdc );
if( !bmp_dc ){
    return FAILUR;
}
hbmp_old = (HBITMAP)SelectObject( bmp_dc, hbmp );
GetObject(hbmp, sizeof(BITMAP), &bmp);

BitBlt(hdc, x, y, bmp.bmWidth, bmp.bmHeight, bmp_dc, 0, 0, SRCCOPY);

SelectObject(bmp_dc, hbmp_old);
DeleteDC(bmp_dc);
DeleteObject( hbmp );

return SUCCESS;
}

FlatWest 2009/10/15(木) 23:37:49
この関数では WM_PAINT が発生するたびに呼ばないと、
表示されているビットマップはすぐに消えるけど・・・そうしているの?

HBITMAP            hbmp, hbmp_old;
HDC            bmp_dc;

これらは保持しておいて、WM_PAINT 時には BitBlt だけおこなうように
変更すれば?

ryo 2009/10/16(金) 01:07:09
状況がいまいちわからないので、質問返し


fnc_aは、どこから、どんなタイミングで呼ばれているのか?

引数のhdcは、表示対象のウィンドウのDCなのかなんなのか?

>「最大化」「最小化」「チェックボックス」といった画像データが読み込まれることが
ここでいう、「読み込まれる」というのは、どういうことがいいたいのか?

表示されるというのなら、表示作業を行っている部分は疑わしい可能性はないのか?

LoadImageの結果として「最大化」ボタンの画像などが読み込まれたことを確認した
というならどういう風に確認したのか?

最大化ボタンなどが読みこまれたという状況におちいったとき、
GetObjectで帰ってくる値は、「読み込みたい画像」の値か?
それとも最大化ボタンのものか?

>この状態になると特定フォルダ内にあるどんなBITMAPを読み込んでも
この際、fnc_aが呼ばれていることを確認してあるか?


現象が発生したときに、
使用メモリやGDIの数が異常に大きくなっていないか?
終了時にエラーがでたりしないか?

HAK [E-Mail] 2009/10/16(金) 09:20:14
以下について追記いたします。

Q)
fnc_aは、どこから、どんなタイミングで呼ばれているのか?

A)
・メインループとして使用していますスレッドでデータを比較し、変化が
 起きたときに呼び出します。  
・マウスのL_KEYUPメッセージ処理より呼び出しています。

Q)
引数のhdcは、表示対象のウィンドウのDCなのかなんなのか?

A)
表示対象のウインドウDCと裏描画用のDCです。


Q)
>「最大化」「最小化」「チェックボックス」といった画像データが読み込まれることが
ここでいう、「読み込まれる」というのは、どういうことがいいたいのか?

A)
GetObject(hbmp, sizeof(BITMAP), &bmp);
を行った結果を読み込まれたとしています。

Q)
表示されるというのなら、表示作業を行っている部分は疑わしい可能性はないのか?
LoadImageの結果として「最大化」ボタンの画像などが読み込まれたことを確認した
というならどういう風に確認したのか?
最大化ボタンなどが読みこまれたという状況におちいったとき、
GetObjectで帰ってくる値は、「読み込みたい画像」の値か?
それとも最大化ボタンのものか?

A)
GetObject(hbmp, sizeof(BITMAP), &bmp);
の結果読み込まれたbmpのサイズが読み込みを行いたいBITMAPとは異なっていました。
読み込みしたいBITMAP 46×47
不具合時のBITMAP 16×16
その後表示を
BitBlt(hdc, x, y, bmp.bmWidth, bmp.bmHeight, bmp_dc, 0, 0, SRCCOPY);
で行っています。

Q)
>この状態になると特定フォルダ内にあるどんなBITMAPを読み込んでも
この際、fnc_aが呼ばれていることを確認してあるか?

A)
デバッガーで確認しました。

Q)
現象が発生したときに、
使用メモリやGDIの数が異常に大きくなっていないか?
終了時にエラーがでたりしないか?

A)
メモリやGDIは未確認でしたので確認します。
関数の戻り値はデバッガーで確認した限りは出ていませんでした。

PATIO 2009/10/16(金) 14:25:55
どーも、PATIOです。

> Q)
> fnc_aは、どこから、どんなタイミングで呼ばれているのか?
> 
> A)
> ・メインループとして使用していますスレッドでデータを比較し、変化が
>  起きたときに呼び出します。  
> ・マウスのL_KEYUPメッセージ処理より呼び出しています。

本当にこれだけしかやっていないならうまく行かないと思います。
他の方も書かれていましたが、Windowsの描画処理のお約束として
WM_PAINTを受け取った時にその時点のウインドウの表示内容を
完全に再現できる処理が必要です。
この場合、アプリ側の状況なんてOS側は考慮してくれませんから
いかなる状況下でイベント待ちになっていても再描画できるように
WM_PAINTを受け取ったときの処理を作成しておく必要があります。

私だったらビットマップやメモリDCは一度作成したら保持して置くようにして
ビットマップファイルの読み込みはファイル名が変更されたタイミングのみに
します。読込が発生したらグローバル変数を更新して画面の無効化を行い、
WM_PAINTを発生させます。描画その物は、WM_PAINTを受け取ったときの処理で
行うようにして、ビットマップが指定されていない時などでも描画処理に
影響が出ないようにコードを書くと思います。
ビットマップとメモリDCの開放は、ウインドウ破棄時に確保されているかを
確認しながら開放するようにすれば、毎回読み込む必要は有りません。

PATIO 2009/10/16(金) 14:35:43
補足。

Windowsでの描画結果は一度描画すれば崩れる事が無いと言うようなものではなく、
いつ何時、他のウインドウの表示等で描画結果が崩されるか分からないものです。
したがって、描画結果が崩れてしまった場合、OSからの要請に従って毎回描画を
しなおす必要があります。
ですから、OSからの描画要請であるWM_PAINTを受け取った場合は、
崩された描画結果を修復する事ができるように描画処理をコーディングしておく
必要があります。と言う事は、描画の再現に必要な情報も全て保持しておく必要が
あることを意味しています。
この辺は設計時にはちゃんと認識しておかないといざ描画する時になって
情報が足りないなんて事になります。

PATIO 2009/10/16(金) 14:47:04
あと気になったんですが、

GetObjectはビットマップ構造体に情報を取り出しているだけで
読み込みその物は、LoadImageがやっているのですよね。
おかしくなっている時にLoadImageが参照しているファイル名は
間違いなく対象のファイルパスになっているかどうかの確認は
行ないましたか?

HAK [E-Mail] 2009/10/16(金) 15:22:57
みなさま色々ご意見ありがとうございます。

基本的な動作について再度確認してみます。
(表示方法を見直すのはいろいろな面で難しいですが、、、)

LoadImageが参照しているファイル名については不具合発生時にデバッガーで
停止させた時、正常なファイル名であることを確認しています。

以下、現在確認出来た点です。
試しに不具合発生時にfnのファイル名をデバッガーにより強制的に書き換えを
行い、違うフォルダのファイル名に書き換えをした所、正常に読み込めました。
不具合が出てからでも特定フォルダ以外は正常にデータが読めるようです。

ZCH 2009/10/16(金) 19:07:39
どこかでリークしてたりオーバフローしているとかも疑った方がいいかもしれません。

func_a関数は2個目のif文で
DeleteObject( hbmp );
呼び忘れてる程度で(リークしますよ)、その他は特に問題なく見えます。

個人的にはCreateCompatibleDCはよいとして、
LoadImageは毎回呼ばないように作ります。

2個上のPATIOさんは、fn が相対パス指定の時に、
カレントフォルダが移動した場合等を指摘しているのかと思います。
相対パスの場合、フルパスで指定してみるとどうなりますか?

HAK [E-Mail] 2009/10/19(月) 08:49:25
ZCHさん

ご指摘ありがとうございます。

DeleteObjectが抜けていました。

fnについてはフルパスにて指定しています。

上にてアドバイスのあった
HBITMAPhbmp, hbmp_old;
BITMAP            bmp;


先週の金曜日より、ソフトをデバッグ状態で走らせていますが現象が出てません。

HAK [E-Mail] 2009/10/19(月) 09:01:25
ZCHさん

ご指摘ありがとうございます。

DeleteObjectが抜けていました。

fnについてはフルパスにて指定しています。

上にてアドバイスがありましたので
HBITMAP hbmp, hbmp_old;
BITMAP   bmp;
をstaticとして先週の金曜日より、ソフトをデバッグ状態で走らせていますが
不具合現象は出てません。
たまたま出ていないだけかもしれませんが、、、。
これで不具合が発生しないとしてもいまいち原因がわかりません。
やはりどこかでリークが発生しているのか?
リークが発生するとこのような現象が発生するんでしょうか?
メモリやリソースを食いつぶしていってソフトが動作しなくなるとは思いますが。

HAK [E-Mail] 2009/10/19(月) 09:03:35
2009/10/19(月) 08:49:25の文章は間違えて書き込み途中に送信してしまいました。
申し訳ありません。

subaru 2009/10/19(月) 15:48:06
もしかしたら本当にバグかもしれません。
ググったらこんなページを見つけました。
http://www.tech-archive.net/Archive/Development/microsoft.public.win32.programmer.gdi/2007-01/msg00033.html

第二引数のファイル名のポインタ値の下位16ビットが
たまたま winuser.h に定義されている OBM_* の値に一致するときに現象が発生します。
回避するには第一引数にインスタンスハンドルを渡せばいいようです。

HAK [E-Mail] 2009/10/22(木) 17:43:10
subaruさんご紹介ありがとうございます。
紹介頂いたサイトを参考に不具合時のLoadImage内部を混合モードで
追っかけてみました。
おそらくですが関数引き渡し時にスタックに書き込まれたfnのアドレスが
LoadImage処理中に別のアドレスに書き換わっていました。
(正常時には書き換わっていませんでしたのでおそらく間違いないと思われます)
結果として指定したファイルと異なるデータが帰ってきたようです。

とりあえずの対策として
1.fnをstaticとしスタックを使用しない
2.第一引数にインスタンスハンドルを渡す
この2点で様子を見ることとしました。

混合モードで見ていたため実際は他スレッドの処理だったのかもしれませんが
確認方法が解らず不明です。

HAK [E-Mail] 2009/12/25(金) 15:06:24
[[解決]]
時間が立ってしまいましたが結果を書き込みます。

対策として
1.fnをstaticとしスタックを使用しない
2.第一引数にインスタンスハンドルを渡す
を行いましたが
1.のみでは現象が再発、1週間ぐらいで再度発生しました。

2.を追加で実施した所、1ヶ月連続動作させましたが発生しませんでした。
はっきりとした原因はわかっていませんので、これで100%解決したとは
言えませんが一応結果として報告し、解決とします。

みなさまありがとうございました。

古いスレッドには追加発言できません。