최근 CFrameWnd를 이용해 원하는 View를 창에 넣어 마치 Dialog 창 처럼 띄우는 방법을 사용해 문제를 해결한 적이 있다. 이번에 또 그런 필요성이 생겼고 이 창 Frame에 포함될 View가 경우에 따라 여러 가지로 확장될 가능성이 있기 때문에 이 부분을 dll로 분리하기로 결정하였다.
그런데 우연히 MFC 확장 DLL로 만들려던 것을 잘못하여 기본 MFC DLL로 프로젝트(_AFXEXT의 pre define 유무에 따라 결정된다)를 생성하게 되었다. 특정 클래스 통째를 AFX_EXT_CLASS 키워드를 통해 외부에 공개할려고 하다가 다음과 같은 compile warnning을 접하게 되었다.
warning C4273: CxUserNoticeDlg::CreateObject' : dll 링크가 일치하지 않습니다.
뭐야 이거! 어떤 놈이야! 하며 찾아보니 위의 warnning을 발생 시키는 구문이 바로 IMPLEMENT_DYNCREATE() 매크로 였다.
이 경고는 이런 이유로 생긴단다.
'function' : DLL 링크가 일치하지 않습니다. 한 파일에 있는 두 개의 정의에 dllimport를 서로 다르게 사용했습니다. 다음 샘플에서는 C4273 경고가 발생하는 경우를 보여 줍니다. // C4273.cpp // compile with: /W1 char __declspec(dllimport) c; char c; // C4273, delete this line or the line above to resolve
int main() { }
즉 선언부인 DECLARE_DYNCREATE() 안에서는 CreateObject() 함수가 __declspec(dllimport)를 달고 있는 반면 정의부인 IMPLEMENT_DYNCREATE() 안에서는 CreateObject()함수가 이를 달지 않고 정의되어 있기 때문이다.
기타 static 속성을 가진 멤버 변수와 함수등에서도 똑 같은 warnning이 발생하였는데 원인을 찾아보니
확장 DLL이 아닌 경우 기본적으로 static 속성의 멤버 변수나 멤버 함수를 외부에 공개하는 것은 불가능하다는 것이 었다. 또한 DECLARE_DYNCREATE() 속을 보면 CreateObject() 함수가 static으로 선언되어 있다는 것도 알게 되었다.
결국 이 문제는 _AFXEXT predefine(MFC 확장 DLL)을 해줌으로서 간단히 해결되었다.
이를 계기로 그 동안은 무심히 지나쳤던(사실 서진택님의 "MFC 구조와 원리" 책의 [10장 RTTI(run-time type identification)의 원리]에서 상세히 접했다-그러나 잊고 있었다는거 --;) DECLEAR_DYNCREAT(), IMPLEMENT_DYNCREATE() 매크로가 뭐하는 놈인지 궁금하게 되었다.
서진택님의 글을 빌리자면 출발은 이러했다.
" MFC 초기 설계자들은 MFC를 설계하면서 클래스의 동적 생성과 실행 시 타입에 관한 정보가 필요하다는 것을 알았다. 하지만 그 당시 이러한 사항들은 C++가 지원하지 않았고, 그들은 당시 C++ 표준을 이용해서 이러한 것을 직접 구현했다."
여기서 말하는 클래스의 동적 생성은 new, delete에 의한 instance 동적 생성을 의미하는 것이 아니다. 내가 생성하고자 하는 Instacne의 Type을 알려주었을 때 해당 Type의 Instance을 생성할 수 있는 능력을 의미하는 것이다.
내가 End User에 속하는 개발자라면 사실 클래스 동적 생성에 대한 필요성이 별로 없다. 그런데 내가 MFC 설계자들 처럼 다른 End User 개발자들에게 Framework을 제공하는 개발자라 생각해 보자. 그 Framework에는 CSingleDocTemplate란 클래스가 존재하고 해당 클래스는 생성자에서 Framework에 존재하는 CView와 CDocument, CFrameWnd 속성을 갖는 객체들을 만들어 주어야 한다고 생각해보자.
나는 이를 각고의 노력끝에 구현해서 End User 개발자들에게 배포하였다. 문제는 End User 개발자가 CView, CDocument, CFrameWnd를 상속받은 자신들만의 클래스를 만들었을때 발생하였다.
End User 개발자는 CSingleDocTemplate에서 제공하는 모든 기능들을 사용하고 싶지만 다만 정말 아쉬운 것은 해당 CSingleDocTemplate가 내가 정의한 CView, CDocument, CFrameWnd를 각각 상속받은 Instance들에 해당 기능들을 적용해 주지 못한다는 것이었다.
이미 CSingleDocTemplate는 생성자에서 CView, CDocument, CFrameWnd 속성의 Instance들을 new로 생성하도록 코딩되어 있기 때문에 End User가 바라는 것이 되는 것은 불가능 했다. 그럼 End User는 CSingleDocTemplate를 상속받아 클래스를 만들어 생성자에서 원하는 Instance들을 생성해 주어야만 한단 말인가?
이를 해결하는 한가지 방법은 End User가 생성하길 원하는 Instance들의 형을 넘겨받아 생성자가 해당 형의 Instance들을 생성해줄 수 있다면 간단히 해결될 수 있다.
바로 이렇게 할 수 있도록 도와주는 MFC의 핵심 클래스가 CRuntimeClass이다. CSingleDocTemplate의 생성자 파라미터에 내가 원하는 CView, CDocument, CFrameWnd를 상속받은 클래스 형을 CRuntimeClass를 통해 넘겨주면 CSingleDocTemplate는 해당 형의 Instance들을 생성해 조작을 가하게 된다.
또한 RTTI는 자바나 C#에서 getClass(), typeof().IsInstanceTypeOf() 의 필요성을 생각해보라. 실행시에 Instance의 타입을 알아야할 필요성은 수시로 발생하게 된다.
내가 정의하는 Class에 클래스 동적 생성과 RTTI 기능을 넣어줄 수 있도록 자동화 해주는 매크로가 바로 MFC의 DECLARE_DYNCREATE()와 IMPLEMENT_DYNCREATE() 매크로 이다.
이 매크로를 사용하는 클래스는 보통 생성자와 파괴자를 protected로 막음으로서 정적으로 사용되거나 new를 사용한 동적생성을 막도록 권장하고 있다. 그러나 사실 필요에 따라서는 생성자와 파괴자를 public으로 하여 정적 사용과 new를 사용한 생성도 충분히 가능하다. 다만 MFC가 전반적으로 CWnd를 상속받은 대분분의 클래스들이 위 매크로를 이용 동적 생성이 가능하도록 하고 정적 사용을 피하고 있기 때문에 protected 속성을 주기를 권장하고 있을 뿐이다.
따라서 아래 글(저자 :Smile Seo님)에서 자동 파괴에 비중을 두어 이 매크로의 필요성을 설명하는 것은 모순이다. 역으로 이 매크로를 사용한 클래스는 특정 method에서 delete this;를 통해 자신을 스스로 파괴하는 동작을 꼭 해주어야 할 필요성이 없기때문이다. CRuntimeClass는 동적 생성과 RTTI를 위한 방법만을 제공할 뿐 동적으로 생성한 Instance를 파괴하는 방법을 제공하지는 않는다.
아래에서 CFrameWnd나 CView가 delete를 해줄 필요가 없는 것은 CWnd::DestroyWindow()호출에의해 수행되는 CFrameWnd의 PostNcDestroy() 함수 내에서 스스로를 delete하도록 설계되어 있기 때문이며, CView도 CFrameWnd와 같거나 또는 항상 CWnd를 상속받은 Parent(Frame 또는 Dialog)를 갖을 것이며 아마 CWnd에서 자신의 자식에 속하는 CView를 delete 하도록 되어 있기 때문일 것이다. 따라서 이를 매크로에 대한 필요성으로 일반화 하는것은 부당하다.
다음은 MSDN의 CFrameWnd에 대한 설명의 일부이다.
Do not use the C++ delete operator to destroy a frame window. Use CWnd::DestroyWindow instead. The CFrameWnd implementation of PostNcDestroy will delete the C++ object when the window is destroyed. When the user closes the frame window, the default OnClose handler will call DestroyWindow
다만 이글을 무단 전제한것은 이 매크로를 사용한 클래스가 protected로 생성자가 막혀있어 new를 사용할 수 없을때 RUNTIME_CLASS() 매크로 및 CreateObject() 함수를 통해 해당 클래스 객체를 생성하는 모법 답안을 보여 주고 있기 때문이다.
DECLARE_DYNCREATE()와 IMPLEMENT_DYNCREATE()
1.요약
MFC프로그램을 하면서 DECLARE_DYNCREATE()와 IMPLEMENT_DYNCREATE()라는 매크로를 자주보게 될 것입니다. 이 매크로가 무엇때문에 사용되는지 알아보겠습니다.
2.본문
DYNCREATE라는 단어서 볼 수 있듯이 동적생성에 관계있습니다. 이 매크로는 CObject를 선조로 하는 class에서 사용될 수 있으며 이 클래스가 동적으로 생성된다는 것을 알립니다.
위 매크로로 정의된 class를 동적으로 생성하기 위해서는 CRuntimeClass를 사용하는데 이 CRuntimeClass를 가져오기 위해서는 RUNTIME_CLASS(class)를 사용합니다.
보통 어떤 클래스를 동적으로 생성할려면 new연산자를 사용하고 delete연산자로 메모리에서 해제합니다. 그런데 왜 이 매크로를 사용할까 궁금해 질겁니다. 이유는 자기 스스로 파괴되는 객체에 대한 명확한 사용법을 제시하기 위해서 입니다. 자기 스스로 파괴되는 객체의 경우 정적으로 선언하여 사용하면 두번 메모리에서 제거되는 시도를 하기 때문에 메모리 참조가 일어나게됩니다. 그러므로 스스로 파괴되는 객체의 경우 반드시 동적으로 생성되어야하고 프로그래머가 정적으로 선언하는 실수를 방지하기 위해 생성자와 소멸자를 protected 멤버로 선언합니다. 생성자와 소멸자를 protected로 선언하면 다른 class에서 new나 delete로 객체를 생성할 수 없기 때문에 이러한 생성을 도와주는 class가 필요하게되는 것입니다. 이게 바로 CRuntimeClass입니다.
3.예제
위에 말이 좀 어려움감이 있는데 쉽게 예를들어보면 CFrameWnd나 CView같은 class들은 new로 생성할 수 없을 것입니다. 이러한 class를 생성할려면 다음과 같이하면 됩니다.
CRuntimeClass *pRuntimeClass = RUNTIME_CLASS(CMyFrameWnd);
CMyFrameWnd *pFrame = (CMyFrameWnd*)pRuntimeClass->CreateObject();
pFrame->Create(...);
CreateObject()함수에서 내부적으로 new를 사용해서 객체를 생성하게되는데 이렇게 생성된 객체는 delete시킬 필요가 없을 겁니다. 그건 이 매크로로 작성된 class는 스스로 파괴되도록 설계되어 있을테니까요...
스스로 파괴되도록 설계되어있지 않다면 delete해야 됩니다.
위의 예제에서 보면 CFrameWnd나 CView를 동적으로 생성할 수 있기 때문에 Dialog base 프로그램에 CFrameWnd나 CView를 생성시킬 수 있습니다.
다음번에는 이 매크로를 사용하여 동적으로 생성되는 modeless dialog class를 만들어보겠습니다.
- 2001.08.19 Smile Seo - | |