DLL (Dynamic Linking Library)

  • 최초 작성일: 2022년 1월 28일(금)

목차

[TOC]

개념

  • 일반적으로 규모가 큰 응용 프로그램은 실행 바이너리(.exe) 파일 하나로 구성되지 않고 매우 다양한 DLL 파일을 갖는다.
  • 이유는 각각의 구조화된 기능을 별도의 모듈로 분리 설계하여 여러 개발자가 동시에 작업을 진행하기 때문이다.
  • 물론 다른 이유가 더 있겠지만, DLL을 만들고 활용하는 능력은 공동 작업을 진행해야 하는 경우 너무도 중요한 기술이 된다.

DLL과 DllMain() 함수

DLL (Dynamic Lionking Library)이란 동적으로 링크해서 사용하는 라이브러리를 말한다. 동적 (Dynamic)이란 런타임 (Runtime)과 비슷한 의미인데 프로그램 실행 도중 링크된다는 의미이다.


우리가 프로그램을 작성하고 빌드(build) 할 때 .c 혹은 .cpp 파일을 빌드해 .obj 파일을 생성하고, 관련 오브젝트(object) 모아 링크해서 .exe 파일을 만든다.

여기서 링크는 빌드 시점에 이루어지는 정적(static)인 것이다. 따라서 링크를 실패하면 실행 바이너리 파일을 만들 수 없다.


그러나 DLL은 빌드 시점에 링크되지 않고, 빌드된 실행 바이너리 파일이 실행 중에 코드에 따라 외부 라이브러리에 링크되거나 언링크(unlink) 되는 라이브러리이다.

그래서 기존의 정적 라이브러리와는 다른 개념으로 실행 바이너리 파일 크기를 줄이거나 특정 코드를 모듈화하여 은닉하거나 객체화하는 장점이 있다.


DLL은 코드의 논리적인 흐름이 함수 단위로 제한된다는 차이점을 갖고 있어서 실제 구현도 임의의 사용자 정의 함수만 열거해서 만든다.

DllMain() 함수가 있는데, 이 함수는 DLL이 링크 / 언링크될 때 호출되는 함수이다.


실습

암시적(implict) 링크

DLL의 암시적 링크는 정적 라이브러리와 유사한 특징을 갖는다. 실행 바이너리가 런타임에 라이브러리를 동적으로 링크하는 것은 동적 라이브러리의 특징에 해당하지만,

최초 실행 시에 자동으로 이뤄질 뿐 아니라 DLL이 경로에 없으면 아예 실행조차 안되기 때문이다. 이런 측면에선 정적 라이브러리와 크게 다르지 않아 보인다.

그럼에도 불구하고, 암시적 링크를 사용하는 이유는 라이브러리를 로드하는 코드를 개발자가 직접 작성할 필요가 없기 때문이다.


그럼 실습을 시작해보자. 먼저, MFC DLL 프로젝트를 생성하자. 프로젝트명은 임의로 demoDLL로 정했고,

응용 프로그램 설정(Application Settings)에서 MFC extension DLL 항목을 선택한다.

image

image

image


이렇게 프로젝트 생성이 끝났으면, Class View에서 DllMain() 함수를 열어보자. 그러면 아래와 같은 코드를 확인할 수 있다.

extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
	// Remove this if you use lpReserved
	UNREFERENCED_PARAMETER(lpReserved);

	if (dwReason == DLL_PROCESS_ATTACH)
	{
		TRACE0("DllDemo.DLL Initializing!\n");

		// Extension DLL one-time initialization
		if (!AfxInitExtensionModule(DllDemoDLL, hInstance))
			return 0;
      
      new CDynLinkLibrary(DllDemoDLL);

	}
	else if (dwReason == DLL_PROCESS_DETACH)
	{
		TRACE0("DllDemo.DLL Terminating!\n");

		// Terminate the library before destructors are called
		AfxTermExtensionModule(DllDemoDLL);
	}
	return 1;   // ok
}


DllMain() 함수는 DLL을 로드하거나 언로드할 때 호출되는 함수이다.

다만, 호출 시점에 따라 dwReson 인자가 달라지는데,

DLL_PROCESS_ATTACH는 라이브러리가 로드되는 시점을,

DLL_PROCESS_DETACH는 언로드되는 시점을 의미한다.

그리고 hInstance 인자는 DLL의 핸들로 라이브러리가 로드된 가상 메모리 상의 기본 주소가 된다.


그럼 이제 추가로, DllMain() 함수 밑에 아래와 같이 GetModulePath()라는 함수를 추가로 작성해주자.

__declspec(dllexport)
BOOL WINAPI GetModulePath(CString& strPath)
{
	TCHAR szBuffer[MAX_PATH];
	::ZeroMemory(szBuffer, sizeof(szBuffer));
	::GetModuleFileName(NULL, szBuffer, MAX_PATH);

	for (int i = lstrlen(szBuffer) - 1; i >= 0; --i)
	{
		if (szBuffer[i] == '\\')
		{
			int j = lstrlen(szBuffer) - 1;
			for (; j >= i; --j)
			{
				szBuffer[j] = NULL;
			}

			if (szBuffer[j] == ':') szBuffer[j + 1] = '\\';

			strPath = szBuffer;
			return TRUE;
		}
	}

	return FALSE;
}


::GetModuleFileName() API 함수는 실행 바이너리 파일의 실제 경로를 알아내서 버퍼에 저장하는 역할을 한다.

그 이후의 코드는 경로 정보에서 확장자를 포함한 파일명을 떼어 내고 파일이 저장된 폴더 경로 추출을 위한 내용이다.

이렇게 추출한 정보는 참조로 전달되는 strPath 인자에 저장된다.


__declspec(dllexport) 선언은 이 함수가 외부로 export되어 사용하는 함수라는 것을 명시한 것이다.