[C++/CLI] Mixed-Type 사용시의 메모리 해제 문제

  • Post author:
  • Post category:
  • Post comments:7 Comments
  • Post last modified:July 19, 2007

네이티브 C++에서의 DLL을 CLR 환경에선 Assembly라 부른다. 그런데 CLR 환경에선 한번 불러들인(Load) 어셈블리를 마음대로 해제(Unload)할 수가 없다. 애초에 System::Reflection::Assembly에는 Unload에 해당하는 함수 자체가 없다. 단 독립적인 메모리 공간을 갖는 애플리케이션 도메인(AppDomain)을 하나 생성해서, 그 안에 어셈블리를 불러들이는 방법이 있다. AppDomain에는 Unload 함수가 있기 때문에 가능한 일이다. 그러나 별도의 AppDomain을 생성하면 골치 아픈 문제가 한둘이 아니다.

아, AppDomain에 관한 이야기를 하려던 게 아니었다. C++/CLI은 정말 C# 난이도와 C++의 난이도를 곱해놓은 만큼 어렵다. 특히 Mixed Type을 쓰면 머리에 쥐가 나는데, 네이티브 객체가 쓰는 CRT 힙과 관리되는 객체가 쓰는 CRL 힙을 둘 다 머리 속에 그려 넣고 작업해야 한다. 뿐만 아니라 자원 해제 문제까지 걸려 있다. 특히 네이티브 객체가 관리되는 객체의 포인터를 갖고 있는 경우에 골치 아프다.

class MemTest
{
private:
	gcroot<Shared::NPC^> npcObj;
public:
	MemTest(Shared::NPC^ obj)
	{
		npcObj = obj;
	}

	~MemTest()
	{
		delete npcObj;
	}
};

클래스 MemTest는 네이티브 객체이고, 클래스 Content::NPCElf는 관리되는 객체이다.

// Create the new AppDomain
AppDomain^ domain = Model2Test::Instance()->GetDomain();
Content::NPCProvider^ provider = static_cast<Content::NPCProvider^>(
	domain->CreateInstanceFromAndUnwrap(Path::Combine(domain->BaseDirectory, "Content.dll"),
	"Content.NPCProvider"
	)
);

// GC Heap Size   0x810a8(528552)
MemTest* dangling = 0;

for(int i=0; i<1000; i++)
{
	dangling = new MemTest(provider->Create("NPCElf"));
	dangling = NULL;
}

AppDomain::Unload(domain);
domain = nullptr;

// GC Heap Size  0x1c535c(1533232)
GC::Collect();
// GC Heap Size  0x1c5d44(853184)

Console::ReadLine();

가비지 수집 시점까지 네이티브 객체 MemTest 내부에서 관리되는 객체에 대한 포인터를 유지하고 있기 때문에, 해당 관리되는 객체는 가비지 수집 대상에서 제외된다.

// Create the new AppDomain
AppDomain^ domain = Model2Test::Instance()->GetDomain();
Content::NPCProvider^ provider = static_cast<Content::NPCProvider^>(
	domain->CreateInstanceFromAndUnwrap(Path::Combine(domain->BaseDirectory, "Content.dll"),
	"Content.NPCProvider"
	)
);

// GC Heap Size   0x810a8(528552)
MemTest* dangling = 0;

for(int i=0; i<1000; i++)
{
	dangling = new MemTest(provider->Create("NPCElf"));
	delete dangling;
}

AppDomain::Unload(domain);
domain = nullptr;

// GC Heap Size  0x1c535c(1292032)
GC::Collect();
// GC Heap Size  0x1c5d44(336296)

Console::ReadLine();

MemTest의 인스턴스를 해제하면서, 그 인스턴스가 갖고 있던 관리되는 객체에 대한 포인터도 함께 해제했다. 덕분에 가비지 수집기가 메모리를 깔끔하게 회수해낼 수 있었다. C#의 IDisposable 패턴과 C++의 메모리 관리 디자인 패턴을 적절히 섞어서 구사해야 하니 골 아프다.

Shared::NPC^ dangling = nullptr;
// GC Heap Size   0x6c098(442520)

for(int i=0; i<1000; i++)
{
	dangling = gcnew Content::NPCElf();
	dangling = nullptr;
}

AppDomain::Unload(domain);
domain = nullptr;

// GC Heap Size   0x5110c(2035648)
GC::Collect();
// GC Heap Size   0x5110c(332268)

Console::ReadLine();

순수하게 관리되는 객체로만 코드를 짜면, 코드가 깔끔해진다. 물론 네이티브의 장점과 CLR의 장점을 적절히 취하려고 C++/CLI를 쓰는 것이니, 무의미한 이야기이다. 단지 Mixed-Type을 쓸 때는 정말 조심해야 한다는 교훈은 얻을 수 있다.

Author Details
Kubernetes, DevSecOps, AWS, 클라우드 보안, 클라우드 비용관리, SaaS 의 활용과 내재화 등 소프트웨어 개발 전반에 도움이 필요하다면 도움을 요청하세요. 지인이라면 가볍게 도와드리겠습니다. 전문적인 도움이 필요하다면 저의 현업에 방해가 되지 않는 선에서 협의가능합니다.
0 0 votes
Article Rating
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

7 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
미친병아리
16 years ago

전 아직도 왜 C++/CLI에 허브셔터나 다른 전문가들이 그리 열심인지 잘 이해가 안갑니다.. .NET에서 C++ 객체를 제대로 사용할 수 없는 아쉬움 때문에 그런 것 같은데, 아직까지는 장점이 드러나 보이지 않는 느낌입니다..

최재훈
16 years ago

아마도 일반적인 애플리케이션이나 시스템에선 굳이 C++/CLI까지 들여다볼 이유가 없을지 모릅니다. 당장 프로젝트에 직접 참가하기엔 한참일 때 들어오기도 했고, 닷넷쪽 경험자이기도 해서 제가 C++/CLI 조사를 맡고 있습니다. 그런데 일부 시스템에선 C++/CLI에 좋은 선택이 될 수도 있다는 생각이 듭니다. C++/CLI의 골치 아픈 면을 감안해도 그 이득이 훨씬 더 큰 상황이 있겠더라구요.

아무래도 C#에선 P/Invoke를 사용해야 Win32 API 호출이 가능한데, 성능적으론 분명 손해거든요. 반대로 생각해서 네이티브 C++은 타입 시스템이 약하니, CLR 환경의 타입 시스템이 필요한 상황도 가정해볼 수 있구요.

저도 C# 같은 환경에서 편하게 일하고 싶습니다만, 불가능한 경우엔 대안을 찾아나서야지 어쩌겠습니까? ^^

최재훈
16 years ago

아, C++/CLI가 아직 제대로 갖춰진 모습이 아니라 난감하기도 합니다. Managed C++이 실패하고, C++/CLI를 내놓았지만 마이크로소프트의 관련 팀에서 말하는 걸 들어보면, 아직도 해결해야 할 이슈가 많더라구요. 통합된 언어라는 느낌이 들게 하려면 아직도 갈 길이 멀어 보입니다. Visual Studio 2008이 다음해에 정식 출시되면, 상당히 많이 개선될 것 같긴 하지만 기대치를 충족시켜 줄지는  또 의문이네요.

미친병아리
16 years ago

MC++은 정말 매력이 없어보였습니다.. C++도 아닌 것이 .NET 세상에 살기위해 만들어진 기형쯤으로 보이더군요.. 나중에 C++/CLI도 비슷한 결론이 나올 것이라는 생각이 더 많이 듭니다.. C++/CLI가 좋은 경우라는 것이 과연 있기는 할까 라는 생각도 들고요..

최재훈
16 years ago

100% 관리되는 환경에서 돌아가는 소프트웨어와 100% 네이티브 환경에서 작동하는 소프트웨어를 잇는 데 쓰기 딱입니다. 우리 회사는 C#만 쓴다. 우리 회사는 네이티브 VC++만 쓴다. 이러면 필요 없겠지만, 양쪽이 만나는 지점에선 아주 유용합니다. ‘C#엔 P/Invoke가 있지 않느냐’라고 할 수도 있겠지만, 성능도 문제고 무엇보다 엉거주춤하죠. 단순히 API 몇 개 호출하고 끝내면 모르겠지만, 상호 간의 밀접한 연결이 필요하다면 C++/CLI가 좋은 대안이 될 수 있습니다. ^^

어쨌건간에
16 years ago

미병님 블로그를 보다가 여기까지 오는군요.
실제로 이전에 다니던 회사에서 C++/CLI을 매우! 유용하게 사용했습니다. 기존 마이닝, 네트워크 엔진은 MFC, C++ 기반이었으며 덩치가 워낙에 커서 전체 이주는 꿈도 못꿀 상황이었죠. 헌데 여기에 엔진 컨트롤러(UI)와 뷰어를 개발해야만 했는데 또 MFC로 가자니 끔찍했습니다. 헌데 여기서 C++/CLI가 결정적인 역할을 했죠. UI의 표면 요소와 validation 루틴 등은 C#으로 진행하고, 이 C# 모듈과 기존 MFC/C++와의 연동을 C++/CLI로 이루었던 것입니다. 이렇게 함으로 기존 코드를 재사용하면서도 새로 작성되는 일부 모듈은 C#을 통한 .NET으로의 이주를 아주 자연스럽게 이룰 수 있었죠. C++/CLI가 아니었으면 꿈!까지만(MC++이 있기는 하니까요) 꾸었을 것입니다.

최재훈
16 years ago

과연! 엔진은 그대로 놔두고 차체만 페라리로 바꾸는 셈인가요? 클릭 사서 튜닝하는 사람들의 기분을 알 것 같네요(좋은 뜻입니다).