DLL 기본 이론
정적 / 동적 라이브러리 소개
- 라이브러리는 쉽게 생각해 함수만 따로 모은 실행 파일
- 함수라는 표현보다 API 라고 부르는 경우가 많고 특정 환경에 맞춘 종합적인 API 묶음을 SDK라 함
- 정적 라이브러리는 실행파일과 link시 결합되어 한 실행파일로 합성
- 동적 라이브러리는 실행 파일과 Link시 합쳐지지 않고 독립적인 실행파일 형태로 생성되며 실행파일이 실행 될 때 "동적으로" 결합
동적lib (dll) = 런타임에 바인딩(링크) 된다. Window API LoadLibrary로 로딩하게된다.
묵시적 로딩 DLL 라이브러리 개발
#include "pch.h"
#include <stdio.h>
__declspec(dllexport) void WINAPI DllTestFunction(int);
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
void WINAPI DllTestFunction(int nParam)
{
printf("DLL TEST: DllTestFunction(param: %d)\n", nParam);
}
DllMain함수는 4가지 case에서 불리게 된다. 프로세스 , 스레드에서 정적로딩 및 해제 할 때 불리게 된다.
해당 코드를 빌드하게 되면 DllTestFunction을 dll형식으로 가져다가 쓸 수 있게 해준다.
#include <iostream>
#include <windows.h>
#pragma comment(lib, "..\\x64\\Debug\\DllTest.lib")
void WINAPI DllTestFunction(int nParam); //선언
int main()
{
DllTestFunction(5);
}
다른 app에서 이런식으로 호출을 할 수 있게 된다.
보통은 #pragma~ 선언 부분까지를 header file로 제공을 하게 된다.
한 solution안에 project들을 여러개 두었을 때 시작 project에 따라 의존성 빌드 순서가 바뀌게됨으로 주의해야한다.
명시적 로딩 DLL 라이브러리 개발
묵시적인 방식은 알아서 dll 을 로딩하게 되지만 만약 dll이 없으면 프로그램이 실행 조차 되지 않는다.
명시적 로딩 관련 핵심 API
- LoadLibrary(), GetModuleHandle()
- 라이브러리를 개발하는 입장에서
- FreeLibrary()
- DllMain()
- 가져다 쓰는쪽에서 Load/Free 할때 마다 불리게된다.
(Process , thread 가 시작하고 끝날때, 총 4가지 경우에 불리게된다.)
- 가져다 쓰는쪽에서 Load/Free 할때 마다 불리게된다.
- GetProcAddress()
- 로드한 라이브러리르의 함수의 주소를 알아내는 것
- 반환 값이 함수 포인터다.
#include "pch.h"
#include <tchar.h>
extern "C" __declspec(dllexport) DWORD WINAPI MyDllFunction(void);
extern "C" __declspec(dllexport) void WINAPI SetStaticTest(int);
extern "C" __declspec(dllexport) int WINAPI GetStaticTest(void);
extern "C" __declspec(dllexport) int g_nTest;
extern "C" __declspec(dllexport) void WINAPI SetTlsData(int);
extern "C" __declspec(dllexport) int WINAPI GetTlsData(void);
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
OutputDebugString(_T("DLL Sample: DLL_PROCESS_ATTACH"));
break;
case DLL_THREAD_ATTACH:
OutputDebugString(_T("DLL Sample: DLL_THREAD_ATTACH"));
break;
case DLL_THREAD_DETACH:
OutputDebugString(_T("DLL Sample: DLL_THREAD_DETACH"));
break;
case DLL_PROCESS_DETACH:
OutputDebugString(_T("DLL Sample: DLL_PROCESS_DETACH"));
break;
}
return TRUE;
}
DWORD WINAPI MyDllFunction(void)
{
OutputDebugString(_T("DLL Sample: MyDllFunction()"));
return 0;
}
int g_nTest = 0;
void WINAPI SetStaticTest(int nParam)
{
g_nTest = nParam;
}
int WINAPI GetStaticTest(void)
{
return g_nTest;
}
__declspec(thread) int g_tlsData = 0;
void WINAPI SetTlsData(int nParam)
{
g_tlsData = nParam;
}
int WINAPI GetTlsData(void)
{
return g_tlsData;
}
extern "C" 를 선언해줌으로써 함수이름에 name mangling 이 일어나지 않도록해야한다.
library를 호출하는 입장을 고려한것이다.
WINAPI 는 __stdcall을 재정의한것이다.
void CLibLoaderDlg::OnBnClickedBtnLoad()
{
m_hDll = ::LoadLibrary(_T("DllSample.dll"));
if (m_hDll == NULL)
{
AfxMessageBox(_T("Failed to load DLLSample.dll"));
return;
}
}
void CLibLoaderDlg::OnBnClickedBtnApiCall()
{
if (m_hDll == NULL)
{
OutputDebugString(_T("m_hDll == NULL"));
return;
}
DWORD(WINAPI * pfMyDllFunc)(void) = NULL;
pfMyDllFunc = (DWORD (WINAPI*)(void))::GetProcAddress(
m_hDll, "MyDllFunction");
pfMyDllFunc();
}
m_hDll으로 dll을 LibraryLoad한후에 거기서 GetProcAddress 에서 원하는 함수이름을 넘겨서 함수 포인터를 받아온다.
DllMain이 호출될떄마다 로그를 남긴 결과이다.
DLL 작성 시 주의사항
- DLL 함수 내부에서 정적 지역 변수사용 및 전역변수 선언 및 정의 시 반드시 멀티스레드 환경을 고려 (TLS적용)
- client가 여러 다른 스레드에서 호출되는 경우를 고려해야함
- Exe에서 동적 할당한 메모리를 DLL에서 해제 하지 말 것
- 메모리 동적할당한곳에서 해제하는것이 맞다
- DllMain() 함수에서 너무 많은 작업을 하지 말 것
- DLL과 Exe 의 빌드 모드는 반드시 일치시켜서 링크
- DLL 모듈을 타인에게 제공 시 반드시 Debug , Release 빌드를 구분해 제공
- 원할 디버깅을 위한 심폴 파일 제공
'Operating System > Windows 시스템 프로그래밍 - 기본' 카테고리의 다른 글
프로세스 관리 (0) | 2023.07.18 |
---|---|
메모리 시스템 (0) | 2023.07.15 |
Win32 파일 입/출력 (0) | 2023.07.10 |
스레드 동기화 (0) | 2023.07.07 |
스레드 생성 및 제어 (0) | 2023.07.05 |