ページ 11

ビットマップ用ハンドルの登録用関数

Posted: 2011年8月17日(水) 17:33
by Integral
こんにちは。以前もこちらで質問させていただいたのですが、自分はWin32APIを用いてゲームを制作しています。
今回はビットマップ用ハンドルの登録用関数を作っている際、上手くいかなかったため質問しました。以下、質問内容です。

ビットマップファイルをロードし、ウィンドウ上に表示するためには次のようなプロセスが必要だと思います。
************************************
ハンドルの宣言→ハンドルの登録→bmp画像の描画→ハンドルの破棄
************************************

コード:

//宣言
static	 HDC hDC;
static HBITMAP hBMP;
static	 BITMAP BMP;

//登録
hDC = CreateCompatibleDC(NULL);
hBMP = (HBITMAP)LoadImage(NULL , TEXT("hoge.bmp") , IMAGE_BITMAP , 0 , 0 , LR_LOADFROMFILE);
GetObject(hBMP , sizeof(BITMAP) , &BMP);
SelectObject(hDC , hBMP);

//描画
BitBlt()等を用いて描画(質問とは直接関係しないため割愛)

//破棄
DeleteDC(hDC);
DeleteObject(hBMP);
以上のようにすれば、画像を表示することができました。
しかし、bmpファイルをロードする度にこの工程を踏んでいたのではあまりにもソースが長くなってしまうため、これを関数にしてみようと思いました。

コード:

//宣言
static HDC hDC;
static	 HBITMAP	hBMP;
static	 BITMAP	BMP;

//ビットマップハンドル登録用の関数
void RegisterBitmapHandle(HDC hDC , HBITMAP hBMP , BITMAP* BMP , TCHAR* BitmapName ) {
	hDC = CreateCompatibleDC(NULL);
	hBMP = (HBITMAP)LoadImage(NULL , BitmapName , IMAGE_BITMAP , 0 , 0 , LR_LOADFROMFILE);
	GetObject(hBMP , sizeof(BITMAP) , BMP);
	SelectObject(hDC , hBMP);
}

//(使用例) RegisterBitmapHandle(hDC , hBMP , &BMP , TEXT("hoge.bmp") );

//ビットマップハンドル破棄用の関数
void DeleteBitmapHandle(HDC hDC , HBITMAP hBMP) {
	DeleteDC(hDC);
	DeleteObject(hBMP);
}

//(使用例) DeleteBitmapHandle(hDC , hBMP);

しかし、この関数を用いてコンパイルするとエラーは出ないものの、ウィンドウだけが表示され画像は表示されません。

問題は自分がポインタを完璧に理解できていない点にあると思うのですが、具体的にどのように定義すればこの関数は正しく動作してくれるのでしょうか。

開発環境:
OS:Windows Vista Home Premium
コンパイラ名:VC++ 2010 Express

Re: ビットマップ用ハンドルの登録用関数

Posted: 2011年8月17日(水) 17:39
by みけCAT
関数の中で作成したhDCを関数の外に持っていっていないですが、大丈夫ですか?

Re: ビットマップ用ハンドルの登録用関数

Posted: 2011年8月17日(水) 17:43
by みけCAT

コード:

//ビットマップハンドル登録用の関数
HDC RegisterBitmapHandle( HBITMAP* hBMP , BITMAP* BMP , TCHAR* BitmapName ) {
    HDC internalhDC;
    internalhDC = CreateCompatibleDC(NULL);
    *hBMP = (HBITMAP)LoadImage(NULL , BitmapName , IMAGE_BITMAP , 0 , 0 , LR_LOADFROMFILE);
    GetObject(*hBMP , sizeof(BITMAP) , BMP);
    SelectObject(internalhDC , *hBMP);
    return internalhDC;
}

//(使用例) hDC=RegisterBitmapHandle( &hBMP , &BMP , TEXT("hoge.bmp") );
として、戻り値のHDCを使用するようにしたらどうでしょうか?
hBMPも書き変わるようにしてください。

Re: ビットマップ用ハンドルの登録用関数

Posted: 2011年8月17日(水) 20:51
by Integral
みけCATさんお返事ありがとうございます。

宣言の部分はグローバル変数として、そのグローバル変数を直接関数内で書き換える・・・という方法はできないのでしょうか?

さらに、もし戻り値として受け取る方法を考える場合も
internalhDC = CreateCompatibleDC(NULL);
など関数内で発生したデバイスコンテキストの解放はしなくてもよいものなのでしょうか?
(自分の理解が怪しいため、このあたりの知識はあやふやなのですが、ハンドルを生成あるいは取得した場合、破棄もしなくてはならないと思っています。)

質問が二つになってしまいましたが、宜しくお願いします。

Re: ビットマップ用ハンドルの登録用関数

Posted: 2011年8月17日(水) 21:11
by みけCAT
Integral さんが書きました:宣言の部分はグローバル変数として、そのグローバル変数を直接関数内で書き換える・・・という方法はできないのでしょうか?
べつにそれでもいいです。

コード:

//宣言
static HDC hDC;
static   HBITMAP  hBMP;
static   BITMAP   BMP;
 
//ビットマップハンドル登録用の関数
void RegisterBitmapHandle(TCHAR* BitmapName ) {
    hDC = CreateCompatibleDC(NULL);
    hBMP = (HBITMAP)LoadImage(NULL , BitmapName , IMAGE_BITMAP , 0 , 0 , LR_LOADFROMFILE);
    GetObject(hBMP , sizeof(BITMAP) , &BMP);
    SelectObject(hDC , hBMP);
}
 
//(使用例) RegisterBitmapHandle(TEXT("hoge.bmp") );
Integral さんが書きました:さらに、もし戻り値として受け取る方法を考える場合も
internalhDC = CreateCompatibleDC(NULL);
など関数内で発生したデバイスコンテキストの解放はしなくてもよいものなのでしょうか?
(自分の理解が怪しいため、このあたりの知識はあやふやなのですが、ハンドルを生成あるいは取得した場合、破棄もしなくてはならないと思っています。)

質問が二つになってしまいましたが、宜しくお願いします。
受け取ったデバイスコンテキストを別の場所で開放すればいいと思います。

Re: ビットマップ用ハンドルの登録用関数

Posted: 2011年8月17日(水) 21:30
by Integral
1番目の回答についてですが、もし宣言が

static HDC h1DC;
static  HBITMAP h1BMP;
static  BITMAP 1BMP;

static HDC h2DC;
static  HBITMAP h2BMP;
static  BITMAP 2BMP;

のように二つになってしまった場合、関数は汎用的なものでなくなってしまうと思います。
できればこれらのアドレスを渡して、渡したアドレスの指すハンドル自体を初期化してくれる関数・・・というものができればありがたいです。
これらのことから、引数は最初の質問で書いた
RegisterBitmapHandle(HDC hDC , HBITMAP hBMP , BITMAP* BMP , TCHAR* BitmapName )
の4つとなるはずだと思いました。(どれをポインタにするかなどの細かい部分は違っている可能性がありますが・・・)


2つ目の回答についてです。

関数内で発生したデバイスコンテキストは解放する必要はなく、戻り値として受け取ったデバイスコンテキストのみを解放しただけでいいという解釈でよろしいでしょうか?

Re: ビットマップ用ハンドルの登録用関数

Posted: 2011年8月17日(水) 21:34
by みけCAT
Integral さんが書きました:1番目の回答についてですが、もし宣言が

static HDC h1DC;
static  HBITMAP h1BMP;
static  BITMAP 1BMP;

static HDC h2DC;
static  HBITMAP h2BMP;
static  BITMAP 2BMP;

のように二つになってしまった場合、関数は汎用的なものでなくなってしまうと思います。
できればこれらのアドレスを渡して、渡したアドレスの指すハンドル自体を初期化してくれる関数・・・というものができればありがたいです。
これらのことから、引数は最初の質問で書いた
RegisterBitmapHandle(HDC hDC , HBITMAP hBMP , BITMAP* BMP , TCHAR* BitmapName )
の4つとなるはずだと思いました。(どれをポインタにするかなどの細かい部分は違っている可能性がありますが・・・)

コード:

//宣言
static HDC hDC;
static   HBITMAP  hBMP;
static   BITMAP   BMP;
 
//ビットマップハンドル登録用の関数
void RegisterBitmapHandle(HDC* hDC , HBITMAP* hBMP , BITMAP* BMP , TCHAR* BitmapName ) {
    *hDC = CreateCompatibleDC(NULL);
    *hBMP = (HBITMAP)LoadImage(NULL , BitmapName , IMAGE_BITMAP , 0 , 0 , LR_LOADFROMFILE);
    GetObject(*hBMP , sizeof(BITMAP) , BMP);
    SelectObject(*hDC , *hBMP);
}
 
//(使用例) RegisterBitmapHandle(&hDC , &hBMP , &BMP , TEXT("hoge.bmp") );
そのまんまアドレスを渡せばいいと思います。
Integral さんが書きました:2つ目の回答についてです。

関数内で発生したデバイスコンテキストは解放する必要はなく、戻り値として受け取ったデバイスコンテキストのみを解放しただけでいいという解釈でよろしいでしょうか?
戻り値として受け取ったデバイスコンテキストと、ポインタで渡されたビットマップハンドルを開放してください。

Re: ビットマップ用ハンドルの登録用関数

Posted: 2011年8月17日(水) 23:24
by ISLe
CreateCompatibleDC(NULL)で得られるデスクトップのデバイスコンテキストは普遍なのでしょうか。
画面モードが変わったときに互換性が無くなったりしないのでしょうか。
その辺りが心配でHDCを保持するコードは書いたことがないのですが。

(追記)
自分なら以下のように分けますね。
#RegisterBitmapHandleは戻り値を使いたいですけど。

コード:

void RegisterBitmapHandle(HBITMAP *phBMP, BITMAP *pBMP, TCHAR *BitmapName) {
	*phBMP = (HBITMAP)LoadImage(NULL , BitmapName , IMAGE_BITMAP , 0 , 0 , LR_LOADFROMFILE);
	if (*phBMP) {
		GetObject(*phBMP, sizeof(BITMAP), pBMP);
	}
}

void DeleteBitmapHandle(HBITMAP hBMP) {
	DeleteObject(hBMP);
}

BOOL BitBlt(HDC hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, HBITMAP hbmpSrc, int nXSrc, int nYSrc, DWORD dwRop) {
	BOOL bResult = FALSE;
	HDC hdcSrc = CreateCompatibleDC(hdcDest);
	if (hdcSrc) {
		HBITMAP hbmpOld = (HBITMAP)SelectObject(hdcSrc , hbmpSrc);
		if (hbmpOld) {
			bResult = BitBlt(hdcDest, nXDest, nYDest, nWidth, nHeight, hdcSrc, nXSrc, nYSrc, dwRop);
			SelectObject(hdcSrc , hbmpOld);
		}
		DeleteDC(hdcSrc);
	}
	return bResult;
}

Re: ビットマップ用ハンドルの登録用関数

Posted: 2011年8月18日(木) 23:30
by Integral
>みけCATさん

何度も教えて頂いてありがとうございました!
結果的にはISLeさんに教えていただいた方法でプログラムを書き直したのですが、ポインタの理解が深まったので自分的には最初の質問をしてよかったなと思います。


>ISLeさん

毎度お世話になってます。
(前回、バックバッファの仕方が分らずに質問をした際もISLeさんに教えていただきました。)

自分は、

1.ウィンドウ生成時にハンドルの登録
2.ハンドルを使って描画
3.ウィンドウ破棄時にハンドルの破棄

という流れは必須のものだと思い込んでいました。教えてもらった関数でプログラムし直してみると、今まで300百行くらいいっていたハンドル登録部分のソースが3分の1以下に減りました。
しかも上記の方法でまずいのが、ハンドルの登録と破棄の工程が分かれているため、うっかり破棄し忘れるというミスが発生したり、そもそもハンドルを登録すること自体がめんどくさ過ぎて、キャラクターなどの画像を増やすのがとても億劫になってしまうんですよね。
描画の際にビットマップをロードして、描画後にそのまま破棄するという流れは全く思いつきませんでした。

しかし、気になることが2つばかりあります。

その1:
画像を毎回ロードする場合、実行速度的にはやはりおちるものなのでしょうか?
その2:
ソースコード内16行目及び19行目
hbmpOldというハンドルを得て、それをデバイスコンテキストと関連付けていますが、このコードの意図が分りません。

宜しくお願いします。

Re: ビットマップ用ハンドルの登録用関数

Posted: 2011年8月19日(金) 00:35
by ISLe
Integral さんが書きました:その1:
画像を毎回ロードする場合、実行速度的にはやはりおちるものなのでしょうか?
画像のロードはRegisterBitmapHandle関数(内のLoadImage API)で行なっていますから毎回ロードしていることにはならないと思いますが。
SelectObjectのことでしたらデバイスコンテキストとビットマップを関連付けるだけですから速度的な問題はありません。

画像の枚数が増えるのが嫌という問題は、1ステージとか1マップごとに使う画像を1枚の画像にまとめてしまう方法があります。
画像のどこにどういう画像が入っているかテーブルデータを作らなくてはならないのでもっと面倒なことになるかもしれませんが、特にメモリ容量が少ない非力なプラットフォームではたいていやってますね。
Integral さんが書きました:その2:
ソースコード内16行目及び19行目
hbmpOldというハンドルを得て、それをデバイスコンテキストと関連付けていますが、このコードの意図が分りません。
SelectObjectすると、それ以前に関連付けられていた同種のGDIオブジェクトのハンドルが返ります。
新規に作成したデバイスコンテキストにもデフォルトで関連付けられているオブジェクトがあります。
GDIオブジェクトは参照カウンタを持っていてデバイスコンテキストに関連付けたときに加算されます。
以前のオブジェクトに関連付けを戻して参照カウンタを減らさないとDeleteObjectを使っても実際には削除されずリークします。
というのが一般的なデバイスコンテキストとGDIオブジェクトの関係です。

今回のコードに関しては戻さなくてもリークしないのですが、それはCreateCompatibleDCで作成されるのがメモリDCで、メモリDCを削除すればそのとき関連付けられていたオブジェクトの参照カウンタも減るからです。
新規作成されたメモリDCに最初から関連付けられているGDIオブジェクトはウインドウズ内部で使い回されていることが知られているので、そちらも削除する必要がありません。

ただし、デバイスコンテキストはメモリDC以外にも自分で作成しない類のものがあって、どちらかというとそっちのほうがメジャーです。
描画対象のデバイスコンテキストにブラシオブジェクトを選択した場合などきちんと戻さないと不具合に繋がります。
なのであえて普遍的なSelectObjectの使い方をしました。

Re: ビットマップ用ハンドルの登録用関数

Posted: 2011年8月19日(金) 17:22
by Integral
[quote="ISLe"]
画像のロードはRegisterBitmapHandle関数(内のLoadImage API)で行なっていますから毎回ロードしていることにはならないと思いますが。
[/quate]
あ、すいません。勘違いして自分のソース内では、LoadImageによるビットマップハンドルの登録と、その破棄までデバイスコンテキストハンドルのように描画関数内に記述していました。
もしこのようにするなら、ビットマップハンドルの登録破棄を分けてする必要がないため非常に楽なのですが、やはり毎回ロードする分の時間のロスは大きくなってしまうのでしょうか?
[quote="ISLe"]
画像の枚数が増えるのが嫌という問題は、1ステージとか1マップごとに使う画像を1枚の画像にまとめてしまう方法があります。
[/quate]
それなんですが、最初そのつもりで設計していたところ、めんどすぎてやめてしまいましたorz
まだどのようなキャラクタが登場するなどといった細かい仕様が決定していないので、後々の大幅な改変があることを考えると危険な気がします。

あと、2つ目の質問の回答は全く知識のない事柄だったので非常に助かりました。
自分は全くの独学で、こういったことを聞けるのはこの掲示板くらいなのですが、こういった知識はどこから得るんでしょうか。
(例えば自分の学習した本にはビットマップを用いた描画の方法については書かれていても「GDIオブジェクトは参照カウンタを持っている」なんてことは書かれていませんでした。)

Re: ビットマップ用ハンドルの登録用関数

Posted: 2011年8月20日(土) 00:02
by ISLe
Integral さんが書きました:あ、すいません。勘違いして自分のソース内では、LoadImageによるビットマップハンドルの登録と、その破棄までデバイスコンテキストハンドルのように描画関数内に記述していました。
もしこのようにするなら、ビットマップハンドルの登録破棄を分けてする必要がないため非常に楽なのですが、やはり毎回ロードする分の時間のロスは大きくなってしまうのでしょうか?
やったことがないので分かりませんが、毎回ロードしたらかなり遅くなると思います。
画像リソースにしてLoadImageにLR_SHAREDフラグを付けると自動的に使い回してくれるので、ロードは初回のみになりますけど。
Integral さんが書きました:まだどのようなキャラクタが登場するなどといった細かい仕様が決定していないので、後々の大幅な改変があることを考えると危険な気がします。
画像を識別するデータをセットにしたテーブルにすると、複数画像で始めてだんだん減らしていく場合でもテーブルデータを編集するだけで済みますよ。
Integral さんが書きました:自分は全くの独学で、こういったことを聞けるのはこの掲示板くらいなのですが、こういった知識はどこから得るんでしょうか。
(例えば自分の学習した本にはビットマップを用いた描画の方法については書かれていても「GDIオブジェクトは参照カウンタを持っている」なんてことは書かれていませんでした。)
「GDIオブジェクトは参照カウンタを持っている」と断言したのは正確ではなかったです。
実際にどうなっているかは知らないので、「参照カウンタを持っているように振る舞う」と表現すべきでしょうかね。

GDIプログラミングの入門書にはSelectObjectのルールは書いてあると思うんですけど、書いてないとしたらひどいですね。
ウインドウズプログラミングに関しては、MSDNライブラリのリファレンスとか解説記事とか、体系的にまとめられたサイトもあるし、マイクロソフトの公式フォーラムとか、技術系サイトの掲示板とか、情報源は事欠かないと思います。

Re: ビットマップ用ハンドルの登録用関数

Posted: 2011年9月05日(月) 11:30
by Integral
ずいぶん前のスレッドになりますが、解決を押してなかった&お礼を言い忘れていたので上げさせてもらいます。
せっかく教えてもらったのに失礼なことをしてごめんなさい。

あれから、悪戦苦闘しながらC++も習得し、画像の描画部分をクラスにして見るとコードもすっきりしました。
もう忘れられてるかもしれませんが、回答者様にはこころからお礼申し上げます。