Operating System/Windows 시스템 프로그래밍 - 기본

DLL

Tony Lim 2023. 7. 20. 12:05

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가지 경우에 불리게된다.)
  • 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