こんにちは、いつもお世話になっています。
ミミです。
ダイアログにビットマップを表示し、さらに線画や文字列を描画させるため、
掲示板や参考書等を参考にし、以下の様なコードを作成しました。
目的とすることは得られたのですが、
ダイアログ表示(DoModal())、非表示(EndDialog())を繰り返していくと、
アプリ起動直後のメモリ使用量よりも、増えていきます。
どこかでリソースリークしている箇所があるのかと思い調査しているのですが、
特に★1、★2の箇所が自分でもよく分かっていないため、
今の私の知識では、最後の ReleaseDC()、DeleteDC()位しか検討がつきません。
どこか不正な箇所や、足りないところがありましたら、
どうかアドバイス願えませんでしょうか?
それと、★1、★2についてもアドバイスしていただけたら幸いです。
環境は、Windows2000、VC6 SP6 です。
よろしくお願いいたします。
<<hに定義したメンバ変数>>
CBitmap m_myBMP; //
HBITMAP m_hBitmap; //
bool m_bDspBMP; // 既にBMP表示済みか否か
<<cpp>>
//---------------------------------------
BOOL CDispBmpDlg::OnInitDialog()
{
CDialog::OnInitDialog();
m_hBitmap = false; // ビットマップハンドルはまだ無効
m_bDspBMP = false; // OnPaintでの BMP 表示フラグ
SelectFile(); // 必ず選択することを前提
return TRUE;
}
//---------------------------------------
void CDispBmpDlg::OnPaint()
{
CPaintDC dc(this); // 描画用のデバイス コンテキスト
if (!m_bDspBMP){ return; } // ファイル未選択状態時は戻る
PaintRtn(); // ペイント処理本線
}
//---------------------------------------
bool CDispBmpDlg::SelectFile()
{
CString sWkName = c:\\Test.bmp;
m_hBitmap = (HBITMAP)LoadImage(NULL, sWkName, IMAGE_BITMAP,
0, 0, LR_DEFAULTCOLOR |
LR_LOADFROMFILE);
if(m_hBitmap){
if(m_myBMP.DeleteObject()){ // 1つ昔のビットマップオブジェクト
削除
m_myBMP.Detach();
}
if(m_myBMP.Attach(m_hBitmap)){
m_bDspBMP = true;
}
}
return true;
}
//---------------------------------------
bool CDispBmpDlg::PaintRtn()
{
BITMAP myBmp;
CRect lRect;
// (1)デバイスコンテキスト生成
CDC* pDC; // デバイスコンテキストへ
のポインタ取得
pDC = GetDC(); // ここにビットマップを描
画して、文字を描く
// (2)ビットマップの表示
CDC myDC;
myDC.CreateCompatibleDC(pDC); // 作業用のデバイスコンテ
キストを作成
CBitmap* pOldBMP;
pOldBMP= myDC.SelectObject(&m_myBMP); // 古いものに関連付け(後で
戻す為)★1
m_myBMP.GetBitmap(&myBmp);
GetClientRect(lRect);
lRect.NormalizeRect();
pDC->StretchBlt(0, 0,
lRect.Width(), lRect.Height(), // ダイアログいっ
ぱいに BMPを表示 &myDC,
// 転送するデバイスコンテキスト
0, 0, myBmp.bmWidth, myBmp.bmHeight,
SRCCOPY);
myDC.SelectObject(pOldBMP); // ★2
// (3)テキストの描画
pDC->SetTextColor(RGB(0,0,0));
pDC->SetBkMode(TRANSPARENT); // テキストの背景色は透明
CRect lTextRect;
lTextRect.top = 10;
lTextRect.bottom = lRect.Height();
lTextRect.left = 0;
lTextRect.right = lRect.Width();
pDC->DrawText(あいうえお, -1, &lTextRect, DT_RIGHT);
// (4)デバイスコンテキスト解放
ReleaseDC(pDC);
DeleteDC(myDC);
return true;
}
すみません、PaintRtn()のところ、注釈がかなり見にくくなってしまいました。
CDialog::DoModal() によって、CDialog::OnInitDialog() が動きますから
DoModal() と EndDialog() とを繰り返すということは CDspBmpDlg::SelectFile()
を繰り返しているということはお分かりでしょうか?
> m_hBitmap = (HBITMAP)LoadImage(NULL, sWkName, IMAGE_BITMAP,
> 0, 0, LR_DEFAULTCOLOR |
>LR_LOADFROMFILE);
が CDispBmpDlg::SelectFile() での処理ですが、これから分かることは、
m_hBitmap が示すビットマップにどんどん読み込んでいるということです
LoadImage() は読み込んだ画像のハンドルを返すので、画像が溜まることになります
勿論(もちろん)DoModal() を抜けたら Windows でのダイアログはもう存在しません
が、CDispBmpDlg のインスタンスはまだ存在します。インスタンスが消滅しても
読み込んだ画像はあなたが削除しない限り残るのですから
(msdn ライブラリーの LoadImage をよくお読み下さい/MSのサイトも調べましょう)
CDispBmpDlg のデストラクターで m_hBitmap を削除(DeleteObject())するといい
でしょう
OnPaint() でせっかく作っている CPaintDC dc; を PaintRtn() で使っていないのは
勿体ないと思います。
void CDispBmpDlg::PaintRtn(DC & dc) {
CDC myDC;
myDC.CreateComaptibleDC(&dc);
/
後(あと)、省略
/
}
ミミです。
島さん、いつもご親切にアドバイスありがとうございます。
>勿論(もちろん)DoModal() を抜けたら Windows でのダイアログはもう存在しませんが、
>CDispBmpDlg のインスタンスはまだ存在します。
LoadImageで得られたハンドルに対し、DeleteObject()しなくてはならないことは、
完全に見落としていました。勉強になります。
デストラクタにて、DeleteObject(m_hBitmap);を実行したところ、
メモリの上昇はなくなりました。
それと、よくよく考えてみますと、OnPaint()でローカル変数として定義しているdc、
使われていませんね。仰る通りです。
大変厚かましいとは思うのですが、
自分の中では、★印の箇所に自分で記述した注釈である「古いものに関連付け(後で戻す為)」
の必要性がいまいちピンときません。
DCを使用した描画に際しては、何故この様に古いものに戻したりする作業が必要になるのです
か?
差し支えがなければ、ご教授頂きたく思います。
>DCを使用した描画に際しては、何故この様に古いものに戻したりする作業が必要になるのです
か?
MSDNのSelectObjectの説明に
An application should always replace a new object with the original, default
object after it has finished drawing with the new object.
と書いてあるからです。
実際、選択したままのオブジェクトをDeleteObjectしても削除されずリークすると思います。
ミミです。
> 選択したままのオブジェクトをDeleteObjectしても削除されず
なるほど、理解ができました。
選択されたままだと、削除そのものが失敗するため、いくらDeleteObject()と
記述していても、実際は削除されていないという事ですね。
島さん、bbbさん、ありがとうございました。
今後もよろしくお願いいたします。