오늘은 C++/CLI에 대해 알아보겠습니다. CLI는 C++ 코드를 감싸 C#에서 호출될 수 있도록 도와주는데, C++코드를 감싼 클래스를 Wrapper Class라고 합니다. Wrapper Class가 빌드되어져 dll 파일로 만들어지면 C#은 dll을 참조하여 C++에서 만들어진 기능을 사용할 수 있게 됩니다.
제가 Cli를 사용했던적은 MFC에서 만들어진 툴을 C#으로 개선할때 사용했었습니다. MFC는 게임상에 적용되어있던 라이브러리를 가지고 제작된 툴이기 때문에 C++라이브러리 C#으로 다시 만드는것보다 Wrapping하여 C#에서 사용하는 방향으로 선택했었습니다. 저는 이미 만들어진 라이브러리를 사용했지만, 그래도 라이브러리 생성과정부터 기록해놓는것이 좋을듯 싶어 처음부터 적어보겠습니다.
( Cli와 C# 툴제작은 vs2015에서 이루어졌기때문에 비쥬얼스튜디오 2015버전으로 설명하겠습니다. )
먼저, 정적라이브러리를 만들어보겠습니다.
프로젝트 생성에서 'C++ > win32 콘솔 응용 프로그램'을 선택합니다. 프로젝트의 이름은 NativeCode로 하겠습니다.
확인을 누르면 응용 프로그램 설정창이 뜨게됩니다. 정적 라이브러리를 만들어야 하기때문에 '응용 프로그램 종류 > 정적 라이브러리' 로 변경해줍니다. 그리고 마침을 누르면 프로젝트가 생성이 됩니다.
데이터를 연산하는 간단한 클래스를 하나 만들어보겠습니다. 클래스 이름은 NativeClass로 추가하겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
#pragma once
class NativeClass
{
public:
NativeClass();
~NativeClass();
public:
void SetData(int data);
void SetAdd(int data);
int GetData(void);
private:
int m_iData;
};
| cs |
NativeClass.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
#include "NativeClass.h"
NativeClass::NativeClass() : m_iData(0)
{
}
NativeClass::~NativeClass()
{
}
void NativeClass::SetData(int data)
{
m_iData = data;
}
void NativeClass::SetAdd(int data)
{
m_iData += data;
}
int NativeClass::GetData(void)
{
return m_iData;
}
| cs |
NativeClass.cpp
아~~~~~~~~~~주 간단하지요. Int형 변수를 하나 가지고 있고, SetData함수를 이용해 데이터값을 변경하고 SetAdd함수를 통해 현재 데이터에 값을 더할 수 있는 함수를 추가했습니다. 그리고 GetData함수를 추가하여 멤버변수의 데이터를 가져오도록 추가했습니다.
이제 빌드에 성공했다면,
이제 빌드에 성공했다면,
위와같이 라이브러리 파일이 생성됩니다.
프로젝트 속성에서 디렉토리를 따로 지정하지 않았다면 현재 Configuration에 지정된 이름으로 프로젝트 폴더안에 폴더가 생성될것입니다. 저는 Debug이기 때문에 Debug폴더에 dll이 생성되겠지요.
정적 라이브러리가 생성되었으니 이제 정적 라이브러리를 래핑할 클래스를 만들어보겠습니다.
'C++ > CLR > 클래스 라이브러리'의 새 프로젝트를 추가해줍니다. 프로젝트 이름은 CLibraryWrapping이라 하겠습니다.
그럼 기본적으로 프로젝트이름의 파일이 추가되어있고, 열어보시면 네임스페이스 안에 클래스가 추가되어있죠.
1
2
3
4
|
namespace CLibraryWrapping {
public ref class Class1
{
| cs |
클래스 이름을 NativeWrap으로 변경하겠습니다. 이제 이 클래스 안에서 C++ 라이브러리 코드를 호출해 C#에서 호출될 수 있도록 작업하겠습니다.
먼저 정적라이브러리와 헤더파일들을 읽을 수 있도록 디렉토리 설정을 해주어야 합니다.
헤더파일 참조를 위한 방법은 크게 두가지로 CLibraryWrapping 프로젝트에 NativeCode 프로젝트에서 작성한 NativeClass파일을 추가하여 참조하는 방법과 프로젝트 속성에서 해당 파일을 찾을 수 있게 디렉토리를 설정해주는 방법이 있습니다 . 저는 프로젝트 속성에서 디렉토리를 지정해주는 방향으로 소개하겠습니다.
CLibraryWrapping 프로젝트 속성에 들어가서 '구성 속성 > C/C++ > 일반 '으로 가시면 오른쪽 상단에 '추가 포함 디렉터리'란에 NativeClass 파일의 위치를 추가해줍니다. 이렇게 되면 헤더파일 참조가 될 수 있도록 설정이 끝났습니다. 이제 라이브러리를 참조하도록 연결해보겠습니다.
라이브러리 참조에도 두가지 방법이 있습니다.
첫번째로 프로젝트 우클릭시 '추가 > 참조' 로 하는방법입니다. 이 방법은 매우 간단하죠
그럼 어떤것을 참조할지 추가창이 뜨게됩니다. '프로젝트 > 솔루션'을 보시면 아까 추가했던 'Native Code' 프로젝트가 목록에 보이실겁니다.
그럼 체크를 해주시고 확인을 눌러주시면,
참조에 Native Code가 추가된것을 보실 수 있습니다.
두번쨰는 프로젝트 속성에서 라이브러리 경로 지정과 읽어들일 라이브러리 파일 이름을 설정해주는것입니다.
저는 두번째 방법을 선택해 진행하겠습니다.
CLibraryWrapping 프로젝트의 속성에 들어가서 '링크 > 일반' 을 보시면 '추가 라이브러리 디렉터리' 란이 있습니다. 이 부분에 NativeCode.lib가 있는 경로를 지정해줍니다.
그리고 어떤 라이브러리를 참조할지 추가해주어야 하는데요,
'일반' 아래 '입력'으로 가면 '추가 종속성'란이 있습니다. 이부분에 참조할 라이브러리를 추가해줍니다. 그리고 확인을 누르고, 에러가 없는지 빌드를 한번 해줍니다.
만약 경로나 라이브러리 이름이 잘못되었을 경우 위와 같은 에러가 발생하니 한번 확인해주셔야합니다.
라이브러리 참조가 무사히 잘 되었다면 이제 코드를 작성해보겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#include "NativeClass.h"
namespace CLibraryWrapping {
public ref class NativeWrap
{
// TODO: 여기에 이 클래스에 대한 메서드를 추가합니다.
public:
NativeWrap();
~NativeWrap();
public:
void SetData(int data);
void SetAdd(int data);
int GetData(void);
private:
NativeClass* m_pNativeClass;
};
}
| cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
#include "CLibraryWrapping.h"
namespace CLibraryWrapping {
NativeWrap::NativeWrap()
{
m_pNativeClass = new NativeClass;
}
NativeWrap::~NativeWrap()
{
if (m_pNativeClass != nullptr)
{
delete m_pNativeClass;
m_pNativeClass = nullptr;
}
}
void NativeWrap::SetData(int data)
{
if (m_pNativeClass == nullptr)
return;
m_pNativeClass->SetData(data);
}
void NativeWrap::SetAdd(int data)
{
if (m_pNativeClass == nullptr)
return;
m_pNativeClass->SetAdd(data);
}
int NativeWrap::GetData(void)
{
if (m_pNativeClass == nullptr)
0;
return m_pNativeClass->GetData();
}
}
| cs |
CLibraryWrapping.cpp
NativeCode의 객체를 포인터 타입으로 멤버변수를 추가해줍니다. 그리고 NativeCode의 함수들을 호출하여 데이터를 전달하거나 얻어올 수 있도록 NativeWrap클래스에 함수들을 추가해주고 멤버변수를 통해 NativeCode의 함수를 호출해줍니다.
그럼 C#에서는 NativeWrap를 통해 NativeCode의 기능들을 사용할 수 있게되지요.
다시 빌드를 해줍니다. 컴파일에 성공하면 CLibraryWrapping.dll 이라는 최종적으로 C#에서 사용할 수 있는 dll이 생성됩니다.
이제 마지막단계입니다. C# 프로젝트를 생성하여 dll파일을 이용해 c++코드를 호출해보겠습니다.
'C# > Windows Forms 응용 프로그램' 프로젝트를 생성하겠습니다. 이름은 ManagedCode로 하겠습니다.
바로 dll을 연결해주겠습니다.
C#은 라이브러리 참조가 C++보다 쉽습니다(저만의 생각일수도 있겠…..) C++에서는 라이브러리를 참조하기위해 헤더파일 경로와 라이브러리 경로 라이브러리 이름까지 지정해주었는데, C#에서는 참조 관리자에서 추가만 해주면 됩니다.
프로젝트를 우클릭하면 메뉴창이 나오면 '추가 > 참조' 를 클릭합니다. 그럼 아래와 같은 '참조 관리자' 창이 생성됩니다.
'프로젝트 > 솔루션'을 보시면 CLibraryWrapping 프로젝트와 NativeCode 프로젝트가 보입니다. CLibraryWrapping 라이브러리를 사용할것이기 때문에 CLibraryWrapping 프로젝트만 선택하고 확인을 눌러줍니다.
C#프로젝트를 처음 생성하게 되면 작은 다이얼로그창의 윈폼하나가 떡하니 보이실겁니다. 해당 창은 디자인 창으로 폼에 위젯들을 추가해 툴의 외적인 부분을 만들 수 있는 작업 창입니다. 여기서 어디에 코드를 작성해야할지 모르시는 분들을 위해!
Form1.cs파일을 우클하면 메뉴가 나오는데 메뉴에 '코드 보기'를 누르시면 C#코드를 작성할 수 있는 cs가 열린답니다.
여기에 이제 Wrapper Class를 생성하고 C++의 기능을 사용해보겠습니다.
여기에 이제 Wrapper Class를 생성하고 C++의 기능을 사용해보겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
namespace ManagedCode
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
CLibraryWrapping.NativeWrap WrapperClass = new CLibraryWrapping.NativeWrap();
WrapperClass.SetData(10);
WrapperClass.SetAdd(7);
textBox1.Text = WrapperClass.GetData().ToString();
}
}
}
| cs |
아주 간단합니다. CLibraryWrapping 네임스페이스 안에 NativeWrap타입으로 WrapperClass이름의 객체를 생성해줍니다.
그리고 SetData()를 호출에 10 데이터를 전달하고, SetAdd()를 통해 7을 더해줍니다.
결과값은 17이겠지요,
마지막으로 GetData()를 호출해 데이터를 가져와 폼에 띄워보겠습니다.
Data값이 17이 나왔습니다. 이로써 C#과 C++ 사이의 CLI작업이 자아아알~~ 된것을 확인할 수 있습니다.