Windows API DLL | DLL 생성·사용 완벽 가이드
이 글의 핵심
DLL은 코드를 공유 가능한 라이브러리로 만듭니다. __declspec(dllexport)로 함수를 내보내고, LoadLibrary와 GetProcAddress로 동적 로드합니다. 정적 링크는 컴파일 시, 동적 로드는 런타임에 연결됩니다.
들어가며
DLL(Dynamic Link Library)은 1980년대 중반 Windows 1.0부터 제공된 공유 라이브러리 메커니즘입니다. 여러 프로그램이 같은 코드를 공유하여 메모리 절약, 모듈화, 업데이트 용이성을 제공합니다. Windows 시스템 자체도 대부분 DLL로 구성되어 있습니다.
// DLL 함수 내보내기
__declspec(dllexport) int Add(int a, int b)
{
return a + b;
}
// 사용
HMODULE hDll = LoadLibrary(L"MyDll.dll");
typedef int (*AddFunc)(int, int);
AddFunc pAdd = (AddFunc)GetProcAddress(hDll, "Add");
int result = pAdd(3, 5); // 8
FreeLibrary(hDll);
1. DLL 프로젝트 생성
1.1 DLL 프로젝트 설정 (Visual Studio)
1. 새 프로젝트 → Visual C++ → Windows Desktop → 동적 연결 라이브러리 (DLL)
2. 프로젝트 이름: MyDll
3. 솔루션 이름: DllExample
1.2 기본 DLL 구조
// MyDll.h
#ifndef MYDLL_H
#define MYDLL_H
#ifdef MYDLL_EXPORTS
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif
extern "C" {
MYDLL_API int Add(int a, int b);
MYDLL_API int Multiply(int a, int b);
MYDLL_API const char* GetVersion();
}
#endif // MYDLL_H
// MyDll.cpp
#include "pch.h" // 또는 #include <windows.h>
#include "MyDll.h"
// DLL 진입점
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
// DLL이 프로세스에 로드됨
OutputDebugStringA("MyDll: DLL_PROCESS_ATTACH\n");
break;
case DLL_THREAD_ATTACH:
// 새 스레드가 생성됨
break;
case DLL_THREAD_DETACH:
// 스레드가 종료됨
break;
case DLL_PROCESS_DETACH:
// DLL이 프로세스에서 언로드됨
OutputDebugStringA("MyDll: DLL_PROCESS_DETACH\n");
break;
}
return TRUE;
}
// 내보내기 함수 구현
int Add(int a, int b)
{
return a + b;
}
int Multiply(int a, int b)
{
return a * b;
}
const char* GetVersion()
{
return "MyDll version 1.0";
}
1.3 프로젝트 설정
프로젝트 속성:
- C/C++ → 전처리기 → MYDLL_EXPORTS 추가
- 링커 → 고급 → 가져오기 라이브러리: MyDll.lib
2. DLL 정적 링크 (Implicit Linking)
2.1 사용 프로젝트 설정
// main.cpp (사용하는 프로그램)
#include <windows.h>
#include <stdio.h>
#include "MyDll.h" // DLL 헤더 포함
#pragma comment(lib, "MyDll.lib") // 링크 라이브러리 지정
int main()
{
// DLL 함수 직접 호출 (LoadLibrary 불필요)
int sum = Add(10, 20);
int product = Multiply(5, 6);
const char* version = GetVersion();
printf("Add(10, 20) = %d\n", sum);
printf("Multiply(5, 6) = %d\n", product);
printf("Version: %s\n", version);
return 0;
}
2.2 배포
실행 파일과 같은 폴더에 MyDll.dll 복사:
- MyApp.exe
- MyDll.dll
3. DLL 동적 로드 (Explicit Linking)
3.1 LoadLibrary와 GetProcAddress
#include <windows.h>
#include <stdio.h>
// 함수 포인터 타입 정의
typedef int (*AddFunc)(int, int);
typedef int (*MultiplyFunc)(int, int);
typedef const char* (*GetVersionFunc)();
int main()
{
// DLL 로드
HMODULE hDll = LoadLibrary(L"MyDll.dll");
if (hDll == NULL) {
printf("DLL 로드 실패! 에러 코드: %d\n", GetLastError());
return 1;
}
// 함수 포인터 얻기
AddFunc pAdd = (AddFunc)GetProcAddress(hDll, "Add");
MultiplyFunc pMultiply = (MultiplyFunc)GetProcAddress(hDll, "Multiply");
GetVersionFunc pGetVersion = (GetVersionFunc)GetProcAddress(hDll, "GetVersion");
if (pAdd == NULL || pMultiply == NULL || pGetVersion == NULL) {
printf("함수를 찾을 수 없습니다!\n");
FreeLibrary(hDll);
return 1;
}
// 함수 호출
int sum = pAdd(10, 20);
int product = pMultiply(5, 6);
const char* version = pGetVersion();
printf("Add(10, 20) = %d\n", sum);
printf("Multiply(5, 6) = %d\n", product);
printf("Version: %s\n", version);
// DLL 언로드
FreeLibrary(hDll);
return 0;
}
3.2 DLL 검색 경로
Windows DLL 검색 순서:
1. 실행 파일이 있는 디렉토리
2. 시스템 디렉토리 (C:\Windows\System32)
3. 16비트 시스템 디렉토리
4. Windows 디렉토리 (C:\Windows)
5. 현재 디렉토리
6. PATH 환경 변수 디렉토리들
// 특정 경로에서 DLL 로드
HMODULE hDll = LoadLibrary(L"C:\\MyApp\\Plugins\\MyDll.dll");
// 또는 SetDllDirectory로 검색 경로 추가
SetDllDirectory(L"C:\\MyApp\\Plugins");
HMODULE hDll = LoadLibrary(L"MyDll.dll");
4. C++ 클래스 내보내기
4.1 클래스 내보내기
// MathLib.h
#ifdef MATHLIB_EXPORTS
#define MATHLIB_API __declspec(dllexport)
#else
#define MATHLIB_API __declspec(dllimport)
#endif
class MATHLIB_API Calculator {
private:
int m_value;
public:
Calculator();
~Calculator();
void SetValue(int value);
int GetValue() const;
int Add(int n);
int Multiply(int n);
void Reset();
};
// MathLib.cpp
#include "MathLib.h"
Calculator::Calculator() : m_value(0) {}
Calculator::~Calculator() {}
void Calculator::SetValue(int value) {
m_value = value;
}
int Calculator::GetValue() const {
return m_value;
}
int Calculator::Add(int n) {
m_value += n;
return m_value;
}
int Calculator::Multiply(int n) {
m_value *= n;
return m_value;
}
void Calculator::Reset() {
m_value = 0;
}
4.2 클래스 사용
// main.cpp
#include "MathLib.h"
#pragma comment(lib, "MathLib.lib")
int main()
{
Calculator calc;
calc.SetValue(10);
printf("초기값: %d\n", calc.GetValue());
calc.Add(5);
printf("Add(5) 후: %d\n", calc.GetValue());
calc.Multiply(3);
printf("Multiply(3) 후: %d\n", calc.GetValue());
calc.Reset();
printf("Reset 후: %d\n", calc.GetValue());
return 0;
}
5. 팩토리 패턴으로 클래스 내보내기
5.1 인터페이스 정의
// ICalculator.h
#ifndef ICALCULATOR_H
#define ICALCULATOR_H
#ifdef MATHLIB_EXPORTS
#define MATHLIB_API __declspec(dllexport)
#else
#define MATHLIB_API __declspec(dllimport)
#endif
// 인터페이스 (순수 가상)
class ICalculator {
public:
virtual ~ICalculator() {}
virtual void SetValue(int value) = 0;
virtual int GetValue() const = 0;
virtual int Add(int n) = 0;
virtual int Multiply(int n) = 0;
virtual void Reset() = 0;
virtual void Destroy() = 0; // 삭제 함수
};
// 팩토리 함수
extern "C" {
MATHLIB_API ICalculator* CreateCalculator();
}
#endif
// Calculator.cpp
#include "ICalculator.h"
class Calculator : public ICalculator {
private:
int m_value;
public:
Calculator() : m_value(0) {}
virtual ~Calculator() {}
virtual void SetValue(int value) override {
m_value = value;
}
virtual int GetValue() const override {
return m_value;
}
virtual int Add(int n) override {
m_value += n;
return m_value;
}
virtual int Multiply(int n) override {
m_value *= n;
return m_value;
}
virtual void Reset() override {
m_value = 0;
}
virtual void Destroy() override {
delete this;
}
};
// 팩토리 구현
extern "C" {
MATHLIB_API ICalculator* CreateCalculator()
{
return new Calculator();
}
}
5.2 팩토리 사용
// main.cpp
typedef ICalculator* (*CreateCalculatorFunc)();
int main()
{
HMODULE hDll = LoadLibrary(L"MathLib.dll");
CreateCalculatorFunc pCreate =
(CreateCalculatorFunc)GetProcAddress(hDll, "CreateCalculator");
ICalculator* pCalc = pCreate();
pCalc->SetValue(10);
printf("초기값: %d\n", pCalc->GetValue());
pCalc->Add(5);
printf("Add(5) 후: %d\n", pCalc->GetValue());
// 삭제
pCalc->Destroy();
FreeLibrary(hDll);
return 0;
}
6. DEF 파일로 함수 내보내기
6.1 DEF 파일 작성
; MyDll.def
LIBRARY MyDll
EXPORTS
Add
Multiply
GetVersion
CreateCalculator
6.2 프로젝트에 추가
프로젝트 속성:
- 링커 → 입력 → 모듈 정의 파일: MyDll.def
이제 __declspec(dllexport) 없이도 함수를 내보낼 수 있습니다.
7. DLL 버전 관리
7.1 버전 정보 리소스
// version.rc
1 VERSIONINFO
FILEVERSION 1,0,0,0
PRODUCTVERSION 1,0,0,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x2L // VFT_DLL
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "041203b5" // 한국어, 유니코드
BEGIN
VALUE "CompanyName", "My Company"
VALUE "FileDescription", "Math Library DLL"
VALUE "FileVersion", "1.0.0.0"
VALUE "InternalName", "MyDll.dll"
VALUE "LegalCopyright", "Copyright (C) 2026"
VALUE "OriginalFilename", "MyDll.dll"
VALUE "ProductName", "My Product"
VALUE "ProductVersion", "1.0.0.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x412, 949 // 한국어, 코드 페이지
END
END
7.2 버전 확인
#include <windows.h>
#include <stdio.h>
void GetDllVersion(const wchar_t* dllPath)
{
DWORD handle;
DWORD size = GetFileVersionInfoSize(dllPath, &handle);
if (size == 0) {
printf("버전 정보 없음\n");
return;
}
BYTE* pVersionInfo = new BYTE[size];
if (!GetFileVersionInfo(dllPath, handle, size, pVersionInfo)) {
delete[] pVersionInfo;
return;
}
VS_FIXEDFILEINFO* pFileInfo;
UINT len;
if (VerQueryValue(pVersionInfo, L"\\", (LPVOID*)&pFileInfo, &len)) {
DWORD major = HIWORD(pFileInfo->dwFileVersionMS);
DWORD minor = LOWORD(pFileInfo->dwFileVersionMS);
DWORD build = HIWORD(pFileInfo->dwFileVersionLS);
DWORD revision = LOWORD(pFileInfo->dwFileVersionLS);
printf("버전: %d.%d.%d.%d\n", major, minor, build, revision);
}
delete[] pVersionInfo;
}
int main()
{
GetDllVersion(L"MyDll.dll");
return 0;
}
8. 플러그인 시스템
8.1 플러그인 인터페이스
// IPlugin.h
class IPlugin {
public:
virtual ~IPlugin() {}
virtual const char* GetName() const = 0;
virtual const char* GetVersion() const = 0;
virtual bool Initialize() = 0;
virtual void Shutdown() = 0;
virtual void Execute() = 0;
};
// 플러그인 생성/삭제 함수
typedef IPlugin* (*CreatePluginFunc)();
typedef void (*DestroyPluginFunc)(IPlugin*);
8.2 플러그인 구현
// MyPlugin.cpp
#include "IPlugin.h"
#include <stdio.h>
class MyPlugin : public IPlugin {
public:
virtual const char* GetName() const override {
return "My Plugin";
}
virtual const char* GetVersion() const override {
return "1.0";
}
virtual bool Initialize() override {
printf("MyPlugin: Initialized\n");
return true;
}
virtual void Shutdown() override {
printf("MyPlugin: Shutdown\n");
}
virtual void Execute() override {
printf("MyPlugin: Execute\n");
}
};
// 팩토리
extern "C" {
__declspec(dllexport) IPlugin* CreatePlugin()
{
return new MyPlugin();
}
__declspec(dllexport) void DestroyPlugin(IPlugin* pPlugin)
{
delete pPlugin;
}
}
8.3 플러그인 매니저
#include <windows.h>
#include <vector>
#include <string>
#include "IPlugin.h"
class PluginManager {
private:
struct PluginInfo {
HMODULE hModule;
IPlugin* pPlugin;
DestroyPluginFunc pDestroy;
};
std::vector<PluginInfo> m_plugins;
public:
~PluginManager() {
UnloadAll();
}
bool LoadPlugin(const wchar_t* dllPath) {
HMODULE hModule = LoadLibrary(dllPath);
if (hModule == NULL) {
return false;
}
CreatePluginFunc pCreate =
(CreatePluginFunc)GetProcAddress(hModule, "CreatePlugin");
DestroyPluginFunc pDestroy =
(DestroyPluginFunc)GetProcAddress(hModule, "DestroyPlugin");
if (pCreate == NULL || pDestroy == NULL) {
FreeLibrary(hModule);
return false;
}
IPlugin* pPlugin = pCreate();
if (pPlugin == NULL) {
FreeLibrary(hModule);
return false;
}
if (!pPlugin->Initialize()) {
pDestroy(pPlugin);
FreeLibrary(hModule);
return false;
}
PluginInfo info;
info.hModule = hModule;
info.pPlugin = pPlugin;
info.pDestroy = pDestroy;
m_plugins.push_back(info);
printf("플러그인 로드: %s (버전 %s)\n",
pPlugin->GetName(), pPlugin->GetVersion());
return true;
}
void ExecuteAll() {
for (auto& info : m_plugins) {
info.pPlugin->Execute();
}
}
void UnloadAll() {
for (auto& info : m_plugins) {
info.pPlugin->Shutdown();
info.pDestroy(info.pPlugin);
FreeLibrary(info.hModule);
}
m_plugins.clear();
}
};
// 사용 예
int main()
{
PluginManager manager;
manager.LoadPlugin(L"MyPlugin.dll");
manager.LoadPlugin(L"AnotherPlugin.dll");
manager.ExecuteAll();
// 소멸자에서 자동으로 UnloadAll 호출
return 0;
}
9. DLL 주입 (DLL Injection)
9.1 CreateRemoteThread 방식
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>
DWORD GetProcessIdByName(const wchar_t* processName)
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) return 0;
PROCESSENTRY32 pe = {};
pe.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnapshot, &pe)) {
do {
if (_wcsicmp(pe.szExeFile, processName) == 0) {
CloseHandle(hSnapshot);
return pe.th32ProcessID;
}
} while (Process32Next(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
return 0;
}
bool InjectDll(DWORD processId, const wchar_t* dllPath)
{
// 프로세스 열기
HANDLE hProcess = OpenProcess(
PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION |
PROCESS_VM_WRITE | PROCESS_VM_READ,
FALSE, processId
);
if (hProcess == NULL) {
printf("프로세스 열기 실패! 에러: %d\n", GetLastError());
return false;
}
// DLL 경로를 대상 프로세스에 쓰기
size_t pathSize = (wcslen(dllPath) + 1) * sizeof(wchar_t);
void* pRemotePath = VirtualAllocEx(
hProcess, NULL, pathSize,
MEM_COMMIT, PAGE_READWRITE
);
if (pRemotePath == NULL) {
CloseHandle(hProcess);
return false;
}
if (!WriteProcessMemory(hProcess, pRemotePath, dllPath, pathSize, NULL)) {
VirtualFreeEx(hProcess, pRemotePath, 0, MEM_RELEASE);
CloseHandle(hProcess);
return false;
}
// LoadLibraryW 주소 얻기
HMODULE hKernel32 = GetModuleHandle(L"kernel32.dll");
FARPROC pLoadLibrary = GetProcAddress(hKernel32, "LoadLibraryW");
// 원격 스레드 생성
HANDLE hThread = CreateRemoteThread(
hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)pLoadLibrary,
pRemotePath, 0, NULL
);
if (hThread == NULL) {
VirtualFreeEx(hProcess, pRemotePath, 0, MEM_RELEASE);
CloseHandle(hProcess);
return false;
}
// 스레드 종료 대기
WaitForSingleObject(hThread, INFINITE);
// 정리
CloseHandle(hThread);
VirtualFreeEx(hProcess, pRemotePath, 0, MEM_RELEASE);
CloseHandle(hProcess);
printf("DLL 주입 성공!\n");
return true;
}
int main()
{
// 대상 프로세스 ID 찾기
DWORD processId = GetProcessIdByName(L"notepad.exe");
if (processId == 0) {
printf("프로세스를 찾을 수 없습니다.\n");
return 1;
}
printf("프로세스 ID: %d\n", processId);
// DLL 주입
if (!InjectDll(processId, L"C:\\MyDll.dll")) {
printf("DLL 주입 실패!\n");
return 1;
}
return 0;
}
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- Windows API 기초 | 메시지 루프와 윈도우 프로시저 완벽 가이드
- Windows API 멀티스레딩 | CreateThread·동기화 완벽 가이드
- C++ 스마트 포인터 | unique_ptr/shared_ptr 메모리 안전 가이드
이 글이 도움이 되셨나요? Windows DLL 생성과 사용을 마스터하는 데 도움이 되었기를 바랍니다!
Windows API 시리즈를 여기서 마무리합니다. 수고하셨습니다! 🎉