- 작성시간 : 2012/09/18 22:48
- 퍼머링크 : mcchae.egloos.com/10938049
- 덧글수 : 2
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() 함수로 주고 받으면 됩니다.
휴우 이것을 알아내는 데도 꽤 여러번 시도를 해 보았네요...
어느분께는 도움이 되셨기를...



덧글