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

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
taketoshi
記事: 222
登録日時: 14年前
住所: 日本国

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

#1

投稿記事 by taketoshi » 13年前

クラス内のメンバ変数のスコープについて教えてください。

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;
}


アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 14年前
住所: 東海地方
連絡を取る:

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

#2

投稿記事 by softya(ソフト屋) » 13年前

CMainWindowがCApplicationを継承しているのにCApplicationのインスタンスを生成していたりよく分からないことをしています。ご自身が仰っているように更にもう一度生成しているのも無駄ですね。
プロシジャもstaticにすればCMainWindowに含められる気がします。

「Win32API C++ クラス化」辺りで検索してみてはどうでしょうか?
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

taketoshi
記事: 222
登録日時: 14年前
住所: 日本国

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

#3

投稿記事 by taketoshi » 13年前

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

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


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

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 14年前
住所: 東海地方
連絡を取る:

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

#4

投稿記事 by softya(ソフト屋) » 13年前

WinMain関数は良いとして、WndProcはマズイです。
何度も生成する必要の無い気もしますが。
今のコードを見せて下さいね。

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

taketoshi
記事: 222
登録日時: 14年前
住所: 日本国

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

#5

投稿記事 by taketoshi » 13年前

クラスにプロシージャを組み込む場合何処に隠蔽すればいいのでしょう?

今こんな感じでコードを書いています。前回よりそんなには変わっていませんが、
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();						//メインウインドウを作る
};

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 14年前
住所: 東海地方
連絡を取る:

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

#6

投稿記事 by softya(ソフト屋) » 13年前

この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();
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

taketoshi
記事: 222
登録日時: 14年前
住所: 日本国

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

#7

投稿記事 by taketoshi » 13年前

返信が遅くなって申し訳ないです。
提示された内容が理解できなかったので、合間に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));
}

beatle
記事: 1281
登録日時: 13年前
住所: 埼玉
連絡を取る:

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

#8

投稿記事 by beatle » 13年前

どの関数でヒープ領域が破壊されてるよのメッセージが出るか分かります?
もしかしたらデストラクタ内でしょうか.デストラクタの定義はソースにありませんので私たちには調べられませんが.

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 14年前
住所: 東海地方
連絡を取る:

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

#9

投稿記事 by softya(ソフト屋) » 13年前

動く形で提供してもらうとこちらでも確認できますのでお願いします。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

taketoshi
記事: 222
登録日時: 14年前
住所: 日本国

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

#10

投稿記事 by taketoshi » 13年前

あああ!皆さん申し訳ないです。

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

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

ISLe
記事: 2650
登録日時: 14年前
連絡を取る:

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

#11

投稿記事 by ISLe » 13年前

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

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

taketoshi
記事: 222
登録日時: 14年前
住所: 日本国

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

#12

投稿記事 by taketoshi » 13年前

>ISLeさん

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

閉鎖

“C言語何でも質問掲示板” へ戻る