ページ 11

クラス内のメンバ変数のスコープについて質問があります

Posted: 2012年1月15日(日) 10:29
by taketoshi
クラス内のメンバ変数のスコープについて教えてください。

main関数内でメインウインドウを作り、ウインドウプロシージャ内でメインウインドウに
コモンコントロールを作ろうと思っています。


ウインドウプロシージャに処理を移しWM_CREATEメッセージでメインウインドウを親として
コントロールを描写しても、スコープを抜けている?せいかハンドルを失っており上手く処理できません
仕方がないので、メインウインドウのハンドルを取る引数付きのコンストラクタをつくり、プロシージャ内で再代入しています。

考えとしては、一度作った値をそのまま保持し続けてほしいのですが。
こういったとき、何処でクラスのインスタンスを生成し記述するのでしょうか?
クラスをグローバル変数とかで宣言してしまうのでしょうか?それとも、ウインドウプロシージャにポインタを渡すのでしょうか。

一般的な書き方のご指導お願いします。

コード:

//アプリケーション基底クラス
class CApplication{
protected:
	static char *szClassName;							//クラスネーム
	static WNDCLASSEX wc;
public:
	static HINSTANCE hInstance;							//インスタンス
	CApplication();
	CApplication(HINSTANCE);
	~CApplication();
	int InitApp();
};
//メインウインドウを作成する
class CMainWindow : public CApplication{
protected:
	static HWND hWnd;							//ウインドウハンドル
public:
	CMainWindow();								//コンストラクタ
	CMainWindow(HWND);							//コンストラクタ
	~CMainWindow();								//デストラクタ
	int CreateMainWindow();						//メインウインドウを作る
};

//メインウインドウを作成するメンバ関数
/////////////////////////////////////////////////////////////////////////////////////////////////////
//関数名			:CreateMainWindow
//機能				:メインウインドウを作る
//引数				:
//戻り値			:
/////////////////////////////////////////////////////////////////////////////////////////////////////
int CMainWindow::CreateMainWindow(){
	hWnd = CreateWindow(szClassName,
		"windowsテンプレート",
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInstance,NULL);

	if(hWnd == NULL){
		return 1;
	}

	ShowWindow(hWnd,true);
	UpdateWindow(hWnd);
	return 0;
}

//メインウインドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
{

	int id;
	CApplication *lpAp = new CApplication();
//ここでhWndを再代入している
	CMainWindow *cw = new CMainWindow(hWnd);

	switch(msg)
	{
	case WM_DESTROY:
		{
			PostQuitMessage(0);
			delete lpAp;
			delete cw;
			break;
		}
	case WM_COMMAND:
		switch(LOWORD(wParam))
		{
		case MAKEINTRESOURCE(ID_END):
			{
				id = MessageBox(hWnd,"終了してよろしいですか?","確認",MB_YESNO|MB_ICONQUESTION);
					if(id == IDYES)
					{
						DestroyWindow(hWnd);
					}
					break;
			}
		case MAKEINTRESOURCE(ID_VerDlg):
			{
				DialogBox(lpAp->hInstance,MAKEINTRESOURCE(IDD_DIALOG1),hWnd,(DLGPROC)DlgVerProc);
				break;
			}
		break;
		}
	default:
		return (DefWindowProc(hWnd,msg,wParam,lParam));
	}
	return 0;
}


//メイン関数
int WINAPI WinMain(HINSTANCE hInst,HINSTANCE hPrevInst,LPSTR lpCmdLine,int nShowCmd)
{

	CApplication *lpApp;
	CMainWindow *cw;

	lpApp = new CApplication(hInst);
	cw = new CMainWindow();
	cw->CreateMainWindow();
	BOOL bRet;
	MSG msg;

	while((bRet = GetMessage(&msg,NULL,0,0)) != 0)
	{
		if (bRet == -1)
		{
			break;
		}
		else
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	return (int)msg.wParam;
}


Re: クラス内のメンバ変数のスコープについて質問があります

Posted: 2012年1月15日(日) 13:45
by softya(ソフト屋)
CMainWindowがCApplicationを継承しているのにCApplicationのインスタンスを生成していたりよく分からないことをしています。ご自身が仰っているように更にもう一度生成しているのも無駄ですね。
プロシジャもstaticにすればCMainWindowに含められる気がします。

「Win32API C++ クラス化」辺りで検索してみてはどうでしょうか?

Re: クラス内のメンバ変数のスコープについて質問があります

Posted: 2012年1月15日(日) 16:00
by taketoshi
softyaさんありがとうございます。
ご提示頂いたキーワードを元に検索しました。
ちょうどよいページを見つけたのでそれを元にクラス設計を変えてみました。
ウィンドウプロシージャも静的関数にして、CMainWindowに組み込むことが出来ました。
まだ完全にクラスを軸としたアプリ開発が出来ませんが、MFCってこんな感じなのでしょうか?
まだクラスは構造体の拡張位の使い方しか出来ておらず、苦戦しています。

今までウィンドウハンドルとかインスタンスはグローバル変数に保存していたので、クラス内部でもずっと保存したかったのですが
必要に応じて取得したり、渡したりしていけばよいってことに気がつきました。無理に保持し続ける必要もないですね。


もうひとつ教えてほしいのですが、ウィンドウプロシージャ内やメイン関数内でクラスのインスタンスを生成したら
ブロックの最後でdeleteをするものでしょうか?ウィンドウプロシージャなんかは何回も呼ばれてるのでメモリリークになってしまいます?

Re: クラス内のメンバ変数のスコープについて質問があります

Posted: 2012年1月15日(日) 19:49
by softya(ソフト屋)
WinMain関数は良いとして、WndProcはマズイです。
何度も生成する必要の無い気もしますが。
今のコードを見せて下さいね。

MFCの場合はもっと隠蔽されています。

Re: クラス内のメンバ変数のスコープについて質問があります

Posted: 2012年1月15日(日) 21:32
by taketoshi
クラスにプロシージャを組み込む場合何処に隠蔽すればいいのでしょう?

今こんな感じでコードを書いています。前回よりそんなには変わっていませんが、
WinProcはCMainWindowクラスに組み込んであります。

コード:

//クラス設計
#include<windows.h>


class CApplication{
protected:
	static char *szClassName;							//クラスネーム
public:
	static HINSTANCE hInstance;							//インスタンス
	CApplication();
	~CApplication();
};

//WinMain
int WINAPI WinMain(HINSTANCE hInst,HINSTANCE hPrevInst,LPSTR lpCmdLine,int nShowCmd)
{

	CMainWindow *cw;

	cw = new CMainWindow();
	cw->InitApp();
	cw->CreateMainWindow();

	BOOL bRet;
	MSG msg;

	while((bRet = GetMessage(&msg,NULL,0,0)) != 0)
	{
		if (bRet == -1)
		{
			break;
		}
		else
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	delete cw;

	return (int)msg.wParam;
}

#include"PreHead.h"

/////////////////////////////////////////////////////////////////////////////////////////////////////
//関数名			:ウインドウプロシージャ
//機能				:
//引数				:
//戻り値			:
/////////////////////////////////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK CMainWindow:: WndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
{

	int id;

	switch(msg)
	{
	case WM_CREATE:
			break;
	case WM_DESTROY:
		{
			PostQuitMessage(0);
			break;
		}
	case WM_COMMAND:
		switch(LOWORD(wParam))
		{
		case MAKEINTRESOURCE(ID_END):
			{
				id = MessageBox(hWnd,"終了してよろしいですか?","確認",MB_YESNO|MB_ICONQUESTION);
					if(id == IDYES)
					{
						DestroyWindow(hWnd);
					}
					break;
			}
		case MAKEINTRESOURCE(ID_VerDlg):
			{
				break;
			}
		break;
		}
	default:
		return (DefWindowProc(hWnd,msg,wParam,lParam));
	}
	return 0;
}

class CMainWindow : public CApplication{
protected:
	static HWND hWnd;							//ウインドウハンドル
	static WNDCLASSEX wc;
public:
	static LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);      //プロシージャ
	int InitApp();								//ウインドウクラスの登録
	CMainWindow();								//コンストラクタ
	CMainWindow(HWND);							//コンストラクタ
	~CMainWindow();								//デストラクタ
	int CreateMainWindow();						//メインウインドウを作る
};

Re: クラス内のメンバ変数のスコープについて質問があります

Posted: 2012年1月15日(日) 22:09
by softya(ソフト屋)
この2つをstaticにすると2つのインスタンスを生成できないと思います。
static HWND hWnd; //ウインドウハンドル
static WNDCLASSEX wc;

これとかを使ってthisポインタを受け渡したほうが良いと思います。
「SetWindowLongPtr 関数」
http://msdn.microsoft.com/ja-jp/library/cc411204.aspx

static WndProc ← hWndでGetWindowLongPtrを使いthisポインタを得る。
 ↓ thisポインタで呼び出す。
WndProcMain(引数同じ) ← これで普通のクラスとして自身のインスタンスにアクセスできます。

あと分かれている意味があるのでしょうか?

コード:

    cw = new CMainWindow();
    cw->InitApp();
    cw->CreateMainWindow();

Re: クラス内のメンバ変数のスコープについて質問があります

Posted: 2012年1月17日(火) 22:37
by taketoshi
返信が遅くなって申し訳ないです。
提示された内容が理解できなかったので、合間にAPIを調べながら書き進めていました。

まず、thisポインタの受け渡し方について、提示されたキーワードをもとに調べてみました。
SetWindowLongPtr関数でウィンドウハンドルにユーザー定義の数値(今回はthisポインタ)を設定でき
好きな時にGetWindowLongPtrで引き出せるってところは理解できました。
それで、オブジェクトポインタを保存して受け渡しするのですね。

調べながら以下の様にコードを書き起こしてみました。
新たに呼び出し用兼、ポインタ受け渡し用のコールバックプロシージャを用意し
そこからさらに本当のウィンドウプロシージャを呼び出す仕組みです。

なんとか、ウインドウの描写が出来て、メニューも動くのですが
ウインドウ破壊時にヒープ領域が破壊されてるよってメッセージが出てしまいます・・・。
どこか変なところにアクセスしているでしょうか・・・?

コード:

//クラス設計
#include<windows.h>


class CApplication{
protected:
	static char *szClassName;							//クラスネーム
public:
	static HINSTANCE hInstance;							//インスタンス
	CApplication();
	~CApplication();
};


class CMainWindow : public CApplication{
protected:
	HWND hWnd;							//ウインドウハンドル
	WNDCLASSEX wc;
public:
	static LRESULT CALLBACK CallWndProc(HWND,UINT,WPARAM,LPARAM);
	virtual LRESULT CALLBACK MainWndProc(HWND,UINT,WPARAM,LPARAM);
	CMainWindow();								//コンストラクタ
	CMainWindow(HWND);							//コンストラクタ
	void SetPointer(HWND);						//ポインタのセット
	~CMainWindow();								//デストラクタ
	int CreateMainWindow();						//メインウインドウを作る
};

//CMainWindowクラス

/////////////////////////////////////////////////////////////////////////////////////////////////////
//関数名			:ポインタのセット
//機能				:
//引数				:
//戻り値			:
/////////////////////////////////////////////////////////////////////////////////////////////////////
void CMainWindow::SetPointer(HWND hWnd){
	SetWindowLongPtr(hWnd,GWLP_USERDATA,(LONG)this);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////
//関数名			:CreateMainWindow
//機能				:メインウインドウを作る
//引数				:
//戻り値			:
/////////////////////////////////////////////////////////////////////////////////////////////////////
int CMainWindow::CreateMainWindow(){

	wc.cbClsExtra = 0;
	wc.cbSize = sizeof(WNDCLASSEX);
	wc.cbWndExtra = 0;
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+6);
	wc.hCursor = LoadCursor(hInstance,IDC_ARROW);
	wc.hIcon = NULL;
	wc.hIconSm = NULL;
	wc.hInstance = hInstance;
	wc.lpfnWndProc = (WNDPROC)CMainWindow::CallWndProc;
	wc.lpszClassName = szClassName;
	wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
	wc.style = CS_HREDRAW | CS_VREDRAW;

	RegisterClassEx(&wc);

	//最後の引数にthisポインタを格納
	hWnd = CreateWindow(szClassName,
		"windowsテンプレート",
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,hInstance,(LPVOID)this);

	if(hWnd == NULL){
		return 1;
	}

	ShowWindow(hWnd,true);
	UpdateWindow(hWnd);
	return 0;
}


//プロシージャ
#include"PreHead.h"
/////////////////////////////////////////////////////////////////////////////////////////////////////
//関数名			:メインウインドウプロシージャ
//機能				:
//引数				:
//戻り値			:
/////////////////////////////////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK CMainWindow:: MainWndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
	int id;
	switch(msg)
	{
	case WM_CREATE:
			break;
	case WM_DESTROY:
		{
			PostQuitMessage(0);
			break;
		}
	case WM_COMMAND:
		switch(LOWORD(wParam))
		{
		case MAKEINTRESOURCE(ID_END):
			{
				id = MessageBox(hWnd,"終了してよろしいですか?","確認",MB_YESNO|MB_ICONQUESTION);
					if(id == IDYES)
					{
						DestroyWindow(hWnd);
					}
					break;
			}
		case MAKEINTRESOURCE(ID_VerDlg):
			{
				break;
			}
		break;
		}
	default:
		return (DefWindowProc(hWnd,msg,wParam,lParam));
	}
	return 0;
}


/////////////////////////////////////////////////////////////////////////////////////////////////////
//関数名			:呼び出し用ウインドウプロシージャ
//機能				:
//引数				:
//戻り値			:
/////////////////////////////////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK CMainWindow:: CallWndProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
{

	//thisポインタを取得
	CMainWindow *CBase = (CMainWindow *)GetWindowLongPtr(hWnd,GWLP_USERDATA);
	//thisポインタが空ならCreateWindow最後の引数に格納したthisポインタを受け取る
	if(CBase == NULL){
		//
		if(msg == WM_CREATE){
			CBase = (CMainWindow *)((LPCREATESTRUCT)lParam)->lpCreateParams;
		}
	
		//thisポインタを受け取ったらプロパティにセット
		if(CBase){
			CBase->SetPointer(hWnd);
		}
	}

	//本当のウィンドウプロシージャの呼び出し
	if(CBase){
		LRESULT lResult = CBase->MainWndProc(hWnd,msg,wParam,lParam);
		return lResult;
	}

	return (DefWindowProc(hWnd,msg,wParam,lParam));
}

Re: クラス内のメンバ変数のスコープについて質問があります

Posted: 2012年1月17日(火) 22:44
by beatle
どの関数でヒープ領域が破壊されてるよのメッセージが出るか分かります?
もしかしたらデストラクタ内でしょうか.デストラクタの定義はソースにありませんので私たちには調べられませんが.

Re: クラス内のメンバ変数のスコープについて質問があります

Posted: 2012年1月17日(火) 23:10
by softya(ソフト屋)
動く形で提供してもらうとこちらでも確認できますのでお願いします。

Re: クラス内のメンバ変数のスコープについて質問があります

Posted: 2012年1月17日(火) 23:38
by taketoshi
あああ!皆さん申し訳ないです。

ソースコードを見直してみたところ、昔の名残で
クラス定義されているウインドウハンドルとは別のhWndがグローバル変数に定義されていて
それを削除したところヒープ領域破壊エラーが出なくなりました。
原因はこれのようでした。

thisポインタの受け渡し方等、思った以上に勉強になりました。
これで解決とさせていただきます。お二方ありがとうございました。

Re: クラス内のメンバ変数のスコープについて質問があります

Posted: 2012年1月18日(水) 04:16
by ISLe
ウィンドウクラス構造体のcbWndExtraが0になってて拡張メモリが確保されていないので、thisポインタ値がどこに書き込まれているか不定です。
sizeof(LONG_PTR)分のサイズを確保する必要があります。

あと、SetWindowLongPtrのところではLONG_PTRにキャストしてください。x64で不具合が出ます。

Re: クラス内のメンバ変数のスコープについて質問があります

Posted: 2012年1月18日(水) 07:32
by taketoshi
>ISLeさん

わかりましたありがとうございます。
基本的な知識がかけていました。