Egloos | Log-in


.NET 어셈블리 동적 로드 기법

.NET 또는 Mono 환경으로 툴을 개발할 때 유연한 기능 확장성을 확보하기 위해서는 외부의 .NET 코드를 플러그인 방식으로 로드하여 실행하는 기능이 필요하다.
예를 들어, C#으로 작성된 게임 코드와 연동되는 툴이라면 게임 코드에 해당하는 DLL을 툴에서 로드하여 그 안에 구현된 기능을 활용할 수 있을 것인데, 게임코드 DLL이 새로 컴파일되어 변경 되었을 때 이를 다시 로드하기 위해 툴을 매번 재시작해야 한다면 개발 프로세스에 지속적인 비효율이 발생한다.
Unity3D와 같은 툴에서 볼 수 있듯이, 로드된 어셈블리(DLL)가 변경되었을 때 툴에서 자동으로 로드되어 그 내용이 즉시 반영되는 기능이 구현된다면 생산성 증가에 매우 큰 도움이 된다.
다행히도 .NET(Mono) 환경에는 이러한 목적을 위해 어셈블리의 동적로드를 지원하는 몇 가지 방법이 존재한다.
프레임웍 수준에서 기본으로 지원하는 기능이기 때문에 웹상에 많은 관련 자료가 존재하기는 하지만, 실제로 적용할 때에 발생하는 여러 문제점들에 대해 정리된 자료는 부족한 것 같아 이 글을 작성하게 되었다.

어셈블리 동적로드 기능의 구현을 위해 필자가 테스트 해 본 바로는 크게 두 가지의 접근 방법을 발견할 수 있었으며, 그 구체적인 방법과 특징에 대해서 아래에서 구체적으로 설명하고자 한다.


단일 AppDomain 내에서의 재로드 방법
Assembly클래스의 Load 메소드를 사용하면 현재의 AppDomain에 동적으로 어셈블리를 로드할 수 있다. 그러나 한 번 로드된 어셈블리는 메모리에서 언로드 할 수 없다는 큰 단점이 있다.
한 가지 다행인점은, 동일한 이름의 어셈블리라고 해도 이미 로드된 것과 변경된 어셈블리 파일의 버전이 다르면 앱에 중복 로드할 수 있다.
로드될 어셈블리 파일의 AssemblyInfo.cs에서 Assembly Version을 기존에 로드된 것과 다르게 수동으로 바꿔서 빌드하거나, 1.0.*와 같이 * 기호를 사용함에 의해 컴파일 될 때 마다 어셈블리의 버전번호가 바뀌에 할 수 있다. 이렇게 빌드된 어셈블리의 파일명을 Assembly.Load에 전달하면 .NET 프레임웍은 새 어셈블리를 추가로 로드할 것이다. 새로 빌드 되었더라도 버전 번호가 같다면 Assembly.Load 호출은 기존에 로드된 Assembly 객체를 반환한다.
단, 기존에 로드된 어셈블리 파일은 어플리케이션에서 사용중인 상태이므로 동일한 파일에 덮어 씌우기가 불가능하다. 따라서 어플리케이션이 실행중이라면 새로 빌드될 때 마다 다른 파일명으로 어셈블리가 복사되어 Load 메소드에서 새 파일명을 이용하여 로드해야 한다. Assembly.Load에서 원본 어셈블리 파일을 직접로드하지 않고, 매번 임의의 복사본 파일을 생성한 후에 Load를 호출하는 방법이 유리할 것이다. 편리하게도 Assembly.LoadFile 메소드는 프레임웍의 어셈블리 로드 경로와 무관하게 절대경로로 파일명을 지정하여 로드할 수 있다.


독립 AppDomain의 사용 방법
동적으로 생성된 AppDomain은 어셈블리를 동적으로 로드할 수 있고, AppDomain의 파괴에 의해 AppDomain내의 모든 어셈블리를 메모리에서 해제할 수 있다.
그러나 아래와 같은 많은 제약사항들이 있다.
  • Assembly.Load 또는 AppDomain.LoadAssembly로 새 AppDomain에 어셈블리를 직접 로드할 수는 없고, AppDomain.CreateInstance 시리즈를 통해 어셈블리 로드와 동시에 어셈블리 내 클래스의 인스턴스를 생성해야 한다.
  • 서로 다른 AppDomain에 있는 객체에 대해 메소드 호출과 반환이 이루질 때에는 원격의 객체와 통신하는 것과 같은 remoting 호출이 일어난다. 심지어 원격객체 timeout 특성도 동일하다. 호출 대상 객체는 MarshalByRef에서 상속받아야 하고, 인자와 반환 값은 모두 serialization을 지원해야 한다. 따라서 다른 AppDomain 간에 호출이 일어나는 시점과 전달되는 타입에 대해서 세심한 주의가 필요하다.
  • 다른 AppDomain으로 메소드 호출시에 Type이나 Assembly 등의 객체가 전달되거나 반환된다면 상대방 AppDomain 측에서 해당 어셈블리의 로드가 자동으로 일어난다. 따라서 어셈블리의 동적 로드와 언로드의 목적 달성을 위해서는 이런 종류의 호출은 피해야 한다.
  • AppDomain이 Unload 되더라도 로드된 어셈블리 파일에 대한 점유가 즉시 해제되지는 않는다. (파일이 해제되는 정확한 시점은 필자도 못 찾았다) 대신, AppDomain 생성시에 AppDomainSetup.ShadowCopyFiles를 "true"로 설정해주면 로드하는 원본 어셈블리 파일이 직접 점유되지 않으므로, 어플리케이션의 실행 도중에 로드된 어셈블리 파일의 갱신이 가능하다.
구체적인 어셈블리 로드는 다음과 같은 절차에 의해 수행된다.
  1. 새 AppDomain 인스턴스 생성
  2. AppDomain.CreateInstanceAndUnwrap 호출을 통해 지정된 어셈블리 내의 클래스 인스턴스 생성. 따라서 로드할 어셈블리 내의 MarshalByRef 에서 상속된 클래스명을 알고 있어야 한다. 또는, 어셈블리를 로드할 대리자 객체를 새 AppDomain내에 CreateInstanceAndUnwrap으로 생성한 후에, 이 대리자 객체에서 Assembly.Load / LoadFile을 수행해도 된다. 이 방법은 아래의 샘플코드에서 사용된다.
  3. 반환된 원격 객체를 통해 필요한 작업 수행
  4. 로드된 어셈블리가 더 이상 불필요하거나 새로운 버전의 어셈블리를 로드해야 한다면 AppDomain.Unload 호출을 통해 AppDomain 해제와 함께 AppDomain에 로드된 모든 어셈블리 해제.

독립 AppDomain을 통한 어셈블리 동적 로드/언로드 샘플코드

// Assembly 파일을 원격 AppDomain에서 로드할 대리자 객체
public class AssemblyLoader : MarshalByRefObject
{
public void LoadAssembly( string filename )
{
// 이 객체가 속한 AppDomain내에 어셈블리 파일 로드
Assembly.LoadFile( filename );
}

public override object InitializeLifetimeService()
{
// 이 객체가 원격 도메인 유휴 시간에 의해 자동으로 삭제되지 않게 함
return null;
}
}

...

// 원격 AppDomain 생성
AppDomainSetup setup = new AppDomainSetup();
setup.ShadowCopyFiles = "true";
AppDomain newAppDomain = AppDomain.CreateDomain( "newDomain", null, setup );

AssemblyLoader proxy = newAppDomain.CreateInstanceAndUnwrap( typeof(AssemblyLoader).Assembly.FullName, "AssemblyLoader" ) as AssemblyLoader;

// 원격 AppDomain에 Assembly 로드
proxy.LoadAssembly( assembly_filename );

// 원격 AppDomain의 코드 실행:
// CreateInstanceAndUnwrap을 사용하여 인스턴스를 만들고 반환된 객체를 통해 호출 가능. 단, 모든 인자들과 반환 타입은serializable해야 한다.
...

// 원격 AppDomain 파괴 및 Assembly들 언로드
AppDomain.Unload( newAppDomain );


* 동적로드 대상 어셈블리가 외부에서 변경되었을 때 자동으로 재로드 하는 기능을 구현하기 위해서는 FileSystemWatcher를 사용하면 된다.

by 김성균 | 2014/07/09 18:58 | 일(Work) | 트랙백 | 덧글(0)

트랙백 주소 : http://littles.egloos.com/tb/3471191
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]

:         :

:

비공개 덧글

◀ 이전 페이지          다음 페이지 ▶