Console에서 Dialog 생성 시

Console project에서 Dialog를 4개 생성한 뒤 하나만 DestroyWindow를 하면 해당 Dialog보다 나중에 생성된 Dialog까지 종료되는 문제가 있었다.

예를 들면 다음과 같은 상황인데,

CDialog *pdlg[4];
int i;

i = 0;

pdlg[i] = new CDialog();
pdlg[i]->Create(IDD_DIALOG1, NULL);
pdlg[i]->ShowWindow(SW_SHOW);
pdlg[i]->MoveWindow(0, 0, 100, 100);
i++;

pdlg[i] = new CDialog();
pdlg[i]->Create(IDD_DIALOG1, NULL);
pdlg[i]->ShowWindow(SW_SHOW);
pdlg[i]->MoveWindow(100, 0, 100, 100);
i++;

pdlg[i] = new CDialog();
pdlg[i]->Create(IDD_DIALOG1, NULL);
pdlg[i]->ShowWindow(SW_SHOW);
pdlg[i]->MoveWindow(200, 0, 100, 100);
i++;

pdlg[i] = new CDialog();
pdlg[i]->Create(IDD_DIALOG1, NULL);
pdlg[i]->ShowWindow(SW_SHOW);
pdlg[i]->MoveWindow(300, 0, 100, 100);
i++;

for(i = 0; i < 4; i++)
{
	printf("Object: 0x%08X, HWND: 0x%08X\n", pdlg[i], pdlg[i]->GetSafeHwnd());
}

pdlg[1]->DestroyWindow();

for(i = 0; i < 4; i++)
{
	printf("Object: 0x%08X, HWND: 0x%08X\n", pdlg[i], pdlg[i]->GetSafeHwnd());
}


여기서 첫번째 for에서 출력되는 것과 두번째 for에서 출력되는 것을 보면
두번째 for에서 1이후의 dialog들의 핸들 값이 0x00000000으로 초기화 되는것을 볼 수 있다.

굳이 CWnd::DestroyWindow()를 호출하였을 때 뿐만이 아니라
::DestroyWindow(pdlg[1]->GetSafeHwnd())를 이용하여도 동일한 증상이 일어난다.
특히 ::DestroyWindow를 호출하였는데 나머지 값들이 변경되는 것은 정말 신기한 일인데,
처음에는 buffer overflow인가? 라는 생각을 갖고 배열 순서를 바꾸어 테스트 해본 결과, bo는 아니었다.

약 한시간 삽질한 끝에, DestroyWindow()하지 않은 Dialog들에 대해서도 CWnd::Detach()가 호출이 되었다는것을 알아내었는데, 콜스택을 따라가 보니 pdlg[1]의 DestroyWindow()에서 호출하거나 그런 일은 전혀 없었다.
다만 커널 dll 중 어디선가 호출을 하였다는 것 밖에 알 수 없었던 상황인데..

지난 금요일 devpia에 질문을 올리고 다른분이 답글을 달아 주신것을 오늘(일요일) 보았다.

"VC++ 6.0 SP2에서는 정상적으로 동작합니다. 혹시 컴파일러 문제는 아닐까요?"

라는 글과 함께.
그래서 당장 VC 6.0을 켜서 해보았는데..
동일한 코드로 작성을 하니 똑같은 증상이었다.
컴파일러 문제는 아니었군.
그러다가 순간 머릿속에 들어오는 생각.
그 사람은 TRACE로 바꾸어서 했다는 것이었다. 즉 Console 기반 프로젝트가 아니라는 것이었다.
혹시나 해서 Dialog based로 해보았더니 제대로 동작한다.
devpia에 감사하다고 답을 달면서 Console기반에서만 안된다고 하였다.

댓글을 단 뒤 갑자기 드는 생각!

여기까지 얘기 하였을 때 이 글을 보는 사람은 답을 찾았는가?

Dialog based와 Console 프로젝트의 차이점은 main wnd가 없다는 것이다.
(여기선 mfc supported된 Console project를 기반으로 설명한다)
AfxGetMainWnd() 하면 NULL이 나오는데...
이게 문제다! 라는 생각으로 당장 실천에 옮겼다.
아래와 같이 DesktopWindow의 핸들을 얻어와서 parent로 값을 넣은 결과

CWnd *pDesktop;

pDesktop = CWnd::GetDesktopWindow();


잘 동작한다~!
parent wnd문제가 맞았다.
그런데 왜 이런 일이 발생하는 것일까?
문제 해결책을 알았으니 원인을 알아내는건 비교적 쉬운일이다.
당연히 parent 문제이므로 pdlg들의 GetParent()를 출력 해보았다.
방금 생성한 Window가 Parent가 된 것을 볼 수 있었다.
그러면 parent와 child의 종속 관계에서 parent가 해제 되니 child도 해제가 되었다고 생각할 수 있다.
이왕 한김에 확실히 해보자는 생각에 AfxGetMainWnd()를 한 결과
pdlg[0]이 생성될 때에는 NULL이, pdlg[0]이 생성된 후에는 pdlg[0]의 핸들이 반환되었다.
즉 AfxGetMainWnd()가 Dialog를 생성할때마다 바뀐다는 사실.
이걸 찍어 본 이유는 새로운 윈도우 생성할 때 Parent를 NULL로 주면 AfxGetMainWnd()에서 얻어온 윈도우를
자동으로 Parent로 지정해주기 때문이다.

지금까지 AfxGetMainWnd() 값은 고정일것이라고 생각했다. 내가 이 값을 전혀 쓰지 않으므로 신경을 쓰지 않고 있었는데, MFC 내부에서 이렇게 처리하고 있을 줄이야..

framework에서 이런 삽질(?)을 해대면 -_-.. 진짜 난감하다.
크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 장현준

2007/12/30 15:48 2007/12/30 15:48
, , ,
Response
No Trackback , No Comment
RSS :
http://b4you.net/blog/rss/response/160

ContinueModal() 에서 ASSERTION FAILED

MFC로 코딩하다.. KillFocus 부분에서 OnCancel() 나 OnOK()를 호출하였는데, 이게 왠일! 오류가 나는게 아닌가? 그것도 내가 짠 코드가 아닌 wincore.cpp 에서..

// wincore.cpp 4530 line
    // acquire and dispatch messages until the modal state is done for (;;)
    {
        ASSERT(ContinueModal()); // 여기서 오류가!
        // phase1: check to see if we can do idle work
        while (bIdle && !::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE))
        {
            ASSERT(ContinueModal()); // show the dialog when the message queue goes idle
            if (bShowIdle)
            {
                ShowWindow(SW_SHOWNORMAL);
                UpdateWindow();
                bShowIdle = FALSE;
            }
...


위에 주석으로 표시된 부분에서 ASSERT 오류가 났다. 왜? 왜? 왜? 그렇다면 ContinueModal()로 들어가보자.

// wincore.cpp 4599 line
BOOL CWnd::ContinueModal()
{
    return m_nFlags & WF_CONTINUEMODAL;
}


결과적으로 WF_CONTINUEMODAL flag가 켜져 있지 않으면 오류가 난다 이거다. 이것도 문제가 생길때가 있고 안생길때가 있어서 사람을 미치게 만드는데.. 그럼 뭐가 문제일까? 결론부터 말하자면 dialog가 없어졌는데 (= 핸들이 해제되었는데) 메시지를 보내서 문제가 되는것이며, 이는 WM_KILLFOCUS같은 메시지가 처리되면서 dialog가 해제되어 발생하는 문제로 파악되어 진다. (SendMessage는 메시지를 바로 처리하는 함수이기 때문에) 해결책은? WM_KILLFOCUS안에서 PostMessage로 Dialog를 종료한다. 그러면 WM_KILLFOCUS가 모두 호출 된 뒤 message queue에서 종료메시지를 받게 된다. 즉,

void CYourDlg::OnKillfocus()
{
    // OnClose나 OnOK 대신 PostMessage(WM_CLOSE);
}


와 같이 코딩하면 된다.
크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 장현준

2007/10/22 11:27 2007/10/22 11:27
, , , , ,
Response
No Trackback , a comment
RSS :
http://b4you.net/blog/rss/response/142

보통 구조체나 클래스 안에서 멤버 변수로 CString과 같은 클래스를 많이 사용한다.
그런데 가끔 문제가 생길 소지가 있는 코딩을 하고는 하는데...

오늘은 이에 대해 알아보자.

1. CString을 (LPSTR)로 casting 하여 사용하기.
매우 위험한일이라고 할 수밖에 없다. LPSTR은? unicode가 아닌 곳에서 char *이다. 즉, c에서 사용하는 기본 문자열인데, 그냥 문자열도 아니고 "수정 가능한" 문자열이다. 수정 불가능한 문자열은 LPCSTR인데, 이것은 const char *이다. const가 붙었으니 수정이 안된다.
CString에서, 기본적으로 제공하는 (연산자 오버로딩에 의해) 연산자는 LPCSTR인데, 이는 클래스에 속한 멤버 변수들의 캡슐화를 위해 내부 데이터가 수정될 수 없게 만들어야 하기 떄문에 당연하 처사이다.
이를 LPSTR로 casting하여 사용한다면... 수정하지 말아야 될 문자열이 수정될 수 있으므로, 결국 CString에서 전혀 건드리지도 않은 부분에서 오류가 날 가능성이 있다는 것이다.

이것은 비단 CString 뿐만이 아니라, 다른 class도 마찬가지인데.. 이같은 행위는 2번에서 설명하는것과 비슷하다.

단, MFC에서 제공하는 CRect와 같이 간단한 클래스는 멤버 변수를 직접 허용하게 하므로 클래스의 동작 여부와는 상관 없다.

2. 구조체 안에 CString 를 사용할 때 주의사항
CString 뿐만이 아니다. 하지만 CString을 예로 든 이유는, 대표적으로 문제가 발생할 소지가 있는 클래스 이기 때문이다.

#include <stdafx.h>

typedef struct _MYSTRUCT
{
   int nNumber;
   CString strName;
} MYSTRUCT, *PMYSTRUCT;

int main()
{
   MYSTRUCT item;
   memset(&item, 0, sizeof(item));
   return 0;
}


보통 코딩을 할 때 위의 코드와 같이 구조체를 memset로 초기화 한다.(ZeroMemory()와 같은것 포함) 이럴 때 발생할 수 있는 문제점은?

스택에 item변수에 속한 멤버변수들을 초기화 하는데, 이 때 CString또한 초기화 된다.
CString은 생성자에서 내부적으로 길이가 0인 문자열 포인터를 alloc해놓고 대기하는데,
이를 0으로 초기화 해버리면 CString 내부에서 문자열을 구할 때 NULL포인터를 참조하므로 문제가 발생할 수 있다.

이것을 코드로 표현해보자면..

#include <stdafx.h>

class CMyString
{
public:
   CMyString() { m_pszString = malloc(1024); }
   virtual ~CMyString() { free(m_pszString); }

private:
   char *m_pszString;
};

typedef struct _MYSTRUCT
{
   int nNumber;
   CMyString strName;
} MYSTRUCT, *PMYSTRUCT;

int main()
{
   MYSTRUCT item;
   memset(&item, 0, sizeof(item));
   return 0;
}


대략 이런 경우랄까?
memset을 하게 되면 m_pszString는 0, 즉 NULL 값을 가지므로 나중에 CMyString의 다른 메소드에서 m_pszString을 호출한다면 당연히 오류가 난다.
이를 방지하기 위해서는 memset로 초기화를 하지 말거나, 메모리가 할당되는 부분을 백업(?) 한 뒤 복구하는 방법이 있다.
크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 장현준

2007/08/17 18:48 2007/08/17 18:48
, ,
Response
No Trackback , 2 Comments
RSS :
http://b4you.net/blog/rss/response/130

MFC Control Subclassing #1

까먹기 전에 MFC에서 컨트롤을 Subclassing 하는 기법에 대해 정리를 해 두어야 겠다.
예전에 프로젝트를 진행하면서, 많은 Control들을 subclassing했었는데..
그냥 썩혀두긴 아까우니 말이다.

아주 간단한 버튼부터 리스트 컨트롤까지..
야매 코드(!!!)와 함께 소개할 예정이다.
(실제로 리스트 컨트롤은 서브클래싱 난이도가 극악이다. 딴건 몰라도 스크롤바 때문에 굉장히 어렵다. 이걸 야매로 처리하는 코드가 있다.)

상용 UI 라이브러리를 사면.. 이런것 모두 깔끔하게 되더만...
왜 우리는 이런걸 만들 수 있는 능력이 없을까.ㅠ_ㅠ

나 또한 부족한점이 많지만, 하나하나 확실히 해가자는 의미에서 다음과 같이 정리할 생각이다.

1. 가장 만만한 CButton
2. 그 담에 내맘대로 요리하는 CStatic
3. 짜증나는 CEdit
4. 비교적 쉬운 CListBox
5. 해본적 없는(-_-) CComboBox
6. 이것 또한 해본적 없는(;;) CTreeCtrl (또는 CTreeView)
7. 야매로 처리하자 CListCtrl (또는 CListView)

소스는 아마 일반적인 Subclassing 기법에 다루기 때문에.. 다들 아는 내용일꺼라 생각한다.
예를들어 CButton에는 그림넣기 이런것?
뭐 subclassing이라는게.. 특이한 요구조건이 없으면 거의다 비슷비슷해서; 별거 없는거같다.

시작은 거창하지만 -ㅅ- subclassing에 대해 모아놓은 자료가 없으니.. 시작을 해봐야겠다.

이것 또한 시작하는 날짜는 11월 초.
현재 진행하는 연구실 프로젝트가 간당간당 하당께요-_-...... 후덜덜덜...
크리에이티브 커먼즈 라이센스
Creative Commons License

Posted by 장현준

2007/08/06 23:25 2007/08/06 23:25
,
Response
No Trackback , No Comment
RSS :
http://b4you.net/blog/rss/response/129


블로그 이미지

빗소리를 먹는 사람.

- 장현준

Notices

Archives

Authors

  1. 장현준

Recent Trackbacks

Calendar

«   2017/09   »
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

Site Stats

Total hits:
1714564
Today:
3719
Yesterday:
3980