본문 바로가기

Development

[VC++] DLL 생성과 익스포트 섹션

[DLL 생성과 익스포트 섹션]

 

 

먼저 DLL 이 먼지 보자

DLL 이란  동적 링크 라이브러리 (Dynamic link library) 로서

일단 라이브러리 부터 보자.. 라이브러리란 우리가 항상 써오던

printf , scanf 등과 같은 입출력 함수들을 라이브러리라고 했다..

일단 DLL도 똑같은 라이브러리 축에 들어간다.. 하지만  

printf, scanf 와 같은 일반 #include <stdio.h> 을 써서 사용하는

함수들은 정적!!!! 정적!!! 정적!!! 라이브러리 이고  

우리가 말하는 DLL 은 동적!! 동적!!! 동적!! 라이브러리이다..


무슨 차이가 있느냐 -_-ㅋ...

정적라이브러리는  컴파일시에 해당 라이브러리 코드가 실행파일과  합쳐진다. (정적)

그래서 printf문을 쓰는 5개의 프로그램이 있다면


| 프로그램 1  (printf코드) |
| 프로그램 2  (printf코드) |
| 프로그램 3  (printf코드) |
| 프로그램 4  (printf코드) |
| 프로그램 5  (printf코드) |

와 같이 각각의 프로그램마다 printf 코드가 들어간다.. 왜냐 하면 컴파일시에

정적으로 해당 라이브러리 가 합체되어 컴파일 되기 때문이다.

그래서 똑같은 코드가 프로그램마다 합쳐저서 용량이 늘어나는 단점이 있다..


하지만  동적링크라이브러리는 다르다...

해당 라이브러리는 일단 메모리에 한번 올리고..

프로그램들이 그 라이브러리를 필요할때 메모리에 올라와 있는걸 가져다 쓰기만 하면된다.

만약 printf 코드를 동적라이브러리로 만들었다고 가정하고

그 동적라이브러리를 쓰는 5개의 프로그램이 있다면


-----------  메모리 ---------------
|  메모리에 printf 라이브러리 코드 딱 한번 로드 (주소 0x10001050) |
-----------  메모리 ---------------


| 프로그램 1 (0x10001050) 호출) |
| 프로그램 2 (0x10001050) 호출) |
| 프로그램 3 (0x10001050) 호출) |
| 프로그램 4 (0x10001050) 호출) |
| 프로그램 5 (0x10001050) 호출) |


해당 프로그램들은 덩치큰 라이브러리 코드들은 포함하고 있는게 아니고

그냥 해당 코드가 있는 메모리 주소를 불러오는 간단한 하고 조그만한 코드가 있으므로

프로그램 용량이 줄어들것이다..

---------------------------------------------------------------


글엄 간단한 DLL파일을 만들어 보자..

비주얼 c++ 6.0 에서 프로젝트 만들때  Win32 Dynamic-Link Library  라는 옵션을 주어서

만들면 된다. 아래에서 아주 간단한 함수 하나를 만들어보고  이 함수를 export 시켜서

일반 실행파일에서 쓰는 예제를 만들어 보자

------------------- test.cpp ----------------------

int Sum(int a, int b)
{
 return a+b;
}

---------------------------------------------------------------

다.. 만들었다... 죠낸 간다하다 -_-ㅋ 그렇다.

그냥 두 수를 인자로 받고 더 해서 결과를 리턴해주는 아주 죠낸 초 간단 함수이다.

이 함수를 dll파일로 만들었으니.. 이 함수는 동적링크 라이브러리 되시겠다... 

WinMain 필요없다.. main 도 필요없다.

그냥 우리가 필요한 함수만 딱 써놨다.. 그래도 된다.. -_-ㅋ


컴파일하면 test.dll 파일이 생성될것이다.


그렇다면 이 dll파일을 사용하는 일반 실행파일을 만들어보자..

일단 이 dll파일을  실행하려는 실행파일에 같은 폴더에 넣는다..

그리고 해당 실행파일이 이 dll을 불러오고.. 그리고 또  dll 않에 있는 함수를

사용하는 예제는 아래와 같다.


------------------ usedll.cpp ------------------

#include <windows.h>

#define DLL_NAME "test.dll" // 불러 오려는 dll 이름이다.
#define DLL_FUNC "Sum" // 불러 오려는 함수 이름이다.


void Error(LPCTSTR str);
void Printf(LPCTSTR str);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst,LPSTR lpCmdLine,int nShowCmd)
{
 HMODULE hModule;
 LPVOID lpFunc = NULL;
 TCHAR buf[512];

 hModule = LoadLibrary(DLL_NAME);

 if(!hModule)
 {
  Error("LoadLibrary Error");
 }
 else
 {
  wsprintf(buf,"hModule = 0x%8x\n",hModule);
  Printf(buf);
 }

 lpFunc = GetProcAddress(hModule,DLL_FUNC);

 if(!lpFunc)
 {
  Error("GetProceAddress Error");
 }
 else
 {
  wsprintf(buf,"lpFunc = 0x%8x\n",lpFunc);
  Printf(buf);
 }

 return 0;
}

void Error(LPCTSTR str)
{
 MessageBox(HWND_DESKTOP,str,"ERROR",MB_OK);
 ExitProcess(1);
}

void Printf(LPCTSTR str)
{
 MessageBox(HWND_DESKTOP,str,"OK",MB_OK);
}

---------------------------------------------------------------


우의 코드는 실제로 함수를 사용하지 않고 그냥 dll을 로드하고 그 함수 주소를 얻어오는 코드인데

일단 함수 주소만 알면 언제든지 불러올수 있으므로 함수 주소를 얻는 코드까지만 작성했다.

이상이 없다면 모듈주소와 함수주소가 출력될것이다.

하지만 안타깝게도 위의 실행파일을 실행하면 DLL 파일은 찾을수 있지만

함수 주소는 찾을 수 없다는 에러를 발생시킨다......

그 이유는 .... 그 이유는... 그 이유는.....

dll 않에서 만든 함수는 외부로 수출???(export) 되야 하는 것이다.. 이것들은 특별하기 때문에

컴파일시에 이 함수는 외부로 링크될 함수야!!!!!! 라고 알려줘야 한ㄷ고 한다.......... 우리는 그 작업을 안한 것이다. -_-;;;


콘솔창으로 들어가서 해당 dll 파일이 있는 위치로 이동후

dumpbin /EXPORTS test.dll      <-- 라고 쳐보자... 이 명령어는 해당 dll 파일에서 export (수출하는 함수?) 리스트를 보여달라고 하는것이다.

결과 화면을 보면 수출하는 함수가 없다고 뜰 것이다...


그러면 export를 수출 하는 두가지 방법이 있다고 하는데.. 그 방법을 살펴보자

첫번째 방법 : 모듈 정의 파일 사용 (.def 파일사용)

두번째 방법 : __declspec(dllexport) 지시자 사용
----------------------------------------------------------


[첫번째 방법 : 모듈 정의 파일 사용 (.def 파일사용)]


비주얼 컴파일에 프로젝트에서 소스파일 추가 하여 확장자를 .def 라고 하고 cpp 파일과 동일한 파일명으로 하나 만든다.

그리고 그 .def 파일에 다음과 같이 써보자


----------------------- dll.def --------------------------------------------

LIBRARY  
EXPORTS
 Sum   @1

---------------------------------------------------------------

LIBRARY  DLL      <-- 라이브러리 이름이다..

EXPORTS                                   <-- export (수출) 할 함수들 리스트 들

 Sum <-- 함수명  @1    <-- 서수지정..

---------------------------------------------------------------


대략 설명은 이렇다... 이렇게 def 파일을 정의하고 다시 컴파일 한 후!!!

생성되는 dll 파일을 다시 dll을 이용하는 실행파일에 넣고 하면!!

아무런 에러 없이 함수 주소를 가져올 것이다.

그리고.. dumpbin /exports test.dll 이라고 하면 전에 없던 export 라는 부분에서 수출하려는 함수 목록으로 Sum이라는 함수가 나타날 것이다...

 

 


--------------------------------------------------------------------------------------------------------------------


[두번째 방법 : __declspec(dllexport) 지시자 사용방법 ]


위에서는.def 파일을 이용했는데 이 방식은 예전 16비트 시절부터 쓰던 옛발 방식이라고 한다..

여기서는 다은방법을 보자면. 해당 수출하려는 함수에 export 하려는 함수 앞에

__declspec(dllexport) 라는 키워드를 쓰면 된다고 한다. -_-ㅋ

근데 여기 책에서 설명하는 방법은 좀 복잡하고 요상하게 쓰는데 -_-ㅋ 일단 아래를 따라가보자..


여기서는 __declspec(dllexport) 를 쓸대  

1. __declspec(dllexport) 용 test.h 라는 헤더파일을 만든다.


2. __declspec(dllexport) 용 test.cpp 라는 함수 구현 파일을 만든다.


------------------ test.h ---------------------------

#ifndef __TEST_H__
#define __TEST_H__

 

#ifndef DLLBASIC_API
#ifdef __cplusplus
#define DLLBASIC_API extern "C" __declspec(dllimport)
#else
#define DLLBASIC_API __declspec(dllimport)
#endif /* __cplusplus */
#endif /* DLLBASIC_API */

 

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

DLLBASIC_API int SumIint a, intb);
#ifdef __cplusplus
}
#endif /* __cplusplus */


#endif /* __TEST_H__ */


 
------------------------------------------------------

위의 Sum 함수는 실제로 책에는 다른 함수로  되어있지만  책의 함수가 조금 복잡해서

독자가 이해하기 휩게 하기 위해 단순하게 바껏다.


----------- test.cpp --------------------

#include "test.h"

 

#ifdef DLLBASIC_API
#undef DLLBASIC_API
#endif /* DLLBASIC_API */


#ifdef __cplusplus
#define DLLBASIC_API extern "C" __declspec(dllexport)
#else
#define DLLBASIC_API __declspec(dllexport)
#endif /* __cplusplus */

 

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

DLLBASIC_API int SumIint a, intb)
{
 return a+b;
}

#ifdef __cplusplus
}
#endif /* __cplusplus */

------------------------------------------------------

책에서는 이렇게 사용한다고 되어있다..

사실 test.cpp위에 부분에 보면 DLLBASIC_API 를 먼저 정의하고 test.h 파일을 포함하기 때문에

test.h 파일에 있는 dllimport 는 아무 의미없이 되고 전부다 dllexport로 대체 될것이다.

근데 굳이 이렇게 해더 파일에는 dllimport 를 쓰고 cpp에는 dllexport를 써서 복잡하기 하는 이유가

독자는 너무 긍금했다.. 하지만 책에 잘 설명이 되어있었다.... 하지만 ..-_-ㅋ 보고도 이해를 잘 하지 못했따.. -=_-ㅋㅋ

일단 책에 나와있는 설명을 그대로 옮겨보겠지만.. 혹시라도  아시는분 있으면 알려주길 바란다....

--- 책의 설명.. --------

하지만 왜 이렇게 따로 전처리기 까지 사용해서 복잡하게 dllimport까지 지정하는 것일까???

후에 해당 DLL 사용 시 test.h를 인클루드하게 되면 별도의 DLLBASIC_API라는 매크로를 정의하지 않기 때문에

해당 DLL을 사용하는 입장에서의 DLLBASIC_API는 __declspec(dllimport)로 정의 된다. 그렇게 되면 자연스럽게 헤더에 선언된 함수가

임포트 할 함수임을 미리 컴파일러에게 알리는 역할을 한다. 물론 굳이 알리지 않아도 사용하는 데에는 전혀 문제가 없다.

하지만 이렇게 임포트 함수임을 미리 알리느냐 알리지 않느냐의 미묘한 차이는 실제 PE이미지가 생성될 때 조그만한 차이를 불러온다.

그 차이는 5장 DLL 로딩과 임포트 섹션에서 논의하도록 한다.

------------------------------------------------------------------------
라고 되어있다 -_-ㅋ 먼말인지 몰것다. 그냥 재꼈다 -_-ㅋ

그러면 해당 dll파일을 dumpbin /export test.dll 이라고 해서 확인해보자

글엄 export 된 함수들을 볼수 있다.. 다만 이경우에는

서수를 사용자 임의대로 붙일수 없다고 한다. ..

그래서 서수를 지정해서 사용하는 방법은 포기해야 한다고 한다 -_-ㅋ


-------------------------------------------------------------------------------


[익스포트 함수명의 문제 함수 호출 방식 - 파스칼 방식 C선언 방식 ]


1. 예전엔 아래와 같이 함수 호출 방식이 2가지였다고 함.
   - 하나 : 파스칼방식
   - 둘    : C선언 방식

2. VC++ 에서 이 두가지 방식의 스택 이용방식을 통일했다고 함.. 둘다 왼쪽에서 오른쪽으로.. ..
   스택이용방식에 있어선 파스칼 방식으로 채택.... 
   스택제거 시점은 아직 구분했다고 했다고함.. 아래와 같이 ...


 -------------- C언선방식 -----------------

함수매개변수 : 매개변수를 오른쪽에서 왼쪽으로 스택에 push 한다.

스택제거      : 함수를 호출한 쪽에서 스택을 제거 한다

키워드 :    : __cdecl

아무것도 정의 안하면 디폴트로 C선언 방식으로 된다.
------------------------------------


 -------------- 파스칼방식 -----------------
 
함수매개변수 : 매개변수를 왼쪽에서 오른쪽으로 스택에 push한다.

스택제거     : 함수가 호출된 쪽에서 스택을 제거

키워드         :  __stdcall

_stdcall  또는 WINAPI 를 함수 앞에 쓰면 파스칼 방식으로 된다.
------------------------------------


그리고 자세히보면 위 코드에

#define DLLBASIC_API extern "C" __declspec(dllexport)

라고 되어 잇는데 extern "C" 를 유심히 보자..

이 부분을 제거하고 컴파일 한후  dumpbin /EXPORTS test.dll 한번해보고

이 부분을 제거 하지 말고  컴파일 한후 dumpbin /EXPORTS test.dll 해보라..

그러면 export 되는 함수 이름이 다를 것이다.

extern "C" 라는 키워드는 C언어 형식으로 함수 이름을 짓고

extern "C" 를 제거하면 C++ 규칙대로 함수 이름을 짓는다..

extern "C" 방식은 모든 컴파일러에서 알아볼수 있는 형식이므로

extern "C" 방식으로 함수를 작성하면 다른 컴퓨터에서 무리 없이 돌아가지만

extern "C"를 제거한 C++ 방식으로 함수를 작성하면

함수이름이 이상하게 변하는데 그 이상하게 변환하는 규칙이 각각 컴파일러 마다

제각각이여서 다른 컴퓨터와 호환이 되지 않는다..

그래서 함수를 export 하고자 할때는 위의 extern "C" 키워드를 꼭 붙인다고 한다.

------------------------------------------------------------------

이렇게 함수이름을 어떤 형식으로 짓냐는 방법을

이름 데콜이션 이라고 한다.. 그 규칙이 버전별로 조금씩 다른데....................

독자는 귀찮아서.. 그냥 이 부분은 넘기려고 한다. -_-ㅋ

 

아래의 출처에서 자료를 구해서 잘못된 부분을 수정했습니다.