어느정도 개발을 진행하다보면 반드시 사용하게 되는 것이 DLL이다.
DLL을 사용했을 떄 장점, 단점 등은 구글링을 해보면 수십개가 나온다.
필자는 DLL 사용을 아주 선호하는 편인데, 그 이유는 내 코드가 간결해 진다는 것이다.
(모듈화의 장점 중 하나)
그냥 깔끔한 코드를 좋아한다.
본론으로 들어가, DLL을 로드하는 방법은 크게 Implicit Link, Explicit Link가 있다.
한국말로 묵시적(암시적) 링크, 명시적 링크라고 하는데.. 뭐
개인적으로 명시적 링크를 선호한다.
예제를 보면, 먼저 DLL을 하나 만들었다. (SampleDLL.dll)
// SampleDLL.h
#pragma once
extern "C" __declspec(dllexport) void PrintName(void);
extern "C" __declspec(dllexport) int AddNumber(int a, int b);
// SampleDLL.cpp
#include "SampleDLL.h"
#include <stdio.h>
void PrintName(void)
{
printf("I'm Sample DLL \n");
}
int AddNumber(int a, int b)
{
return (a + b);
}
별거 없는 아주 간단한 코드이다.
PrintName은 단순하게 내가 DLL이라고 알려주는 내용이고, AddNumber은 두 숫자 더하는 것이다;
거의 Hello World 급으로 간단한데,
SampleDLL.h 파일에 보면 이상하게 생긴 문법이 나온다.
extern "C" 와 __declspec(dllexport) ...
간단하게 설명하면 extern "C" 는 쉽게 말해 Name Mangling 을 하지 않겠다는 뜻이며,
(Name Mangling에 대해 설명하려면 컴파일러 단부터 설명해야되서.. 나중에 따로..)
__declspec(dllexport)는 뒤에 나올 함수(PrintName, AddNumber 등)가 DLL을 통해 사용될? 함수라는 뜻으로 이해하면 된다.
DLL을 만들었으니 이제 DLL을 호출할 프로그램을 만들어야 하는데,
아래 예제코드는 #define 으로 구분했으니 참고하면 된다.
// Dll_Linking_Sample.cpp
#include <stdio.h>
#include <tchar.h>
#define _IMPLICIT_LINK_
#ifdef _IMPLICIT_LINK_ // Implicit Link 예제
#pragma comment (lib, "SampleDLL.lib") // 테스트용 DLL 이름
#include "SampleDLL.h"
int main()
{
printf("Implicit Linking Test \n");
// 테스트 1
PrintName();
// 테스트 2
printf("10 + 20 = %d \n", AddNumber(10, 20));
return 0;
}
#else // Explicit Link 예제
#include <windows.h>
typedef void (*fpPrintName)(void);
typedef int (*fpAddNumber)(int, int);
int main()
{
printf("Explicit Linking Test \n");
HINSTANCE hModule = LoadLibrary("SampleDLL.dll");
if (hModule != NULL)
{
// 테스트 1
fpPrintName funcPrintName = (fpPrintName)GetProcAddress(hModule, "PrintName");
if (funcPrintName)
{
funcPrintName();
}
// 테스트 2
fpAddNumber funcAddNumber = (fpAddNumber)GetProcAddress(hModule, "AddNumber");
if (funcAddNumber)
{
printf("10 + 20 = %d \n", funcAddNumber(10, 20));
}
FreeLibrary(hModule);
}
else
{
printf("DLL 로드 실패\n");
}
}
#endif
전처리문은 기본적으로 알것이라 보고..
(기본 상태로는 Implicit Link로 동작하고, #define _IMPLICIT_LINK_ 를 지우면 Explicit Link로 동작한다..)
코드를 보면,
Implicit Link의 경우 보시다시피 .lib 라는 것과 .h 라는게 추가된다.
#include를 통해 DLL에서 사용할 함수가 정의된 헤더 파일.. 다시 말해 아까 __declspec이라는 문구가 들어간 부분이 필요하다는 것이다.
헤더 파일 외, .lib 파일이 필요한데, 이를 로드하는 법도 뭐.. 프로젝트 속성에서 추가 종속성으로 넣어도되는데,
난 귀찮아서 pragma를 주로 사용한다..
마지막으로 .h 파일과 .lib 파일을 보면 경로에 대한 지정이 없다.
이는 두 파일이 현재 실행되는 위치(DLL_Linking_Sample.cpp)에 있어야 한다는 뜻..
따라서 두 파일을 복사해서 아래 사진처럼 넣어주자..
사람마다 개인차가 있겠지만..
필자는 이런 방법을 아주 싫어한다. 가급적 코드 안에서 모든걸 처리하고 싶은 마음이랄까..
물론 Build Event로 lib, h 파일 등을 복사해줄 수도 있고, 상대경로로 지정할 수도 있는데..
따라서 필자가 선호하는 방법은 Exlicit Link이다.
이 방법은 쉽게 말해 DLL을 로드해서(LoadLibrary) 지정된 이름의 함수 주소를 찾고(GetProcAddress)
해당 함수 주소(포인터)를 사용하겠다는 것이다.(funcPrintName, funcAddNumber)
이를 위해 먼저 함수 포인터(보통 접두어로 fp를 씀)를 typedef 해주고 해당 포인터 변수를 선언한다.
나머지는 위에서 설명한 그대로다.
LoadLibrary라는 함수는 HINSTANCE를 반환하는데, 그럼 뭔진 몰라도 HINSTANCE 변수를 하나 만들고,
해당 변수를 통해 LoadLibrary를 return 받는다. 여기서 인자는 DLL 이름이다.(SampleDLL.dll)
디버깅 해보면, 정상적으로 로드될 경우 해당 DLL이 로드된 Address 값이 반환되는데,
정상적으로 로드되지 않을 경우 0x00000000, 즉 NULL이 리턴된다.
(이러한 경우는 대부분 DLL의 위치나 Dependency 문제일 것..)
그리고나서 GetProcAddress를 통해 지정된 함수를 찾는데, 함수 인자는 HINSTANCE와 찾고자 하는 함수 명이다.
쉽게 말해 LoadLibrary한 HINSTANCE에서 지정된 함수명("~~")을 찾겠다는 뜻이다.
찾으면 해당 함수 포인터를 통해 사용하면되고,
주의할 점은 항상 FreeLibrary를 해주는 것을 습관화하자는 것.
파일도 fopen하면 fclose해주듯, DLL로 Load했으니 Free해주는 습관이 필요하다.
좀 더 공부해보고 싶은 사람은 아래 예제 코드를 이것 저것 변경하며 직접 디버깅 해보길 추천한다.
한가지만 예를 들면, Implicit Link 를 사용하고 위에서 설명했듯이 lib파일과 .h파일을 복사 후
SampleDLL.cpp 내에서 코드를 변경하면 어떻게 되는지..
이러한 과정을 공부해보면 lib파일의 용도를 알 수 있을 것이다.
개발환경: Visual Studio 2015 (VC 14)
'Language > C++' 카테고리의 다른 글
리눅스 - Crypto++ 라이브러리 테스트 (2) | 2024.01.02 |
---|---|
윈도우 프로그래밍 - 폴더 삭제하기 (0) | 2022.02.21 |
윈도우 프로그래밍 - DLL에서 데이터를 전달하는 방법 (Notify) (0) | 2020.05.06 |
윈도우 프로그래밍 - 쓰레드 기반 동기화 예제 (0) | 2020.02.17 |
윈도우 프로그래밍 - MFC 개요 및 배경 지식 (0) | 2018.12.28 |