■ 라이브러리
라이브러리(Library)란 함수,데이터,타입 등 여러가지 프로그래밍 요소들의 집합이며 보통 LIB확장자를 가진다. (DLL사용시 h,dll파일 필요) 자주 사용되는 표준적인 함수를 매번 직접 작성해서 사용하는 것은 지나치게 시간 소모적이므로 표준화할 수 있는 함수를 미리 만들어서 모아 놓은 것이 라이브러리이다. 라이브러리를 한 번 구축해 놓기만 하면 다시 만들 필요없이 불러서 사용할 수 있으므로 개발 속도도 빨라지고 신뢰성도 확보할 수 있다.
한글 입출력을 하는 라이브러리를 생각해 보자. 한글은 무척 복잡한 언어이기 때문에 한글을 사용하는 모든 프로그램에서 한글 입출력 함수를 일일이 만들어 사용하기 어렵다. 그래서 누군가가 한 번만 한글 입출력 함수를 만들어 라이브러리로 배포하면 나머지 사람들은 이 라이브러리에 있는 함수를 불러 한글을 출력하기도 하고 입력받기도 한다.
■ STATIC LINK
ProA.exe를 만드는 사람은 자신의 고유 코드만 ProA.cpp에 작성하고 HAN.LIB와 연결하면 한글 입출력 기능을 가진 ProA.exe. 실행 파일을 만들 수 있다. HAN.LIB에 있는 함수와 데이터는 링커에 의해 실행 파일에 그대로 옮겨지면 실행 파일의 일부분이 된다.
이런 전통적인 라이브러리 연결 방법을 정적 링크(Static Link)라고 하면 컴파일시에 라이브러리에 코드를 실행파일에 복사한다.
■ DLL
DLL은 동적 링크(Dynamic Link)를 사용한다. 동적 링크란 컴파일시에 함수의 코드가 실행 파일에 복사되는 것이 아니라 실행 중에 라이브러리에 있는 함수를 호출하는 방법을 말한다.
ProA.exe를 만드는 사람은 자신의 고유 코드만 ProA.cpp에 작성하고 이 소스를 컴파일하여 ProA.exe를 만든다. ProA.cpp에서는 한글 입출력 함수를 호출하지만 ProA.exe파일에는 한글 입출력 함수가 포함되어 있지 않다. 대신 ProA.exe는 한글 입출력 함수의 위치에 대한 정보를 가지고 있으며 이 프로그램이 실행될 때 HAN.DLL이 메모리에 같이 로드되며 ProA.exe에서 HAN.DLL에 있는 함수를 호출한다.
■ DLL의 장점
① 한 코드를 여러 프로그램이 동시에 사용하기 때문에 메모리가 절약된다.
② 정적링크를 사용하는 경우 실행 파일에 라이브러리의 함수가 모두 포함되어 실행파일이 커지지만 DLL을 사용하는 프로그램은 크기가 작다.
③ DLL을 교체하여 프로그램의 성능을 향상시키기 쉽다.
④ 리소스의 교체가 가능하다.
⑤ 코드의 양이 적어지므로 디버깅이 용이해진다
⑥ 혼합 프로그래밍이 가능해 진다
⑦ 프로그래머끼리 분담 작업이 용이하며 재사용성도 뛰어나다.
■ 정적링크와 동적 링크
정적 링크 |
컴파일 시에 함수가 실행 파일에 연결된다. 실행 파일에 함수의 코드가 복사되기 때문에 실행 파일의 크기가 커지는 단점이 있지만 실행 파일은 완전한 단독 실행 파일이 된다. 실행파일에 함수의 코드가 포함되어 있기 때문에 컴파일이 끝나면 라이브러라 파일(LIB)이 없어도 프로그램을 실행할 수 있다. |
동적 링크 |
실행시에 함수가 실행 파일에 연결된다. 실행 파일에는 호출할 함수의 정보만 포함되고 실제 함수 코드는 복사되지 않ㅇ므로 실행 파일의 크기가 작아진다. 하지만 실행 파일은 함수에 대한 정보만 가지고 있을 뿐 실제 코드를 가지고 있지는 않으므로 프로그램 실행시에 DLL이 꼭 있어야 한다. |
DLL관리
두 번째 클라이언트인 ProB.exe가 실행될 때는 HAN.DLL이 이미 메모리에 올라와 있으면 HAN.DLL은 읽어올 필요가 없으며 ProB.exe만 읽어온다. 대신 윈도우는 ProB.exe의 주소 영역에 HAN.DLL이 로드된 메로리를 맵핑시켜 ProB.exe에서도 HAN.DLL의 함수를 자유롭게 호출할 수 있도록 한다. 세번째 클라이언트인 ProC.exe가 실행될 때도 마찬가지로 같은절차를 거칠 것이며 이 상태에서는 세 개의 클라이언트 프로그램이 하나의 DLL을 공유한다.
DLL은 가상 메모리에 한 번 로드되면 다시 로드되지 않는다. 단 코드의 경우만 드렇지 DLL의 고유 변수는 클라이언트 프로그램이 실행될 때마다 매번 다시 메모리를 할당받아야 한다. 클라이언트 프로그램끼리 코드는 공유하지만 데이터는 공유할 수 없기 때문이다. C++클래스의 멤버 함수는 이 클래스로부터 만들어지는 모든 객체가 가 공유하지만 멤버 변수는 개별적으로 가지는 것과 마찬가지이다.
예를 들어 HAN.DLL에 현재 입력 상태가 한글 모드인지 영문 모드인지를 기억하는 변수가 있다고 하자. 이 변수의 값은 클라이언트 프로그램별로 다를 수 있기 때문에 프로그램끼리 공유할 수 없다. 만약 이 변수를 공유한 상태에서 ProA.exe에서 한글 입력 상태로 바꾸면 ProB.exe와 ProC.exe도 같이 한글 입력 상태로 바뀌어 버릴 것이며 이는 논리적으로 바람직하지 못한 결과를 가져온다. 이런 식으로 DLL내부의 값을 기억하는 변수는 공유가 불가능하기 때문에 클라이언트 프로그램이 실행될 때마다 다시 메모리를 할당받아야 한다.
DLL이 메모리에서 지워지는 시기는 언제 쯤일까? 필요가 없어진 DLL은 당연히 메모리에서 사라져야 하지만 DLL은 사용자가 직접 사용하는 것이 아니기 때문에 종료시기를 쉽게 판단할 수 없다. DLL을 처음 메모리로 올린 ProA.exe가 종료될 때 HAN.DLL을 종료하는 방법이 가장 쉽게 떠올릴 수 있는 방법이다. 그러나 최초 DLL을 로드한 ProA.exe가 종료되어도 ProB.exe나 ProC.exe가 이 DLL을 계속 사용하고 있을 수도 있기 때문이다. DLL이 메모리에서 삭제되어야 할 시기는 DLL을 사용하는 모든 클라이언트 프로그램이 종료되었을 때이다.
윈도우는 DLL별로 사용 카운트라는 것을 유지하고 있으며 클라이언트 프로그램이 실행될 때마다 카운트를 1씩 증가시키고 클라이언트가 종료될 때마다 카운트를 1 감소시킨다. 이렇게 사용 카운트를 유지하면서 카운트가 0이 될 때 DLL을 메모리에서 삭제하면 아무런 문제가 없다.
ProA.exe실행 |
ProB.exe실행 |
ProC.exe실행 |
ProA.exe종료 |
ProC.exe종료 |
ProB.exe종료 |
HAN.DLL로드 |
|
|
|
|
HAN.DLL삭제 |
COUNT = 1 |
COUNT = 2 |
COUNT = 3 |
COUNT = 2 |
COUNT = 1 |
COUNT = 0 |
DLL접속
__declspec |
DLL을 사용하려면 우선 함수를 제공하는 DLL에서는 자신이 제공하고자 하는 함수에 대한 정보를 밖으로 공개해 놓아야 하며 이 동작을 EXPORT라고 한다. 반대로 DLL을 사용하는 클라이언트에서는 어떤 DLL에 있는 어떤 함수를 사용하겠다고 선언해야 하는데 이 동작을 IMPORT라고 한다. 즉 함수를 제공하는 측은 어떤 함수를 제공하겠다는 선언이 있어야 하며 함수를 사용하는 측에서는 어떤 함수를 사용하겠다는 선언이 있어야 한다.
__declspec은 함수에 대한 정보를 제공하는 선언문이며 엑스포트 또는 임포트하는 함수 앞에 수식어로 이 문구가 있어야 한다. 원형은 다음과 같으며 앞의 밑줄이 두 개임을 유의하다
__declspec(extended-attribute) declaratory
__declspec문은 기억부류(Storage Class)에 관한 정보를 단순화,표준화하며 원래의 C++에는 없는 문장이지만 마이크로소프트에서 C++문법을 확장한 예 중 하나에 해단한다. 언뜻 보기에는 괄호가 있어 함수같지만 컴파일러가 제공하는 키워드이다. 기억 부류의 속성을 괄호 안에 인수로 지정한다. 사용가능한 인수는 네 가지
가 있다.
인수 |
설명 | |
Thread |
TSL(Thread Local Storage) 데이터로 지정한다. 이 지정자가 붙은 변수는 해당 스레드에서만 사용할 수 있는 변수가 된다. | |
Naked |
접두(prolog),접미(epilog)를 생성하지 않는다. 어셈블리 언어를 사용하여 직접 접두,접미를 달고자 할 때 사용한다. 어셈블리 언어를 사용하여 가상 디바이스 드라이버를 작성할 때 이 기억부류를 사용한다. 함수에만 적용되며 변수에는 적용되지 않는다. | |
Dllimport |
DLL에 있는 데이터,오브젝트,함수를 임포트한다. DLL에 있는 이렇게 생긴 함수를 앞으로 사용하겠다는 선언이다 | |
dllexport |
DLL에 있는 데이터,오브젝트,함수를 엑스포트한다. DLL이 사용하는 클라이언트(실행파일이거나 또는 다른 DLL)에게 DLL의 정보를 명시적으로 제공하는 역할을 한다. Dllexport로 함수를 선언하면 DEF파일의 Exports란에 이 함수를 명시하지 않아도 되며 __export 키워드를 대체한다. | |
n 사용방법
DLL에서 엑스포트 |
extern “c” __deslpsec(dllexport) 함수원형; |
client에서 임포트 |
extern “c” __deslpsec(dllimport) 함수원형; |
■ 암시적 연결 & 명시적 연결
■ 암시적 연결
함수가 어느 DLL에 있는지 밝히지 않고 그냥 사용한다. 프로젝트에 임포트 라이브러리를 포함해야 하며 윈도우즈는 임포트 라이브러리의 정보를 참조하여 알아서 DLL을 로드하고 함수를 찾는다. 클라이언트 프로그램이 로드될 때 DLL이 같이 로드되거나 이미 DLL이 로드되어 있으면 사용 카운트를 1 증가시킨다. 클라이언트 프로그램이 실행될 때 DLL이 로드되므로 실행시 연결이라고 한다.
n 임포트 라이브러리 DLL파일 찾는 순서
① 클라이언트 프로그램이 포함된 디렉토리
② 프로그램의 현재 디렉토리
③ 윈도우의 시스템 디렉토리
④ 윈도우 디렉토리
⑤ PATH환경 변수가 지정하는 모든 디렉토리
만약 이 순서대로 DLL을 찾아보고 원하는 DLL이 발견되지 않으면 클라이언트 프로그램은 다음과 같은 에러메세지를 출력하고 실행을 종료한다.
※ 암시적 연결 실습
① 새 프로젝트를 만든다(Win32 Dynamic-Link Library 형태로 생성)
② 함수를 작성한다.
xyz.c |
#define DLL_SOURCE #include "xyz.h" // 이 안에 있는 DLLFUNC는 __declspec(dllexport)로 된다. int Add( int a, int b ) { return a + b; } |
xyz.h |
#ifdef DLL_SOURCE #define DLLFUNC __declspec(dllexport) #else #define DLLFUNC __declspec(dllimport) #endif #include <windows.h> EXTERN_C DLLFUNC int Add( int a, int b); |
③ 컴파일하면 DLL파일,LIB파일이 생성된다.
④ XYZ.Dll , XYZ.lib , XYZ.h 파일을 사용할 폴더에 복사한다.
⑤ 사용할 프로젝트에서 사용하고자 하는 DLL의 임포트 라이브러리를 프로젝트에 포함시킨다
Usxxyz.cpp |
#include <windows.h> // user32.dll의 모든 함수 선언. #include <stdio.h> // DLL 사용하기 #include "xyz.h" // 관련 헤더 include #pragma comment(lib, "xyz.lib") // 라이브러리 추가 void main() { MessageBox(0,"A","",MB_OK); int s = Add(10,20); // DLL 함수 사용 printf("결과 : %d\n", s); void* p1 = GetModuleHandle( "xyz.dll"); printf("xyz.dll 주소 : %p\n", p1); printf("Add 주소 : %p\n", Add); } |
정상적으로 값 30이 메시지박스로 뜨고 콘솔창에는 결과와 각각의 주소값이 출력
명시적연결의 사용법은 적지않았다
장점만 알아보고 가겠다.
■ 명시적 연결의 장점
- 필요할 때만 DLL을 읽어와 사용하기 때문에 메모리와 리소스가 절약된다. 암시적 연결의 경우 프로그램이 시작 될 때 DLL도 같이 메모리로 올라오므로 프로그램 실행중에 항상 DLL이 메모리에 상주하고 그만큼 메모리가 더 소비된다.
- 경우에 따라 사용할 DLL을 교체할 수 있다. LoadLibray에서 DLL 이름을 문자열로 줄 수 있으므로 상황에 맞게 DLL을 선택적으로 사용할 수 있다. 마찬가지로 호출할 함수도 문자열로 지정하므로 선택 가능하다.
- 필요한 DLL이 없는 경우에도 프로그램을 실행할 수 있다. 물론 이 경우 DLL에게 분담된 작업은 제대로 처리할 수 없겠지만 최소한 프로그램이 실행되지도 못하는 상황은 발생하지 않는다. DLL이 없을 경우 다른 방법으로 문제를 해결하거나 최소한 에러 메시지라도 보여줄 수 있다. 반면 암시적 연결의 경우는 DLL이 없으면 아예 실행을 시작할 수 조차 없다.
- 클라이언트 프로그램의 시작이 빠르다. 암시적 연결은 클라이언트 실행전에 DLL을 읽어와야 하는데 필요한 DLL의 개수가 수십개가 넘으면(실제로 그정도 된다)이 시간이 무시 못할 정도로 길어질 수도 있다.명시적 연결은 일단 프로그램이 실행된 후 필요할 때 DLL을 로드하므로 신속하게 실행된다. 최종 사용자 입장에서는 이 정도 시간 차이가 굉장히 크게 느껴진다.