2011. 12. 12. 16:32



클래스 마샬링을 할일이 없엇 신경을 안쓰다가 요번에 스카이프 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#쪽에서 해야할 작업이 많습니다.
프로젝트는 아무것이나 상관없으나 이샘플자체가 콘솔응용프로그램이므로 'C# 콘솔 응용프로그램'으로 프로젝트를 생성합니다.

 

2-1. CSimpleClass 클래스 생성

 

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"라는 클래스가 없기 때문이죠 ㅎㅎㅎ

 

 

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. 출력 확인

화면이 순식간에 꺼진다면 알아서 화면 챙기시고 ㅋㅋㅋㅋ(그냥 중단점만 걸어도 됩니다-_-a)

 

 

 

정확하게 출력 되는 것을 볼수 있습니다.

 

 

마무리

위에 내용들을 정확하게 이해했다면 샘플에 나와있는 다른 클래스 맴버들도 마샬링을 할수 있습니다.

당연한 이야기지만 클래스는 무조건 통으로 마샬링해야 합니다.

안그러면 사용을 할수 없어요.

 

샘플 프로젝트

Projects.zip