본문 바로가기

IT 살이/04. 기술 - 프로그래밍

LoadFrom 컨텍스트

오랜만에 .NET 이야기를 써 보고자 한다. 이전 포스팅에서 어셈블리 바인딩에 대해서 이야기한 적이 있다. 

2009/04/23 - [01. 기술-APP] - 어셈블리 바인딩 1

2009/04/23 - [01. 기술-APP] - 어셈블리 바인딩 2


그때 "LoadFrom 컨텍스트"라는 것을 것을 그림에서 보여준 적이 있었다.

이제 이 LoadFrom 컨텍스트라는 것에 대해서 좀 더 상세히 정리하고 싶다. LoadFrom 메소드를 요즘 자주 사용하다 보니 관련된 이슈가 계속 생기고 있어서 정리를 해야 겠다는 생각을 하고 있었다.

 

LoadFrom을 사용해서 원격에 있는 어셈블리를 호출하게 되면 어셈블리의 코드가 수정되는 경우 재배포에 대한 편리함이 있고 해서 개발단계, 유지 보수 단계에서도 편의성을 제공하게 된다. 기업형 어플리케이션을 제작할때, 어셈블리 배포에 많이 사용되는 방법이다.

 

Assembly.LoadFrom("http://~/F.dll");

 

( AppDomain.CreateInstanceFrom도 내부적으로는 LoadFrom을 호출한다. 따라서 다음에 설명하는 이슈는 동일하게 CreateInstanceFrom에도 적용된다)

 

 

LoadFrom을 사용하면 일종의 플러그인 방식의 모듈을 제작할때 매우 편리하다. 플러그인은 어플리케이션에 기능을 부가적으로 추가해주는 역할을 하므로, 플러그인 파일이 존재하지 않아도 어플리케이션은 정상 작동한다. LoadFrom으로 가져온 어셈블리는 개념적으로 플러그인과 동일한 존재라는 것을 기억해 둘 필요가 있다. 

 

■ LoadFrom 컨텍스트

 

 

 

 

.NET에서는 어플리케이션이 실행되면 정적으로 참조된 어셈블리( Visual Studio에서 "참조 추가"로 추가한 어셈블리)가 로딩된 공간과 LoadFrom으로 로딩된 어셈블리의 공간을 분리해서 관리한다. LoadFrom으로 추가된 어셈블리들은 어플리케이션에서 정적으로 추가한 어셈블리와 동일하게 취급해서는 안되기때문이다. 왜냐면 어플리케이션이 정상적으로 작동하기 위해서 정적으로 추가된 어셈블리는 없어서는 안되는 기본적인 어셈블리들이지만 LoadFrom으로 추가된 어셈블리는 없어도 상관없다. 따라서, 어플리케이션에서 사용하는 필수 어셈블리(default assembly)들이 있어도 그만 없어도 그만인 플러그인 어셈블리를 맘놓고 의존(dependent)하게 해서는 안되기 때문이다.

그림을 보면 .NET 어플리케이션의 공간인 AppDomain 공간에 LoadFrom컨텍스트라는 이름의 공간이 별도로 분리되어 있다.

LoadFrom 메소드를 통해서 어셈블리를 로딩하면 LoadFrom 컨텍스트에 보관된다( 보관된다는 것은 어셈블리에 대한 정보이다. 물리적인 파일은 파일 시스템에 저장된다). 그림을 보면 LoadFrom 컨텍스트에는 F.dll이 로딩되어 있음을 보여주고 있다.

LoadFrom 컨텍스트 공간을 제외한 나머지 공간을 기본 로드 컨텍스트(default load context)라고 한다. D.dll은 어플리케이션에서 정적으로 참조해서 사용하는 어셈블리로 기본 로드 컨텍스트에 포함되어 있다. 그림을 보면 기본 로드 컨텍스트에 F.dll이 로딩되는 것이 가능하다는 것을 보여주고 있는데, 이 F.dll은 LoadFrom 이 아닌 정적 참조에 의해 추가된 어셈블리이다. 이렇게 동일한 어셈블리라 하더라도 컨텍스트가 다르면 한 어플리케이션 내부에 동시에 로딩이 될 수 있다. 

 

타입 참조 규칙

 

그림에서 보여지는 것처럼 LoadFrom 컨텍스트에 있는 F.dll의 ClassF가 기본 로드 컨텍스트에 있는 D.dll의 ClassD 타입을 참조할 수는 있다. 그러나 그 반대는 허용되지 않는다. 예를 들어 다음과 같은 코드가 ClassD에 포함되어 있다고 해 보자.

 

// ClassD 클래스  내용

 

Assembly a = Assembly.LoadFrom("http://~/F.dll");

Object o = AppDomain.CreateInstance( ClassF );

ClassF f = (ClassF)o;  // 실패

 

"실패"로 표시된 코드에서 ClassF 타입은 기본 로드 컨텍스트에 있는 F.dll에 포함된 타입이다. 타입 캐스팅을 시도하는 순간 ClassD에서 LoadFrom 컨텍스트에 포함된 ClassF에 대한 참조를 시도하게 되어 에러가 발생하는 것이다.

 

동일한 이름의 어셈블리를 복수개 LoadFrom

 

LoadFrom은 어셈블리를 위치 기반으로 로딩하는 방법이다. LoadFrom으로 로딩된 어셈블리는 위치 기반의 참조 목록 즉 List of path-based refernces라는 곳에서 관리한다. 이 관리 목록에 포함될지 여부는 어셈블리 파일의 위치가 결정하는 것이 아니라 어셈블리의 아이덴터티가 결정한다.

 

Assembly assemblyX = Assembly.LoadFrom(http://~/x/comp.dll);

Assembly assemblyY = Assembly.LoadFrom(http://~/y/comp.dll);

 

만약  경로 x와 y에 있는 어셈블리 comp.dll이 "동일하다면" 변수 assemblyX, assemblyY에 로딩된 어셈블리는 모두 http://~/x/comp.dll에서 로딩된 어셈블리 객체를 참조하게 된다. 즉 http://~/y/comp.dll 어셈블리는 목록에서 관리되지 않는다.

그럼, "두 어셈블리 파일이 동일하다"는 것을 어떻게 판단하는가? 강력한 이름의 어셈블리(strongly named assembly)과 약한 이름의 어셈블리(weakly named assembly)에 따라서 판단이 달라진다.

강력한 이름의 어셈블리인 경우는 경로와 어셈블리 파일명이 같다고 하더라도 어셈블리의 아이덴터티를 구성하는 요소 즉 어셈블리명, 버전, 공개키 토큰, 컬쳐중 하나라도 다르면 다른 어셈블리로 판단된다. 그래서 어셈블리의 경로와 파일명이 같다고 하더라도 아이덴터티가 다르면 두 어셈블리는 모두 로딩되어 목록에 포함된다.

그러나 약한 이름의 어셈블리라면 어셈블리 이름만으로 동일함이 판단된다. 나머지 3개의 동일 여부는 고려되지 않는다. 예를 들어 다음 2개의 어셈블리는 CLR에 의해서 동일하다고 판단된다.

 

Comp, version=1.2.3.4, publicKeyToken=null, culture=neutral

Comp, version=5.4.3.1, publicKeyToken=null, culture=neutral

 

그래서 첫번째 chttp://~/x/comp.dll 어셈블리만 로딩된 어셈블리 목록에서 관리된다.

 

참조 어셈블리 검색

 

검색 경로에 LoadFrom 위치 추가

 

이전 포스트에서 .NET에서 참조되는 어셈블리를 검색하는 표준적인 절차에 대해서 알아봤다. LoadFrom에 의해서 로딩된 어셈블리가 참조되는 어셈블리를 검색할때는 이 표준적인 절차에 LoadFrom에 의해서 로딩된 어셈블리의 경로가 마지막으로 검색 절차에 추가된다.

 

Assembly assemblyX = Assembly.LoadFrom(http://~/x/comp.dll);

dependent.dll

 

만약 comp.dll이 dependent.dll을 참조하고 있다면, 런타임시 dependent.dll을 찾기 위해서 어플리케이션 베이스 디렉토리, 그리고 GAC 등 표준적인 위치를 검색한 뒤에 그래도 찾지 못하게 되면 마지막으로 comp.dll을 로딩한 위치 http://~/x/에서도 검색하게 된다.

 

LoadFrom 위치 검색 순서 이슈

 

가끔 LoadFrom 위치를 검색하는 순서가 맨 나중이라는 것이 문제가 되는 경우가 있다. 예를 들어 comp.dll이 office.dll 이라는 어셈블리를 참조한다고 하자. 그리고 office.dll 어셈블리는 예를 들면 Microsoft의 Office 제품군을 설치하면 함께 GAC에 등록되는 강력한 이름의 어셈블리라고 하자. 이런 상황에서 comp.dll 개발에 사용한 office.dll 버전과 클라이언트 PC에서 Office 제품군의 설치로 GAC에 등록된 어셈블리의 버전이 다르다고 해 보자.  

이런 상황에 서 comp.dll이 LoadFrom에 의해서 클라이언트 PC로 배포되어 office.dll을 찾는다고 해 보자. office.dll을 검색하는 순서에 따라서 먼저 GAC을 검색할 것이고 따라서 comp.dll을 개발할때 참조한 어셈블리와는 다른 버전의 office.dll을 찾게 될 것이다. 그래서 GAC에서 찾을 office.dll의 로딩을 시도하나 그 버전이 다를 것이고 따라서 동일한 어셈블리 아이덴터티를 갖는 파일을 찾을 수 없다는 예외가 발생하게 된다. 기억해 두는 것이 정신 건강에 좋을 것이다.