클래스 마샬링을 할일이 없엇 신경을 안쓰다가 요번에 스카이프 api나 제가 직접 마샬링 해볼까 해서 클래스 마샬링을 정리해 보았습니다.
근데 왜이렇게 자료가 없지?
겨우 찾은것이 비주얼C++ 팀블로그인데....네...영어입니다 ㅡ.-;
(참고 : Visual C++ Team Blog - Inheriting From a Native C++ Class in C#)
일단 변환방법이 마음에 들지가 않아서 위글에 있는 내용을 그대로 사용하여 만들고 자료를 더 찾는다면 파트2로 돌아오 겠습니다 ㅎㅎㅎㅎ
그전에 이 글은 크게 2부분으로 나누어 설명할 예정입니다.
어찌됬건 프로그래머라면 일단 샘플부터 만들고 생각해야 하지 않겠습니까?
C#에서 노출 시킬 클래스가 담겨있는 C++ DLL을 만들겠습니다.
프로젝트 이름은 꼭 "cppexp"로 합니다.
진입점 문제 때문인데 해결방법은 있으나....좀더 편한 테스트 환경을 위해 "cppexp"로 합니다 ㅎㅎㅎ
클래스 이름도 "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" 이 두개 파일이 중요합니다.
이 샘플코드를 사용하기 위해서는 C#쪽에서 해야할 작업이 많습니다.
프로젝트는 아무것이나 상관없으나 이샘플자체가 콘솔응용프로그램이므로 'C# 콘솔 응용프로그램'으로 프로젝트를 생성합니다.
cppexp.dll를 직접 핸들릴할 클래스입니다.
아래코드는 클래스의 전체 코드입니다.
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"라는 클래스가 없기 때문이죠 ㅎㅎㅎ
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-1.에서 'M1()' 만 마샬링을 해두었으므로 'M1()'만 호출이 가능합니다.
프로그램의 진입점인 'Main()'으로 가서 다음 코드를 추가 합니다.
CSimpleClass sc = new CSimpleClass(10);
using (sc)
{
//M1 calls all of the virtual functions V0,V1,V2
sc.M1();
}
빌드를 돌리면
"Unsafe code may only appear if compiling with /unsafe"
이런 에러가 납니다.
비관리 코드를 사용하기위한 옵션을 켜라는 소리입니다 ㅡ.-;;
프로젝트 속성 > 빌드 > 안전하지 않는 코드 허용
옵션을 켜줍니다.
화면이 순식간에 꺼진다면 알아서 화면 챙기시고 ㅋㅋㅋㅋ(그냥 중단점만 걸어도 됩니다-_-a)
정확하게 출력 되는 것을 볼수 있습니다.
위에 내용들을 정확하게 이해했다면 샘플에 나와있는 다른 클래스 맴버들도 마샬링을 할수 있습니다.
당연한 이야기지만 클래스는 무조건 통으로 마샬링해야 합니다.
안그러면 사용을 할수 없어요.
샘플 프로젝트
[.Net] 디버그 심볼 이용하기 (4) | 2011.12.20 |
---|---|
[VB.net] 엑티브엑스 형태의 DLL을 사용하려고 할때 나는 에러 - Cannot create ActiveX component. (0) | 2011.12.13 |
c++ DLL을 C#에서 사용해보자 ( 클래스 마샬링 ) (14) | 2011.12.12 |
[.Net] 다중상속이 안된다면 조부모 상속이다!! - 여러개의 베이스를 상속받기 (4) | 2011.11.26 |
접근하기 더 힘들다 오류 - 컴파일러 오류 CS0052 (0) | 2011.11.06 |
감사합니다
잘쓰세요
감사합니다.
잘쓰세요~
감사해요!. 찾던거였는데 엄청 잘 정리해놓으셨네요.
잘쓰세요~
안녕하세요. C++ dll을 받아서 C#윈폼을 만들려고 하고 있습니다.
그런데 제가 받은 dll파일에는 lib파일이 포함되어 있지 않네요. 검색을 해봐도 lib파일이 있는 경우만 나와서 어떻게 해야 하나 하고 있습니다. 혹시 어떻게 해야 하는지 아시나요?
클래스마샬링의 경우 lib파일없이 가능한지 모르겠습니다.
함수마샬링의 경우 없어도 사용가능합니다.
http://blog.danggun.net/16
감사합니다.
그런데 유니티에 붙였더니 모듈을 찾을 수 없다고 나와서 해결책을 찾고 있는데,
프로젝트 이름을 cppexp여야 하는 이유를 알 수 있을까요?
없습니다.
샘플이니까 대충지은거죠 ㅎㅎㅎ
훌륭한 자료 정말 감사합니다.
어이쿠 과찬이십니다 ㅎㅎㅎ
만약 // cppexp.h에 int M2(int dNum)처럼 파라미터가 있는 함수들은 어떻게 해야 하나요?
저도 오래되서 기억이 잘나지 않는데.....
.lib를 검색해서 같은 이름의 함수를 찾고
private static extern int _CSimpleClass_Constructor(__CSimpleClass* ths, int value);
이런 식으로 구현해 줍니다.