C++에서 간단한 dll를 만들어 C++에서 사용하기
MS사에서 제공하는 MVC++ 컴파일러는 모던 C 표준 지원을 제대로 하지 않는다. 이러한 이유 때문인지 프로그래밍 입문 과목중에 C/C++이라는 과목이 존재하는 듯 하다.
MVC++에서는 함수를 다른 프로그램에 사용할 수 있도록 내보내거나 들여오는 매크로를 지원한다. MSDN에서는 간단한 예시를 제공한다.
내보내기 예시)
우선 MathFuncsDll 이름의 프로젝트를 생성한다. 생성할때 dll프로젝트로 생성해야 하는데, VS 2017부터는 기존 마법사로 생설할 경우 "Visual C++" 하위의 "Windows 데스크톱" 항목의 Windows 데스크톱 마법사에서 생성하면된다. 마법사가 귀찮다면, DLL로 생성하면 된다.
// MathFuncsDll.h
#ifdef MATHFUNCSDLL_EXPORTS
#define MATHFUNCSDLL_API __declspec(dllexport)
#else
#define MATHFUNCSDLL_API __declspec(dllimport)
#endif
namespace MathFuncs
{
// This class is exported from the MathFuncsDll.dll
class MyMathFuncs
{
public:
// Return a + b
static MATHFUNCSDLL_API double Add(double a, double b);
// Return a - b
static MATHFUNCSDLL_API double Subtract(double a, double b);
// Return a * b
static MATHFUNCSDLL_API double Multiply(double a, double b);
// Return a / b
// Throws const std::invalid-argument & if b is 0
static MATHFUNCSDLL_API double Divide(double a, double b);
};
}
위의 매크로 함수를 정의 하는데, dll프로젝트일 경우 컴파일러의 전처리 옵션에 [대문자 프로젝트 명] + "_EXPORTS"(여기서는 "MATHFUNCSDLL_EXPORTS")의 형태의 매크로가 자동으로 추가 되어 있다. 이러한 특징을 이용해서 헤더 파일에 DLL 프로젝트의 경우 내보내는 매크로 함수"__declspec(dllexport)"가 되고 그외 프로젝트에서는 가져오는 매크로 함수 "__declspec(dllimport)"로 치환이 된다.
이렇게 만들어진 헤더 파일은 나중에 참조하는 실행 프로그램에서도 include 해야 한다.
// MathFuncsDll.cpp
#include <iostream>
#include "mathfuncsdll.h"
using namespace std;
namespace MathFuncs
{
double MyMathFuncs::Add(double a, double b)
{ return a + b; }
double MyMathFuncs::Subtract(double a, double b)
{ return a - b; }
double MyMathFuncs::Multiply(double a, double b)
{ return a * b; }
double MyMathFuncs::Divide(double a, double b)
{
if(b == 0)
{
throw invallid_argument("b cannot be zero");
}
return a / b;
}
}
cpp 파일까지 작성하였다면 빌드를 하면 된다. 이때 "win32"로 했는지 "x64"으로 했는지 구분하는 것이 좋다. 에러가 없다면, 가능하면 Release로 빌드하여 최적화를 하자.
들여오기 사용 예시1)
실행하는 프로젝트의 경우 선행 작업이 조금 필요하다. 우선 새로운 프로젝트를 만들자 이름은 대략 "MyExceRefDll"로 빈프로젝트로 만들어도 무난하다. 앞의 프로젝트에서 빌드한 파일중에 .lib와 dll 파일이 생성된 것을 알 수 있다. 빌드와 링크과정에는 헤더파일과 lib 파일이 필요하다. 헤더파일은 솔루션 탐색기에 추가를 시켜야 한다. "솔루션 탐색기 -> 헤더파일 -> 우클릭 -> 추가 -> 기존항목 -> 헤더파일"
// MyExecRefsDll.cpp
#include <iostream>
#include "MathFuncsDll.h"
using namespace std;
int main(void)
{
double a = 7.4;
int b = 99;
cout << "a + b = " << MathFuncs::MyMathfuncs::Add(a, b) << endl;
cout << "a - b = " << MathFuncs::MyMathfuncs::Subtract(a, b) << endl;
cout << "a * b = " << MathFuncs::MyMathfuncs::Multiply(a, b) << endl;
cout << "a / b = " << MathFuncs::MyMathfuncs::Divide(a, b) << endl;
try
{
cout << "a / 0 = " << MathFuncs::MyMathFuncs::Divide(a, 0) << endl;
}
catch(const invalid_argument & e)
{
cout << "Caught exception: " << e.what() << endl;
}
return 0;
}
lib파일은 "솔루션 탐색기 -> 프로젝트 -> 우클릭 -> 속성" 에서 "링커 -> 일반" 항목중 "추가 라이브러리 디렉토리"에 lib파일이 있는 경로를 추가 시킨다. 이후 "링커 -> 입력" 항목중 "추가 종속성"에 추가할 lib 파일명을 적어주면 된다. 이 예시에서는 "MathFuncsDll.lib"를 적어주면 된다.
링크 옵션을 마치면, 컴파일을 한다. 이때 공부할때 습관적으로 F5키로 실행까지 하는데, 실행파일이 있는 폴더에 dll 파일(여기서는 "MathFuncsDll.dll")을 넣고 실행해야 한다. 별다른 오류 메시지 창이 안보이면, 잘 된거다.
들여오기 사용 예시2)
앞의 예시는 MVC++ 컴파일러의 옵션 값을 넣어서 dll을 참조했다면, 이번에는 옵션 값을 수정하지 않고 참조 해보자. 앞의 예시와 동일하게 헤더파일을 솔루션 탐색기에서 인식할 수 있도록 추가 해야 한다.
실행 프로그램의 소스 코드중에 MVC++에서 사용하느 매크로 함수 "#pragma comment()"를 사용하여 참조할 lib 파일의 경로와 파일명을 알려주면 된다. 그럼, 앞의 "예시1"의 컴파일 옵션을 컴파일러가 해당 소스를 읽을때 컴파일 옵션을 설정하기 때문에 따로 속성창을 열어서 수정하지 않아도 된다.
// MyExecrefsDll.cpp
// ...
#pragma comment(lib, "../../MathFuncsDll/Debug/MathFuncsDll.lib");
// ...
정리
원리상 컴파일 옵션으로 작성이 되는 것이고 그 역할을 하는 매크로 함수를 컴파일러 수준에서 제공을 해주는 것을 이용하는 것이다. 이는 헤더파일이 없어도 참조함수를 선언해서 사용할 수 있지만, 복잡하고 많은 수의 함수를 일일히 선언해주는 것은 비 생산적인 일이 때문에 헤더파일은 그대로 사용하는데, 이 때문에 타사의 상용 라이브러리(흔히 dll)를 사용할 때 헤더파일은 대부분 같이 제공해준다.
여기서는 간단하게 작성했지만, 가능하면 정신건강을 위해서 정리를 하는게 좋다. 들여오기 예시1, 2는 각각 장단점이 있으니 자신이 우선시 하거나 취향대로 사용하면 될 것 같다.
참조자료
MSDN VS2015(한글)
MSDN VS2015(영문)
DLL 작성 및 사용하기(한글 블로그)