[C#] Unmanaged C++ Struct 포인터 패러미터 Develop Tip

C#은 DotNetFramework의 CRL 환경에 맞는 managed 코드이고,
이전 C, C++ 코드는 Unmanaged Code라 하여 호환이 되지 않습니다.
다해히 C, C++에서 만든 DLL을 바로 불러다 사용할 수 있는 방법이 있습니다만,
생각처럼 쉽지많은 않습니다.
바로 패러미터 혹은 리턴값이 int, double 등의 value가 아니,
class object인 경우 managed <==> unmanaged 간에
자료형을 변환시켜야 하는데 이것을 마샬링, 언마샬링 이라 합니다.

결론적으로 여러 시행착오를 거쳐 다음과 같이 제목과 같은
Struct (C#에서는 클래스)를 서로 주고 받을 수 있도록 해 보았습니다.

제일 먼저 unmanaged C++에서는 다음과 같이 구조체와 함수를 정의하였습니다.

#pragma pack(push,8)
struct stC
{
LPCWSTR name; // read-only
int Qlen;
double* Q;
bool debug;
int data_len;
byte* data;
};
#pragma pack(pop)

extern "C" __declspec(dllimport) int TestStruct(stC *stc);
__declspec(dllexport) int TestStruct(stC *stc)
{
int i;
//strcpy(stc->data, stc->name);
CString strMsg;

strMsg.Format("name=%S", stc->name);
AfxMessageBox(strMsg); 

strMsg.Format("Qlen=%d, Q[0]=%f", stc->Qlen, stc->Q[0]);
AfxMessageBox(strMsg);

for (i = 0; i < stc->Qlen; ++i) {
stc->Q[i] *= -1.0;
}

strMsg.Format("debug=%d, data_len=%d", stc->debug, stc->data_len);
AfxMessageBox(strMsg);
// revert boolean
stc->debug = !stc->debug;

for (i = 0; i < stc->data_len; ++i) {
stc->data[i] = i % 15;
}
return stc->data_len / 2;
}

이제 C#에서 위의 C DLL을 이용하기 위하여 Wrapping Class를 만들어 봅니다.



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Xml.Serialization;

namespace DllCaller
{
    public unsafe class DllWrap
    {
        [DllImport(@"CallTestDll2.dll")]
        public static extern int TestStruct([MarshalAs(UnmanagedType.LPStruct)] stC stptr);

        public static int DW_TestStruct(stC stc)
        {
            int rc = 0;
            rc = TestStruct(stc);

            double[] rQ = stc.GetQ();
            return rc;
        }
    }

    // 그리고 패러미터 패싱을 위한 동일한 구조체 입니다.
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 8)]
    public class stC : IDisposable
    {
        #region Variables
        // string constant to read-only
        public string name;
        // double pointer to read
        public int Qlen;
        public IntPtr Q;
        // boolean 
        public bool debug;
        // fill data
        public int data_len;
        public IntPtr data;
        // not use at C++
        public string version = "0.0.0";
        // for internal only
        [XmlIgnoreAttribute]
        private bool disposed = false;

        #endregion

        public stC()
        {
            this.data_len = 1000;
            this.debug = true;
        }

        ~stC()
        {
            Dispose(false);
        }


        #region Public Methods
        public void SetArray(double[] iQ, int data_len)
        {
            this.data_len = data_len;
            Qlen = iQ.Length;
            //Blank our arrays if they hold data
            if (Q == IntPtr.Zero)
                ReleaseMemory();

            int size = Marshal.SizeOf(iQ[0]) * iQ.Length;
            try
            {
                Q = Marshal.AllocHGlobal(size);
                Marshal.Copy(iQ, 0, Q, iQ.Length);

                data = Marshal.AllocHGlobal(data_len);
            }
            catch (Exception ex)
            {
                //error handling
                throw ex;
            }
        }

        public double[] GetQ()
        {
            double[] rQ = new double[Qlen];
            Marshal.Copy(Q, rQ, 0, Qlen);
            return rQ;
        }

        public byte[] GetData()
        {
            byte[] rData = new byte[data_len];
            Marshal.Copy(data, rData, 0, data_len); 
            return rData;
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                // Call the appropriate methods to clean up
                // unmanaged resources here.
                // If disposing is false,
                // only the following code is executed.
                ReleaseMemory();
                // Note disposing has been done.
                disposed = true;
            }
        }

        private void ReleaseMemory()
        {
            if (Q != IntPtr.Zero)
                Marshal.FreeHGlobal(Q);
            if (data != IntPtr.Zero)
                Marshal.FreeHGlobal(data);
        }

        #endregion
    }
}


이제 위의 래핑클래스를 이용해 봅니다.

            stC stc = new stC();
            stc.name = "Hello? Name";
            double[] iQ = new double[3];
            iQ[0] = 1.1;
            iQ[1] = 2.2;
            iQ[2] = 3.3;
            stc.SetArray(iQ, 100); 
            j = DllWrap.DW_TestStruct(stc);
            byte[] rData = stc.GetData();

결국 Struct 는 UnmanagedType.LPStruct 로 마샬링되고,
Class 안에 있는 멤머는 ReadOnly로 (C#의 ref 개념이 안됨) 되는데
그 안에 내용을 채우려면 

Marshal.AllocHGlobal(size);
를 이용하여 글로벌 메모리에 Alloc하여 그것을 managed Array로 
Marshal.Copy() 함수로 주고 받으면 됩니다.

휴우 이것을 알아내는 데도 꽤 여러번 시도를 해 보았네요...


어느분께는 도움이 되셨기를...

덧글

  • eun.j 2015/01/19 11:10 # 삭제 답글

    감사합니다. 해봐야 하겠지만.. 많은 도움이 되었어요!
  • 지훈현서아빠 2015/01/19 15:32 #

    도움이 되셨다니 저의 보람입니다~~ ^^
댓글 입력 영역

구글애드텍스트