How to Use __declspec(dllexport) in an MFC Extension DLL

반응형

이 기사의 정보는 다음에 적용된다 :
다음을 포함하는 Microsoft Foundation Classes(MFC) : Microsoft Visual C++, 32bit Edition, 버전 2.0, 2.1, 4.0 

SUMMARY 

이 기사는 MFC 기술노트 #33을 보충한다. 그것은 약간의 중복된 정보, 약간의 수정을 포함하며, 클래스와 멤버를 확장 DLL로부터 익스포트하기 위해 도움을 주는 보충 정보들을 제공한다. 

MORE INFORMATION 

확장 DLL 생성하기 

MFC Extension DLL 프로젝트를 생성하기 위해 AppWizard를 사용할 수 있으며, 그것은 자동으로 적절한 컴파일러와 링커 설정을 생성한다. 더 세부적인 정보를 원한다면 AppWizard entry in the MFC Encycopedia를 참조하라. 이는 Books Online이나 인쇄 문서에서 찾을 수 있다. 

만약 현재 프로젝트를 MFC Extension DLL로 바꾸고자 한다면, MFC의 _AFXDLL 버전을 사용하는 응용프로그램을 생성하는 표준 규칙을 보아라. 그리고 나서 다음 단계를 밟는다 : 

1. /D_AFXEXT를 컴파일러 플래그에 추갛나다. Project Settings 다이얼로그에서, C/C++ 탭을 선택한다. 그리고 나서 Preprocessor 카테고리를 선택한다. Preprocessor Definitions 항목에 _AFXEXT를 추가하는데, 각 아이템은 콤마(,)로 구분한다. 

2. /Gy 컴파일러 switch를 제거한다. Project Setting dialog에서, C/C++탭을 선택한다. 그리고 나서 C Language 카테고리를 선택한다. "Enable Function-Level Linking" 체크박스가 해제되었는지 확인하라. 이것은 클래스들을 익스포트하기 쉽게 만들어 줄 것이다. 왜냐하면 링커가 참조되지 않는 함수들을 제거하지 않을 것이기 때문이다. 만약 원래 프로젝트가 정적으로 MFC에 링크된 Regular DLL을 생성하는데 사용되었다면, /MT[d] 컴파일러 옵션을 /MD[d]로 바꿔라. 

3. LINK를 위해서 /DLL 옵션을 가진 익스포트 라이브러리를 생성하라(Build an export library with the /DLL option to LINK). 이것은 새로운 대상(target)을 생성할 때 설정될 것이며, Win32 Dynamic-Link Library를 대상 유형으로 식별한다. 

헤더파일 바꾸기 

확장 DLL의 목적은 보통 어떤 공통 기능을 사용하는 하나 이상의 응용프로그램에 그 기능을 익스포트하는 것이다. 이것은 클라이언트 응용프로그램에 이용가능한 클래스와 전역 함수를 익스포트하는 것으로 요약된다.  

이를 수행하기 위해 그 멤버 함수의 각각이 적절하게 import 나 export로 표시되게 하여야 한다. 이것은 특별한 선언을 요구한다 :

__declspec(dllexport)와 __declspec(dllimport)이다. 클래스들이 클라이언트 응용프로그램에 사용될 때, 그것들을 __declspec(dllimport)로 선언되어야 한다. 확장 DLL 자체가 생성되고 있을 때, 그것들은 __declspec(dllexport)로 선언되어야 한다. 부가적으로 그 함수들은 반드시 실제적으로 익스포트되어야만 한다. 그래야 클라이언트 프로그램들은 로드 시간에 그것들을 묶을(bind) 수 있다. 

전체 클래스를 익스포트하기 위해서는 AFX_EXT_CLASS를 클래스 선언에서 사용한다. 이 매크로는 프레임워크에 의해 _AFXDLL과 _AFXEXT가 정의되었을 때 __declspec(dllexport)로 정의되어 있다. 그러나 _AFXEXT가 정의되지 않았을 때는 __declspec(dllimport)로 정의되어 있다. 위에 기술되어 있듯이 _AFXEXT는 단지 확장 DLL을 생성할 때만 정의된다. 

예를 들어    

class AFX_FXT_CLASS CExampleExport : public CObject
{   ... class definition    ...}; 

이 예제는 전체 클래스를 익스포트한다. 

전체 클래스를 익스포트하지 않을 경우 

가끔 클래스에서 개별적으로 필요한 멤버만을 익스포트하고자 하는 경우가 있다. MFC 기술문서 #33은 "Ordinals and class _export and naming"이라는 섹션에서 이것에 대한 약간의 이유를 논의했다. 예를 들어 CDialog 상속한 클래스를 익스포트하고 있다면, 단지 생성자와 DoModal 호출만을 익스포트할 필요가 있을 것이다. 이 멤버들을 DLL의 DEF 파일을 사용해 익스포트할 수 있다. 그러나 AFX_EXT_CLASS를 사용해 익스포트하고자 하는 개별 멤버들에 대해 같은 방식으로 익스포트할 수도 있다. 

예를 들어
    class CExampleDialog : public CDialog
    {
    public :
        AFX_EXT_CLASS CExampleDialog();
        AFX_EXT_CLASS int DoModal();
        ...
        // 나머지 클래스 정의
        ...
}; 

이렇게 할 때, 클래스의 모든 멤버들을 더이상 익스포트하고 있을 필요가 없다는 진실에 기인한 부차적인 문제들로 뛰어들게 될 것이다. 그 문제는 MFC 매크로들이 작동하는 방식에 존재한다. MFC helper 매크로들 중 일부는 실제적으로 데이터 멤버들을 선언하거나 정의한다. 결국 이러한 데이터 멤버들 또한 DLL로부터 익스포트될 필요가 있을 것이다. 

예를 들어 확장 DLL을 생성할 때, DECALRE_DYNAMIC 매크로는 다음과 같이 정의된다 : 
    #define DECLARE_DYNAMIC(class_name) \
    protected : \
        static CRuntimeClass* PASCAL _GetBaseClass(); \
    public : \
        static AFX_DATA CRuntimeClass class##class_nam; \
        virtual CRuntimeClass* GetRuntimeClass() const; \ 

"static AFX_DATA"로 시작하는 라인은 클래스 내부의 정적 개체를 선언하고 있다. 이 클래스를 올바르게 익스포트하기 위해서는 runtime 정보에 클라이언트의 .EXE로부터 접근해야 하는데, 이 정적 개체를 익스포트할 필요가 있다. 정적 개체는 AFX_DATA 변경자와 함께 선언되기 때문에, DLL을 생성할 때는 AFX_DATA를 __declspec(dllexport)로 정의할 필요가 있을 뿐이며, 클라이언트의 실행가능한 파일을 생성할 때는 __declspec(dllimport)로 정의할 필요가 있다. 위에서 언급했듯이 AFX_EXT_CLASS는 이미 이러한 방식으로 정의되어 있다. 그래서 단지 AFX_DATA를 클래스 정의 부근에서 AFX_EXT_CLASS와 같이 재정의할 필요가 있는 것이다.  

예를 들어 :
    #undef AFX_DATA
    #define AFXDATA AFX_EXT_CLASS
    class CExampleView : public CView
   {
        DECLARE_DYNAMIC();
        // ... 클래스 정의 ...
};

#undef AFX_DATA
#define AFX_DATA 

MFC always uses the AFX_DATA symbol on data items it defines within its macros, 그래서 이 기법은 그러한 모든 상황에서 작동할 것이다. 예를 들어 DECLARE_MESSAGE_MAP에 대해서도 작동할 것이다. 

주의 : 만약 클래스의 선택된 멤버가 아니라 전체 클래스를 익스포트하고 있다면, 정적 멤버들은 자동적으로 익스포트된다. 

_AFXEXT의 한계 

당신이 확장 DLLs의 다중 레이어를 사용하고 있지 않는 한 _AFXEXT 전처리 심볼을 확장 DLLs에 사용할 수 있다. 만약 MFC 클래스로부터 상속된 당신 자신의 확장 DLL에서의 클래스로부터 호출하거나 상속하는 확장 DLLs를 가지고 있다면, 애매함을 피하기 위해서 반드시 자신만의 전처리 심볼을 사용해야만 한다(원문 : If you have extension DLLs that call or derive from classes in your own extension DLLs, which then derive from the MFC classes , you must use your own preprocessor symbol to avoid ambiguity). 

문제는 Win32에서이다. 만약 DLL로부터 익스포트된 데이터가 있다면, 당신은 반드시 명시적으로 그것을 __declspec(dllexport)로 선언해야만 한다. 그리고 DLL로부터 임포트되는 데이터가 있다면 그것을 __declspec(dllimport)로 선언해야만 한다. _AFXEXT를 정의할 때, MFC 헤더는 AFX_EXT_CLASS가 올바르게 정의되어 있다고 간주한다. 당신이 다중 레이어를 사용할 때, FAX_EXT_CLASS같은 하나의 심볼은 적절하지 않다. 왜냐하면 확장 DLL은 새로운 클래스들을 익스포트하고 있을 뿐 아니라 다른 확장 DLL로부터 다른 클래스들을 임포트하고 있기 때문이다. 

이 문제를 다루기 위해서는 특별한 전처리 심볼을 사용하는데, 이는 당신이 DLL 자체를 생성하는지, 단지 DLL을 사용하는지를 지시한다. 예를 들어 두 개의 확장 DLL을 가정해 보자(A.DLL, B.DLL). 그것들은 각각 A.H와 B.H를 익스포트한다. B.DLL은 A.DLL로부터 클래스들을 사용한다. 헤더 파일은 다음과 같이 보일 것이다 : 

    // A.H
    #ifdef A_IMPL
        #define CLASS_DECL_A __declspec(dllexport)
    #else
        #define CLASS_DECL_A __declspec(dllimport)
    #endif 

    class CLASS_DECL_A CExampleA : public CObject
    {   ... 클래스 정의 ...}; 

    // B.H
    #ifdef B_IMPL
        #define CLASS_DECL_B __declspec(dllexport)
    #else
        #define CALSS_DECL_B __declspec(dllimport)
    #endif

     class CLASS_DECL_B CExampleB : public CExampleA
    {   ... 클래스 정의 ...}; 

A.DLL이 생성될 때, 그것은 /D A_IMPL과 함게 생성되며, B.DLL이 생성될 때, 그것은 /D B_IMPL과 함께 생성된다. 각 DLL에 분리된 심볼을 사용함으로써, B.DLL을 생성할 때 CExampleB가 익스포트되고 CExampleA가 임포트되었음을 보증한다. CExampleA는 A.DLL이 생성될 때 익스포트되며, B.DLL이나 다른 클라이언트에 의해 사용될 때 임포트된다. 이러한 유형의 레이어화는 AFX_EXT_CLASS와 _AFXEXT 전처리 심볼이 사용되어 생성될 때는 수행될 수 없다. 위에 기술된 이러한 기법은 MFC 자체가 OLE, Database, Network 확장 DLL들을 생성할 때 사용하는 것과는 다른 방식으로 이 문제를 해결한다. 

전체 클래스를 익스포트하지 않을 경우 

전체 클래스를 익스포트하고 있지 않을 경우 당신은 다시 한 번 특별한 주의를 기울여야만 할 것이다. MFC 매크로에 의해 생성된 필요한 데이터 아이템이 정확하게 익스포트되는지를 보증해야만 한다. 이것은 AFX_DATA를 당신이 지정한 클래스의 매크로로 재정의 함으로써 수행된다. 이것은 전체 클래스를 익스포트하고 있지 않은 경우 언제나 수행되어야 한다. 

예를 들어 

    // A.H
    #ifdef A_IMPL
        #define CALSS_DECL_A __declspec(dllexport)
    #else
        #define CLASS_DECL_A __declspec(dllimport)
   #endif

        #undef AFX_DATA
   #define AFX_DATA CLASS_DECL_A 

    class CExampleA : public CObject
    {
        DECLARE_DYNAMIC()
        CLASS_DECL_A int SomeFunction();
        // ...  클래스 정의 ...
};

 #undef AFX_DATA
#define AFX_DATA 

REFERENCES

MFC TechNote #33
DLLHUSK 샘플 

C++ Language 레퍼런스 : "Using dllimport and dllexport in C++"의 "Extended Storage-Class Attributes"안의 Appendix B Microsoft-Specific Modifiers 

    

'Study > C++' 카테고리의 다른 글

pascall stdcall cdecl  (0) 2009.07.31
EC++ 2. define 대신 const, enum, inline 쓰기  (0) 2009.07.29
stl set  (0) 2009.07.23
bitarray 제작, 활용  (0) 2009.07.20
bitarray  (0) 2009.07.18
TAGS.

Comments