質問者
Windows Embedde CE 6.0 R2のアプリでダイアログ表示でメモリリークする

質問
-
お世話になります。
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); }
2013年2月26日 5:48
すべての返信
-
レスつきませんね。
ここは、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をもうちょっと使いかって良く加工すればダイアログの
メンバーとして保持することもできますよね。- 編集済み 仲澤@失業者 2013年2月27日 8:19
2013年2月27日 8:05 -
詳しい説明ありがとうございます。
方針1を使っていたのですが、メモリリークしている状態でした。
投稿時のコードでは、モーダルダイアログを閉じた時しか、OnIdle()を呼び出していませんでしたが、タイマーイベント時に必ず行う様に変更しましたが、変わらずリークして空きメモリが減っていきます。ボタンをご指摘のコードに変更しても変わらなかったのですが、以下の様に変更したところリークしていなさそうです。(今まで減っていた間隔では減らなくなりましたが、長時間動かしていないので別の問題もあるかもしれません)
修正前
LONG lIdle=0;
while( AfxGetApp()->OnIdle(lIdle++));修正後
AfxGetApp()->OnIdle(0);
while( AfxGetApp()->OnIdle(1));理由は分からないのですが、とりあえず、ここまま様子を見るようにします。
ありがとうございました。
2013年2月28日 2:46