Search
Duplicate

C++/ DLL 만들고 사용하기

C++에서 DLL 만들기

일단 새 프로젝트를 DLL(동적 연결 라이브러리)로 만든다.

전처리기

DLL로 내보낼 —외부에서 사용될— 파일의 header 파일에 다음과 같은 전처리문을 추가한다. —cpp 파일에는 별도로 설정할 필요가 없다. cpp는 일반적인 프로젝트와 동일하게 사용하면 된다.
#ifdef (프로젝트 이름)_EXPORTS #define (매크로 이름) __declspec(dllexport) #else #define (매크로 이름) __declspec(dllimport) #endif
C++
이렇게 정의된 전처리문은 DLL로 내보낼 함수와 클래스에 동일하게 사용된다. 이를 사용하는 방법은 아래 코드와 같다. —아직 아래 코드만으로는 내보내기가 안되므로 주의
#ifdef MATHLIBRARY_EXPORTS #define MATHLIBRARY_API __declspec(dllexport) #else #define MATHLIBRARY_API __declspec(dllimport) #endif MATHLIBRARY_API void fibonacci_init(const unsigned long long a, const unsigned long long b); MATHLIBRARY_API bool fibonacci_next(); MATHLIBRARY_API unsigned long long fibonacci_current(); MATHLIBRARY_API unsigned fibonacci_index();
C++
함수 앞에 전처리문 MATHLIBRARY_API를 쓰지 않고 그냥 __declspec(dllexport)라고 써도 무방하다. 다만 그렇게 쓰면 코드가 지저분해 보이기 때문에 MATHLIBRARY_API와 같이 별도의 전처리문을 사용하는 것이다.

extern "C"

__declspec(dllexport) 키워드만으로 DLL export가 되면 편할텐데 추가로 한 단계가 더 필요한데, 그것이 바로 extern "C" 키워드 이다. —extern 키워드에 "C"가 붙은 것은 C 스타일로 한다는 뜻이다. C 스타일로 extern 하는 것이기 때문에 Template에 대해서는 사용할 수 없다.
extern "C" 의 의미는 C++ 컴파일러에게 네임 맹글링(Name Mangling)을 하지 말라고 선언하는 내용인데, 네임 맹글링이란 C++ 링커가 동일한 함수명을 구분하기 위해 적절한 규칙을 이용해서 함수명을 변형하는 것을 말한다.
이게 무슨 말이냐면 C++은 C와 달리 다른 네임스페이스나 클래스에 속한 함수가 같은 이름을 가질 수 있고, 또 같은 범위(scope)에 존재해도 매개변수가 다르면 같은 함수 이름을 가질 수 있는데, 링커는 이것을 모르기 때문에, 컴파일러가 함수명을 바꾸어서 링커가 헷갈리지 않게 한다는 것이다.
그런데 이렇게 네임 맹글링을 해버리면 이번에는 외부 모듈이 링크를 할 수 없기 때문에 —분명 코드에서는 finocacci_init 이라는 이름이었는데, 컴파일러가 멋대로 이름을 바꾸어 버리면 찾을 수가 없는 것이다— 이를 방지하기 위해 네임 맹글링을 하지 말라고 선언하는 것이다.
쉽게 요약하면 외부로 내보낼 함수나 클래스 등에 대해서는 extern "C" 키워드를 써줘야 외부 모듈에서 해당 함수나 클래스를 찾을 수 있게 된다는 것이다.
extern "C" 키워드는 아래와 같이 함수의 가장 앞에 사용한다.
extern "C" MATHLIBRARY_API void fibonacci_init(const unsigned long long a, const unsigned long long b); extern "C" MATHLIBRARY_API bool fibonacci_next(); extern "C" MATHLIBRARY_API unsigned long long fibonacci_current(); extern "C" MATHLIBRARY_API unsigned fibonacci_index();
C++
그런데 이 extern "C" 키워드는 위와 같이 번거롭게 쓰지 않고 아래와 같이 하나로 묶을 수도 있다.
extern "C" { MATHLIBRARY_API void fibonacci_init(const unsigned long long a, const unsigned long long b); MATHLIBRARY_API bool fibonacci_next(); MATHLIBRARY_API unsigned long long fibonacci_current(); MATHLIBRARY_API unsigned fibonacci_index(); }
C++

함수 내보내기

함수 내보내기는 쉽다. 위와 같은 코드를 헤더파일에 선언하고, 해당 헤더 파일을 참조하는 cpp 파일을 만들고, 구현을 작성하면 된다. cpp 파일에서는 위와 같은 전처리문이나 extern "C" 키워드를 쓰지 않아도 된다.
// MathLibrary.h 파일 #pragma once #ifdef MATHLIBRARY_EXPORTS #define MATHLIBRARY_API __declspec(dllexport) #else #define MATHLIBRARY_API __declspec(dllimport) #endif extern "C" { MATHLIBRARY_API void fibonacci_init(const unsigned long long a, const unsigned long long b); MATHLIBRARY_API bool fibonacci_next(); MATHLIBRARY_API unsigned long long fibonacci_current(); MATHLIBRARY_API unsigned fibonacci_index(); }
C++
// MathLibrary.cpp 파일 #include "pch.h" // use stdafx.h in Visual Studio 2017 and earlier #include <utility> #include <limits.h> #include "MathLibrary.h" static unsigned long long previous_; static unsigned long long current_; static unsigned index_; void fibonacci_init(const unsigned long long a, const unsigned long long b) { index_ = 0; current_ = a; previous_ = b; // see special case when initialized } bool fibonacci_next() { // check to see if we'd overflow result or position if ((ULLONG_MAX - previous_ < current_) || (UINT_MAX == index_)) { return false; } // Special case when index == 0, just return b value if (index_ > 0) { // otherwise, calculate next sequence value previous_ += current_; } std::swap(current_, previous_); ++index_; return true; } unsigned long long fibonacci_current() { return current_; } unsigned fibonacci_index() { return index_; }
C++

클래스 내보내기

클래스도 내보내는 방법은 비슷하다. 아래와 같이 사칙연산을 하는 클래스가 있다고 가정하자. —아래 코드는 static 클래스를 예시로 하였지만 static이 아닌 클래스도 동일하다.
// MathUtil.h 파일 #pragma once #ifdef MATHLIBRARY_EXPORTS #define MATHLIBRARY_API __declspec(dllexport) #else #define MATHLIBRARY_API __declspec(dllimport) #endif extern "C" { MATHLIBRARY_API static class MathUtil1 { public: static int Sum(int a, int b); static int Subtract(int a, int b); static int Multiply(int a, int b); static int Divide(int a, int b); } static class MathUtil2 { public: MATHLIBRARY_API static int Sum(int a, int b); MATHLIBRARY_API static int Subtract(int a, int b); static int Multiply(int a, int b); static int Divide(int a, int b); } }
C++
위의 코드에서 MathUtil1 클래스는 MATHLIBRARY_API 전처리문을 클래스 앞에 선언했고, MathUtil2 클래스는 내부의 함수 앞에 선언했다는 점에만 차이가 있다.
상식적으로 예상 할 수 있듯이, 클래스 앞에 MATHLIBRARY_API 를 선언한 MathUtil1에 속한 함수는 모두 내보내기가 되고, MathUtil2 클래스에 속한 함수는 Sum과 Subtract 함수만 내보내기가 된다. —아래에서 DLL을 참조하는 부분을 보면 알겠지만, 외부 모듈에서 MathUtil2의 Multiply, Divide 함수가 보이기는 한다. 다만 컴파일하면 링크 에러가 발생한다.

C++에서 DLL 사용하기

C#에서 C#으로 만든 DLL을 사용할 때는 해당 DLL 파일을 그냥 참조만 하면 되는데, C++에서 C++로 만들어진 DLL을 사용할 때는 그것보다 번거로운 단계가 많다. 심지어 DLL 파일 자체를 참조하지도 않는다.
DLL 프로젝트를 빌드해서 dll과 lib 파일이 만들어졌다고 가정한다. 이 파일과 DLL 프로젝트의 header경로를 DLL을 사용하려는 프로젝트에 추가해 줘야 한다.
1.
헤더 파일 디렉터리 추가
C++에서 C++ DLL을 참조하려면 우선 헤더파일 디렉터리를 추가해야 한다. —dll, lib 파일만추가하는 것으로는 참조가 안 된다.
DLL을 사용하려는 C++ 프로젝트에서 속성 페이지를 열고 왼쪽 메뉴에서 'C/C++ → 일반(General)'을 선택한다.
오른쪽 화면에서 '추가 포함 디렉터리(Additional Include Directories)'을 편집하여 참조하려는 DLL 프로젝트의 Header 파일이 있는 경로를 추가한다. (상대 경로를 사용할 수 있으면 상대 경로를 쓰면 좋다. 아래 이미지의 ..\은 현재 디렉토리 기준 상위 디렉토리를 의미한다)
2.
추가 라이브러리 디렉터리 추가
다음으로 DLL의 프로젝트의 lib 디렉터리를 추가한다.
속성 페이지의 '링커(Linker) → 일반(General)' 을 선택한 후, 오른쪽 화면에서 '추가 라이브러리 디렉터리(Additional Library Directories)'를 편집해서 DLL 프로젝트의 빌드된 lib 파일이 존재하는 디렉터리를 추가한다.
3.
lib 파일 추가
다음으로 DLL 프로젝트의 lib 파일을 추가한다. —dll 파일이 아니라 lib 파일을 추가한다.
속성 페이지의 '링커(Linker) → 입력(Input)'을 선택한 후, 오른쪽 화면에서 '추가 종속성(Additional Dependencies)'을 편집해서 DLL 프로젝트의 lib 빌드 파일의 파일 이름을 추가한다.
이렇게 3가지 —header 파일 디렉터리 추가, lib 파일 디렉터리 추가, lib 파일 추가— 를 했다면, DLL로 내보낸 파일을 #include 하여 사용할 수 있다.

참조 자료