개발하는 두더지

[C/C++/C#/UWP] UWP C#에서 MFC C++ DLL 사용하기 본문

C,C++

[C/C++/C#/UWP] UWP C#에서 MFC C++ DLL 사용하기

덜지 2016. 10. 10. 17:53

이번에는 C# UWP 프로젝트 환경에서  C++ 로 빌드된 DLL을 로드하여 그 안의 기능을 호출하는 방법을 배워보도록 하겠습니다.



목차

  • C++ MFC dll 생성
  • C# UWP 프로젝트 생성
  • C#에서 C++ MFC dll 및 extern 메소드 로드 
  • C++ <-> C# 간 타입 캐스팅





C++ MFC dll 생성

MFC DLL 을 생성합니다.


프로젝트 속성에 들어가서 '정적 라이브러리에서 MFC 사용' 과 '유니코드 문자 집합 사용'을 선택합니다.

( 다국어 지원을 위해 유니코드 환경으로 프로젝트를 만드는 것이 좋습니다. )



'TestSample' 이라는 이름으로 MFC Dll을 만들었고 프로젝트의 구성을 아래 사진과 같습니다.


헤더 파일 폴더의 'TestSample.h' 에  C#에서 사용할 메소드들을 정의하고

소스 파일 폴더의 'TestSample.cpp' 에서 그 메소드들을 구현하도록 하겠습니다.


### TestSample.h ###

아래 코드를 TestSample.h 안에 적절한 위치에 작성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct testStruct {
    char strTest[128];
    int intTest;
    BYTE byteTest[64];
} ts;
 
extern "C" __declspec(dllexportvoid test1(void);
extern "C" __declspec(dllexportint test2(int value);
extern "C" __declspec(dllexportchar* test3(char* value);
extern "C" __declspec(dllexportvoid test4(ts* structValue);
extern "C" __declspec(dllexportvoid test5(char* in, char* out);
extern "C" __declspec(dllexport) WCHAR* test6(void);
cs


### TestSample.cpp ###

아래 코드를 TestSample.cpp 안에 적절한 위치에 작성합니다.

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
void test1(void) {
 
}
int test2(int value) {
    return value + 1;
}
char* test3(char* value) {
    int nSize = strlen(value);
    //char* returnStr = (char*)malloc(sizeof(char) * (nSize + 15));
    char* returnStr = (char*)LocalAlloc(LPTR, sizeof(char* (nSize + 15));
    sprintf(returnStr, "%s success", value);
    return returnStr;
}
void test4(ts* structValue) {
    structValue->byteTest[0= 1;
    structValue->intTest = structValue->intTest + 2;
    sprintf(structValue->strTest, "%s success", structValue->strTest);
}
void test5(char* in, char* out) {
    strcpy(out, in);
}
 
WCHAR* test6(void) {
    return L"테스트";
}
cs



프로젝트를 빌드하면 'TestSample.dll' 파일이 생성됩니다. ( Debug/Release 폴더 안에 있음 )


이 파일을 UWP exe파일과 같은 경로에 넣어줘야 합니다.





C# UWP 프로젝트 생성



비어있는 앱(유니버셜 Windows) 프로젝트를 생성합니다.





'App4' 라는 이름의 프로젝트를 생성하면 프로젝트의 구성은 아래와 같습니다.




MainPage.xaml 은 UI를 구성하는 파일이고 옆에 화살표 모양을 클릭하면 MainPage.cs 파일과 연결되어 있습니다.
이 MainPage.cs 가 UI의 ViewContainer 역할을 하고 버튼, 레이블등의 액션이벤트 코드를 작성 및 실행하는 부분입니다.



MainPage.xaml 에서 버튼을 6개를 생성합니다.


Grid 블럭안에 TextBlock과 Button의 컴포넌트들이 있습니다.

x:Name은 각 컴포넌트의 고유 ID 값이고 Content="test1" 은 MainPage.cs 의 test1() 메소드와 연결이되고 이것이 컴포넌트의액션 이벤트입니다.



C#에서 C++ MFC dll 및 extern 메소드 로드 


C++ DLL을 로드할 클래스를 먼저 만들어줍니다.

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
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
using System.Runtime.InteropServices;
 
namespace App4
{
    public struct typeTest
    {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string strTest;
        public int intTest;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)]
        public byte[] byteTest;
    }
    class Class1
    {
        [DllImport("TestSample")]
        public static extern void test1();
 
        [DllImport("TestSample")]
        public static extern int test2(int intTemp);
 
        [DllImport("TestSample", CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr test3(StringBuilder strTemp);
 
        [DllImport("TestSample")]
        public static extern void test4(ref typeTest testTemp);
 
        [DllImport("TestSample", CallingConvention = CallingConvention.Cdecl)]
        public static extern void test5(StringBuilder _in, StringBuilder _out);
 
        [DllImport("TestSample", CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr test6();
 
    }
}
 
cs


using System.Runtime.InteropServices; 를 선언해야 Marshal , DllImport 같은 키워드를 네임스페이스 없이 사용할 수 있습니다.


DllImport("TestSample") 라고만 적었는데 App4 UWP 프로젝트에서 앱이 실행되는 폴더에 TestSample.dll 이 있어야 절대경로 없이 dll 이름만 import 시켜서 로드드 할 수있습니다.



각 버튼의 액션 이벤트 구현은 아래와 같습니다.


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
47
48
49
50
51
52
53
54
55
56
57
58
59
       private void test1(object sender, RoutedEventArgs e)
        {
            // 내부 함수 구현이 없음
            Class1.test1();
            textBlock.Text = "Success";
        }
 
        private void test2(object sender, RoutedEventArgs e)
        {
            textBlock.Text = "" + Class1.test2(15);
        }
 
        private void test3(object sender, RoutedEventArgs e)
        {
            // Hello 라는 문자열을 가진 StrungBuilder 객체를 초기화합니다.
            StringBuilder _input = new StringBuilder("Hello");
 
            // MFC C++ Dll 의 test3 메소드의 리턴은 char* 주소값 입니다.
            // C#에서 C++ 포인터와 매칭되는 키워드는 IntPtr 입니다.
            IntPtr ptr = Class1.test3(_input);
 
            // char*은 Ansi코드 문자열이므로 PtrToStringAnsi를 사용하여 저장합니다.
            string Message = Marshal.PtrToStringAnsi(ptr);
 
            // MFC C++ Dll에서 받은 문자열 포인터는 내부적으로 LocalAlloc 으로
            // 메모리를 할당받았으므로 FreeHGlobal을 통해 메모리를 해제합니다.
            Marshal.FreeHGlobal(ptr);
            textBlock.Text = Message;
        }
 
        private void test4(object sender, RoutedEventArgs e)
        {
            typeTest testTemp = new typeTest();
            testTemp.byteTest = new byte[64];
            testTemp.strTest = "Hello";
            testTemp.intTest = int.Parse("15");
            testTemp.byteTest[0= byte.Parse("15");
            Class1.test4(ref testTemp);
 
            textBlock.Text = testTemp.strTest + " , " + testTemp.intTest;
        }
 
        private void test5(object sender, RoutedEventArgs e)
        {
            StringBuilder _input = new StringBuilder("Hello");
            StringBuilder _output = new StringBuilder();
            Class1.test5(_input, _output);
 
            textBlock.Text = _output.ToString();
        }
 
        private void test6(object sender, RoutedEventArgs e)
        {
            IntPtr ptr = Class1.test6();
            // 유니코드 문자열의 주소를 리턴받았으므로 PtrToStringUni를 사용합니다.
            // DLL에서 메모리를 할당받지 않았으므로 메모리 해제 또한 하지 않습니다.
            string message = Marshal.PtrToStringUni(ptr);
            textBlock.Text = message;
        }
cs



실행시키고 test3 버튼을 누르면 'Hello success' 결과값이 출력됩니다.




C++ <-> C# 간 타입 캐스팅


C++ 과 C# 에 동일하게 존재하는 타입의 경우 똑같은 타입을 써주면 변환이 잘되지만


동일하게 존재하지 않는경우 아무 타입으로 변환 시키면 오류가 발생합니다.


타입이 동일한 것이 없을 경우 아래의 표를 참조하면 됩니다.


Win32 

비관리C 데이터타입 

C# 

HANDLE 

int

int

BYTE 

unsigned char 

byte 

SHORT 

short 

short 

WORD 

unsigned short 

ushort 

INT 

int 

int 

UINT 

unsigned int 

uint 또는 int 

LONG 

long 

int 

BOOL 

long 

int 

DWORD 

unsigned long 

uint 

ULONG 

unsigned long 

uint 

CHAR 

char 

char 

LPSTR 

char* 

string 또는 StringBuilder ,  리턴시 IntPtr 

LPCSTR

const char* 

string 

LPWSTR 

wchar_t* 

string 또는 StringBuilder ,  리턴시 IntPtr

LPCWSTR 

const wchar_t* 

string 

FLOAT 

float 

flaot 

DOUBLE 

double 

double 



C++ , C# 간 마샬링은 주로 C++로 작성된 kernel.dll 이나 user32.dll 을 사용할때 쓰게 됩니다.
자신이 만든 dll외에 범용적으로 사용하는 함수를 c#에서 사용하려면 아래의 사이트에서 검색하면 좋습니다.






Comments