C++/기타

[C++] DLL로 class export / import - 1

sseram 2023. 3. 16. 21:38
반응형

-- 전체 코드 맨 하단에 첨부

 

 

당연하게도, 솔루션이 커지면 커질수록 하나의 프로젝트에서 모든 것을 다 할 수는 없다.

 

 

그러다 보니 외부 라이브러리를 가져다 쓰는 경우가 많은데,

항상 가져다 쓰기만 하고, 내가 직접 만든 적은 없어서 그런지 직접 만드려고 하니 상당히 고통스러웠다.

 

 

 

 

그래서 복습할 겸 정리.

 

 

 

 

 

 

먼저 dll 프로젝트를 만든다.

적당히.. baseclass라고 하나를 만들어보자.

 

#pragma once


#include <map>
#include <string>
#include <iostream>

class baseClass {
public:
	std::string myname;
	void init(std::string name);
	int money();
};
#include "pch.h"
#include "SomeBaseClass.h"

void baseClass::init(std::string name)
{
	myname = name;
	std::cout << "hello " + name << std::endl;
}

int baseClass::money()
{
	return myname.size() + 3;
}

 

뭐하는 코드인지는 나도 모른다.

그냥.. 그냥 무언가를 하는 코드이다.

 

 

 

 

그리고 저 class를 dll import를 통해 불러와서 사용할 프로젝트도 하나 만들어 보자.

 

#include <iostream>
#include <thread>
#include <chrono>
#include "../dlltest_cls/SomeBaseClass.h"


int main()
{
    
    /*
    load library

    create class

    use function

    free library    
    */


    return 0;
}

 

 

이런 식으로.

 

어떻게 생겨먹은 class인지는 알아야 하기 때문에, 만들어 둔 헤더 파일만 가져왔다.

 

 

 

 

 

 

 

이렇게 세 개의 파일만 만들었다.

 

 

 

 

 

dll을 main.cpp에서 사용하려면 문제가 몇 가지가 있다.

 

1. class header만 가져왔다. 구현부를 모르는 상태이기 때문에 만들어진 dll에서 찾아보라고 알려줘야 한다.

 

2. class를 생성하고, 소멸시킬 것이다.

    즉 함수를 가져와서 사용만 하면 안되고, 생성자/ 소멸자를 제대로 불러 줘야 한다.

 

 

 

 

 

 

 

 

2번을 해결하기 위해 SomeBaseClass.h를 변경해 보자.

 

#pragma once


#include <map>
#include <string>
#include <iostream>


#ifdef _DLL_EXPORT
	#define CLS_TYPE __declspec (dllexport)
#else if
	#define CLS_TYPE __declspec (dllimport)
#endif

class CLS_TYPE baseClass {
public:
	baseClass();
	~baseClass();

	std::string myname;
	virtual void init(std::string name);
	virtual int money();
};

using CONSTRUCTOR = baseClass* (*)();
using DESTRUCTOR = void (*)(baseClass*);

#ifdef __cplusplus
extern "C"
{
#endif
	CLS_TYPE baseClass* BC_constructor();
	CLS_TYPE void BC_destructor(baseClass* cls);
#ifdef __cplusplus
}
#endif

 

위 baseclass 헤더 코드를 다음과 같이 바꾸어 주었다.

 

 

 

전처리기가 뭔가가 많이 추가 되었는데... 먼저 위쪽부터 보자.

#ifdef _DLL_EXPORT
	#define CLS_TYPE __declspec (dllexport)
#else if
	#define CLS_TYPE __declspec (dllimport)
#endif

class CLS_TYPE baseClass {
public:
	baseClass();
	~baseClass();

	std::string myname;
	virtual void init(std::string name);
	virtual int money();
};

 

_DLL_EXPORT라는 것이 정의가 되어 있냐 아니냐에 따라서

CLS_TYPE 이 변화한다.

 

그리고 이 CLS_TYPE은 baseClass 앞에 키워드로 붙게 되었다.

 

 

전처리기에 따라 dllexport 인지, dllimport인지 알려주게 된다.

 

 

dlltest_cls 에서는 dllexport가 되어야 하고,

cppTest 에서는 dllimport가 되어야 하므로

 

dlltest_cls의 속성 -> C/C++ -> 전처리기에 들어와서

이런 식으로 _DLL_EXPORT를 추가해 주도록 하자!

 

그러면 dlltest_cls 에서는 dllexport로,

이 헤더를 가져다 쓰는 곳에서는 dllimport로 컴파일 하게 될 것이다.

 

 

밑에 init / money라는 함수는 virtual로 선언을 해 주었다.

 

이렇게 선언해 주지 않으면, main.cpp 에서 이 헤더를 볼텐데,

컴파일러는 virtual이 아닌데 어딘가에 정의가 되어있지 않다고 build error를 내뿜을 것이다!

 

그것을 위해서 virtual로 만들어 주었다.

 

 

 

 

 

using CONSTRUCTOR = baseClass* (*)();
using DESTRUCTOR = void (*)(baseClass*);

#ifdef __cplusplus
extern "C"
{
#endif
	CLS_TYPE baseClass* BC_constructor();
	CLS_TYPE void BC_destructor(baseClass* cls);
#ifdef __cplusplus
}
#endif

밑에 부분은 이렇게, constructor / destructor 함수를 선언을 해 주었다.

이 두 개의 함수는 .cpp 쪽에서 정의를 할 것이다.

 

함수를 export할 때, 받는 쪽에서 어떤 타입인지는 알아야 하니 using을 통하여 미리 타입도 정의해 두었다.

 

 

 

 

cpp는

 

#include "pch.h"
#include "SomeBaseClass.h"

baseClass::baseClass() {
	std::cout << "create!" << std::endl;
	myname = "default";
}

baseClass::~baseClass() {
	std::cout << "destroy!" << std::endl;
}

void baseClass::init(std::string name)
{
	myname = name;
	std::cout << "hello " + name << std::endl;
}

int baseClass::money()
{
	return myname.size() + 3;
}

CLS_TYPE baseClass* BC_constructor() {
	return new baseClass;
}

CLS_TYPE void BC_destructor(baseClass* cls) {
	delete cls;
}

이런 식으로 구현해 주었다.

거의.. 뭐 제대로 되나 로그 찍는 수준으로.

 

이 이후에 빌드하면

 

성공!

 

저 dll의 path를 잘 복사해 두자.

 

 

 

 

 

 

 

 

 

 

1번을 해결하기 위해 main.cpp를 변경해 보자.

 

 

dll을 가져오는 방식에는 명시적 / 암시적이 있는데, 여기서는 명시적 방법을 사용한다.

#include <Windows.h>
#include <iostream>
#include <thread>
#include <chrono>
#include "../dlltest_cls/SomeBaseClass.h"
#include <libloaderapi.h>



int main()
{
    std::string path{ R"(dll 경로)" };

    // load library
    auto HModule = LoadLibraryA(path.c_str());

    // get constructor / destructor function
    auto ClsConst = reinterpret_cast<CONSTRUCTOR>(GetProcAddress(HModule, "BC_constructor"));
    auto ClsDest = reinterpret_cast<DESTRUCTOR>(GetProcAddress(HModule, "BC_destructor"));

    // create class
    std::shared_ptr<baseClass> wow = std::shared_ptr<baseClass>(ClsConst(), ClsDest);

    // use function
    wow->init("myname");
    std::cout << wow->money() << std::endl;

    // delete class
    wow.reset();

    // free library
    FreeLibrary(HModule);

    return 0;
}

 

 

 

별 특이사항은 없다.

path를 통하여 dll을 가져오고,

 

constructor / destructor를 가져와 shared_ptr을 만든다.

 

그 후, 가져온 class를 열심히 사용하고 delete 한다.

 

마지막으로 프로그램을 끝내기 전에 Free Library를 해 준다.

 

 

 

이렇게 하고 실행을 하면?

 

 

 

짜잔!

 

 

 

이것을 이용하여 dll 프로젝트 내부에선 class를 상속받아 여러 일을 할 수 있다.

 

그건 2에서...

 

 

 

 

전체 코드->>

더보기

 

main.cpp

#include <windows.h>
#include <iostream>
#include <thread>
#include <string>
#include <chrono>
#include "../dlltest_cls/SomeBaseClass.h"
#include <libloaderapi.h>

int main()
{
    std::string path{ R"(C:\Users\kideok hwang\source\repos\cppTest\x64\Debug\dlltest_cls.dll)" };

    // load library
    auto HModule = LoadLibraryA(path.c_str());

    // get constructor / destructor function
    auto ClsConst = reinterpret_cast<CONSTRUCTOR>(GetProcAddress(HModule, "BC_constructor"));
    auto ClsDest = reinterpret_cast<DESTRUCTOR>(GetProcAddress(HModule, "BC_destructor"));

    // create class
    std::shared_ptr<baseClass> wow = std::shared_ptr<baseClass>(ClsConst(), ClsDest);


    // use function
    wow->init("myname");
    std::cout << wow->money() << std::endl;

    // delete class
    wow.reset();

    // free library
    FreeLibrary(HModule);

    return 0;
}

 

 

SomeBaseClass.h  (전처리기 사용 필수!)

#pragma once

#include <map>
#include <string>
#include <iostream>

#ifdef _DLL_EXPORT
	#define CLS_TYPE __declspec (dllexport)
#else if
	#define CLS_TYPE __declspec (dllimport)
#endif

class CLS_TYPE baseClass {
public:
	baseClass();
	~baseClass();

	std::string myname;
	virtual void init(std::string name);
	virtual int money();
};

using CONSTRUCTOR = baseClass* (*)();
using DESTRUCTOR = void (*)(baseClass*);

#ifdef __cplusplus
extern "C"
{
#endif
	CLS_TYPE baseClass* BC_constructor();
	CLS_TYPE void BC_destructor(baseClass* cls);
#ifdef __cplusplus
}
#endif

 

SomeBaseClass.cpp

#include "pch.h"
#include "SomeBaseClass.h"

baseClass::baseClass() {
	std::cout << "create!" << std::endl;
	myname = "default";
}

baseClass::~baseClass() {
	std::cout << "destroy!" << std::endl;
}

void baseClass::init(std::string name)
{
	myname = name;
	std::cout << "hello " + name << std::endl;
}

int baseClass::money()
{
	return myname.size() + 3;
}

CLS_TYPE baseClass* BC_constructor() {
	return new baseClass;
}

CLS_TYPE void BC_destructor(baseClass* cls) {
	delete cls;
}

 

 

 

 

 

2023.03.20 - [C++/기타] - [C++] DLL로 class export / import - 2

 

반응형