DECLARE_DYNCREATE 매크로와 RUNTIME_CLASS 매크로의 관계
MFC로 생성한 SDI 또는 MDI 기반의 애플리케이션의 경우 CWinApp::InitInstance()에 보면 다음의 코드가 있다.
CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate( IDR_TABBEDTYPE, RUNTIME_CLASS(CTabbedViewDoc), RUNTIME_CLASS(CChildFrame), // custom MDI child frame RUNTIME_CLASS(CTabbedViewView)); AddDocTemplate(pDocTemplate); |
RUNTIME_CLASS 매크로 내에 CDocument를 상속한 CTabbedViewDoc 클래스가 선언되어 있다. 그리고 CTabbedViewDoc 클래스 선언부에는 반드시 DECLARE_DYNCREATE 매크로가 선언되어 있다.
DECLARE_DYNCREATE 매크로를 정의한 코드를 살펴보면,
#define DECLARE_DYNCREATE(class_name) \ DECLARE_DYNAMIC(class_name) \ static CObject* PASCAL CreateObject(); |
그리고, 내부에 선언된 DECLARE_DYNAMIC 매크로를 살펴보면
#define DECLARE_DYNAMIC(class_name) \ public: \ static const CRuntimeClass class##class_name; \ virtual CRuntimeClass* GetRuntimeClass() const; \ |
CTestClass 내에 위 매크로를
DECLARE_DYNCREATE(CTestClass)
과 같이 선언했다고 가정했을 경우 아래와 같은 코드가 추가되는 것과 같다.
class CTestClass { // …. Static const CRuntimeClass classCTestClass; // ##은 문자열 결합 virtual CRuntimeClass * GetRuntimeClass() const; static CObject * PASCAL CreateObject(); // …. } |
그리고 RUNTIME_CLASS 매크로의 정의 코드를 살펴보면 다음과 같다.
#define _RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name)) #ifdef _AFXDLL #define RUNTIME_CLASS(class_name) (class_name::GetThisClass()) #else #define RUNTIME_CLASS(class_name) _RUNTIME_CLASS(class_name) #endif |
위와 같이 선언되어 있는데, APP의 경우 DLL이 아니므로 결국
#define RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name)) |
와 같다. 그러면 다시 처음에 소개된 코드에서 매크로를 원 코드로 변환하면 아래와 같다.
CMultiDocTemplate* pDocTemplate; pDocTemplate = new CMultiDocTemplate( IDR_TABBEDTYPE, ((CRuntimeClass*)(&class_name::classCTabbedViewDoc)), ((CRuntimeClass*)(&class_name::classCChildFrame)), ((CRuntimeClass*)(&class_name::classCTabbedViewView)) ); AddDocTemplate(pDocTemplate); |
위와 같이 CMultiDocTemplate에 인자로 넘어가는 값은 DECLARE_DYNCREATE 매크로에 의해 선언된 멤버 변수의 포인터를 넘기는 것이고, 이 포인터는 CMultiDocTemplate의 부모 클래스인 CDocTemplate 의 생성자에서 다음과 같이 저장된다.
CDocTemplate::CDocTemplate(UINT nIDResource, CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass) { // … 중략 m_pDocClass = pDocClass; m_pFrameClass = pFrameClass; m_pViewClass = pViewClass; // … 중략 } |
CDocTemplate은 위의 멤버 변수들을 통해서 필요할 때 각각의 클래스에 해당하는 새로운 인스턴스를 생성한다.
아래 코드를 살펴보면, CDocTemplate::CreateNewFrame 내에서 CreateObject 함수 호출을 통해서 CFrameWnd 인스턴스를 생성하고, 그 인스턴스를 통해서 CFrameWnd::LoadFrame 을 호출하면 CDocTemplate::CreateNewFrame 에서 넘겨준 CreateContext 인자를 이용하여 CFrameWnd::LoadFrame 함수 내에서 Create를 호출하여 View를 생성한다. 그리고 CDocTemplate::CreateNewDocument() 내에서 CreateObject 함수를 호출하여 Document 인스턴스를 생성한다.
CFrameWnd* CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther) { // … 중략 CCreateContext context; context.m_pCurrentFrame = pOther; context.m_pCurrentDoc = pDoc; context.m_pNewViewClass = m_pViewClass; context.m_pNewDocTemplate = this; CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject(); // … 중략 // create new from resource if (!pFrame->LoadFrame(m_nIDResource, WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, // default frame styles NULL, &context)) { TRACE(traceAppMsg, 0, "Warning: CDocTemplate couldn't create a frame.\n"); // frame will be deleted in PostNcDestroy cleanup return NULL; } // it worked ! return pFrame; } |
BOOL CFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle, CWnd* pParentWnd, CCreateContext* pContext) { // … 중략 if (!Create(lpszClass, strTitle, dwDefaultStyle, rectDefault, pParentWnd, MAKEINTRESOURCE(nIDResource), 0L, pContext)) // 뷰 생성 { return FALSE; // will self destruct on failure normally } // … 중략 return TRUE; } |
CDocument* CDocTemplate::CreateNewDocument() { // 중략 CDocument* pDocument = (CDocument*)m_pDocClass->CreateObject(); // 중략 return pDocument; } |
긴 내용이지만 위의 내용을 짧게 요약하면 다음과 같다.
DECLARE_DYNCREATE 매크로는 RUNTIME_CLASS 매크로에서 필요로 하는 멤버 변수(class##class_name)와 MFC 프레임워크 내에서 동적으로 인스턴스를 생성하기 위한 멤버 함수(CreateObject)를 제공하기 위해서 선언을 하는 것이다.