원문 출처 : 당근로리야스님 블로그 (http://blog.danggun.net/799)
클래스 마샬링을 할일이 없엇 신경을 안쓰다가 요번에 스카이프 api나 제가 직접 마샬링 해볼까 해서 클래스 마샬링을 정리해 보았습니다.
근데 왜이렇게 자료가 없지?
겨우 찾은것이 비주얼C++ 팀블로그인데....네...영어입니다 ㅡ.-;
(참고 : Visual C++ Team Blog - Inheriting From a Native C++ Class in C#)
일단 변환방법이 마음에 들지가 않아서 위글에 있는 내용을 그대로 사용하여 만들고 자료를 더 찾는다면 파트2로 돌아오 겠습니다 ㅎㅎㅎㅎ
그전에 이 글은 크게 2부분으로 나누어 설명할 예정입니다.
어찌됬건 프로그래머라면 일단 샘플부터 만들고 생각해야 하지 않겠습니까?
1. C++ DLL 만들기
C#에서 노출 시킬 클래스가 담겨있는 C++ DLL을 만들겠습니다.
1-1.프로젝트 생성
프로젝트 이름은 꼭 "cppexp"로 합니다.
진입점 문제 때문인데 해결방법은 있으나....좀더 편한 테스트 환경을 위해 "cppexp"로 합니다 ㅎㅎㅎ
1-2.코드 넣기(CSimpleClass.h)
클래스 이름도 "CSimpleClass"로 바꿔 줍니다.
샘플 코드와 마찬가지로 저도 해더에 모든 코드를 넣습니다.
아래 코드는 전체 코드입니다-_-;
잘 모고 따라하시길 ㅎㅎㅎ
// cppexp.h
#include <stdo.h>
#pragma once
using namespace System;
class __declspec(dllexport) CSimpleClass {
public:
int value;
CSimpleClass(int value) : value(value)
{
}
~CSimpleClass()
{
printf("~CSimpleClass\n");
}
void M1()
{
printf("C++/CSimpleClass::M1()\n");
V0();
V1(value);
V2();
}
virtual void V0()
{
printf("C++/CSimpleClass::V0()\n");
}
virtual void V1(int x)
{
printf("C++/CSimpleClass::V1(%d)\n", x);
}
virtual void V2()
{
printf("C++/CSimpleClass::V2()\n", value);
}
};
빌드를 돌려보면 별다른 문제 없이 dll이 생성됩니다.
여기서 생성된 "cppexp.dll", "cppexp.lib" 이 두개 파일이 중요합니다.
2. C# 샘플 만들기
프로젝트는 아무것이나 상관없으나 이샘플자체가 콘솔응용프로그램이므로 'C# 콘솔 응용프로그램'으로 프로젝트를 생성합니다.
2-1. CSimpleClass 클래스 생성
아래코드는 클래스의 전체 코드입니다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace ConsoleApplication1
{
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public unsafe struct __CSimpleClass
{
public IntPtr* _vtable;
public int value;
}
public unsafe class CSimpleClass : IDisposable
{
private __CSimpleClass* _cpp;
// CSimpleClass constructor and destructor
[DllImport("cppexp.dll", EntryPoint = "??0CSimpleClass@@QAE@H@Z", CallingConvention = CallingConvention.ThisCall)]
private static extern int _CSimpleClass_Constructor(__CSimpleClass* ths, int value);
[DllImport("cppexp.dll", EntryPoint = "??1CSimpleClass@@QAE@XZ", CallingConvention = CallingConvention.ThisCall)]
private static extern int _CSimpleClass_Destructor(__CSimpleClass* ths);
// void M1();
[DllImport("cppexp.dll", EntryPoint = "?M1@CSimpleClass@@QAEXXZ", CallingConvention = CallingConvention.ThisCall)]
private static extern void _M1(__CSimpleClass* ths);
public CSimpleClass(int value)
{
//Allocate storage for object
_cpp = (__CSimpleClass*)Memory.Alloc(sizeof(__CSimpleClass));
//Call constructor
_CSimpleClass_Constructor(_cpp, value);
}
public void Dispose()
{
//call destructor
_CSimpleClass_Destructor(_cpp);
//release memory
Memory.Free(_cpp);
_cpp = null;
}
public void M1()
{
_M1(_cpp);
}
}
}
"??0CSimpleClass@@QAE@H@Z"와 같은 알수 없는 코드들은 코드의 진입점입니다-_-;
이 방법으로 클래스를 마샬링 하기 위해서는 위와같이 진입점 코드가 있어야 합니다.
진입점 코드는 어떻게 찾느냐? 아까 말했던 .lib파일을 메모장으로 열어보시면 나와 있습니다.
0CSimpleClass : 0클래스 이름 이 생성자
1CSimpleClass : 1클래스 이름 이 파괴자
나머지 함수들은 이름으로 검색하면 코드를 알수 있습니다.
이 클래스를 만들면 "Memory.Alloc"와 "Memory.Free" 같은 곳에서 에러가 납니다.
"Memory"라는 클래스가 없기 때문이죠 ㅎㅎㅎ
2-2. Memory 클래스 생성
Memory이 클래스는 내용이 어떤것인가 하고 찾아보았습니다.
C#에서 동적 메모리 할당을 위해 사용하는 클래스 샘플입니다.
닷넷에서는 메모리관리는 특별한 경우를 제외하면 가비지컬랙터가 처리합니다.
이 가바지컬랙터를 거치지 않고 사용할수 있게 하는 샘풀이죠.
말이 샘플이지 이대로 쓰는대 전혀 손색이 없습니다.
참고 : MSDN - A.8 동적 메모리 할당
아래 코드는 클래스 전체 코드 입니다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
using System;
using System.Runtime.InteropServices;
public unsafe class Memory
{
// Handle for the process heap. This handle is used in all calls to the
// HeapXXX APIs in the methods below.
static int ph = GetProcessHeap();
// Private instance constructor to prevent instantiation.
private Memory() { }
// Allocates a memory block of the given size. The allocated memory is
// automatically initialized to zero.
public static void* Alloc(int size)
{
void* result = HeapAlloc(ph, HEAP_ZERO_MEMORY, size);
if (result == null) throw new OutOfMemoryException();
return result;
}
// Copies count bytes from src to dst. The source and destination
// blocks are permitted to overlap.
public static void Copy(void* src, void* dst, int count)
{
byte* ps = (byte*)src;
byte* pd = (byte*)dst;
if (ps > pd)
{
for (; count != 0; count--) *pd++ = *ps++;
}
else if (ps < pd)
{
for (ps += count, pd += count; count != 0; count--) *--pd = *--ps;
}
}
// Frees a memory block.
public static void Free(void* block)
{
if (!HeapFree(ph, 0, block)) throw new InvalidOperationException();
}
// Re-allocates a memory block. If the reallocation request is for a
// larger size, the additional region of memory is automatically
// initialized to zero.
public static void* ReAlloc(void* block, int size)
{
void* result = HeapReAlloc(ph, HEAP_ZERO_MEMORY, block, size);
if (result == null) throw new OutOfMemoryException();
return result;
}
// Returns the size of a memory block.
public static int SizeOf(void* block)
{
int result = HeapSize(ph, 0, block);
if (result == -1) throw new InvalidOperationException();
return result;
}
// Heap API flags
const int HEAP_ZERO_MEMORY = 0x00000008;
// Heap API functions
[DllImport("kernel32")]
static extern int GetProcessHeap();
[DllImport("kernel32")]
static extern void* HeapAlloc(int hHeap, int flags, int size);
[DllImport("kernel32")]
static extern bool HeapFree(int hHeap, int flags, void* block);
[DllImport("kernel32")]
static extern void* HeapReAlloc(int hHeap, int flags,
void* block, int size);
[DllImport("kernel32")]
static extern int HeapSize(int hHeap, int flags, void* block);
}
}
코드에 [DllImport("kernel32")]가 있는데 이소리는 커널을 불러다 사용한다는 소리입니다.운영체제에 따라 작동안할수도 있다는 것입니다 ㅡ.-;;
어찌됬건 이렇게 "Memory"라는 클래스를 만들고 유징하시면 에러는 사라 집니다.
2-3.테스트 코드
2-1.에서 'M1()' 만 마샬링을 해두었으므로 'M1()'만 호출이 가능합니다.
프로그램의 진입점인 'Main()'으로 가서 다음 코드를 추가 합니다.
CSimpleClass sc = new CSimpleClass(10);
using (sc)
{
//M1 calls all of the virtual functions V0,V1,V2
sc.M1();
}
3.확인
"Unsafe code may only appear if compiling with /unsafe"
이런 에러가 납니다.
비관리 코드를 사용하기위한 옵션을 켜라는 소리입니다 ㅡ.-;;
3-1.비관리 코드 사용 옵션
옵션을 켜줍니다.
3-2. 출력 확인
정확하게 출력 되는 것을 볼수 있습니다.
5.마무리
당연한 이야기지만 클래스는 무조건 통으로 마샬링해야 합니다.
안그러면 사용을 할수 없어요.
샘플 프로젝트