DLL (Dynamic Link Library)

C++ 2010. 10. 27. 15:22
 
Visual C++ DLL (Dynamic Link Library) 사용 방법 정리
 
 
 
차  례

DLL (Dynamic Link Library)
DLL 종류
DLL 링크 방법
선택의 길
 
Regular DLL(일반 DLL) 작성
Extention DLL(확장 DLL) 작성
 
Implicit Linkage (암시적 연결) 사용
Delay Loading(지연 로딩)
Explicit Linkage(명시적 연결) 사용
 
DEF 파일(.def) - Module Definition File
DllMain() 함수
DLL 디버깅
DLL과 EXE project를 한 곳에서 작업



 
 
 
DLL (Dynamic Link Library)
DLL은 코드만 공유될 뿐 데이터는 독립적으로 관리 됨.
DLL은 자체 스택이 없기 때문에 호출한 프로그램의 스택 사용.
DLL에서 여러 프로세스가 메모리 공유하려면 Memory Mapped File 사용해야 함.
 
 
 
 
 
DLL 종류
1. Regular   DLL(일반 DLL) : C함수 형태로 작성되어 C++, 다른 언어 에서도 사용할 수 있는 범용 DLL
 
2. Extention DLL(확장 DLL) : C++로 작성하며 Visual C++에서만 사용 가능. (Class Export)
 
 
 
 
 
 
DLL 링크 방법
1. implicit linkage(암시적 연결)
.dll, .lib 파일 필요.
project에 lib 추가 후 header(.h) 정의하여 호출.
 
2. Delay Load (지연 로딩)
implicit linkage 와 explicit linkage의 혼합 방식.
특별한 경우에 사용될 수 있음.
 
3. explicit linkage(명시적 연결)
.dll 파일 경로 지정하여 사용.
LoadLibrary(), GetProcAddress(), FreeLibrary() 로 호출.
 
**.참고 : MFC DLL 링크 설정
MFC도 DLL 이므로 linkage  방식 설정 가능.
MFC project property의 "Configuration Properties > General > Use of MFC"에서
Use MFC in a Shared DLL 혹은 Use MFC in a Static Library 로 동적 링크 사용 여부 선택.  

**.참고 : .dll 파일 찾는 순서
1).dll을 사용하는 .exe 파일의 디렉토리
2)process의 현재 디렉토리
3)윈도우 시스템 디렉토리(C:\WINDOWS\system32)
4)윈도우 디렉토리(C:\WINDOWS)
5)환경변수 PATH에 지정된 디렉토리 
 
**. 참고 : extention DLL(확장 DLL) 과 explicit linkage(명시적 연결) 사용
별로 안 좋은 방법인가? 거의 안쓰이는 듯 하다. (시도 해봤는데 소스만 더럽히는 기분)
 
댓글에서 본 방법은
DLL의 class 안에 해당 클래스를 넘겨주는 static 팩토리 메소드 패턴을 구현하고 이 메소드를 노출시킨다.
EXE project 에 .dll을 배포하고 DLL class의 헤더를 include 한다.
LoadLibrary() 로 explicit linkage 한 후 GetProcAddress() 로 해당 메소드를 호출하여
DLL에 정의된 class를 리턴 받는다.
 
서핑으로 찾은 자세한 설명은 http://dolphin.ivyro.net/file/windows_api/dll_class.html 
DLL의 클래스 사용.htm
다운로드
<내용이 사라질까봐 저장 해둠>


 
 
 
선택의 길
언어에 상관 없이 하려면 (C/C++/Delphi/VB) Regular DLL(일반 DLL)
clss 사용 및 VC++ 에서만 사용할거라면 Extention DLL(확장 DLL)
 
많이 이용될 거라면 implicit linkage(암시적 연결)
프로그램에 특화된거라면 explicit linkage(명시적 연결)
특수한 경우 Delay Load(지연 로딩)
 
Extention DLL과 explicit linkage의 조합은 최악이다.
(개인적인 생각 : 너무 지저분해 보인다.)
 
 
 
 
 
Regular DLL(일반 DLL) 작성
1. DLL project 생성
MFC 의 경우 project 생성
"new project > MFC > MFC DLL" 로 project 생성.
"Application Settings" 에서 
"Regular DLL using shared MFC DLL" 혹은 "Regular DLL with MFC statically linked" 선택
 
Win32 의 경우 project 생성
"new project > Win32 > Win32 project" 로 project 생성.
"Application Settings" 에서
"DLL" 선택
("Static Library"는 컴파일시 exe 파일에 포함됨)
 
**. 참고 : Automation 설정
MFC DLL project 생성시
"Application Settings" 에서
"Additional features"의 Automation 을 체크할 경우
IDispatch 인터페이스를 구현하여 pointer 미지원 언어에서도 사용할 수 있게 한 기능.
Dispatch ID를 이용하여 함수 호출. 
 
2. DLL의 C 함수 작성
extern "C" __declspec(dllexport) int plus(int a, int b)
{
 AFX_MANAGE_STATE(AfxGetStaticModuleState()); //MFC에서 호출 가능하도록 설정
 return a+b;
} ​


보통 해더를 DLL 및 EXE(DLL 사용 프로그램) 양쪽에서 모두 사용하기 위해 다음과 같이 정의 한다. (관례적이 된듯)
//XXXX.h
#ifdef DLLBUILD
#define DLLEXPORT extern "C" __declspec(dllexport)
#else
#define DLLEXPORT extern "C" __declspec(dllimport)
#endif
DLLEXPORT int plus(int a, int b); 

//XXXX.cpp
#define DLLEXPORT
#include "XXXX.h"
..​


 
 
 
Extention DLL(확장 DLL) 작성
DLL에서 class 사용시 extention dll 임.
 
1. MFC의 경우
MFC dll은 MFC application에서만 사용 가능.
project 생성
"new project > MFC > MFC DLL" 로 project 생성.
"Application Settings" 에서 
"MFC extension DLL" 선택
 
DLL 작성
class 생성 및 
class 헤더에 AFX_EXT_CLASS 추가.
Test.h
#pragma once
class AFX_EXT_CLASS CTest
{
public:
 CTest(void);
 ~CTest(void);
 int plus(int a, int b);
};

Test.cpp
#include "StdAfx.h"
#include "Test.h"

CTest::CTest(void) {}
CTest::~CTest(void){}
int CTest::plus(int a, int b)
{
 return a+b;
} 
 
 
implecit linkage (암시적 연결) 사용
.h, .dll, .lib 을 읽을 수 있게 위치 시킴.
#include "Test1.h"
#pragma comment(lib, "XXXXX.lib")

CTest test;
int ret = test.plus(1, 2) ;​


 
2. Win32 의 경우
win32/MFC application 에서 사용 가능.
project 생성
"new project > Win32 > Win32 project" 로 project 생성.
"Application Settings" 에서
"DLL" 선택
("Static Library"는 컴파일시 exe 파일에 포함됨)
 
class 생성
 
Test1.h
#pragma once

#ifdef DLLEXPORT
#define CINTDLL __declspec(dllexport)
#else
#define CINTDLL __declspec(dllimport)
#endif

class CINTDLL CTest1
{
public:
 CTest1(void);
 ~CTest1(void);
 int plus(int a, int b);
};


Test1.cpp
#include "StdAfx.h" 
#define DLLEXPORT
#include "Test1.h"

CTest1::CTest1(void) {}
CTest1::~CTest1(void){}
int CTest1::plus(int a, int b)
{
 return a+b;
}
 
 
implecit linkage(암시적 연결) 사용
.dll, .lib 을 읽을 수 있게 위치 시킴.
#include "Test1.h"
#pragma comment(lib, "XXXXX.lib")

CTest test;
int ret = test.plus(1, 2); 
 
 
 
3. DLL 에서 CString 매개변수 사용의 문제
CString을 매개변수로 갖는 DLL의 method 사용시 해당 method를 찾지 못하는 컴파일 에러가 발생한다.
작성한 코드
//호출쪽 코드(MFC)
CString str = _T("test");
cls.configure(str);

//DLL쪽 코드 (Extention DLL)
void CTest::configure(CString& str) {
  //이런 저런 처리..
} 
 
 
에러 내용
error LNK2001: unresolved external symbol "__declspec(dllimport) public: static void __cdecl CTest::configure(... 어쩌고 저쩌고..


원인은 CSttring의 실제 구현이 다르기 때문이다. (혹은 VC++ 버전에 따라 구현이 다를 수 있다.)
(MFC의 CString은 cstringt.h에 있고 Win32의 CString은 atlstr.h 이니 실제로 보면 다른듯..)
 
해결방법으론 CString 대신 LPCTSTR로 주고 받으면 되지만 가급적이면 char 같은 Native한 데이터형이나
STL을 사용하는게 나은 것 같다. (DLL에서 STL 사용은 Export 문제가 찝찝해질 수도 있지만 래핑한다면 뭐..)
어쩃든 꼭 CString을 써야 한다면 LPCTSTR 을 사용하는 방법이 있다.
수정된 코드
//호출쪽 코드(MFC)
CString str = _T("test");
cls.configure((LPCTSTR)str);

//DLL쪽 코드 (Extention DLL)
void CTest::configure(LPCTSTR str) {
    //이런 저런 처리..
}  
 

다음은 동일한 현상을 겪은 분들의 포스팅
(난 이분들보다 3년이 늦었다.. 잃어버린 세월이여..)
 
 
 
 
 
Implicit Linkage (암시적 연결) 사용 1. .dll, .lib 파일 복사 .dll : 글 상단이 "**.참고 : .dll 파일 찾는 순서" 참고.
.lib : "project property > Linker > General > Additional Library Directories" 에 해당 위치 지정
2. 라이브러리 추가
#pragma comment(lib, "XXXXX.lib")
혹은
"project property > Linker > Input > Additional Dependencies"에 .lib 파일 기술 
 
3. 해더 정의 및 사용
extern "C" __declspec(dllimport) int plus(int a, int b);
//편의를 위해 .h 정의 및 배포. ..

int ret = plus(1, 2); 
 
 
 
 
Delay Loading(지연 로딩) implicit linkage 와 explicit linkage의 혼합 방식으로 실제 DLL 함수가 처음 호출될떄 DLL이 로딩된다. 사용방법은 implicit linkage 와 동일하며 project property 설정을 통해 Delay Loading을 설정한다. 설정 방법은 "exe project property > Link > Input > Additional Dependencies" 에 DelayImp.lib 추가. "exe project property > Link > Input > Delay Loaded DLLs" 에 .lib 파일명 추가.
**. 참고 : DLL Unload__FUnloadDelayLoadedDLL2() 함수 "exe project property > Linker > Advanced > Delay Loaded DLL" 에서 Support Unload(/DELAY:UNLOAD) 선택시 __FUnloadDelayLoadedDLL2("XXXXX.dll"); 를 통해 DLL Unload가 가능하다. 
 
 
Explicit Linkage(명시적 연결) 사용
.dll 파일만 필요.
#include <iostream>
#include <tchar.h>
#include <windows.h>

using namespace std;

void printError(TCHAR *errMsg)
{
	TCHAR* lpOSMsg;
	FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,
		GetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPTSTR)&lpOSMsg,
		0,
		NULL);
	_tprintf(_T("[ERROR] %s :: %s\n"), errMsg, lpOSMsg);
	LocalFree(lpOSMsg);
}

int main()
{
	setlocale(LC_ALL, "");
	HMODULE hDLL;
	try
	{
		hDLL = LoadLibrary(_T("TestMFCDLL.dll"));
		if (hDLL == NULL)
			throw _T("LoadLibrary error");

		typedef int (DLL)(int, int);
		DLL* funcDLL = (DLL*)GetProcAddress(hDLL, "plus");
		//DLL* funcDLL = (int (*)(int, int))GetProcAddress(hDLL, "plus");

		int ret = (*funcDLL)(1, 5);
		_tprintf(_T("ret = %d\n"), ret);
	}
	catch (TCHAR* errmsg)
	{
		printError(errmsg);
	}
	if (hDLL) FreeLibrary(hDLL);
}
 

 

 
 
 
 
DEF 파일(.def) - Module Definition File
.dll의 속성 정의 파일.
(.exe에선 더이상 사용되지 않으며 .dll 에서도 사라질 운명)
 
DLL프로젝트명.DEF 파일을 만들어 프로젝트에 포함시킨다.
ex]
LIBRARY DLLProject
EXPORTS
MyFunc1=fucntion1
MyFunc2=function2
 
LIBRARY는 프로젝트 명이며
EXPORTS 에서 function1 함수는 MyFunc1로 이름을 재정의 한다.
 

표현식]     EXPORTS 익스포트명[=내부함수명][@서수]

 
.def 파일 사용하지 않고 export 하는 방법

파일 맨 아래에 다음처럼 정의

#pragma comment(linker, "/export:함수명=_함수명@인자총바이트수")

 

ex)
DLL_METHOD DWORD WINAPI PerfOpen(LPCTSTR lpDevNames) {...}
DLL_METHOD DWORD WINAPI PerfClose(VOID) {...}
 
#pragma comment(linker, "/export:PerfOpen=_PerfOpen@4")
#pragma comment(linker, "/export:PerfClose=_PerfClose@0")

자세한 설명 : http://b4you.net/blog/115?category=0

 
 
 
 
 
DllMain() 함수
DLL이 메모리에 처음 올라올때와 제거될때 호출됨.
 
int DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved);
hInstance : DLL의 인스턴스 핸들
dwReason  : 함수 호출 이유
DLL_PROCESS_ATTACH : implicit linkage인 경우 DLL 사용 프로그램 실행시를 의미한다.
      explicit linkage인 경우 LoadLibrary 실행시를 의미한다.
DLL_THREAD_ATTACH   : 스레드 생성시 마다 호출되어 스레드별 초기화를 수행할 수 있다.
                          첫 스레드는 DLL_PROCESS_ATTACH 로 전달된다.
DLL_THREAD_DETACH   : 스레드 종료시 마다 호출되어 스레드별 종료처리를 수행할 수 있다.
      DLL 로드 전에 생성된 스레드가 있을 경우 DLL_THREAD_DETACH 만
      전달 될 수 있으므로 초기화된 스레드인지 체크 필요.
DLL_PROCESS_DETACH : implicit linkage의 경우 DLL 사용 프로그램 종료시를 의미한다.
      explicit linkage의 경우 FreeLibrary 실행시를 의미한다.
lpReserved: TRUE면 implicit linkage, FALSE면 explicit linkage
 
 
 
 
 
DLL 디버깅
DLL project 디버깅(F5)시 Executable file name 에 DLL 호출 프로그램 지정.
또는
"project property > Debugging > Command" 에서 DLL 호출 프로그램 지정.
 
혹은 아래 "DLL과 EXE project를 한 곳에서 작업"의 방법으로 디버깅 한다.
 
 
 
 
 
DLL과 EXE project를 한 곳에서 작업
(여러개 project를 한곳에서 사용)
1. EXE project 에서 DLL project 추가 "File 메뉴 > Add > Existing Project..."
   (또는 "New Project..." 로 새로 생성)
 
2. Solution Explorer 에서 EXE project 이름 오른쪽 마우스 메뉴에서
   "Set as StartUp project" 선택. (지정된 project는 bold체로 표시됨)
 
3. 각 project property의 "General > Output Directory"를 같은 곳으로 맞추어 동일 폴더에
   .dll, .exe 생성하도록 설정.
   또는
   project property의 "Build Events > Post-Build Event" 에 직접 copy 명령등을 써서
    사용할 수 있다.