2010년 1월 5일 화요일

[.NET Compact Framework-C#] Native DLL(Marshaling) 사용 시 주의할 점

.NET Compact Framework 을 사용하여 프로젝트를 진행하는 사람이라면 느끼겠지만... 너무 느리고... 너무 부족하다... 필자도 이번 프로젝트를 진행하면서 JAVA와의 코드 전환을 용이하게 하려고 .NET Compact Framework 을 이용했지만 몇달전 쯤 부터 "C/C++ 로 개발할 것을..." 하는 후회를 밥먹듯이 하고 있다.

여튼... 앞에 너무 느리고 너무 부족하다는 말을 했는데... 이번에는 너무 부족한 부분에 대해 얘기해 볼까 한다.  그냥 PC에서 돌아가는 .NET Framework은 그래도 어느 정도 API가 있어서 쓸만할지 모르지만 .NET Compact Framework 에서 먼가를 하려고 하면 VS의 자동완성 UI에서 함수를 찾을 수 없는 경우가 부지기수다. MSDN을 찾아보면 결국 돌아오는 답은...

".NET Framework에서 모든 플랫폼의 모든 버전을 지원하지는 않습니다. 지원되는 버전의 목록은 시스템 요구 사항을 참조하십시오."

결국 대부분의 C# 개발자들은 이 부족함을 극복하기 위하여 Native DLL을 사용하게 된다.  이 때 능숙하지 않는 개발자라면 데이터 타입을 어떻게 맞춰야 하는지... 즉 마샬링을 어떻게 해야 하는지에 대한 문제에 고민을 하게 된다. (여기서 능숙하다는 표현은 프로그래밍 언어에 대한 전반적인 이해가 아니라 마샬링에 대해 능숙함을 나타낸다.) 이에 대해 구글링을 해보고... 책도 찾아보고... 난이도가 높은 구조체 마샬링도 해보고... 하지만... 불규칙적으로 Native Exception(0x80000002)을 토해내며 죽는 어플리케이션...

마샬링에 대해 정통한 사람이 이 글을 읽으면 비웃겠지만... 필자는 아래와 같은 방법을 사용하여 오류를 피해갔다.

예를 들어 다음과 같은 C 코드로 abc.dll 을 만들었다고 가정한다.

typedef struct ABC
{
int a;
int b;
int c;
} ABC;

ABC* g_abc = NULL;

void SetABC(ABC* abc)
{
g_abc = abc;
}

void SetA(int a)
{
if (!g_abc)
{
return;
}
g_abc->a = a;
}

void SetB(int b)
{
if (!g_abc)
{
return;
}
g_abc->b = b;
}

void SetC(int c)
{
if (!g_abc)
{
return;
}
g_abc->c = c;
}

int GetA()
{
if (!g_abc)
{
return 0;
}
return g_abc->a;
}

int GetB()
{
if (!g_abc)
{
return 0;
}
return g_abc->b;
}

int GetC()
{
if (!g_abc)
{
return 0;
}
return g_abc->c;
}


abc.dll 을 다음과 같이 C#에서 사용한다.

[DllImport("abc.dll")]
private static extern void SetABC(IntPtr pABC);
[DllImport("abc.dll")]
private static extern void SetA(int a);
[DllImport("abc.dll")]
private static extern int GetA();
...
IntPtr pAbc = Marshal.AllocHGlobal(12);
// 관리되지 않는 메모리 사용구간: 시작
SetABC(pABC);
SetA(10);
int a = GetA();
...
// 관리되지 않는 메모리 사용구간: 끝
Marshal
.FreeHGlobal(pABC);

위 코드에서 일반적인 사용방법과 다른 부분이 있다면 구조체나 버퍼를 할당할 필요가 있을 때 Marshal.AllocHGlobal(int)를 사용하고 해제할 때 Marshal.FreeHGlobal(IntPtr)을 사용했다는 점이다. 메모리 할당 시에 12를 사용한것은 C에서 sizeof(ABC) 하면 나오는 값이다.

원리를 살펴보자면... C#에서 사용하는 관리되지 않는 메모리와 관리되는 메모리의 차이점을 이용한건데...
관리되지 않는 Native DLL내의 함수에 관리되는 메모리 영역의 버퍼를 넣었을 경우 소스에서 명시된 "관리되지 않는 메모리 사용구간"이 수행되는 동안 다른 사용자 쓰레드에 의해 가비지 컬렉션등의 메모리 관리가 이루어졌을 경우 문제가 생길 여지가 있다는 것이다.

위 소스로 해당 문제가 발생하는지는 사실 확인을 하지 못했다. 하지만 공개는 할 수 없고... 유사한 구조를 가진 소스에서 문제가 발생하는 것은 확인하였고 위 방법으로 해결하였다.

1차적으로 Native DLL을 위와 같이 만들면 안되겠지만... 다른 사람이 만들어 배포된것을 사용할 수 밖에 없을 때 마샬링이 찜찜할 경우 위와 같이 사용하면 대부분의 문제는 피해갈 수 있을 것으로 생각된다.

먼가 설명이 조금 허접한 것 같은데 글이 잘못되었거나 이해가 가지 않으면 태클 환영함...-_-

댓글 없음:

댓글 쓰기