MFC

DECLARE_DYNCREATE 매크로 풀어쓰기...

디버그정 2008. 7. 30. 15:49

아래 발췌내용에서 조금 잘못된 부분이 있다.

런타임클래스를 이용하여 객체를 생성하는 것을 동적 생성이라고 용어정의를 내리는 것이
혼동을 줄 수 있다... 보통 동적 생성이라 함은 메모리 할당 함수나 연산자를 이용하여
실행중에 생성하는 것을 의미한다. new연산자를 이용하여 생성하는 것이 그 대표적인 예라고 볼 수 있다.

그리고 원래 DECLARE_DYNCREATE - IMPLEMENT_DYNCREATE 설정되었다고 해서 new 연산자를
사용하지 못하는 것은 아니다. 단지 런타임클래스의 m_pfnCreateObject  함수포인터 멤버(이 포인터는 클래스의 CreateObject 멤버 함수를 가리킴)를 이용하여 객체를 생성할 수 있는 기능을 추가한 것일 뿐이다.
다만 권고사항으로 위 매크로 선언시 생성자에 public 속성을 주지 않음으로써
정적인 생성이나 외부에서 new 연산자를 통한 동적 생성을 막도록 한다고들 한다...;;;
개념에 따른 당연한 귀결이 아니라는 소리이다.


/////////////// 발 췌 ////////////////////////////////


RTCI는 RTTI와 개념이 비슷하기는 하다.

여러분들도 잘 알겠지만 원래 STL이나 RTTI와 같은 개념은 MFC가 먼저 시작한 것이다.

예를 들어 MFC의 CList는 C++/STL의 vector와 같은 형태로 변형되었다.

하지만 요즘 시대는 STL의 사용도 많아지고 있으니... STL을 먼저 배우고 MFC를 배워서일까~?!


MFC에서는 실행 시간에 클래스 정보에 어떻게 접근하는지 알아보자. 다음 예를 보자.

아래의 킨 코드와 설명은 어떻게 보면 IsKindOf 함수를 설명하고자 함이 아닐까 ㅋㅋㅋ


#include <afxwin.h>   // 이주성씨 제공 MFC 헤더 파일...


class CBase : public CObject
{
    DECLARE_DYNCREATE(CBase)
};


IMPLEMENT_DYNCREATE(CBase, CObject)


class CTest : public CBase
{
    DECLARE_DYNCREATE(CTest)
};


IMPLEMENT_DYNCREATE(CTest, CBase)


void main( void )
{
    CTest *pTest = new CTest;


    if( pTest->IsKindOf( RUNTIME_CLASS(CBase) ) )
    {
        puts( "CBase 상속" );
    }


    if( pTest->IsKindOf( RUNTIME_CLASS(CObject) ) )
    {
        puts( "CObject를 상속" );
    }
 
    if( pTest->IsKindOf( RUNTIME_CLASS(CTest) ) )
    {
        puts( "CTest 클래스" );
    }
}


MFC에서 RTCI의 사용을 위해서 3개의 매크로 쌍을 다음과 같이 제공한다.


DECLARE_DYNCREATE / IMPLEMENT_DYNCREATE

DECLARE_DYNAMIC      / IMPLEMENT_DYNAMIC

DECLARE_SERIAL         / IMPLEMENT_SERIAL


위 3쌍의 매크로는 각각 IsKindOf 함수를 사용할 수 있도록 해준다. IsKindOf 함수는 위의 예와 같이

현재 포인터 또는 객체가 (1)특정 클래스로부터 상속을 받았는지 또는 (2)나의 클래스인지를 판단할 수 있게 한다.

여기에 사용된 RUNTIME_CLASS는 매크로이며, CObject 클래스를 classCObject와 같은 이름으로 변경해준다.

자세한 것은 아래를 참조하면 된다.


그리고 위의 각 매크로는 다음과 같은 차이를 갖고 있다. 각 클래스는 사용되는 매크로에 따라 사용할 수 있는 기능의

차이가 나는데, IsKindOf 함수는 모든 매크로가 다 사용할 수 있음을 알 수 있다. 또한 DECLARE_DYNAMIC 매크로는

CreateObject 함수를 호출할 수 없다. 그러므로 MDI 환경에서 CMainFrame 같은 경우는 DECLARE_DYNAMIC

매크로가 사용되고 있음을 알 수 있고, CChildFrameDECLARE_DYNCREATE가 사용되고 있음을 볼 수 있다.

그것은 단순한 우연이 아니며, CMainFrame은 초기에 한 번 생성되며, CChildFrame은 새로운 창이 생성될 때 마다

생성되어야 하기 때문이다. 클래스의 생성은 CreateObject를 사용해서 MFC 코드 내부적으로 실행된다.

SDICMainFrame, Document, View가 모두 DECLARE_DYNCREATE로 되어 있는 것도 유의해서 볼 사항이다.

DECLARE_DYNCREATE로 매크로가 설정되어 있다는 것은 모든 클래스가 동적으로 생성된다는 것을 뜻하는 것이기

때문이다. 마지막으로 대부분 잘 사용하지 않을 수도 있는 매크로가 있는데 DECLARE_SERIAL은 Serialize에 사용되는

것이다. 여러분이 Serialize를 사용하기 위한 클래스를 만들려면, 반드시 CObject를 상속 받은 후 DECLARE_SERIAL

매크로를 사용해야 한다.


사용된 매크로

CObject:: IsKindOf

CRuntimeClass:: CreateObject

CArchive::operator>> CArchive::operator<<

Basic CObject functionality

사용 못함

사용 못함

사용 못함

DECLARE_DYNAMIC

사용 못함

사용 못함

DECLARE_DYNCREATE

사용 못함

DECLARE_SERIAL



다음은 각 매크로의 코드이다.

매크로 끝의 \ 는 행 계속 문자이고, #은 해당 이름을 문자열로 만드는 것이고, ##은 두 개의 이름을 붙이는 것이다.

예를 들어, #abc -> "abc"로 변환되며, class##CTest -> classCTest로 변환된다.


#define DECLARE_DYNCREATE(class_name) \
 DECLARE_DYNAMIC(class_name) \
 static CObject* PASCAL CreateObject();                  // 왜 DECLARE_DYNCREATE가 CreateObject를 지원하는지.


#define DECLARE_DYNAMIC(class_name) \        // DECLARE_DYNAMIC은 CreateObject가 없다.
protected: \
 static CRuntimeClass* PASCAL _GetBaseClass(); \
public: \
 static const AFX_DATA CRuntimeClass class##class_name; \
 virtual CRuntimeClass* GetRuntimeClass() const; \


#define IMPLEMENT_DYNCREATE(class_name, base_class_name) \
 CObject* PASCAL class_name::CreateObject() \
  { return new class_name; } \
 IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, \
  class_name::CreateObject)


#define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew) \
 CRuntimeClass* PASCAL class_name::_GetBaseClass() \
  { return RUNTIME_CLASS(base_class_name); } \
 AFX_COMDAT const AFX_DATADEF CRuntimeClass class_name::class##class_name = { \
  #class_name, sizeof(class class_name), wSchema, pfnNew, \
   &class_name::_GetBaseClass, NULL }; \
 CRuntimeClass* class_name::GetRuntimeClass() const \
  { return RUNTIME_CLASS(class_name); } \


#define RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))


다음을 보고 위 클래스의 DECLARE_DYNCREATE를 변형시켜보면 아래와 같다.

#include <afxwin.h>


class CBase : public CObject
{
    //DECLARE_DYNCREATE(CBase)
protected:
    static CRuntimeClass* PASCAL _GetBaseClass();

public:
    static const AFX_DATA CRuntimeClass classCBase;
    virtual CRuntimeClass* GetRuntimeClass() const;

    static CObject* PASCAL CreateObject();                  // 왜 DECLARE_DYNCREATE가 CreateObject를 지원하는지.

};

...



위의 변환에서 보면, DECLARE_DYNCREATE는 _GetBaseClass(), GetRuntimeClass(), CreateObject() 함수와

classCBase라는 정적 멤버 변수로 변환됨을 알 수 있다. 각각의 함수들이 무엇을 하는지는 차차 알아보기로 하고,

여기서는 CRuntimeClass를 탐구해 보자. 이 클래스가 바로 RTCI의 핵심이 되는 클래스(구조체)이다.


struct CRuntimeClass
{
// 속성들
 LPCSTR m_lpszClassName;        // 클래스의 문자열 이름
 int m_nObjectSize;
 UINT m_wSchema; // schema number of the loaded class
 CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class
#ifdef _AFXDLL       // MFC Shared DLL을 사용하는 경우
 CRuntimeClass* (PASCAL* m_pfnGetBaseClass)();
#else
 CRuntimeClass* m_pBaseClass;
#endif

// Operations
 CObject* CreateObject();
 BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;

// 구현
 void Store(CArchive& ar) const;
 static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum);

 // CRuntimeClass objects linked together in simple list
 CRuntimeClass* m_pNextClass;       // linked list of registered classes
};


좀 어렵죠? ㅋㅋㅋ 코딩해보구.. 테스트해보구.. 그러다 보면 쉬어져요...///


다음은 디버깅을 통하여 객체의 내부를 확인해 본 것이다. CObject라는 최상위 클래스에서 가상 함수 테이블이

보이며, 그 아래로 classCBase, classCTest가 존재하고 있다.


 

사용자 삽입 이미지



다음은 IMPLEMENT_DYNCREATE 매크로를 적용시킨 것이다.


#include <afxwin.h>


class CBase : public CObject
{
    //DECLARE_DYNCREATE(CBase)
protected:
    static CRuntimeClass* PASCAL _GetBaseClass();

public:
    static const AFX_DATA CRuntimeClass classCBase;
    virtual CRuntimeClass* GetRuntimeClass() const;

    static CObject* PASCAL CreateObject();

};


//IMPLEMENT_DYNCREATE(CBase, CObject)
CObject* PASCAL CBase::CreateObject()
{
    return new CBase;
}


CRuntimeClass* PASCAL CBase::_GetBaseClass()
{
    return ( (CRuntimeClass*)(&CObject::classCObject) );
}


AFX_COMDAT const AFX_DATADEF CRuntimeClass CBase::classCBase =
{
    "CBase",
    sizeof(class CBase),
    0xFFFF,
    CBase::CreateObject,
    &CBase::_GetBaseClass,
    NULL
};


CRuntimeClass* CBase::GetRuntimeClass() const
{
    return ( (CRuntimeClass*)(&CBase::classCBase) );
}


이젠 나머지 매크로에 대해서도 스스로 해 보기를 바란다...


휴~~~~~~~~~~~~~~~~~~~~~~~

'MFC' 카테고리의 다른 글

강력한 에디터 CRichEditView  (0) 2008.08.30
◎ MFC Class 레퍼런스(한글번역)  (0) 2008.08.30
지킴이 기초 강좌  (1) 2008.08.29
CCmdTarget Class MFC  (1) 2008.08.07
MFC 클래스의 최상위 클래스 CObject  (2) 2008.08.07
MFC 클래스 계층도  (1) 2008.08.07
DECLARE_DYNCREATE와 IMPLEMENT_DYNCREATE  (2) 2008.07.30