MFC CWnd 클래스의 SubclassWindow()를 사용함에 있어 주의해야 하는 제약 사항이 있다.
이미 CWnd 오브젝트에 맵핑되어 있는 hWnd를 파라미터로 하여 CWnd::SubclassWindow()를 호출하게 되면 충돌이 발생한다. 이런 경우는 서브클래싱을 통해 추가적인 기능을 제공하는 범용 클래스를 소스 레벨에서 통합시킬 때 발생할 수 있다.
정확히 어떤 경우인지 예를 들어 살펴보자.
class CExWnd : public CWnd
{
...
};
class CSomeTool : public CObject
{
CExWnd m_exWnd;
void ExtendWnd(CWnd* pWnd);
};
void CSomeTool::ExtendWnd(CWnd* pWnd)
{
m_exWnd.SubclassWindow(pWnd->GetSafeHwnd());
}
CSomeTool::ExtendWnd(CWnd* pWnd) 함수는 파라미터로 받은 pWnd의 윈도 핸들을 CExWnd로 서브클래싱을 시도하고 있다. 이때 pWnd가 permanent 오브젝트인 경우 CWnd::SubclassWindow() 함수에서 충돌이 발생한다. Permanent 오브젝트의 여부는 CWnd::FromHandlePermanent(HWND hWnd)를 통해 검사할 수 있다.
충돌의 원인은 MFC가 CWnd 오브젝트와 윈도 핸들을 맵핑시키는 메카니즘에서 기인한다.
MFC는 내부에 윈도 맵이라는 해쉬 테이블을 통해 윈도 핸들과 CWnd 오브젝트를 연관시켜 관리한다. 해쉬의 특성상 하나의 윈도 핸들에는 오직 하나의 CWnd 오브젝트만이 맵핑될 수 있다.
그런데 CWnd 오브젝트에 이미 맵핑되어 있는 윈도 핸들을 또 다른 CWnd 오브젝트인 CExWnd에 서브클래싱을 시도하게 되면 하나의 윈도 핸들에 두 개의 CWnd 오브젝트가 존재하는 상황이 발생하여 MFC의 내부 윈도 맵에 충돌이 발생하는 것이다.
단순히 이것만이 문제라면 SubclassWindow()를 호출하기 전에 CWnd::FromHandlePermanent()를 통해 얻은 permanent CWnd 오브젝트의 Detach() 함수를 먼저 호출하여 MFC 윈도 맵의 충돌을 피할 수 있다.
그러나 이것보다 더 심각한 문제가 있다.
MFC는 모든 CWnd 클래스에 대해 AfxWndProc()이라는 윈도 프로시저를 사용하고 있다. 즉, 서브클래싱 하고자 하는 permanent 윈도 오브젝트도 AfxWndProc()을 사용하고 있고 서브클래싱된 CExWnd 클래스도 AfxWndProc()을 사용하고 있다. CWnd::SubclassWindow()는 파라미터로 전달된 윈도의 윈도 프로시저를 상위 윈도 프로시저(oldWndProc)로 저장하고 자신의 윈도 프로시저(AfxWndProc)를 설정한다. AfxWndProc()에서는 CallWindowProc()을 호출하여 서브클래싱 되기 전의 상위 윈도 프로시저(oldWndProc)를 호출한다. 그런데 서브클래싱된 CExWnd는 상위 윈도 프로시저(oldWndProc)에 AfxWndProc을 담고 있으므로 AfxWndProc이 무한 반복 호출되는 상황이 발생한다.
이것은 MFC의 내부 구조상 어쩔 수 없는 제약사항이다.
해결책은 두 가지가 있을 수 있다.
첫째는 범용 클래스를 소스 레벨에서 통합하지 말고 별도의 DLL로 빌드하여 통합할 것.
DLL로 빌드하면 CExWnd는 DLL 컨텍스트에서 관리되는 MFC 윈도 맵과 AfxWndProc을 사용하게 된다. 파라미터로 넘겨지는 윈도 핸들이 permanent CWnd 오브젝트의 핸들이라 할지라도 Exe 컨텍스트에서 사용되는 CWnd 오브젝트라면 충돌이 일어나지 않는다.
둘째는 직접 WNDPROC 타입의 콜백 함수(fnExWndProc)를 만들어 SetWindowLongPtr(hWnd, GWLP_WNDPROC, fnExWndProc)로 직접 서브클래싱할 것.
당연하다. MFC를 사용하지 않는 서브클래싱이면 아무 문제도 없다.
댓글 없음:
댓글 쓰기