Question

chikama on Tue, 26 Feb 2013 05:48:15


お世話になります。

WindowsCEのフォーラムが閉鎖されてしまったため、こちらで質問させて下さい。

Windows Embedde CE 6.0 R2で、ダイアログを開いて閉じる度にメモリリークする問題が発生しています。

1機種の開発会社に聞いたところ、Microsoftの仕様であり対応できないとの回答を頂きました。

また、別件でMicrosoftに問い合わせて頂いたところ、OSにはメモリリークはないとの回答も頂きました。

実際には以下の様なKBがあります。

http://support.microsoft.com/kb/839859/ja?wa=wsignin1.0

http://support.microsoft.com/kb/871196/ja

http://support.microsoft.com/kb/105286/JA

後述するソースコードで試したところ、あるタイミングで4KByte毎、空きメモリが減っています。

私のアプリがいけないのかと思いますが、対策方法やMicrosoftの仕様であると書いてあるページを知っている方がいらっしゃいましたら、教えていただけないでしょうか?

以下、ソースの抜粋になります。

dialog_testDlg.cpp

// dialog_testDlg.cpp : 実装ファイル
//
#include "stdafx.h"
#include "dialog_test.h"
#include "dialog_testDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// タイマーID定義
#define	TIMER_ID	12345
// Cdialog_testDlg ダイアログ
Cdialog_testDlg::Cdialog_testDlg(CWnd* pParent /*=NULL*/)
	: CDialog(Cdialog_testDlg::IDD, pParent)
	, m_StcMem(_T(""))
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void Cdialog_testDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	DDX_Text(pDX, IDC_STC_MEM, m_StcMem);
}
BEGIN_MESSAGE_MAP(Cdialog_testDlg, CDialog)
#if defined(_DEVICE_RESOLUTION_AWARE) && !defined(WIN32_PLATFORM_WFSP)
	ON_WM_SIZE()
#endif
	//}}AFX_MSG_MAP
	ON_WM_DESTROY()
	ON_WM_TIMER()
	ON_BN_CLICKED(IDC_BTN_START, &Cdialog_testDlg::OnBnClickedBtnStart)
	ON_BN_CLICKED(IDC_BTN_STOP, &Cdialog_testDlg::OnBnClickedBtnStop)
END_MESSAGE_MAP()
// Cdialog_testDlg メッセージ ハンドラ
BOOL Cdialog_testDlg::OnInitDialog()
{
	CDialog::OnInitDialog();
	// このダイアログのアイコンを設定します。アプリケーションのメイン ウィンドウがダイアログでない場合、
	//  Framework は、この設定を自動的に行います。
	SetIcon(m_hIcon, TRUE);			// 大きいアイコンの設定
	SetIcon(m_hIcon, FALSE);		// 小さいアイコンの設定
	// TODO: 初期化をここに追加します。
	m_IsStart = FALSE;
	GetDlgItem(IDC_BTN_STOP)->EnableWindow(FALSE);
	
	return TRUE;  // フォーカスをコントロールに設定した場合を除き、TRUE を返します。
}
#if defined(_DEVICE_RESOLUTION_AWARE) && !defined(WIN32_PLATFORM_WFSP)
void Cdialog_testDlg::OnSize(UINT /*nType*/, int /*cx*/, int /*cy*/)
{
	if (AfxIsDRAEnabled())
	{
		DRA::RelayoutDialog(
			AfxGetResourceHandle(), 
			this->m_hWnd, 
			DRA::GetDisplayMode() != DRA::Portrait ? 
			MAKEINTRESOURCE(IDD_DIALOG_TEST_DIALOG_WIDE) : 
			MAKEINTRESOURCE(IDD_DIALOG_TEST_DIALOG));
	}
}
#endif
void Cdialog_testDlg::OnDestroy()
{
	CDialog::OnDestroy();
	// TODO: ここにメッセージ ハンドラ コードを追加します。
}
void Cdialog_testDlg::OnTimer(UINT_PTR nIDEvent)
{
	// TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
	if(m_IsStart){
		if(m_IsChildOpen == FALSE){
			m_IsChildOpen = TRUE;
			
			{
				CDialog1 * pcx1 = new CDialog1;
				pcx1->DoModal();
				delete pcx1;
				pcx1 = NULL;
			}
			
			LONG lIdle=0;
			while( AfxGetApp()->OnIdle(lIdle++));
			m_IsChildOpen = FALSE;
		}else{
			//
//			LONG lIdle=0;
//			while( AfxGetApp()->OnIdle(lIdle++));
		}
	}
	
	//
	MEMORYSTATUS stat;
	stat.dwLength = sizeof(stat);
	try{
		GlobalMemoryStatus(&stat);
	}catch(...){
		MessageBox(_T("GlobalMemoryStatus() error!\n"));
	}
	m_StcMem.Format(L"%lu", stat.dwAvailPhys);
	UpdateData(FALSE);
	
	CDialog::OnTimer(nIDEvent);
}
void Cdialog_testDlg::OnBnClickedBtnStart()
{
	// TODO: ここにコントロール通知ハンドラ コードを追加します。
	hTimerPtr = SetTimer(TIMER_ID, 1500, NULL);
	if(hTimerPtr == NULL){
		MessageBox(_T("SetTimer() error"));
	}
	
	GetDlgItem(IDC_BTN_START)->EnableWindow(FALSE);
	GetDlgItem(IDC_BTN_STOP)->EnableWindow(TRUE);
	
	m_IsStart = TRUE;
	m_IsChildOpen = FALSE;
}
void Cdialog_testDlg::OnBnClickedBtnStop()
{
	// TODO: ここにコントロール通知ハンドラ コードを追加します。
	m_IsStart = FALSE;
	
	GetDlgItem(IDC_BTN_START)->EnableWindow(TRUE);
	GetDlgItem(IDC_BTN_STOP)->EnableWindow(FALSE);
}

Dialog1.cpp

// Dialog1.cpp : 実装ファイル
//
#include "stdafx.h"
#include "dialog_test.h"
#include "Dialog1.h"
#define	TIMER_ID	1
// CDialog1 ダイアログ
IMPLEMENT_DYNAMIC(CDialog1, CDialog)
CDialog1::CDialog1(CWnd* pParent /*=NULL*/)
	: CDialog(CDialog1::IDD, pParent)
{
}
CDialog1::~CDialog1()
{
}
void CDialog1::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CDialog1, CDialog)
	ON_WM_TIMER()
END_MESSAGE_MAP()
// CDialog1 メッセージ ハンドラ
BOOL CDialog1::OnInitDialog()
{
	CDialog::OnInitDialog();
	hTimerPtr = SetTimer(TIMER_ID, 200, NULL);
	if(hTimerPtr == NULL){
		MessageBox(_T("SetTimer() error"));
	}
	
	
	return TRUE;
}
void CDialog1::OnTimer(UINT_PTR nIDEvent)
{
	// TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
	KillTimer(TIMER_ID);
	hTimerPtr = NULL;
	EndDialog(IDCANCEL);
	CDialog::OnTimer(nIDEvent);
}


Sponsored



Replies

仲澤@失業者 on Wed, 27 Feb 2013 08:05:12


レスつきませんね。

ここは、Windows Phoneなのでかなりカテ違いかもしれません。
しかし、自分もマッチするフォーラムがわかりませんので、
わかっている人になんとかしてもらいましょう(vv;)。

まず、自分はCEについてはほとんど素人なので細かいオリエンテーションはできません。
さて、ご質問の件について詳しく説明しているページを見つけることはできませんでしたが、
掲示されたこれらのページの言わんとしている事はわかります。
要は「CEでは、MFCが戻す一時オブジェクトを開放できないので、使わずにすますか、
OnIdle()を強制的で頻繁に呼び出して自分で掃除しろ」ということですね。

従って、2種類の方針が立てられます。
 方針1.言われた通り、CWinApp等のOnIdle()を無理やり呼び出す。
 方針2.一時オブジェクトを戻す関数を一切使わない。
ですね。

個人的には「方針2.」の方が好きなので、こっちを説明させてもらいます。
この方針通りに実装するには「一時オブジェクトを戻す」関数について
知らなければなりません。ダイアログ周りだと、

 CWnd::GetDlgItem()

が代表的なものでしょう。この関数のマニュアルによると
「返されるポインタは、一時的なポインタです。後で使用するために保存しておくことはできません。」
とあります。同様の説明のある、全ての関数が対象となるわけですね。

つまり
  GetDlgItem(IDC_BTN_START)->EnableWindow(TRUE);

などが、相当すると考えられ、Win32SDKに書き換えるか、CWnd::Attach()する必要があるわけですね。
Attach()は、まぁ読めばわかるので省略して、Win32SDKの方を説明すると。

  HWND  hWnd_IDC_BTN_START = ::GetDlgItem( m_hWnd, IDC_BTN_START);
 ::EnableWindow( hWnd_IDC_BTN_START, TRUE);

の様に書き換えれば良いと考えられます。
この処理を関数化するなり、Class化するなりすれば、
もうちょっと簡単に書けますよね。
class DLG_CTRL{
  protected: HWND  m_hwnd; WORD MyId;
  public:DLG_CTRL( HWND ex_Parent, WORD ex_Id){
  MyId = ex_Id;
    m_hwnd = ::GetDlgItem( ex_Parent, MyId);
  }
  void EnableWindow( BOOL flg){ ::EnableWindow( m_hwnd, flg);}
};

みたいにすると、先のコードは
  DLG_CTRL  Ctrl_BTN_START( m_hWnd, IDC_BTN_START);
  Ctrl_BTN_START.EnableWindow( TRUE);
とかけるわけですね。
また、DLG_CTRLをもうちょっと使いかって良く加工すればダイアログの
メンバーとして保持することもできますよね。


Azulean on Wed, 27 Feb 2013 13:45:22


本筋でなくてすみません。

現状、残念ながら、Windows CE を扱うフォーラムはありません。
あえてスレッドを置くと考えた場合、Windows クライアント フォーラムに CE 関連の質問が出ていたのでそこがらしいかと思ってはいますが、回答がつかないのが実情です。

なお、Windows Phone 7 は確かに Windows CE ベースの OS ですが、MFC のコードを含むネイティブコードを実行できない環境なので、CE と MFC の組み合わせの質問は助言を得られないとお考えいただいた方がよいかと思います。

chikama on Thu, 28 Feb 2013 02:46:12


詳しい説明ありがとうございます。

方針1を使っていたのですが、メモリリークしている状態でした。

投稿時のコードでは、モーダルダイアログを閉じた時しか、OnIdle()を呼び出していませんでしたが、タイマーイベント時に必ず行う様に変更しましたが、変わらずリークして空きメモリが減っていきます。ボタンをご指摘のコードに変更しても変わらなかったのですが、以下の様に変更したところリークしていなさそうです。(今まで減っていた間隔では減らなくなりましたが、長時間動かしていないので別の問題もあるかもしれません)

修正前

LONG lIdle=0;
while( AfxGetApp()->OnIdle(lIdle++));

修正後

AfxGetApp()->OnIdle(0);
while( AfxGetApp()->OnIdle(1));

理由は分からないのですが、とりあえず、ここまま様子を見るようにします。

ありがとうございました。

chikama on Thu, 28 Feb 2013 02:56:33


回答ありがとうございます。

Windows Phone 7がネイティブコードを使えなかったのは知りませんでした。かなりのカテ違いになってしまいました。

回答は期待できないかも知れませんが、次回からはWindows クライアント フォーラムに投稿します。やはり、CEはあんまり広まらずに使われないものになってしまったのですかね。Windows Embedded Compact シリーズの展開を期待します。

仲澤@失業者 on Thu, 28 Feb 2013 05:42:14


>修正前

>LONG lIdle=0;
>while( AfxGetApp()->OnIdle(lIdle++));

ですが、「後置の++演算子」を使っているため、初回だけ、
while( AfxGetApp()->OnIdle(0));
となってしまいませんか。
引数0の場合のOnIdle()の動作説明は見つかりませんでしたか、
仮に、「何もしないで0を戻す」仕様だと、お掃除に失敗するかもしれませんね。