반응형

Static Linked Library, 정적 링크 라이브러리

컴파일시 실행파일에 라이브러리의 함수코드가 추가되어 작성되기 때문에 실행파일이 커지는 단점이 있으나 별도의 DLL이 필요없다.

VC++6에서는 New Projects에 Win32 Static Library로 작성하고 2005에서는 Win32 콘솔 응용프로그램에서 정적라이브러리(미리 컴파일된 헤더 해제)를 선택하여 작성한다.
명령줄에서 작성하려면 cl.exe /c /EHsc 파일명.cpp(/c 옵션은 링크없이 컴파일) 로 컴파일 하여 obj 파일 생성후 lib.exe ~.obj 으로 ~.lib 파일을 생성한다.

만들어진 LIB(~.lib파일)과 ~.h(header파일)을 작성하고자 하는 Project 생성후라이브러리는 Project->Setting->Library modules에서 ~.lib를 추가하고 헤더는 원하는 코드에 추가하여 빌드한다.

Dynamic Linked Libarary(DLL), 동적 링크 라이브러리

우리가 흔히 말하는 DLLs 이다. 컴파일시 실행파일에 라이브러리의 함수코드를 넣지 않고 DLL 이라는 파일에 따로 두어 모듈화를 통해 관리와 테스트가 용이하며 공유를 통해 코드의 낭비를 줄이고 다국어버전등의 리소스교체를 용이하게 한다.

New Projects에서 Win32 Dynamic-link library로 작성한다. 2005에서는 win32 응용프로그램작성에서 DLL 을 선택하면 된다.

Header파일을 공통적으로 사용하기 위한 예시

#ifdef _USRDLL
#define DLLFunction  __declspec(dllexport)
#elseif
#define DLLFunction  __declspec(dllimport)
#endif

#ifdef __cplusplus
extern “C” {
#endif

DLLFunction void somefunc();
#ifdef __cplusplus
}
#endif


~.DEF 파일 
예전에는 ~.def 파일을 통해 dll을 export 하였지만 VC++에서는 __declspec(dllexport)를 통해서 dll 의 함수들을 export한다. 반대로 import시에는 __declspec(dllimport)를 사용한다.

LIBRARY              "TESTDLL.DLL"
DESCRIPTION 'SIMPLEDLL Windows Dynamic Link Library'

EXPORTS
  somefunc   @1


Implicit Linkage, 암시적 링크
LoadLibrary를 사용하지 않는다. 따라서 프로그램이 실행될때 dll이 로드된다.
그러기 위해서는 Static Library와 같이 ~.lib파일과 ~.h 파일이 필요하고 실행시는 같은 폴더 또는 시스템폴더(dll을 찾는 정책에 따라)에 dll 파일이 존재하여야 한다. 없을시 찾을수 없다면 에러가 발생한다.
dll을 생성하고 나면 생성된 폴더에 lib 파일도 같이 생성되는데 Static Library와는 달리 함수의 코드가 없다. 다만 함수형이 정의되어 있을뿐이다. 실행파일의 implict section에서 실행시 로드되어야 할 dll을 추가하여 나중에 프로그램이 실행되면 dll이 로드된다.

Explicit Linkage,  명시적 링크
  LoadLibrary로 명시적으로 dll을 로드한다. GetProcAddress로 사용하고자 하는 함수의 주소를 얻어서 사용한다. dll이 load되는 시점은 프로그램이 실행되는 순간이 아니고 LoadLibrary가 호출되는 시점이다.

Delay Linkage, 지연 링크
빌드시 옵션에 /delayload:dllname 으로 작성한다.
로드되는 시점은 프로그램 실행시가 아니라 함수가 사용되는 시점이 된다.
/delay 옵션도 참고할것

확장DLL
MFC 클래스에서 파생된 재사용 가능한 클래스를 구현한 DLL. 따라서 MFC로 만든 응용 프로그램에서만 확장 DLL이 사용되어 질수 있다.
이 DLL로부터 익스포트할 클래스에 대해서 여러분이 해줄일은 클래스 선언문에 AFX_EXT_CLASS란 매크로만 붙여준다.

예시

Class AFX_EXT_CLASS CDllDialog : public CDialog
{
...
}

반응형

DLL - 함수 호출시 __declspec(dllimport)의 사용

Level of Difficulty 1 2 3

DLL을 통해서 API를 제공하기 위해서 DLL(PE)의 Export Table에 함수를 나타내도록 __declspec(dllexport)지시자를 사용합니다.
반면에 함수를 호출하는 쪽에서는 특별히 __declspec(dllimport)으로 정의된 함수원형을 사용하지 않아도 DLL에서 제공된 함수를 사용 할 수 있습니다.


결론 부터 말씀드리면 DLL에서 제공되는 함수를 사용할 때에는 함수선언 앞에 __declspec(dllimport) 지시자를 사용할 것을 강력히 권합니다.

대부분 DLL의 API는 헤더에서 다음과 같이 선언 되어있습니다.

// API Export/Import Header

#ifdef CALLEE_EXPORTS
#define CALLEE_API __declspec(dllexport)
#else
#define CALLEE_API __declspec(dllimport)
#endif

CALLEE_API DWORD __stdcall no_no_stdcall(DWORD); // WINAPI
CALLEE_API DWORD __cdecl no_no_cdecl(DWORD); // CDECL
CALLEE_API DWORD __fastcall no_no_fastcall(DWORD);
위 헤더의 핵심은 동일한 헤더를 공유해서 사용하게 되는 것입니다.
(특히 ThirdParty 벤더에서 제공되는 API모듈이라면 더욱더 그렇겠죠)

즉, Preprocessor definitions에 CALLEE_EXPORTS가 선언되어 있지 않다면 헤더는 함수를 __declspec(dllimport)형식으로 정의하게 되겠죠.
(위의 API 제공하는 벤더는 프로젝트 설정에 CALLEE_EXPORTS를 정의해서 컴파일하고 있을 것입니다.)

위의 예는 가장 확실하게 __declspec(dllimport)를 선언해서 사용할 수 있는 경우이고 헤더가 중복해서 관리해야하는 어려움도 필요없는 최적의 케이스 입니다.

하지만 내부 프로젝트 모듈이건 다른 벤더의 모듈이던 이렇게 사용되지 않고 다음과 같이 import용으로 별도로 정의되어서 사용되는 경우가 많이 있습니다.

// API Import Header

DWORD __stdcall no_no_stdcall(DWORD); // WINAPI
DWORD __cdecl no_no_cdecl(DWORD); // CDECL
DWORD __fastcall no_no_fastcall(DWORD);
이 경우는 헤더 관리의 복잡성을 떠나서 별로 좋지 않은 케이스 입니다.
(최소한 다른 DLL에 선언되어 있는 함수를 호출하는 경우에...)

DWORD __stdcall no_no_stdcall(DWORD);
__declspec(dllimport) DWORD __stdcall no_no_stdcall(DWORD);
DLL을 static link 할 경우에 위의 두가지 경우 모두 정상적으로 수행됩니다.
그렇다면 차이점이 무엇일까요?


DWORD __stdcall no_no_stdcall(DWORD);
위와 같이 선언된 상태에서 링커는 DLL에서 생성된 빈 Lib에서 가르키고 있는 Stub을 직접 호출하도록 합니다. 왜냐하면 컴파일러는 no_no_stdcall() 이라는 함수가 내부에 선언된 함수인지 다른 모듈(DLL)에 선언되어 있는 함수인지를 구별 할 수 없기 때문입니다.

그렇다면 이 경우 실제 해당 DLL의 함수가 어떻게 호출 될까요?
DLL생성시 함께 생성되는 빈 Lib의 Stub내부에는 실제 모듈(DLL)의 함수로 점프할 수 있는 코드가 컴파일 타임에 이미 생성되서 Stub안에 존재하기 때문에 가능한 것입니다.
즉 해당 Stub을 내부 함수처럼 호출하면 그곳에서 실제 모듈(DLL)의 함수를 가르키는 IAT(Import Address Table)를 참조해서 실제 API가 점프하게 되는 코드가 수행되는 것입니다.


정리해서 말씀드리면, DLL의 함수를 호출할 경우에 아래와 같은 두가지 형태의 코드가 생성될 수 있습니다.

// 1. 함수원형 앞에 __declspec(dllimport) 지시자를 사용할 경우
0xXXXXXXXX : CALL DWORD PTR [0x56780000]; // CALL DWORD PTR[_imp__MyFunc];


// 2. 지시자 없이 그냥 사용할 경우
0xXXXXXXXX : CALL 0x12340000;
0x12340000 : JMP DWORD PTR[0x56780000];


(0x5678000 : 실제함수주소를 가르키고있는 IAT의 주소)
위에서 설명드렸듯이 당연히 1번의 경우 DLL 함수호출시 훨씬 효과적인 것을 알 수 있습니다.
2번의 경우에는 JMP를 위한 추가적인 5Byte의 코드가 더 필요하게 되는 경우이므로 비효율적이겠죠.

__declspec(dllimport)지시자를 사용함으로서 1번의 코드형태로 DLL함수가 호출 되는 것을 알았습니다.
좀 더 설명드리면 컴파일러는 __declspec(dllimport) 지시자를 보았을 경우에 실제함수 MyFunc을 참조하지 않고 __imp__MyFunc을 참조하는 코드를 만들어 놓습니다.
그후에 링커는 __imp_ Prefix를 보고 실제 DLL에 존재하는 함수를 직접 호출하는 것임을 알게 되는 것입니다.

즉, 컴파일리가 링커에게 DLL에 존재하는 함수를 호출한다는 것을 알려주기 위해 __imp_ Prefix를 함수앞에 추가해 주는 것입니다.


: error LNK2001: unresolved external symbol __imp__MyFunc

: fatal error LNK1120: 1 unresolved externals
위의 에러를 보면 실제 함수앞에 __imp__ Prefix가 존재하는 것을 알 수 있습니다.

그런데 이 경우에 링크에러가 발생했는데 링크에러가 발생하지 않으려면 빈 Lib안에 __imp__MyFunc에 대한 Stub이 존재하고 있어야 겠죠.
DLL생성시 만들어진 Lib의 내용을 보면 MyFunc, __imp__MyFunc에대한 두가지 Stub이 모두 존재하고 있음을 볼 수 있습니다.

----------------------------------------------------------------------------------
// 확장DLL사용시 참고 사항

AFX_EXT_CLASS는  요렇게 정의되어 있습니다..


#ifdef _AFXEXT

    #define AFX_EXT_CLASS    __declspec(dllexport)

#else

    #define AFX_EXT_CALSS    __declspec(dllimport)

#endif // _AFXEXT


이 선언은 Extended DLL에서 export, 다른 프로젝트에서는 import로 해석됩니다...



그래서 결론만 말하면...........


#ifdef _MYEXT

    #define MY_EXT_CLASS    __declspec(dllexport)

#else

    #define MY_EXT_CLASS    __declspec(dllimport)

#endif


이렇게 헤더파일 처음에 정의하고,


class AFX_EXT_CALSS AAA

{

};



class MY_EXT_CLASS AAA

{

};


이렇게 선언합니다.......


그리고 DLL의 프로젝트 Setting에서 _MYEXT를 선언합니다.

(Project Menu의 Setting 다이얼로그에서 C++ tab의 Preprocessor

definitions에 _MYEXT를 추가합니다.)



이렇게 하면 class AAA는 DLL에서는 export, 다른 곳에서는 import가

됩니다.....

반응형

__declspec(extended-decl-modifier-seq) : 데이터나 모듈의 저장형태를 지정.

 

align(#)

    자주 사용되는 데이터를 특정 프로세서의 캐시라인 크기로 정렬해 캐시성능을 높인다. #값은

    1부터 8192 (바이트) 까지 2의 거듭제곱 수 즉 2, 4, 8, 16, 32, 64.

    Visual C++은 기본적으로 데이터 크기에 따라 경계정렬을 한다. 가령 4바이트 정수는 4바이트

    경계, 8바이트 더블은 8바이트 경계에 정렬되며, 클래스나 구조체의 데이터들은 현재 패킹 설정

    (#pragma pack(n) 또는 /Zp 옵션)에 따라 클래스나 구조체 내에 최소크기로 정렬된다.

    구조체, 공용체, 클래스, 변수에 대해 사용할 수 있지만, 스택변수에는 사용불가.

 

    #define CACHE_LINE      32

    #define CACHE_ALIGN    __declspec(align(CACHE_LINE))

    struct CACHE_ALIGN S1

    {

        int a, b, c, d;                // 이 타입의 모든 인스턴스는 32바이트 경계에서 시작해야하며,

    };                                    // sizeof(struct S1)는 32. 뒤따르는 16바이트는 채우기용

   

    __declspec(align(8)) struct S2

    {

        int a, b, c, d;               // sizeof(struct S2)는 16

    };

 

    struct S3

    {

        int a;                          // 4바이트 뒤를 잇는 28 바이트가 채우기용으로 쓰이며,

        struct S1 s1;              // s1은 오프셋 32에서 시작. sizeof(struct S3)는 64

    };

 

    // 배열의 시작주소는 32바이트로 정렬되지만, 각 배열멤버는 그렇지 않다.

    CACHE_ALIGN int array[128];

    // 배열의 각 멤버를 정렬하는 예

    typedef CACHE_ALIGN struct { int a; } S4;

    S4 array[10];

 

    TLS(Thread Local Storage : 정적변수가 스레드마다 다른 값을 갖도록 윈도우에서 제공

    하는 메모리 영역)에서 데이터 정렬

    __declspec(thread) 속성으로 만들어지고, 이미지에서 .tls 섹션에 놓인 TLS는 일반적인 정적

    데이터와 똑같이 정렬된다. 운영체제는 .tls 섹션 크기만큼 데이터를 할당하고 .tls 섹션 정렬

    속성을 고려해 TLS 데이터를 만든다.

 

    아래는 TLS에 정렬된 데이터를 놓는 다양한 방법들이다.

    __declspec(thread) __declspec(align(32)) int a;    // TLS에 정렬된 정수 놓기

 

    // 정렬된 구조체를 정의하고 구조체 타입의 변수를 TLS에 놓기

    __declspec(thread) __declspec(align(32)) struct F1 { int a; int b; } a;

 

    이터 패킹과 함께 정렬하는 방식

    /Zp 옵션과 #pragma pack(n)은 구조체와 공용체 멤버 데이터를 패킹하는 효과가 있다.

    /Zp 옵션과 __declspec(align(#))을 같이 사용한 예와 각 멤버의 오프셋

 

    struct S{                                // 변수        /Zp1    /Zp2    /Zp4    /Zp8

        char a;                               //   a            0          0         0         0

        short b;                              //   b           1          2         2         2

        double c;                            //   c            3          4         4         4

        CACHE_ALIGN double d;    //   d           32        32       32       32

        char e;                               //   e           40        40       40        40

        double f;                            //    f           41        42       44        48

    };                                           //sizeof(S)  64        64       64        64

 

    따라서 객체의 오프셋은 객체가 __declspec(align(#)) 속성을 갖지 않으면, 이전 객체의

    오프셋과 현재 패킹 설정에 따라 결정되며, 그렇지 않으면 이전 객체의 오프셋과 객체의

    __declspec(align(#)) 값에 따라 결정된다.



allocate(segname)

    데이터가 할당될 데이터 세그먼트 지정. 가능한 segname pragma는

    code_seg, const_seg, data_seg, init_seg, section

 

    ㄱ. #pragma code_seg( ["섹션이름"[, "섹션클래스"]] )

    함수가 할당될 기본 코드섹션 지정. 클래스와 섹션이름은 선택적이며, 섹션이름이 없는 경우

    컴파일 시작시 값으로 재설정된다

 

    ㄴ. #pragma const_seg( ["섹션이름"[, "섹션클래스"]] )

    상수데이터의 기본 섹션 지정. 모든 상수데이터를 하나의 읽기전용 섹션에 넣을 때 사용

    #pragma const_seg("MY_DATA")    // 이후의 모든 상수데이터는 MY_DATA에 놓여짐

    const_seg pragma로 할당된 데이터는 어떤 위치정보도 유지하지 않는다.

    "섹션클래스"는 Visual C+ 2.0 이전 버전과의 호환을 위해 포함되며, 이제는 무시된다

 

    ㄷ. #pragma data_seg( ["섹션이름"[, "섹션클래스"]] )

    초기화된 데이터의 기본 섹션 지정

    #pragma data_seg("MY_DATA")    // 이후의 모든 데이터는 MY_DATA에 놓여짐

    data_seg pragma로 할당된 데이터는 어떤 위치정보도 유지하지 않는다.

    "섹션클래스"는 Visual C++ 2.0 이전 버전과의 호환을 위해 포함되며, 이제는 무시된다

 

    ㄹ. #pragma init_seg( {compiler | lib | user | "섹션이름"[, 함수이름]} )

    시작코드가 실행되는 순서에 영향을 주는 키워드나 코드섹션 지정. 전역 정적 객체의 초기화는

    실행코드를 포함할 수 있으므로, 언제 객체가 생성되는지를 정의하는 키워드를 지정해야한다.

    특히 이는 초기화가 필요한 라이브러리 또는 DLL에서 init_seg pragma 사용시 중요하다.

    compiler

             Microsoft C 런타임 라이브러리 초기화를 위해 예약됨. 이 그룹의 객체가 먼저 생성됨

    lib

             타사의 클래스 라이브러리 초기화. 이 그룹의 객체는 compiler 그룹 바로 뒤에 생성됨

    user

             그 외. 이 그룹의 객체는 마지막에 생성됨

    섹션이름

             초기화 섹션을 명시적으로 지정. 사용자지정된 섹션이름의 객체는 암묵적으로 생성되지

             않지만 그 주소는 명명된 섹션에 놓여진다.

             섹션이름은 pragma 이후의 모듈에 선언된 전역 객체를 생성할 도우미 함수에 대한

             포인터를 포함한다.

    함수이름

             프로그램 종료시 atexit 대신에 호출될 함수 지정. 또한 이 도우미 함수는 전역객체용

             파괴자에 대한 포인터를 갖고 atexit 또한 호출한다. 다음처럼 함수 식별자를 지정하면,

             int myexit( void (__cdecl *pf) (void) )

             C 런타임 라이브러리인 atexit 대신 사용자 함수가 호출된다. 이로써 객체를 파괴할 준비

             가 됐을 때 호출되어야 할 파괴자 목록을 만들 수 있다.

 

    초기화를 연기해야 한다면(DLL의 경우에), 섹션이름을 명시적으로 지정할 수 있다.

    atexit 대체 식별자는 인용부호가 없으며, 여전히 사용자 객체는 XXX_seg pragma로 정의된

    섹션에 놓일 수 있다.

    모듈에 선언된 객체는 C 런타임에 의해 자동으로 초기화되지 않으므로, 직접 해주어야 한다.

    #include <stdio.h>

    // init_seg pragma는 허용되지 않은 섹션이름을 사용하므로 C4075 경고(초기자가 허용되지

    // 않은 영역에 놓인다)를 발생시키는데 이를 무시

    #pragma warning(disable : 4075)

 

    typedef void (__cdecl *PF)(void);

    int    cxpf = 0;     // 호출해야 할 파괴자 수

    PF    pfx[200];    // 파괴자에 대한 포인터. 오버플로우에 주의!

 

    int myexit(PF pf){

        pfx[cxpf++] = pf;

        return 0;

    }

 

    struct A{

        A(){ puts("A()"); }

        ~A(){ puts("~A()"; }

    }

 

    // pragma init_seg 이전이므로, 생성자와 파괴자는 CRT 시작코드에서 호출

    A aaaa;

 

    // 여기서 순서가 중요하며, 섹션이름은 8자 이하다.

    // $ 이전에 동일한 이름의 섹션은 하나의 섹션으로 합쳐진다. 합쳐지는 순서는 $ 이후 문자를

    // 정렬함으로써 결정된다.

    // InitSegStart와 InitSegEnd는 경계를 설정하는데 사용되며, 이로써 초기화를 위해 호출할

    // 실제 함수를 찾을 수 있다.

 

    #pragma data_seg(".mine$a")

    PF InitSegStart = (PF)1;

    #pragma data_seg(".mine$z")

    PF InitSegEnd = (PF)1;

    #pragma data_seg()

 

    // 0값 비교는 중요하다.

    // 지금부터, 각 섹션은 256바이트다. 각 섹션이 통합될 때, 각각은 0으로 채워진다.

    // 섹션이 256바이트인것은 보장할 수 없지만, 0으로 채워지는 건 보장할 수 있다.

 

    void InitializeObjects(){

        PF *x = &InitSegStart;

        for(++x; x<&InitSegEnd; ++x){

            if(*x) (*x)();

        }

    }

 

    void DestroyObjects(){

        while(cxpf>0){

            --cxpf;

            (pfx[cxpf])();

        }

    }

 

    #pragma init_seg(".mine$m", myexit)

 

    // 지금부터 생성자와 파괴자를 호출한다

    A bbbb;

    A cccc;

 

    int main(){

        InitializeObjects();

 

        // 여기서 잡다한 작업을 한다

 

        DestroyObjects();

        return 0;

    }

dllimport, dllexport

    DLL에서(로) 함수, 데이터, 객체를 가져오는(내보내는) 속성. 함수를 dllexport로 선언하면,

    적어도 노출된 함수와 관련된 .DEF (module-definition) 파일이 필요없어진다.

    또한 dllexport__export 키워드를 대체한다.

 

    __declspec( dllimport ) int i;

    __declspec( dllexport ) void func();

 

    좀 더 보기좋게 다음과 같이 매크로를 쓸 수도 있겠다.

 

    #define DllImport __declspec( dllimport )

    #define DllExport __declspec( dllexport )

 

    DLL 인터페이스에 포함된 모든 선언은 선언을 내포하고 있는 dllimport 또는 정의를 내포하고

    있는 dllexport이며, externdllexport를 같이 사용해 선언을 강제화할 수도 있다.

 

    DllImport int func(){ return 1; }    // 에러 : dllimport는 정의할 수 없다

    DllImport int i = 10;                       // 요것도 정의이므로 에러

    DllExport int i = 10;                       // 요건 OK

 

    extern DllImport int k;                   // 둘다 OK

    DllImport int j;

 

    static __declspec( dllimport ) int l;          // 에러. extern으로 선언되지 않음

    void func()

    {

        static __declspec( dllimport ) int s;      // 에러. extern으로 선언되지 않음

        __declspec( dllimport ) int m;              // OK. 이것은 선언

        __declspec( dllexport ) int n;               // 에러. 지역범위에서 외부정의를 포함

        extern __declspec( dllimport ) int i;     // OK. 이것은 선언

        extern __declspec( dllexport ) int k;    // OK. extern은 선언을 포함

        __declspec( dllexport ) int x = 5;         // 에러. 지역범위에서 외부정의를 포함

    }

 

    라인함수로 선언하기

 

    dllexport 함수를 인라인으로 선언하면, 모듈이 그 함수를 참조하던말던 함수는 항상 초기화

    되고 익스포트된다. 함수는 다른 프로그램에 의해 임포트된다고 가정한다.

    dllimport 함수를 인라인으로 선언하면, 그 함수는 확장될 수 있지만 (/Ob가 지정돼 있을 때)

    절대 초기화되지는 않는다. 특히 인라인 임포트 함수의 주소를 가지면, DLL 내의 함수 주소가

    리턴된다. 이는 인라인이 아닌 임포트 함수의 주소를 가져오는 것과 같은 방식이다.

    이 규칙은 클래스 안에서 정의된 인라인 함수에 적용된다. 게다가, 인라인 함수 내 정적 지역

    데이터와 문자열은 마치 같은 프로그램 (즉, DLL 인터페이스가 없는 실행 파일) 안에 있는 것

    처럼 DLL과 클라이언트 사이에 동일성을 유지한다.

    임포트된 인라인 함수는 주의깊게 제공해야한다. 가령 DLL을 업데이트할 때, 클라이언트가

    바뀐 버전의 DLL을 사용할 거라고 가정하지 않는다. 적절한 버전의 DLL을 로딩하려면, DLL

    클라이언트 또한 재빌드해야한다.

 

    반 규칙과 제한사항

 

    ■ 함수나 객체를 dllimportdllexport 없이 선언하면 이는 DLL 인터페이스 일부로 간주되

    지 않으므로, 그들의 정의는 선언한 모듈이나 같은 프로그램 안의 다른 모듈에 있어야한다. 함수

    나 객체를 DLL 인터페이스로 만들려면 다른 모듈에서 그 정의를 dllexport로 선언해야하며, 그

    렇지 않을 경우 링커에러가 발생한다.

    함수나 객체를 dllexport로 선언하면, 그 정의는 같은 프로그램 모듈에 있어야하며, 그렇지 않

    을 경우 링커에러가 발생한다.

 

    ■ 프로그램 내 단일 모듈이 같은 함수나 객체에 대해 dllimport와 dllexport 2개의 선언을 가지

    면, dllexport 속성이 dllimport 속성보다 우선한다. 하지만, 컴파일러 경고는 발생한다.

 

    __declspec( dllimport ) int i;

    __declspec( dllexport ) int i;    // 경고; 불일치; dllexport가 우선한다

 

    ■ C++에서는 전역 포인터 데이터 또는 정적 지역 포인터 데이터를 dllimport 데이터 객체의

    주소로 초기화할 수 있다. 하지만 C에서는 에러다. 또한, 정적 지역 포인터 함수를

    dllimport 함수 주소로 초기화할 수 있다. C에서는 함수 주소가 아닌 DLL 임포트 청크 (함수에

    대한 제어를 전송하는 코드 조각) 의 주소로 설정한다.

 

    __declspec( dllimport ) void func1( void );

    __declspec( dllimport ) int i;

 

    int *pi = &i;                                       // C에서는 에러

    static void ( *pf )( void ) = &func1;    // C에서는 청크 주소, C++에선 함수 주소

 

    void func2()

    {

        static int *pi = &i;                              // C에서는 에러

        static void ( *pf )( void ) = &func1;    // C에서는 청크 주소, C++에선 함수 주소

    }

 

    하지만 dllexport 객체 선언을 가진 프로그램은 정의도 포함해야하므로, 전역 또는 지역 정적

    포인터 함수를 dllexport 함수의 주소로 초기화할 수 있다. 비슷하게, 전역 또는 지역 정적 포인

    터 데이터를 dllexport 데이터 객체의 주소로 초기화할 수도 있다. 예를 들어 다음 코드는 C와

    C++에서 모두 에러가 발생하지 않는다:

 

    __declspec( dllexport ) void func1( void );

    __declspec( dllexport ) int i;

 

    int *pi = &i;

    static void ( *pf )( void ) = &func1;

 

    void func2()

    {

        static int *pi = &i;

        static void ( *pf )( void ) = &func1;

    }

 

    래스에서 사용하기

 

    dllimportdllexport로 클래스를 선언하면, 하나의 클래스 전체에 적용된다. 즉 모든 멤버함

    수와 정적 데이터가 익스포트된다. 이렇게 익스포트 된 클래스를 익스포터블 클래스라 한다.

 

    #define  DllExport  __declspec( dllexport )

    class DllExport C

    {

        int i;

        virtual int func( void ){ return 1; }

    };

 

    참고 익스포터블 클래스의 멤버는 dllimportdllexport를 명시적으로 쓸 수 없다.

 

    dllexport 클래스는 모든 멤버함수와 정적 데이터멤버가 익스포트되므로, 프로그램 내에 정의

    를 포함해야한다. 그렇지않으면 링커에러가 발생한다. 단 순수가상함수의 경우 예외지만, 가상

    클래스 파괴자는 기본클래스 파괴자에 의해 항상 호출되므로, 순수가상파괴자는 항상 정의해야

    한다.

    이런 규칙들은 익스포터블 클래스가 아니여도 적용되므로, 클래스타입의 데이터나 클래스를 리

    턴하는 함수를 익스포트하려면, 클래스를 익스포트해야한다.

 

    dllimport 클래스는 모든 멤버함수와 정적 데이터멤버가 임포트된다. 비클래스 타입의

    dllimport/dllexport와는 달리, 정적 데이터멤버는 dllimport 클래스가 정의된 프로그램 내에

    정의할 수 없다.

 

    익스포터블 클래스의 모든 기본 클래스는 익스포트될 수 있어야한다. 아니면, 컴파일러 경고가

    발생한다.

이로써 dllexport 클래스는 dllimport 클래스에서 상속받을 수 있고, dllimport 클래스는 dllexport 클래스에서 상속받을 수 있다

+ Recent posts