BMPを表示するには

[戻る]
ミミ 2005/02/12(土) 11:41:14
こんにちは、いつもお世話になっています。
ミミです。

ダイアログにビットマップを表示し、さらに線画や文字列を描画させるため、
掲示板や参考書等を参考にし、以下の様なコードを作成しました。

目的とすることは得られたのですが、
ダイアログ表示(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;
}

ミミ 2005/02/12(土) 11:42:30
すみません、PaintRtn()のところ、注釈がかなり見にくくなってしまいました。

2005/02/12(土) 14:12:44
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);
/***
  後(あと)、省略
***/
}

ミミ 2005/02/13(日) 00:51:13
ミミです。

島さん、いつもご親切にアドバイスありがとうございます。

>勿論(もちろん)DoModal() を抜けたら Windows でのダイアログはもう存在しませんが、
>CDispBmpDlg のインスタンスはまだ存在します。

LoadImageで得られたハンドルに対し、DeleteObject()しなくてはならないことは、
完全に見落としていました。勉強になります。
デストラクタにて、DeleteObject(m_hBitmap);を実行したところ、
メモリの上昇はなくなりました。

それと、よくよく考えてみますと、OnPaint()でローカル変数として定義しているdc、
使われていませんね。仰る通りです。

大変厚かましいとは思うのですが、
自分の中では、★印の箇所に自分で記述した注釈である「古いものに関連付け(後で戻す為)」
の必要性がいまいちピンときません。
DCを使用した描画に際しては、何故この様に古いものに戻したりする作業が必要になるのです
か?
差し支えがなければ、ご教授頂きたく思います。

bbb 2005/02/13(日) 01:24:01
>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しても削除されずリークすると思います。

ミミ 2005/02/13(日) 09:07:36
[[解決]]
ミミです。

> 選択したままのオブジェクトをDeleteObjectしても削除されず
なるほど、理解ができました。
選択されたままだと、削除そのものが失敗するため、いくらDeleteObject()と
記述していても、実際は削除されていないという事ですね。

島さん、bbbさん、ありがとうございました。
今後もよろしくお願いいたします。

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