당분간은 C++/CLI에서 손 떼고 Visual C++에 전념할 듯 하다. 그 전에 마지막으로 그 동안 해왔던 일을 정리해볼까 한다.
어셈블리 동적 적재와 해제
일반 DLL와 달리 어셈블리는 마음대로 불러들였다가, 떼낼 수가 없다. System.Assembly
에는 Load
메써드가 있지만 Unload
메써드는 없다. 어셈블리를 닷넷의 DLL 정도로 생각해왔다면, 이쯤에서 헉! 소리가 나온다. 그럼 어쩌라고?
우회할 방법은 있다. 우선 새로운 응용프로그램 도메인, 즉 AppDomain
을 생성한다. 응용프로그램 도메인은 격리된 메모리 모델을 제공한다. 우선 .NET으로 작성된 응용프로그램을 실행시키면, 해당 프로세스 안에 기본 응용프로그램 도메인이 생성되고, 그 안에 진입점 Main을 포함한 코드가 들어간다. 만약 사용자가 AppDomain.CreateDomain()
메써드를 실행시키면, 하나의 프로세스 안에 두 개의 AppDomain이 존재하게 된다.
기본 응용프로그램 도메인을 제외한 다른 도메인은 프로세스 실행 중간에 Unload
할 수가 있다. 그러니 이렇게 하면 동적 적재와 해제가 가능해진다. 우선 새 응용프로그램 도메인을 생성한다. 새로 생성된 AppDomain에 어셈블리를 적재한다. 어셈블리가 필요 없어졌거나 어셈블리를 교체해야 하면, 응용프로그램 도메인 전체를 내렸다가 다시 올린다.
자, 문제는 해결됐다. 정말 그럴까? 예상했겠지만 세상 일, 쉽지 않은 법이다.
동적 해제하려고 이 고생을?
별도의 응용프로그램 도메인을 생성하면 또 다른 문제가 발생한다. 격리된 메모리 모델이다 보니 평상시에 하듯 간단하게 객체를 생성하고 메써드를 호출할 수 없다. 외부 응용프로그램 도메인에 접근하려면 원격 또는 리모팅(Remoting)이라 불리는 기술을 써야 한다. 문제는 크게 두 가지이다. 우선 성능 저하가 있다는 점이다. MSDN 라이브러리를 보면 해당 응용프로그램 도메인이 같은 프로세스 안에 있다면, 다른 프로세스 안에 있을 때보다 최적화해 준다고 한다. 그러니 조금 봐주기로 한다.
두번째 문제는 진짜 심각하다. 개체를 원격화하려면, 즉 다른 응용프로그램 도메인에서 접근할 수 있는 개체를 만들려면 방법은 크게 세 가지이다. 값으로 마샬링되는 개체, 참조로 마샬링되는 개체, 컨텍스트 바인딩 개체가 가능하다. 문제는 참조 개체로 선언하려면 반드시 System.MarshalByRefObject
를 상속받아야 한다는 사실이다. C# 등에선 단일 상속만 지원하기 때문에, System.MarshalByRefObject
를 상속받고 나면 추가 상속을 받을 수 없다. 이를 회피하고자 System.MarshalByRefObject
를 상속받는 클래스 A를 만들고, 원래 부모 클래스 격이었던 클래스 B가 클래스 A를 상속 받은 후에, 최종적으로 원격화할 최종 클래스가 클래스 B를 상속받는 구조를 시도해봤다. 물론 허사였다.
결국 원격화할 클래스는 Provider나 Factory 패턴을 취하게 된다. 이 문제를 회피하려고 별의별 방법을 생각해보고 있긴 하지만, 현재로선 답이 없다.
MS의 개발자들은 바보인가?
지금까지의 상황을 이해하면 이런 생각이 든다. 이런 바보들! 그냥
MarshalByRefObject
같은 클래스 말고 [Serializable]
같은 애트리뷰트만 제공했으면 얼마나 좋았겠어!
뭐, MS의 개발자들이 바보일 리는 없고 나름대로 이유가 있다. Inheriting from MarshalByRefObject에서 직접 답을 들어보자.
The reason has to do with performance. The CLR has a large number of optimizations which it can apply to objects that are guaranteed to be local. If the object is possibly remote, then these optimizations are invalidated. Examples include method inlining by the JIT, direct field access, fast instantiation in the local heap, direct method invocation for non-virtuals and more efficient type tests like cast operations.
When the benefit of these optimizations is considered, it completely outweighs the programming model impact to the inheritance hierarchy. This decision is key to achieving our long term goal of performance parity with native code.
역시나 성능 이슈 때문이다. 거봐라! 세상 일, 결코 쉽지 않은 법이다.
그래도 의문이 가시지 않는다면 MarshalByRefObject에 대한 좀더 상세한 논의를 살펴보자.
소박한 바램. 어떻게든 동적 적재와 해제 문제를 쉽게 해결할 방법 좀 마련해줘요~~~
KAISTZEN님, 흥미 있는 기사네요!! C/C++로 CLI/CLR 접근하려면 국내 개발자들이 그리 많치 않습니다. 아직 Visual Studio 6.0의 MFC 에서 Native Developing에 머물러 있는 사람이 많습니다. 혹시 Jeffery Richeter 의 CLR via C++ 이란 책을 읽어 보셨나요? Performance 도 문제고 사실 동적 로딩보다 언로딩 이슈가 큽니다. 가버지 콜렉션과의 상관 관계 때문에요
아무튼 흥미로운 기사입니다!!
언로딩 때문에 죽을 맛이네요. 동적 로딩과 해제 문제만 해결되면 좀더 유연한 설계를 할 수 있어서, 다른 회피 수단을 좀더 찾아볼 생각입니다. ㅎㅎ
Jeffery Richeter 아저씨 책은 항상 읽을만 했는데, CLR via C++은 아직 읽지 못했습니다. 아직 기술 조사 수준이니 본격적인 개발에 들어가게 되면 한권 사야겠습니다.
제 블로그 구독자님을 위한 이벤트가 있습니다. playin.innori.com/1867 참조. 암호는 1234 입니다.
멋진 이벤트인데 막내동생도 이젠 중3이라 제가 참여하긴 힘드네요. ^^