336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

RTTI(Run-Time Type Information)

JAVA 와는 달리 C++ 에서는 RTTI를 완벽하게 지원하지 않습니다. 때문에 MS Compiler 차원에서 RTTI를 지원해주기 위한 방법을 구현해냈는데 그게 CRuntimeClass 와 여러가지 매크로함수를 제공하고 있습니다. 오늘은 이에대해 알아보겠습니다.

 

먼저 RTTI MFC 에서 지원하게 하려면 다음과 같은 규칙이 필요합니다.

1.     클래스는 반드시 CRuntimeClass 타입의 static 멤버를 갖고 있어야 한다.

A.     CRuntimeClass는 객체를 생성하기 전, 클래스 레벨에서 접근해야 한다
.

2.     1 에 선언된 static 멤버를 초기화하는 루틴을 구현파일에 가져야 한다.

A.     초기화 루틴은 클래스 이름, 클래스 크기를 설정하고, CRuntimeClass::m_pfnCreateObject를 클래스의 정적 멤버 함수 CreateObject 로 초기화한다.

3.     클래스는 반드시 CObject * 를 리턴하는 정적 CreateObject 함수를 선언해야 한다.

A.     클래스 레벨에서 접근해야 하므로 static 이어야 한다

4.     3 에 선언된 static 멤버 함수의 몸체를 구현파일에 가져야 한다.

A.     CreateObject 는 자기 자신을 동적으로 생성하고, 생성된 객체의 시작 주소를 리턴한다.

 

MFC 소스를 분석해 나가다보면 다음과 같은 코드를 종종 볼 수 있습니다.

RUNTIME_CLASS(CLOVE_JAENAMDoc)

DECLARE_DYNCREATE(CLOVE_JAENAMView)

IMPLEMENT_DYNCREATE(CLOVE_JAENAMView, CView)

 

그리고 다음과 같은 클래스도 볼 수 있습니다.

CRuntimeClass

 

먼저 CRuntimeClass 구조체를 살펴보면

 

struct CRuntimeClass

{

// Attributes

        LPCSTR m_lpszClassName;

        int m_nObjectSize;

        UINT m_wSchema; // schema number of the loaded class

        CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class

#ifdef _AFXDLL

        CRuntimeClass* (PASCAL* m_pfnGetBaseClass)();

#else

        CRuntimeClass* m_pBaseClass;

#endif

 

// Operations

        CObject* CreateObject();

        BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;

 

        // dynamic name lookup and creation

        static CRuntimeClass* PASCAL FromName(LPCSTR lpszClassName);

        static CRuntimeClass* PASCAL FromName(LPCWSTR lpszClassName);

        static CObject* PASCAL CreateObject(LPCSTR lpszClassName);

        static CObject* PASCAL CreateObject(LPCWSTR lpszClassName);

 

// Implementation

        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

        const AFX_CLASSINIT* m_pClassInit;

};

 

상당히 많은 양의 코드가 있는데 놀라지 말고 간단하게 몇가지만 살펴보고 넘어갈껍니다.

먼저 속성 부분에는 클래스 이름, 클래스 크기, 현재 스키마의 번호를 갖고 있습니다.

// Attributes

        LPCSTR m_lpszClassName; // 클래스 이름

        int m_nObjectSize; // 클래스 크기

        UINT m_wSchema; // 클래스 스키마 번호

        CObject* (PASCAL* m_pfnCreateObject)(); // 기본생성자의 함수 포인터

생성자의 함수 포인터를 가리키는게 약간 특이합니다만, 이부분에 대해서는 나중에 자세히 설명을 할 것이니, 지금은 동적 생성을 지원하기 위해 사용하는 거라고 간단히 알고 넘어갑시다.

다음은 전처리기 부분입니다.

#ifdef _AFXDLL

        CRuntimeClass* (PASCAL* m_pfnGetBaseClass)();

#else

        CRuntimeClass* m_pBaseClass;

#endif

 

먼저 _AFXDLL 에 대해 간단하게 설명하자면, MFC 에서 빌드할 때 MFC 관련 라이브러리 (보통 AFX 로 시작하는..)를 어떻게 링크할 것인가를 물어보는 옵션이 있습니다. 동적으로 링크했을 경우는  _AFXDLL define 상태가 되고, 정적링크를 할 경우에는 _AFXDLL undefine 상태가 됩니다 따라서 동적 링크 시에는

CRuntimeClass* (PASCAL* m_pfnGetBaseClass)();

정적 링크 시에는

CRuntimeClass* m_pBaseClass;

부분이 실행이 됩니다.

 

다음으로 함수 몇 개가 더 나옵니다.

 

// Operations

        CObject* CreateObject();

        BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;

 

먼저 RTTI를 지원하는 클래스는 CObject 에서 상속을 받아야 합니다. 그 이유는 지금 CreateObject() 함수처럼 동적으로 클래스를 생성해야 하기 때문이져… CreateObject 함수는 해당 객체를 동적으로 생성해주는 함수 입니다. 여기서 잠깐!! 지금까지의 코드는 함수의 포인터나 객체의 포인터를 사용해서 연결을 시킨 후 함수를 실행해주는데 지금의 함수 구현부는 어디있나요? 정답은 잠시후에 알 수 있을 것입니다. 잠시만 참아주세요

 

다음으로 IsDerivedFrom 함수는 인자로 받은 클래스가 현재 클래스의 상위클래스인지를 검사하는 함수입니다. 리턴값은 당연히, 상속관계면 TRUE, 아니면 FALSE 가 되겠져?

 

그 다음부분은 저도 잘 모르고…(후다닥;;;)

 

이젠 이렇게 구현해놓은 CRuntimeClass 를 실제로 어떻게 사용하는지 보겠습니다.

일단, SDI MDI MFC 프로젝트를 하나 만들어 두시고 다음을 계속 읽어내려가 주세요

저는 VS 2008 사용자라 이런저런 클래스가 많이도 생기네요

일단 2005에서도 익숙했던 도큐먼트 클래스를 보도록 하겠습니다.

헤더파일을 보면 다음과 같은 매크로 함수가 버티고 있습니다.

DECLARE_DYNCREATE(CLOVE_JAENAMDoc)

맨 처음 MFC를 접하면 수도없이 많은 매크로 함수 때문에 상당한 혼돈이 오게 되는데, 당황하지 마시고 천천히 뜯어보면 됩니다, 그러면 지금부터 저 히한하고 요상하게 생긴 매크로 함수부터 뜯어보겠습니다.

DECLARE_DYNCREATE(CLOVE_JAENAMDoc) 의 원형은 다음과 같습니다.

#define DECLARE_DYNCREATE(class_name) \

        DECLARE_DYNAMIC(class_name) \

        static CObject* PASCAL CreateObject();

 

즉 다음과 같은 매크로 함수가

DECLARE_DYNCREATE(CLOVE_JAENAMDoc)

컴파일시에는 다음과 같은 코드로 바뀝니다.

DECLARE_DYNAMIC(class_name)

static CObject* PASCAL CreateObject();

 

DECLARE_DYNAMIC(class_name) 의 원형은

 

#define DECLARE_DYNAMIC(class_name) \

protected: \

        static CRuntimeClass* PASCAL _GetBaseClass(); \

public: \

        static const CRuntimeClass class##class_name; \

        static CRuntimeClass* PASCAL GetThisClass(); \

        virtual CRuntimeClass* GetRuntimeClass() const; \

이므로 DECLARE_DYNCREATE(CLOVE_JAENAMDoc) 는 최종적으로

다음과 같은 코드로 바뀝니다

protected:

        static CRuntimeClass* PASCAL _GetBaseClass();

public:

        static const CRuntimeClass classCLOVE_JAENAMDoc;

        static CRuntimeClass* PASCAL GetThisClass();

        virtual CRuntimeClass* GetRuntimeClass() const;

           static CObject* PASCAL CreateObject();

, 그러면 여기서 RTTI를 사용하기 위한 규칙 1, 3번 을 떠올려봅시다!!

클래스는 반드시 CRuntimeClass 타입의 static 멤버를 갖고 있어야 한다

클래스는 반드시 CObject * 를 리턴하는 정적 CreateObject 함수를

선언해야 한다.

 

어때요? 참 쉽죠? (밥 아저씨가 생각나네요) 현재 클래스의 정보를 갖고있는 CRuntimeClass의 포인터를 얻어내는 함수를 제외하면 1번과 3번의 조건을 매크로 함수 하나로 처리를 해주고 있습니다

 

그러면 눈치빠른 분들은 이미 아시겠져? 다음은 무얼 할껀지

구현부분에서 어떻게 되어있는지 보겠습니다. 이제 남은 규칙은 두가지네요

 

1 에 선언된 static 멤버를 초기화하는 루틴을 구현파일에 가져야 한다.

3 에 선언된 static 멤버 함수의 몸체를 구현파일에 가져야 한다.

 

구현부분도 역시 매크로 함수 하나로 다해주고 있는게 보이네요

 

IMPLEMENT_DYNCREATE(CLOVE_JAENAMDoc, CDocument)

 

이 매크로 함수가 어떻게 정의되는지 보겠습니다.

 

#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, NULL)

 

그리고 IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, \

               class_name::CreateObject, NULL) 는 다시

 

CRuntimeClass* PASCAL class_name::_GetBaseClass() \

               { return RUNTIME_CLASS(base_class_name); } \

AFX_COMDAT const CRuntimeClass class_name::class##class_name = { \

        #class_name, sizeof(class class_name), wSchema, pfnNew, \

               &class_name::_GetBaseClass, NULL, class_init }; \

CRuntimeClass* PASCAL class_name::GetThisClass() \

        { return _RUNTIME_CLASS(class_name); } \

CRuntimeClass* class_name::GetRuntimeClass() const \

        { return _RUNTIME_CLASS(class_name); }

 

로 정의되고 있습니다. 따라서 IMPLEMENT_DYNCREATE(CLOVE_JAENAMDoc, CDocument)

CObject* PASCAL class_name::CreateObject()

        { return new CLOVE_JAENAMDoc; }

 

CRuntimeClass* PASCAL CLOVE_JAENAMDoc::_GetBaseClass()

        { return RUNTIME_CLASS(CDocument); }

 

AFX_COMDAT const CRuntimeClass CLOVE_JAENAMDoc::classCLOVE_JAENAMDoc = {

        CLOVE_JAENAMDoc, sizeof(class CLOVE_JAENAMDoc), wSchema, pfnNew,

               & CLOVE_JAENAMDoc::_GetBaseClass, NULL, class_init };

 

CRuntimeClass* PASCAL CLOVE_JAENAMDoc::GetThisClass()

        { return _RUNTIME_CLASS(CLOVE_JAENAMDoc); }

 

CRuntimeClass* CLOVE_JAENAMDoc::GetRuntimeClass() const

        { return _RUNTIME_CLASS(CLOVE_JAENAMDoc); }

 

로 다소 복잡하게 바뀌지만 헤더에서 선언한 부분을 모두 구현해주고 있습니다.

마지막으로 RUNTIME_CLASS(CLOVE_JAENAMDoc),_RUNTIME_CLASS(CLOVE_JAENAMDoc) 는 다음과

같이 정의됩니다.

 

#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

정리하자면, _RUNTIME_CLASS(class_name) 는 현재 클래스의 이름을, 직접 리턴해주고

RUNTIME_CLASS(class_name) 은 간접적으로 _RUNTIME_CLASS(class_name) 를 호출하여 현재 클래스를 리턴해줍니다

 

이상 RTTI MFC 에서 어떻게 구현이 되어있는지 알아봤습니다.

SDI MDI 의 소스를 찬찬히 뜯어보게되면 RUNTIME_CLASS 같은 매크로 함수는 많이 보게 되니 이해하시는데 도움이 되었으면 좋겠습니다.

'프로그래밍 > MFC&API' 카테고리의 다른 글

Visual C++ 2008의 파일읽기 버그  (0) 2008.06.15
Windows Via C/C++  (0) 2008.05.21
블로그 이미지

캡틴토마스

그저 걷고 있는거지...

,