DLL의 클래스 사용

프로그램을 짤때 DLL로 만들면 메모리가 절약 되기 때문에 DLL을 사용 할까? 그렇지 않다. 운영체제나 컴포넌트를 만드는게 아니면 메모리 절약 되는 경우는 없다.
DLL을 사용하는 최대의 이점은 모듈화가 아닐까 한다. DLL로 만들게 되면, 소스간의 종속성이 많이 줄어든다.

DLL에는 묵시적 링크와 명시적 링크가 있다.
묵시적 링크는 프로그램이 실행 됨과 동시에 DLL이 로딩된다. 명시적 링크는 우리가 원하는 시기에 로딩과 해제를 할 수 있다.

1. 묵시적 링크 (dll_test1.zip)

DLL을 만드는데 사용된 파일은 dllserver.h dllserver.cpp 파일이다.

*.dll을 만들때는 __declspec(dllexport) 선언해 외부로 노출시킬 클래스나 함수를 지정한다.
클래스 CDllServer를 외부로 알려주기 위해, 비쥬얼 스튜디오의 "구성 속성  - C/C++ - 전처리기"에 DLL_EXPORT를
선언해주는 방법이 첫 번째고 , 두 번째가 dllserver.cpp의 예와 같이 #define DLL_EXPORT를 넣는 방법이 있다.

__declspec(dllexport)를 선언하지 않으면 다음 에러가 뜬다.

f:\work\dll_test\dll_server\dllserver.cpp(5) : warning C4273: 'CDllServer::CDllServer' : dll 링크가 일치하지 않습니다.

 ~~~~~~~~~~~~~~~~~~~~~  중략  ~~~~~~~~~~~~~~~~~~~~~~~~~~~

속성 - 구성 속성 - 빌드 이벤트 - 빌드 후 이벤트 - 명령줄에 다음 명령을 추가하여 빌드가 끝난후 dll_server.dll을
자동으로 복사하도록 한다.

copy  "$(SolutionDir)$(OutDir)\$(TargetFileName)"  "..\dll_client\Debug\$(TargetFileName)"

 

1)DLL 소스

//dllserver.h

#ifndef        __DLLSERVER_H

#define        __DLLSERVER_H

 

#ifdef DLL_EXPORT

   #define DLL_DECLSPEC  __declspec(dllexport)   

#else

   #define DLL_DECLSPEC  __declspec(dllimport)   

#endif

 

 

class DLL_DECLSPEC    CDllServer

{

public:

    CDllServer();

    ~CDllServer();

    int        Add(int num);

    int        Sub(int num);

    int        GetNum();

 

private:

    int        m_iSum;

};

 

#endif

 

//dllserver.cpp

#define DLL_EXPORT

#include "dllserver.h"

 

CDllServer::CDllServer()

{

    m_iSum = 0;

}

 

//~~~~~~~~~~~~~~~~~~~~~~~~~~  중      략 ~~~~~~~~~~~~~~~~~~~~~~~~~~

 

 

 

2) DLL을 호출하는 프로그램

dllserver.h 파일이 포함될 때 DLL_EXPORT 선언이 안되어 있기 때문에, DLL_DECLSPEC은 __declspec(dllimport)로 변환된다.

함수 선언에서 __declspec(dllimport)은 사용하지 않아도 되지만,  이 키워드를 사용하면 컴파일러에서 보다 효율적인
코드를 생성할 수 있습니다.
그러나 실행 파일이 DLL의 공용 데이터 기호 및 개체에 액세스할 수 있도록 하려면 __declspec(dllimport)을 사용해야 한다.
묵시적 링크에서 클래스를 사용하는 경우, __declspec(dllimport)를 사용하지 않더라도 코드를 생성 할 수 있다.

묵시적 링크는 *.lib 파일을 포함 시켜야 한다. 라이브러리 파일을 포함하는 방법은 세가지가 있다.

라이브러리 파일 포함 시키는 방법

. DLL의 lib 파일을 프로젝트 구성 속성에 직접 포함시키는 방법.

. DLL 프로젝트와 DLL을 호출하는 프로젝트를 같은 솔루션에 있으면 자동으로 링크된다.

. #pragma comment(lib, "lib이름")  를 소스 코드에 포함 시키는 방법.

 

그리고 빌드.....

//dll_client.cpp

#include <iostream>

#include "../dll_server/dllserver.h"

 

void main()

{

    CDllServer test;

    test.Add(1);

    test.Add(2);

    test.Add(3);

    test.Sub(5);

 

    std::cout << "Value is " << test.GetNum() << std::endl;

    getchar();

}

 

2. 명시적 링크 dll_test2.zip

아래는 함수 호출 규약에 따른 스택 정리 방식이다.

호출 규약 파라미터 스  택 특  징
__cdecl 오른쪽 --> 왼쪽 호출한 곳 가변 인자 지원
__fastcall 오른쪽 --> 왼쪽 호출 당한 곳 파라미터 두 개를 ecx, edx를 통해서 전달하기 때문에 두 개 이하의 인자를 가진 함수에 대해서 빠름
__stdcall 오른쪽 --> 왼쪽 호출 당한 곳 Windows 표준 호출 규약
__thiscall 오른쪽 --> 왼쪽 호출 당한 곳 ecx를 통해서 this 포인터를 전달함

명시적 링크는 *.lib 파일 없이 LoadLibrary(), GetProcAddress(), FreeLibrary()를 이용해
DLL을 로딩, 함수 주소구하여 실행, DLL 해제순으로 실행한다.

클래스의 멤버 함수는 C로 작성된 함수와는 달리 name mangling이 되기 때문에 DUMPBIN 유틸리티로 export 된
함수이름으로 GetProcAddress()를 실행한다.

클래스 멤버 함수 실행 방법

헤더파일과 DLL 파일만 이용해 멤버 함수를 호출하는 방법은 세가지다.

    //첫번째 방법: 멤버 함수 Sub를 가상함수로 바꾼다.

    pTest->Sub(5);

 

    //두번째 방법

    *************  __thiscall 예약어가 적용되는 경우    *****************

    int (__thiscall *pSub)(int) = (int (__thiscall *)(int) )GetProcAddress(hInst, "?Sub@CDllServer@@QAEHH@Z");

    pSub(pTest, 5);

 

    //세번째 방법 (함수를 통하여 클래스의 인스턴스 전달)

    int (*pFnSub)(CDllServer*, int)  = (int (*)(CDllServer*, int)) GetProcAddress(hInst, "Sub"); //명시적   

    pFnSub(pTest, 5);

1. 멤버 함수 앞에 virtual을 선언하여 가상함수로 선언한다.
가상 함수로 선언할 경우 특별하게 해줄 일이 없다.

2. __thiscall을 사용하는 방법
클래스의 멤버 함수는 __thiscall을 사용한다. 하지만 .Net 2003에서 이 예약어는 지원되지 않는다.
.Net 2005나 .Net 2008에서 지원 되는지는 잘모르겠다.

CDllServer 클래스의 Sub 멤버 함수 원형은 int Sub(int num) 이다.
두군데의 __thiscall 호출 부분이 있는데, 복잡해 보이지만 원형과 비슷하다.
GetProcAddress()함수안의 "?Sub@CDllServer@@QAEHH@Z" 이름은 DUMPBIN 유틸리티나 링크 에러가 뜨면 출력된다.

함수 주소를 구해서 실행 할 때는 "함수주소( 오브젝트 주소, 함수로 넘길인자)" 식으로 멤버 함수를 실행한다.

3. 함수를 통하여 클래스의 인스턴스 전달

가상 함수를 사용하지 않을 때, 사용할 수 있는 방법으로 DLL의 클래스에 전역함수를 만들어 클래스의 오브젝트 주소와
인자를 넘긴다.

extern "C" DLL_DECLSPEC  int Sub(CDllServer * pObj, int num)

{

    return pObj->Sub(num);

}

 

DLL 모듈에서 CreateCDllServer()를 통해 오브젝트를 생성 하였다면, 오브젝트 해제의 경우도 DLL안에 만드는 것이 바람직하다.

오브젝트 해제 ===> fpRelease(pTest)

//DLL 모듈의 오브젝트 해제 함수

extern "C" DLL_DECLSPEC    void ReleaseCDllServer(CDllServer *& pObj)

{

    delete pObj;

    pObj = NULL;

}

C++이 나오기 이전에는 참조가 없었기 때문에 이중 포인터를 이용해 링크드 리스트를 구현 했었다.
이중 포인터를 포인터 참조로 변환하면 혼란이 덜 생길것이다.


참조:

Game Programming gems2 정보문화사 번역판
http://www.jiniya.net/lecture/techbox/callconv.html  (신영진님의 함수 호출 규약)

__declspec(dllimport)을 사용하여 응용 프로그램으로 가져오기(MSDN)