'개발/.NET'에 해당되는 글 29건

  1. 2011/09/28 [메모] Snippy - Visual Studio Code Snippet Editor by dalbong2
  2. 2011/04/18 [메모] ASP.NET Thread Usage on IIS 7.0 and 6.0 by dalbong2
  3. 2010/03/12 [메모] 유용한 샘플 코드들 by dalbong2
  4. 2009/04/23 어셈블리 바인딩 1 by dalbong2 (3)
  5. 2009/04/23 어셈블리 바인딩 2 by dalbong2
  6. 2009/04/23 어셈블리 구조 by dalbong2
  7. 2009/04/23 GAC은 어떻게 생겼을까 by dalbong2
  8. 2009/04/23 애플리케이션 도메인과 속성들(베이스 디렉토리) by dalbong2
  9. 2009/04/23 강력한 / 약한 이름의 어셈블리(일치 비교, 배포, 보안체크) by dalbong2
  10. 2009/04/23 애플리케이션 도메인 FAQ by dalbong2
  11. 2009/04/23 기본 AppDomain 생성자 변경하기 by dalbong2
  12. 2009/04/23 CLR의 Global 예외처리 by dalbong2
  13. 2009/04/23 Reducing Startup Time Due To Strong Name Verification by dalbong2
  14. 2009/04/23 로딩되는 CLR 버전 결정하기 by dalbong2
  15. 2009/04/23 호스트 타입별 CLR 호스팅 및 AppDomain 관리 방법 by dalbong2
  16. 2009/04/23 .NET 3.0 으로 갈아타기 by dalbong2
  17. 2009/04/23 시스템시작 또는 사용자가 로그인시 자동으로 프로그램 실행시키기 by dalbong2
  18. 2009/04/23 .NET 프레임워크 설치 여부 확인하는 방법 by dalbong2
  19. 2009/04/23 CLR 버전 선택하기, 어셈블리 바인딩 리다이렉트시키기 by dalbong2
  20. 2009/04/23 IIS 7 관리 닷넷 Microsoft.Web.Administration 클래스(펌) by dalbong2
  21. 2009/04/23 Why Vista Matters to Developers by dalbong2
  22. 2009/04/23 null, Finalize 메소드, Dispose 메소드, Dispose 패턴 by dalbong2
  23. 2009/04/23 AppDomainManager, Hosting Process..... by dalbong2
  24. 2009/04/23 .NET 어플리케이션의 호환성 by dalbong2
  25. 2009/04/23 이벤트 핸들러에 의한 메모리 증가 by dalbong2
  26. 2009/04/23 .NET Framework의 심볼 파일(.pdb)들이 공개되었습니다. by dalbong2
  27. 2009/04/23 WCF세미나 - MSDN 주간 세미나 (11월 28일)-유경상 수석 by dalbong2
  28. 2009/04/23 트랜잭션 NTFS by dalbong2
  29. 2007/04/24 BindingContext - Load() vs. LoadFrom() by dalbong2

Snippy - Visual Studio Code Snippet Editor

http://snippy.codeplex.com/

저작자 표시

'개발 > .NET' 카테고리의 다른 글

[메모] Snippy - Visual Studio Code Snippet Editor  (0) 2011/09/28
[메모] ASP.NET Thread Usage on IIS 7.0 and 6.0  (0) 2011/04/18
[메모] 유용한 샘플 코드들  (0) 2010/03/12
어셈블리 바인딩 1  (3) 2009/04/23
어셈블리 바인딩 2  (0) 2009/04/23
어셈블리 구조  (0) 2009/04/23
Posted by dalbong2

Http Request, IIS, ASP.NET, CLR Thread( Threadpool ), Application, Cpu 관계

http://blogs.msdn.com/b/tmarq/archive/2007/07/21/asp-net-thread-usage-on-iis-7-0-and-6-0.aspx 

저작자 표시

'개발 > .NET' 카테고리의 다른 글

[메모] Snippy - Visual Studio Code Snippet Editor  (0) 2011/09/28
[메모] ASP.NET Thread Usage on IIS 7.0 and 6.0  (0) 2011/04/18
[메모] 유용한 샘플 코드들  (0) 2010/03/12
어셈블리 바인딩 1  (3) 2009/04/23
어셈블리 바인딩 2  (0) 2009/04/23
어셈블리 구조  (0) 2009/04/23
Posted by dalbong2

괜찮음 !
Mike Woodring's .NET Sample Page ( http://www.bearcanyon.com/dotnet/ )

저작자 표시

'개발 > .NET' 카테고리의 다른 글

[메모] Snippy - Visual Studio Code Snippet Editor  (0) 2011/09/28
[메모] ASP.NET Thread Usage on IIS 7.0 and 6.0  (0) 2011/04/18
[메모] 유용한 샘플 코드들  (0) 2010/03/12
어셈블리 바인딩 1  (3) 2009/04/23
어셈블리 바인딩 2  (0) 2009/04/23
어셈블리 구조  (0) 2009/04/23
Posted by dalbong2

어셈블리 바인딩 1

이번 포스트를 이해한다면 .NET 애플리케이션 특히 NTD 스마트클라이언트 애플리케이션의 많은 부분을 이해할 수 있는 기초가 다져지게 될 것이다.

우리는 다른 어셈블리를 사용하고 싶을때는 VS.NET의 솔루션 탐색기의 프로젝트에서 참조 추가를 통해서 원하는 작업을 쉽게 수행한다. 또는 코드상에서 직접 Assembly.Load(), Assembly.LoadFrom() 메소드를 사용해서 원하는 어셈블리를 메모리로 로딩시키는 경우도 있다.

정적이든 동적이든 대상 어셈블리 파일을 로딩하는 과정은 어셈블리 바인딩(assembly binding)이라는 다소 복잡한 과정을 거친다. 그러나 .NET 애플리케이션을 제작하다 보면 이 바인딩 과정에 대한 이해가 필요한 경우가 자주 등장한다. 특히 웹 서버로부터 자동 배포를 구현하는 NTD 애플리케이션에서는 아주 중요한 개념으로 다가온다.

이제 이 바인딩과 로딩에 대해서 알아볼텐데 우선 이 바인딩을 주관하는 엔진이 있는데 이른바 "퓨전(fusion)"이라고 부른다. 퓨전이 바인딩과 로딩을 수행하는 과정에서 남기는 로그는 fuslogvw.exe 툴을 통해서 볼 수 있다.  이 로그는 스마트클라이언트 애플리케이션의 디버깅 및 어셈블리의 바인딩 과정 이해 및 디버깅에 중요한 단서로 사용된다.

최종적으로 어셈블리의 완전한 로딩이 목표인데 이 단계까지 가기 위해서는 퓨전은 몇 단계의 중요 과정을 거친다.

첫번째 단계는 어셈블리가 이미 로딩되어 있는지, 바인딩 컨텍스트라는 캐시를 확인하는 단계이다. 그렇지 않다면 두번째 단계로 어셈블리 파일을 찾아가는 과정이 있고 그 다음은 그 어셈블리 파일이 가지고 있는 어셈블리가 자신이 찾는 어셈블리인지를 확인하는 단계를 거친다.

바인딩의 방법에는 다음 두가지로 분류가 되는데 미리 정의를 하고 시작하자.
1) 이름 기반의 바인딩
이 방법은 호출하는 어셈블리(referencing or calling assembly)가 가지고 있는 대상 어셈블리(referenced assembly)에 대한 이름 정보를 기반으로 해서 어셈블리를 찾아가는 과정을 말한다.
2) 경로 기반의 바인딩
이 방법은 대상 어셈블리 파일에 대한 경로가 주어지게 되는데 이 경로를 통해서 바로 해당 파일을 찾고 로딩하는 과정을 말한다. 경로 기반의 바인딩에서는 바인딩이 두번 일어난다. 첫번째 바인딩에서는 인자로 주어진 경로의 어셈블리에서 어셈블리 아이덴터티 정보( 이름, 버전, 컬쳐, 공개키)를 가져온다. 두번째 바인딩에서는 그 정보를 이용해서 Load()와 같은 바인딩을 한번 더 수행한다. 이것은 v2.0에서 변경된 LoadFrom()의 바인딩 방식이다.

어셈블리가 이미 로딩되어 있는지를 확인하는 단계에 대해서는 이전 포스트에서 별도로 다뤘다. 만약 이미 로딩되어 있지 않은 경우라면, 어셈블리를 찾아나서게 된다.

■ 어셈블리 찾아가기( Assembly Locating )

어셈블리 바인딩 로직을 많은 책들이 설명하고 있지만 그 복잡한 로직을 모두 다 전하고 있지는 않고 있다. 그리고 중요한 포인트가 될 만한 곳이 누락된 곳도 있다. MSDN에서도 그 로직을 정확하게 전하고 있지는 않다. 달봉이는 어셈블리 바인딩에서 대해서 자세히 알면 알 수록 시스템의 개발과 운영에 나름대로의 노하우와 융통성이 생길 수 있다고 본다.

■ 퓨전의 어셈블리 바인딩

어셈블리가 다른 어셈블리에 속하는 타입을 사용하려 할 때는 그 어셈블리에 대한 이름풀이 과정이 수행되어야 한다. 그리고 나서 그 어셈블리 파일의 위치를 결정하고 그 다음 해당 파일을 로딩하는 단계를 거친다.

이런 과정을 어셈블리 바인딩이라고 하는데, 말 그대로 풀이하자면 논리적인 정보를 이용해서 실제 존재하는 물리적인 파일과 연결시킨다는 의미로 말할 수 있을 것이다.  대상 어셈블리에 대해 주어지는 논리적인 정보에는 어셈블리의 이름정보(4가지, 사용자 친화적인 어셈블리명, 버전, 컬쳐, 공개키(토큰))일 수도 있고 위치 정보 일 수도 있다. 여기서 위치 정보란 것은 "어디에 가면 어떤 파일이 있을 것이다"라는 말 그대로 정보이다. 이 정보를 받아서 실제로 파일이 있는지를 확인하는 과정이 바인딩에 포함된다.

이름 정보를 기반으로 해서 어셈블리의 위치를 확인하는 것을 이름 기반의 바인딩(name-based binding)이라 하고 위치 정보를 이용해서 어셈블리의 실제 위치를 확인하는 것을 위치 기반의 바인딩(location-based binding)이라고 한다. 이런 바인딩 과정을 수행하는 .NET 프레임워크 하부 시스템의 이름을 퓨전(fusion)이라고 부른다. 퓨전이 바인딩 작업을 하면서 로그를 남기고 fuslogvw.exe라는 어셈블리 바인딩 로그 뷰어라는 툴로 볼 수 있는 것이다.

어셈블리 바인딩을 영어에서는 resolving이라고 표현하고 이것을 담당하는 주체를 퓨전이라는 말 대신에 assembly resolver라는 말로 표현하기도 한다. 이 resolver라는 표현을 우리나라 말로 옮겨 놓은 것이 재밌다. MSDN에서는 "어셈블리 확인기"라는 말로 옮겼고 어떤 곳에서는 "어셈블리 이름풀이기"라는 말을 쓰기도 했다. "어셈블리 이름풀이"라는 말은 이름 기반의 바인딩(name-based binding)의 부분을 나타낼때는 괜찮은 표현이지만 그러나 바인딩을 모두 담아내지는 못하는 표현이다. 달봉이는 "어셈블리 바인딩"이라는 말과 그리고 원래의 용어 "퓨전"이라는 말을 그대로 사용하겠다. 그러나 이름 기반의 바인딩의 일부 과정을  나타낼 때는 때로 이름풀이라는 용어도 사용할 것이다.

이름 기반의 바인딩의 대표적인 메소드가 Assembly클래스의 Load()이고 위치기반의 대표적인 예가 LoadFrom() 메소드이다. 즉 Load() 메소드는 이름 정보를 인자로 받아서 그 물리적인 위치를 결정하는 단계를 밟고 LoadFrom()은 위치에 대한 정보를 바로 받아서 해당 어셈블리 파일을 결정하게 된다. 이름 기반의 바인딩을 Load타입 그리고 위치 기반의 바인딩을 LoadFrom타입이라는 말로도 표현한다. 상황에 따라서는 달봉이도 이 표현들을 사용하겠다.

이런 바인딩 타입에 따라 어셈블리가 로딩되는 과정이 달라진다. 그림은 바인딩 타입에 따라 달라지는 로딩 과정을 나타내고 있다.

1248119399

이름 기반의 어셈블리 로딩

이름기반의 바인딩에 속하는 대표적인 예가 참조에 의한 어셈블리 로딩과 Assembly.Load(이름정보) 메소드에 의한 로딩이다. 참조에 의한 로딩이나 Asembly.Load()에 의한 로딩을 같은 타입으로 묶은 것은 둘 다 이름풀이 과정을 거치기 때문이다. 즉 두 방법 모두 부분적이거나 또는 완전한 이름이 주어지고 그 이름 정보를 통해서 해당 어셈블리의 물리적인 위치를 결정한다.
어셈블리의 위치가 결정되고 파일이 발견되면 주어진 이름 정보와 실제 발견된 어셈블리의 이름 정보가 모두 일치하는지를 확인한다. 예를 들어 약한 이름의 어셈블리의 경우, 주어지는 정보로 어셈블리 이름과 컬쳐가 있을 수 있다. 실제 파일 위치를 확인하는데는 어셈블리 이름만 이용된다. 그러나 위치가 확인되고 나서 비교 확인하는 단계에서는 나머지 정보도 모두 비교하게 되는 것이다. 이제 이후 과정부터서는 결정된 어셈블리 파일의 메너페스트에서 얻은 완전한 이름 정보를 이용하게 된다.
Assembly.Load(string assemblyName)의 인자 assemblyName은 어셈블리의 완전한 이름(fully qualified name)을 나타내는 문자열이다.

Assembly assm = Assembly.Load("myAssem,"+
  "version=1.0.0.0,publicKeyToken=a1690a5ea44bab32,"+
  "culture=neutral");
설정 파일 .config에 < qualifyAssembly >이라는 속성이 설정되어 있다면 간략한 이름을 사용할 수 있다.
<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <qualifyAssembly
partialName="myAssembly"
              fullName="myAssembly,version=1.0.0.0,
           publicKeyToken=a1690a5ea44bab32,
    culture=neutral"/>
    </assemblyBinding>
  </runtime>
</configuration>

이런 설정이 있다면 다음처럼 메소드를 호출해도 된다.

Assembly assm = Assembly.Load("myAssembly");

<qualifyAssembly> 설정은 뒤의 "이름모으기"에서 설명한다.
이름풀이의 구체적인 과정은 중요한 문제이므로 뒤에서 자세히 다룬다. 하여튼 어셈블리 이름풀이를 통해서 파일의 물리적인 경로와 완전한 이름정보가 구해지게 된다.
어셈블리 이름풀이를 거치고 나서 바로 어셈블리를 로딩하는 것이 아니라 구해진 어셈블리의 아이덴터티 정보( 완전한 이름정보 )를 통해서 동일한 어셈블리가 이미 로딩 완료된 어셈블리 캐시에 있는지를 확인하는 과정을 거친다. 만약 이미 동일한 어셈블리가 로딩되어 있다면 기존의 로딩된 어셈블리를 사용하고 동일한 어셈블리가 발견되지 않은 경우는 새롭게 로딩하게 된다.

1402014011

위치 기반의 어셈블리 로딩

Assembly.LoadFrom(경로)로 대표되는 위치기반의 바인딩에서는 어셈블리에 대한 위치가 인자로 주어진다고 했다. 위치 기반의 바인딩에서는 두 번의 바인딩이 일어난다. 첫번째는 인자로 주어진 경로값에 있는 어셈블리에서 먼저 완전한 이름 정보를 조회한다. 이제 이름 정보를 얻게 되었으니 이름 기반의 바인딩 과정이 가능해진다. 앞에서 봤던 이름 기반의 바인딩 과정을 수행한다. 만약 그래도 원하는 어셈블리가 없다면 경로로 주어진 어셈블리를 로딩하게 된다.

.NET 2.0에서 새로 도입된 방식이다. 이전 버전에서는 경로상의 어셈블리를 바로 로딩했다.

바인딩 과정을 거친다는 것은 예를 들어 LoadFrom()에 명확한 경로를 주더라도 바인딩 과정에서 버전 정책등에 의해서 다른 경로상에 있는 다른 버전의 어셈블리가 로딩될 수 도 있다는 것을 의미한다. 즉 특정 어셈블리에 대한 요청이 들어오면 다른 버전의 어셈블리 요청으로 리다이렉트시키는 일이 바인딩 과정중에 일어날 수 있다는 것인데, 바인딩의 구체적인 과정에 대해서는 뒤에서 다루겠다.

LoadFrom()처럼 경로를 인자로 받아서 로딩하는 메소드는 여러가지가 있다. LoadFrom()외에도 CreateInstanceFrom(), ExecuteAssembly()등도 LoadFrom() 타입에 속하는 방법이다(이런 메소드들에 대해서는 MSDN을 참고하기 바란다). 인자로 주어지는 어셈블리에 대한 경로 정보는 파일 시스템상의 경로(file://)도 될 수 있고, URL(http://)도 될 수 있다.

Assembly a = Assembly.LoadFrom("file://C:/usr/bin/xyzzy.dll");
Assembly a = Assembly.LoadFrom("http://myserver/myapp/xyzzy.dll");

다음 포스트에서는 구체적으로 어셈블리 바인딩하는 과정을 알아볼 것이다.

'개발 > .NET' 카테고리의 다른 글

[메모] ASP.NET Thread Usage on IIS 7.0 and 6.0  (0) 2011/04/18
[메모] 유용한 샘플 코드들  (0) 2010/03/12
어셈블리 바인딩 1  (3) 2009/04/23
어셈블리 바인딩 2  (0) 2009/04/23
어셈블리 구조  (0) 2009/04/23
GAC은 어떻게 생겼을까  (0) 2009/04/23
Posted by dalbong2

어셈블리 바인딩 2

퓨전의 바인딩을 알아보기 전에 미리 알아봐야 하는 개념들이 있다. 그것들을 정리한다. 애플리케이션에는 애플리케이션 전체에 대한 설정도 있고 특정 어셈블리에만 적용될 수 있는 환경 설정이 있을 수 있다. 이런 애플리케이션 설정들은 XML형식의 .config 파일로 주어진다.

애플리케이션은 시작하면서 이 설정 파일을 읽어들인다. 퓨전은 어셈블리 바인딩을 수행하면서 설정 정보 중에서 대상 어셈블리에 대한 설정이 있는지를 확인하고, 만약 있다면 그 설정을 대상 어셈블리 검색 과정에 반영하게 된다. 바인딩 과정에서 적용될 수 있는 애플리케이션의 설정은 다음과 같은 것들이 있을 수 있다.

1159706153

바인딩시 사용되는 애플리케이션 설정

그림은 바인딩 과정에서 여러 설정을 검토할 수 있다는 것을 보여주고 있다. 주어진 어셈블리 이름에 따라서 적용할 수 있는 설정들에는, 버전 정책이 설정되어 있을 수 있고 CODEBASE 설정이 있을 수 있다. 그리고 이름풀이에 적용할 수 있는 설정에는 APPBASE 값도 있지만 이 설정은 .config에 없다. 그리고 privatePath에 대한 설정도 있을 수 있다.

어셈블리 이름풀이 과정에서는 결국 어셈블리의 물리적인 위치를 찾아내는 과정이다. 여기서 찾게 되는 물리적인 위치를 CODEBASE 힌트라고 한다. CODEBASE 힌트는 그림에서 처럼 .config 파일에 명시적으로 설정할 수도 있다. CODEBASE는 "여기에 가면 파일이 있을 거예요"하는 말이지 실제는 그곳에 있지 않을 수도 있다. 이름을 풀이하는 동안은 실제 파일이 있는지는 확인하지 않는다. 말 그대로 이름만 풀이하는 것이다. 따라서 때로는 CODEBASE를 나타내면서 "힌트"라는 말도 함께 사용하는 것이다.

■ 이름풀이 관련 설정들

이름풀이 과정에 영향을 미칠 수 있는 .config 설정들이 어떤 포맷으로 있는지를 소개한다. 어셈블리 이름풀이 기능을 십분 이용하기 위해서는 관련 설정들에 대한 이해가 필수이다. 지금은 이름풀이 관련 설정이 .config에서 어떤 모습으로 있을 수 있는지 그 포맷을 먼저 보여주겠다. 그리고 뒤에 이어지는 이름풀이 프로세스를 이야기하면서 각 설정에 대한 설명과 실질적인 XML 코드를 보여주겠다.

1408527257

바인딩에 적용될 수 있는 설정 요소

"0..1" 표시는 하위요소로 설정 요소가 없을 수도 있지만 있다면 최대 1개의 설정요소가 있을 수 있다는 것을 말한다. "0..N"에서의 N은 설정요소가 여러 개 포함될 수 있다는 것을 말한다.
<assemblyBinding>이하의 요소는 전부 이름풀이와 관련있는 요소들이다. 이름풀이시 이런 설정을 읽어들여 최종적으로 어떤 위치(디렉토리, GAC 등)에서 어떤 버전을 로딩할것인가를 결정할 수 있다.

표시된 설정들은 크게 두 부류로 나눌 수 있다. 하나는 애플리케이션 전체에 영향을 미칠 수 있는 설정이고 다른 하나는 애플리케이션내의 특정 어셈블리에 영향을 미칠 수 있는 설정들이 있다. 애플리케이션 차원의 설정들은 <assemblyBinding>의 하위에 있고 그리고 <dependentAssembly> 이하의 요소가 참조되는 특정 어셈블리에 대한 설정이다.

<runtime>하위에 <assemblyBinding>요소가 있는데, 애플리케이션에 대한 설정은 없을 수도 있겠지만 현재 애플리케이션 차원의 설정은 당연히 1개는 넘을 수 없을 것이다. 따라서 "0..1"로 표시되어 있다. 애플리케이션 차원에서 모든 어셈블리에 적용될 수 있는 설정은 <publisherPolicy>, <qualifyAssembly>, <probing>이 있다.

<assemblyBinding>요소밑에는 <dependentIdentity>요소가 있다. 이곳에 애플리케이션에 속하는 어셈블리별 설정이 있다면 이곳에 모두 포함된다. 애플리케이션에 속한 어셈블리는 여러 개 일수 있으므로 "0..N"으로 표시되어 있다.

<dependentIdentity>요소는 <assemblyIdentity>요소를 하나 이상 가지고 있어야 한다. <assemblyIdentity> 요소는 특정 어셈블리가 어떤 어셈블리인지를 나타내고 있는지 어셈블리에 대한 정의를 가지고 있다. 풀어서 이야기하자면, "어셈블리의 이름이 뭐고, 컬쳐가 어떠하며 그리고 공개키 토큰값이 어떤 그런 어셈블리에 내가 가지고 있는 설정을 적용할 것이다."는 것을 나타내고 있는 것이다.

<assemblyIdentity>에 의해 어셈블리 정의에는 버전 번호가 없다. 즉 특정 어셈블리의 버전 별로는 다른 설정이 있을 수 있기 때문이다.  버전별로 설정할 수 있는 것들이 <codebase>와 <bindingRedirect>, <pubulisher>이 있을 수 있다. 그리고 이 속성들은 특정 어셈블리의 버전별로 적용할 수 있다. <assemblyIdentity>에 의해 정의에는 버전 번호가 없다. 즉 특정 어셈블리의 버전 별로는 다른 설정이 있을 수 있기 때문이다된 어셈블리있기 때문에 "0..N"으로 표시되어 있다.

지금까지 어셈블리 이름풀이와 관련된 설정들이 어떤 포맷으로 오고 그 포맷의 의미가 무엇인지를 알아보았다. 이제 이름풀이 프로세스를 알아본다.

■ 어셈블리 바인딩(assembly binding) 프로세스

이제 퓨전이 이름 정보를 통해서 어떻게 물리적인 경로를 결정하고 마침내 로딩에 이르는지 그 과정을 알아볼 것이다. 더불어 앞에서 본 각 설정들에 대한 설명과 설정을 적용하는 XML 코드도 함께 본다. 그림은 어셈블리 이름풀이와 로딩의 전체 과정을 표시하고 있다.

1156331875

어셈블리 바인딩 전체 프로세스

이 그림은 퓨전이 적절한 어셈블리 파일을 찾아가는 과정을 모두 표시하고 있다. 그 과정을 하나씩 좇아가 보자.

■ 이름정보 모으기

참조되는 어셈블리에 대한 요청이 있거나(이 요청은 JIT 컴파일러가 자신이 알지 못하는 새로운 타입을 만났는데, 참조되는 타입을 가지고 있는 어셈블리가 아직 로딩되어 있지 않은 경우 일어날것이다) 또는 요청 코드상에서 Assembly.Load() 메소드가 호출되거나 하면 이름풀이 과정이 시작될 것이다.

요청하는 쪽에서는 어떤 어셈블리를 찾아야 할지에 대한 정보를 넘겨줄 것이다. 그러나 어셈블리의 이름 정보는 .config파일에도 있을 수 있다. 설정 요소중에서 <qualifyAssembly> 요소를 통해서 어셈블리 파일명 외에 다른 정보를 제공할 수 있다. <qualifyAssembly>라는 요소에 partialName, fullName이라는 어트리뷰트가 있는데 다음과 같은 XML 설정이 있을 수 있다.

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <qualifyAssembly
partialName="myAssembly"
              fullName="myAssembly,version=1.0.0.0,
           publicKeyToken=a1690a5ea44bab32,
    culture=neutral"/>
    </assemblyBinding>
  </runtime>
</configuration>

partialName 어트리뷰트는 흔히 코드상에서 어셈블리를 호출할 때 사용하는 사용자 친화적인 이름(주로 어셈블리 파일명)이다. 그리고 fullName은 완전한 이름 정보를 가지고 있다.

이런 설정이 있는 애플리케이션의 코드상에서 "myAssembly"라는 이름으로 어셈블리를 호출하면 퓨전은 fullName에 지정된 완전한 이름으로 해석한다. 예를 들어 코드처럼 부분 정보로 Assembly.Load() 메소드를 호출해서 이름풀이가 시작되었다하더라도 <qulifyAssembly> 설정을 적용하고 나면 완전한 이름으로 호출한거나 마찬가지가 된다.

Assembly.Load("myAssembly")
->
Assembly.Load("myAssembly,version=1.0.0.0,
    publicKeyToken=a1690a5ea44bab32,
  culture=neutral");

이렇게 해서 찾을 어셈블리의 이름에 대한 완전한 정보를 얻은 후에 퓨전은 요청 어셈블리에 공개키 정보가 있는지 여부를 확인한다. 그리고 공개키가 있다면 버전 정책을 적용하는 단계로 넘어가고 만약 공개키가 없다면 바로 프로빙(probing)이라는 단계로 넘어간다.

■ 버전 정책(version policy)

어셈블리를 GAC에 배포한 다음, 어셈블리를 수정할 일이 생겼다고 해보자. 그래서 버전 번호도 변경하고 재빌드해서 다시 GAC에 재배포했다. 그렇지만 이전 버전의 어셈블리가 GAC에 그대로 등록되어 있다면 변경된 어셈블리가 사용되지 못한다. 호출하는 어셈블리는 여전히 이전 버전의 어셈블리를 사용할 것이기 때문이다. 그렇다고 이전 버전을 삭제하면 에러가 발생한다. 흔한 말로 "참조가 깨지게" 되는 것이다.

이런 경우 설정 파일 .config에 특별한 설정을 추가함으로써, 예전 버전의 어셈블리를 찾는 요청이 들어오면 새로운 버전의 어셈블리로 그 요청을 리다이렉트시킬 수 있다. 이런 정책을 버전 정책이라 한다.

조금 전에 말했지만 요청되는 어셈블리가 약한 이름의 어셈블리(weakly named assembly)라면  단지 어셈블리의 파일명을 통해서만 대상 어셈블리의 위치를 결정하게 된다. 이름풀이 프로세스에서도 나타나 있지만 약한 이름의 어셈블리인 경우는 버전 정책이 적용되지 않는다. 버전 정책은 강력한 이름의 어셈블리에만 적용된다.

앞에서 보여준 XML 형식에서 이제 설명할 XML 요소가 <assemblyBinding>하위의 <publisherPolicy>인데, 이것을 설명하자면 publisher 정책이 뭔지를 먼저 알아야 한다. 이야기 순서상에서는 약간 벗어나긴 하지만 이 정책을 먼저 알아보도록 한다.

하나의 애플리케이션에 영향을 미칠 수 있는 설정은 3가지의 종류가 있다 이름하여 애플리케이션 정책, publisher 정책, 그리고 관리자(또는 머신)정책이 그것들이다. 지금까지 우리가 머리속에 그린 것은 애플리케이션 차원에서 적용될 수 있는 애플리케이션 정책용 설정들이었다. 그러나 설정 파일 .config의 포맷은 사소한 차이는 있지만 앞에서 보여준 XML 형식과 동일하다고 여겨도 된다.

애플리케이션 정책은 하나의 애플리케이션에 속하는 모든 어셈블리들에 영향을 미친다. 이 정책은 애플리케이션별 설정 파일 .config(이 파일의 위치가 AppDomain의 APPBASE 값을 결정한다고 앞에서 말했다)를 이용해서 적용할 수 있다.

반면 publisher 정책은 바로 이어서 설명하겠지만, 머신내 특정 어셈블리에만 영향을 미치는 정책이다. 이것도 동일한 포맷의 .config 파일을 사용한다. 다만 .config 파일이 존재하는 위치가 다르다. publisher 정책용 .config는 마치 이미지를 리소스 어셈블리에 포함시키듯이 우선 어셈블리에 포함되어 있다. .config를 감싸고 있는 어셈블리를 다시 GAC에 등록시켜야 한다. 이 어셈블리는 퓨전이 인식할 수 있는 특별한 포맷의 이름으로 되어있다. publisher 정책은 말한것처럼 머신내의 특정 어셈블리에는 모두 적용된다. 즉 publisher 정책용 .config에는 특정 어셈블리에 대한 설정이 들어 있고 그 어셈블리가 머신내에서 로딩되려고 하면 애플리케이션에 상관없이 적용된다.

마지막으로 관리자(또는 머신) 정책은 하나의 머신에 있는 모든 어셈블리에 영향을 미칠 수 있는 정책이다. 머신 정책용 .config 파일은 머신내의 모든 어셈블리에 적용될 수 있는 설정으로 machine.config에 설정되어 있다. 위치는 다음 경로에 있다.

%windir%\Microsoft.NET\Framework\닷넷 버전 #\CONFIG

그러나 Machine.config도 다른 버전의 .NET 프레임워크용 애플리케이션에는 적용되지 않는다.

1101765531

버전 정책의 오버라이딩

세 종류의 정책이 있으므로 어떤 어셈블리에 대한 버전 정책이 각 레벨별로 중복이 될 수 있다. 이런 경우 버전 정책이 적용되는 순서를 그림에서 보여주고 있다. 만약 이름풀이 대상의 어셈블리가 최초에는 버전 "1.1.0.0"이었지만 최종 머신 정책을 통과하고 나서는 버전 "1.2.0.0"이 된다. 그럼 다음 단계부터는 버전 번호 "1.2.0.0"을 사용하게 되는 것이다.

이제 버전 정책에 대해서 계속 이야기해가겠다. 앞에서 보여준 설정 요소들중에는 버전 정책과 관련된 설정 요소는 몇가지가 있다. 특정 어셈블리 차원의 관련 요소로는 <bindingRedirect>가 있다. 그리고 <publisherPolicy>요소는 애플리케이션 차원에도 있고 어셈블리 차원에도 있다. 

publisher 버전 정책이 사용할 수 있는 경우를 보자. 벤더(vendor)가 상용 컴포넌트를 개발해서 출시까지 마쳤다고 하자. 출시 이후 수정 사항이 생겨서 새로운 버전을 배포해야 할 필요가 생겼다면 기존 버전에 대한 어셈블리 요청을 새로운 버전으로 리다이렉트시킬 설정이 필요할 것이다. 벤더는 새로운 버전에 publisher 정책 어셈블리를 함께 포함시켜 배포할 수 있다. 개발자는 새로운 버전의 어셈블리와 publisher 정책 어셈블리를 GAC에 설치하고 설정 파일에는 <publisherPolicy>의 apply="yes"로 해서 publisher 정책을 적용시킬 수 있다. 설정을 생략하면 기본적으로 publisher 정책이 적용된다. 따라서 publisher 정책을 적용하지 않을려면 명시적으로 apply="no"을 입력해야 한다.

publisher 정책 파일을 GAC에 등록하면 특정 어셈블리를 사용하는 모든 애플리케이션에 영향을 줄 수 있기 때문에 신중을 기해야 한다. 현재 애플리케이션에서 사용되는 모든 어셈블리에 대해서 publisher 정책 사용을 막으려면 <assemblyBinding>하위에 설정을 두면 된다.

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <publisherPolicy apply="no"/>
    </assemblyBinding>
  </runtime>
</configuration>
그러나 머신내의 특정 어셈블리에 대해서만 publisher 정책을 사용하지 않으려면 해당 어셈블리를 <assemblyBinding/dependentAssembly>에 표시를 해주면 된다.
<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <dependentAssembly>
          <assemblyIdentity name="myAssembly"
                                  publicKeyToken="32ab4ba45e0a69a1"
                                  culture="neutral" />
          <publisherPolicy apply="no"/>
       </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

이것은 이름풀이 대상 어셈블리의 이름, 공개키토큰, 컬쳐가 코드와 같은 어셈블리인경우는 publisher 정책을 사용하지 않겠다는 의미이다.

<publisherpPolicy apply=>는 publisher 버전 정책을 사용하겠다 않하겠다는 의미이고, 버전을 결정하는 실제 설정 요소는 <bindingRedirect>이다. 버전 정책을 적용한다함은 특정 어셈블리에 대해 <bindingRedirect>요소를 설정한다는 것을 말한다. publisher 정책을 사용한다고 했을 때, 퓨전이 GAC에 등록된 어셈블리가 가지고 있는 .config 파일을 가서 보더라도 만나게 되는 것이 이 <bindingRedirect>이다. <bindingRedirect> 설정 내용을 보자. 다음은 MSND에 나오는 예제 설정이다.

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <!-- 특정 어셈블리별 하나의 dependentAssembly가 설정될 수 있다.-->
       <dependentAssembly>
          <assemblyIdentity
name="myAssembly"
              publicKeyToken="32ab4ba45e0a69a1"
              culture="neutral" />
  <!-- 버전 리다이렉션별 하나의 bindingRedirect 가 설정될 수 있다. -->
          <bindingRedirect
oldVersion="1.0.0.0"
              newVersion="2.0.0.0"/>
<bindingRedirect
oldVersion="1.0.0.5-1.0.0.9"
              newVersion="3.0.0.0"/>
       </dependentAssembly>
<dependentAssembly>

</dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

이 설정은 특정 어셈블리 즉 파일명이 "myAssembly"이고 공개키토큰이 "32ab4ba45e0a69a1"이고 컬쳐가 "neutral"인 어셈블리에 대한 버전 정책을 나타내고 있다. 이런 어셈블리에 대한 이름풀이 요청이 들어왔을 때 그 요청이 버전 "1.0.0.0"에 대한 것이라면 버전 "2.0.0.0"으로 요청을 리다이렉트시키는 것이다. 앞으로 찾게 될 버전은 "1.0.0.0"이 아니라 "2.0.0.0" 이라는 말이다. 그리고 "1.0.0.5"부터 "1.0.0.9"사이의 버전에 대한 이름풀이 요청이 들어오면 버전 "3.0.0.0"으로 요청을 리다이렉트시킨다.

버전 정책으로 결정된 버전은 이름풀이 프로세스상 뒤에 오는 GAC 조사나 CODEBASE 기반의 조사, 프로빙(probing) 과정에도 적용된다. 즉 뒤에 오는 과정에서는 "1.0.0.0"을 찾는 것이 아니라 "2.0.0.0"을 찾게 된다. 만약 GAC에 등록된 것중에서 "2.0.0.0"이 없으면 이 단계에서는 파일을 찾지 못하고 다음 단계로 넘어간다. 이런 버전 정책은 어셈블리별(<dependentAssembly>)로 여러 개 존재할 수 있다.

지금까지의 버전 정책을 정리해 보자. 그림은 지금까지의 버전 정책을 다른 방식으로 요약해 놓은 그림이다.

1393958395

버전 정책 적용 결과

이 그림은 이름풀이가 참조에 의해서 시작되었음을 보여준다. 제일 위쪽의 그림은 어떤 어셈블리를 ildasm.exe로 본 메너페스트의 일부분이다. 어셈블리가 이런 정보를 가지고 있는 다른 어셈블리를 참조하고 있는 것이다.

이 참조 정보에 해당하는 어셈블리가 아직 로딩되어 있지 않으면 퓨전은 이름풀이를 시작하는 것이다. 그림에서는 애플리케이션 정책에만 버전 정책이 포함되어 있다는 것을 보여주고 있다.

.NET 프레임워크 v1.1에서 <bindingRedirect> 설정을 사용하기 위해서는 애플리케이션이 내 컴퓨터(MyComputer)이나 로컬 인트라넷 보안 영역에 속해야 한다. 혹시 이 설정이 작동하지 않는담녀 이 애플리케이션에 FullTrust 권한을 부여하도록 해 보라.

버전 정책을 적용하는 단계를 거치면 모든 이름 정보가 확정되는 것이다. 이제부터는 이름 정보와 일치하는 파일의 위치를 결정하는 단계이다. 먼저 GAC에 있는지를 확인하고, 이름 정보와 일치되는 어셈블리를 발견하면 바로 해당 파일에 대한 경로를 어셈블리 로더에게 넘기고 로딩단계로 넘어간다. GAC에서의 파일 위치 결정 GAC의 구조를 설명하는 부분을 참고하라. 만약 GAC에서 이름정보와 일치하는 어셈블리가 발견되지 못하면 다음은 CODEBASE를 사용해서 위치를 결정하는 단계로 넘어간다.

■ CODEBASE 힌트

만약 GAC에서 원하는 어셈블리를 찾지 못하면 퓨전은 설정 파일에서CODEBASE 힌트에 대한 설정이 있는지를 확인한다. CODEBASE 힌트는 <codeBase>요소를 통해서 해당 어셈블리를 찾을 수 있는 URL을 제공한다.

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
  <!-- 특정 어셈블리별로 하나의 dependentAssembly 요소-->
       <dependentAssembly>
          <assemblyIdentity
name="myAssembly"
              publicKeyToken="32ab4ba45e0a69a1" />
  <!-- 버전별로 하나의 codeBase 요소-->
          <codeBase
version="2.0.0.0"                     
href="http://www.mycorp.com/myAssembly.dll"/>
          <codeBase
version="2.1.0.0"
href="file://c:/mystuff/myAssembly.dll"/>
       </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

CODEBASE 힌트를 가지고 있는 환경설정 파일에 대한 예이다. CODEBASE힌트는 특정 어셈블리의 버전별로 존재할 수 있다. 즉 "myAssembly 어셈블리의 "2.0.0.0" 버전은 이쪽에 있고, "2.1.0.0" 버전은 이쪽에 있습니다."라는 메시지이다. 첫번째 CODEBASE 힌트와 두번째 힌트의 URL 프로토콜이 다르다. CODEBASE 힌트를 사용하면 이렇게 버전별로 어셈블리를 이곳(로컬) 저곳(원격)에 구분해서 둘 수 있다는 것이다. CODEBASE 힌트를 사용하면 해당 어셈블리가 반드시 APPBASE 하위에 있을 필요는 없다.

만약 <codeBase> 힌트가 주어진다면, 그때부터는 LoadFrom(경로)을 호출했던 경우와 동일한 방식으로 어셈블리 로딩 과정이 진행된다. 만약 <codeBase>설정이 되었는데 그곳에서 찾을 수 없다면, 에러가 발생한다. 비록 privatePath 설정이 되어 있더라도 더 이상 바인딩 과정을 진행하지 않는다.

■ 프로빙(probing)

GAC에서도 CODEBASE 힌트를 사용하여도 요청 어셈블리를 찾지 못한 경우 퓨전은 프로빙(probing)이라는 단계로 넘어간다.

"probing"이라는 단어를 어떻게 우리말로 옮길까 많은 고민을 했다. 사전을 찾아보면 "검색"조사"등으로 번역이 되어있다. 우주 탐사선이 탐사를 하는 것도 이 단어를 쓴다고 한다. 해서 처음에는 "검색"이라는 말을 사용하려고 했다. 그러나 "일반적인 검색(search)"이라는 말과 구분을 할 수 없었다. 그래서 일반적인 의미는 "검색"으로 하고 probing을 나타낼때는 "검색(probing)"이라고 원어를 함께 표기하기도 했다. 그러나 그것도 혼란스럽기는 마찬가지였다. "probing만이 가지고 있는 검색"이라는 의미를 전달할 용어를 찾지 못해 결국 그대로 소리나는 대로 표현하기로 했다. "프로빙" ! 근데 이것도 어색하다. 그러나 어쩔 수 없다. 퓨전을 설계한 사람들이 search가 아닌 probe라는 단어를 사용하고 있기 때문이다 ! 쩝 -_-;;!

프로빙 과정에 영향을 미치는 요소가 <probing>이다. 이것은 <assemblyBinding>의 하위 요소로서 애플리케이션 차원의 설정이다. 애플리케이션 내에서 일어나는 모든 프로빙(probing)과정에 적용되는 설정이다. 이제 그 내용을 알아보자.

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <probing privatePath="shared;shared\bin;common"/>
    </assemblyBinding>
  </runtime>

</configuration>
privatePath 어트리뷰트의 값은 세미콜론(;)으로 구분된 여러 경로를 포함할 수 있다. 각 경로는 APPBASE 디렉토리에 대한 상대 경로로 해석된다. 즉 절대 경로를 지정하거나 APPBASE 디렉토리 외부의 다른 디렉토리에 대한 상대 경로를 지정할 수 없다.

1219617661

어셈블리 프로빙 대상 경로

그림처럼 http://서버/MyApp가 APPBASE 디렉토리로 설정되어 있는 디렉토리 구조가 있다면, 프로빙 과정에서 퓨전의 검색 대상이 될 수 있는 경로는 다음과 같다.

그러나 애플리케이션의 APPBASE 디렉토리와 그 하위 디렉토리이외의 다른 디렉토리는 privatePath에 포함될 수 없다. 즉 다음 경로는 검색 대상이 될 수 없다.

http://서버
http://서버/dir1

이것은 애플리케이션이 자신의 디렉토리와 그 하위 디렉토리는 제어할 수 있지만 다른 디렉토리에 대한 제어권은 없다는 것을 말한다.

프로빙(probing)단계는 애플리케이션의 APPBASE값, 참조되는 어셈블리의 컬쳐명, privatePath값을 이용해서 CODEBASE 힌트를 구성하고 이 위치에 어셈블리 파일이 존재하는지 여부를 조사하는 단계이다. privatePath로 설정된 모든 디렉토리에 대해서 반복적인 작업을 한다. 다음은 참조되는 어셈블리를 찾기위해서 퓨전이 만들 수 있는 경로들을 보여주고 있다.

어셈블리 파일명  : myAsssem.dll
컬쳐명    : neutral
APPBASE   : http://서버/MyApp/
privatePath   : "shared; common"

이런 정보를 가지고 구성할 수 있는 URL은 다음과 같다.

퓨전은 먼저 APPBASE 디렉토리에서 해당 어셈블리를 찾는다. 거기서 찾지 못하면 퓨전은 찾는 어셈블리 이름과 동일한 이름의 디렉토리에 있다고 가정한다. 거기서도 찾지 못하면 이제 privatePath에 설정된 디렉토리에 대해서 이런 과정을 반복한다. 이 과정에서 해당 파일을 찾게 되면 프로빙을 멈춘다. 그러나 찾지 못하면 다시 .exe 파일에 대해서 다시 전체 과정을 반복한다. 

만약 어셈블리에 컬쳐가 설정되어 있다면 검색 URL은 더 복잡하게 구성된다. 앞에서 설명한 검색 로직에 컬쳐명을 검색 경로에 포함시키는 로직이 더 추가된다.

http://서버/MyApp/ko-KR/myAssem.dll
http://서버/MyApp/ko-KR/myAssem.dll
http://서버/MyApp/myAssem/myAssem.dll
http://서버/MyApp/shared/ko-KR/myAssem.dll
http://서버/MyApp/shared/ko-KR/myAssem/myAssem.dll
http://서버/MyApp/common/ko-KR/myAssem.dll
http://서버/MyApp/common/ko-KR/myAssem/myAssem.dll


만약 프로빙(probing)에서 파일을 찾게되면 마지막으로 찾은 어셈블리가 가지고 있는 4가지 이름정보와 호출하는 어셈블리가 가지고 정보가 모두 일치하는지를 비교한다. 버전 정책이 설정되어 있다면 찾은 어셈블리의 버전번호는 버전 정책에 의해 결정된 번호인지를 확인한다. 하나라도 일치하지 않으면 로딩은 실패한다.

이 검증작업을 모두 거치고 나면 어셈블리가 로딩되고 사용할 준비가된다. http://~ 로 시작하는 어셈블리를 로딩하려고 할때는 로컬의 다운로드 캐시로 일단 내려받은 후 어셈블리를 로드하게 된다.

지금까지 설명한 버전 정책, GAC 조사, CODEBASE 기반의 조사, 프로빙(probing)은 참조되는 어셈블리가 강력한 이름의 어셈블리인 경우만 밟게 되는 단계들이다. 약한 이름의 어셈블리는 바로 프로빙(probing) 단계로 간다. 이 단계에서 CODEBASE URL을 구성하는 방법은 강력한 이름의 어셈블리의 경우와 마찬가지로 APPBASE, 참조되는 어셈블리의 컬쳐, privatePath 디렉토리를 이용한다.

같은 이름의 어셈블리 파일이 발견되면 검색된 어셈블리의 정보와 호출하는 어셈블리가 가지고 있는 정보가 일치하는지 비교한다. 약한 이름의 어셈블리의 경우는 버전 비교는 하지 않는다. 그러나 컬쳐나 공개키토큰 비교는 한다. 예를 들어 참조하고 있는 어셈블리가 가지고 있는 정보에는 공개키가 없는데, 실제로 프로빙(probing)에 의해 찾은 어셈블리에는 공개키가 있다면 에러가 발생한다. 컬쳐도 마찬가지이다.

■ 내가 찾는 어셈블리 맞어? ( Assembly Identity )

내가 찾은 어셈블리 파일이 "원하는 어셈블리인가"를 확인하는 단계이다. [각주:1] 무슨 말인지 잠시 생각해보자. 찾았으면 되었지 그것이 원하는 어셈블리가를 또 확인한다는 것은 무슨 말인지 달봉이는 처음에 이해를 못했다. "찾는 과정 자체" 즉 프로빙하는 과정이 "원하는 어셈블리"를 찾아가는 것이 아닌가?

달봉이는 이제서야 이게 무슨 말인지를 알게 되었다.  이런 미스 언더스탠딩이 왔던 것은 바로 "어셈블리 파일명과 어셈블리명"은 다르다는 것이다. 어셈블리 locating하는 단계는 참조하는(referencing)하는 쪽에서 가지고 있는 어셈블리명(assembly fully qualified name)을 이용해서 어셈블리 파일을 찾는 단계이다. 다음 소개하는 블로그 포스트를 보면 어셈블리의 파일명과 어셈블리명은 다를 수 있다는 것을 잘 말해주고 있다.

Assembly Identity --- ReferenceIdentity and DefinitionIdentity, Comparison and Transformation by Junfeng Zhang
foo.exe and foo.dll by Junfeng Zhang
Name of your-app.exe by Junfeng Zhang

이 블로그 내용을 보면 asm1.dll, asm2.dll처럼 파일명은 다르지만 그것이 포함하고 있는 어셈블리의 이름이 asm으로 같다면 두 어셈블리는 CLR 입장(정확히 말하면 fusion)에게는 동일한 것으로 여겨진다는 것이다. 이것은 다시 말하면 동일한 파일명으로 있다 하더라도 그 안에 다른 어셈블리가 포함되어 있을 수도 있다는 것이다. 즉 파일명과 어셈블리명은 다르다는 것이다..

사정이 이러하므로 어셈블리 파일의 위치를 결정했다하더라도 그 파일이 진정 원하는 어셈블리를 가지고 있는지를 확인한다는 것이다.

그러나 내가 찾는 어셈블리가 맞는지를 확인하는 단계에서 강력한 이름의 어셈블리(strongly named assembly)와 약한 이름의 어셈블리(weakly named assembly)의 경우에 있어서 그 비교 로직이 다르다.

오늘은 이만 줄이고, 다음 포스트에서 강력한 그리고 약한 이름의 어셈블리와 관련해서 간단히 정리하겠다.

  1. 이 경우는 이름 기반의 바인딩인 경우만 거치는 단계이다. [본문으로]

'개발 > .NET' 카테고리의 다른 글

[메모] 유용한 샘플 코드들  (0) 2010/03/12
어셈블리 바인딩 1  (3) 2009/04/23
어셈블리 바인딩 2  (0) 2009/04/23
어셈블리 구조  (0) 2009/04/23
GAC은 어떻게 생겼을까  (0) 2009/04/23
애플리케이션 도메인과 속성들(베이스 디렉토리)  (0) 2009/04/23
Posted by dalbong2

어셈블리 구조

개발/.NET 2009/04/23 22:40

어셈블리(Assembly) 구조

이제 어셈블리(Assembly)라는 것을 구체적으로 알아볼텐데, 어셈블리에 대한 이해가 스마트클라이언트 애플리케이션의 어떤 문맥에서 필요한지 그 상황을 먼저 정리해 본다.

어셈블리와 바인딩 그리고 NTD 배포

어셈블리의 바인딩과 로딩은 스마트클라이언트 애플리케이션에서는 중요한 주제중의 하나이다. 어셈블리에 대한 이해가 필요한 곳이 바로 이 바인딩/로딩과 관련이 있다.  애플리케이션이 어셈블리를 호출( 즉 어셈블리에 포함된 타입을 사용)하게 되면 해당 어셈블리가 아무 고민없이 바로 로딩되는 것은 아니다. 애플리케이션이 참조하고 있는 어셈블리에 대한 정보는 우선 .NET 프레임워크의 CLR에 전달된다. 그 CLR은 이 어셈블리를 어느 위치에서 찾아야 하는가를 고민해서 결정해야 한다. 어느 위치에서 어떤 버전의 어셈블리를 로딩해야 하는지를 결정해야 한다. 어셈블리 바인딩 부분을 참조하라.

CLR은 참조되는(referenced) 어셈블리 파일의 물리적인 위치를 결정할때는 참조하는(calling, referencing) 어셈블리 내부에 있는 참조 정보와 그리고 때로는 어셈블리 외부에 존재하는 설정을 읽어 들여서 결정한다. 최초 엔트리 포인트를 가지고 있는 어셈블리는 그 경로가 이미 결정될 것이다. 예를 들어 사용자가 exe 어셈블리를 더블클릭하거나 또는 IE의 <object> 태그의 classid 속성에 의해 정확한 위치가 결정된다. 그러나 참조에 의해 호출되는 어셈블리는 그 물리적인 최종 위치를 결정하기 위한 과정이 필요하다. 최종 어셈블리를 찾아 가는 것을 어셈블리 바인딩(Assembly binding)이라고 하는데, CLR의 내부에 있는 퓨전(fusion)이라는 엔진이 담당한다.

퓨전이 어셈블리 바인딩을 하기 위해서 제일 먼저 확인하는 정보는 참조(referencing) 어셈블리 자체에 있다. 즉 어셈블리는 자신이 참조하고 모든 어셈블리에 대한 정보를 직접 가지고 있다. CLR은 제일 먼저 어셈블리 자체에 있는 참조 정보를 이용한다. 그리고 참조되는 어셈블리 검색에 필요한 정보는 애플리케이션의 설정 파일(.config)에도 있을 수 있다.

검색 대상이 되는 어셈블리는 호출한는 어셈블리와 같은 PC에 있을 수도 있지만 원격의 서버에도 있을 수 있다. 바인딩을 이해하고 나면 NTD(No-Touch Demployment)를 자연스럽게 이해하게 될 것이다. 바인딩과 로딩이 수행된다는 것은 바로 NTD 배포가 이뤄지는 것이다.

마이크로소프트사에서는 여러 가지 이유로 인해 어셈블리(Assembly)라는 개념을 내놓게 되는데, 여러가지 이유 중에서 바로 윈도우의 안정성에 가장 큰 위협이 되고 있는 “DLL Hell” 문제가 있다. 예를 들어 두 애플리케이션이 공유 참조하고 있는 공통 DLL을 어떤 한 애플리케이션이 수정, 재배포하게 되면 다른 애플리케이션은 그 DLL과의 참조가 깨져서 실행이 되지 않는 경우이다. DLL때문에 생기는 비슷한 여러 문제들이 윈도우의 안정성을 훼손시키고 있었다. 버전 관리에 대한 대책이 절실히 필요로 되는 상황이었다. .NET이 어떻게 여러 버전의 동일한 어셈블리가 동시에 존재할 수 있는지는 GAC(Global Assembly Cache)을 설명하는 부분에서 설명한다.

어셈블리를 PE(portable executable) 파일이라고도 하는데, 어셈블리만 가지고도 이 머신 저 머신으로 가져가서(portable) 실행시킬 수 있다(executable)는 의미가 아닌가 한다. Executable 하다는 것은 우리가 흔히 윈도우 탐색기에서 exe 파일을 더블 클릭해서 애플리케이션을 실행시키는 것만을 의미하는 것은 아니다. 그뿐만 아니라 .NET 프레임워크가 설치된 머신에서라면 CLR이 동적으로 메모리에 로딩시켜 뭔가를 할 수 있다는 의미이다.

먼저 어셈블리의 구조와 어셈블리가 가지고 있는 참조(referenced) 어셈블리에 대한 정보에 대해서 알아본다. 어셈블리는 보통 하나의 파일로 구성되지만, 여러 개의 파일로도 구성될 수 있다. 하나의 어셈블리를 구성하는 각각의 파일을 모듈(module)이라고 한다
1354890166

어셈블리 구조(단일 파일의 어셈블리, 복수 파일의 어셈블리)

어셈블리는 그림처럼 크게 어셈블리를 설명하는 메타 정보를 가지고 있는 테이블, IL코드를 가지고 있는 부분, 리소스를 가지고 있는 부분으로 구성된다.

어셈블리 메타 데이터 부분은 다시 몇 개로 구분되는데, 메너페스트라는 부분도 있고 그리고 자신과 그리고 다른 어셈블리가 가지고 있는 타입에 대한 정보도 있다.

JIT 컴파일러가 어셈블리의 IL 코드를 기계어 코드로 컴파일하는 과정을 상상해보자. Main() 메소드의 코드를 컴파일해가다가 어떤 타입(클래스)를 만난다. 이 타입이 현재 어셈블리내에 정의되어 있는지를 확인한다. 만약 그렇다면 그 타입을 정의해 놓은 곳에서 불러와 사용한다. 그러나 다른 어셈블리에 있다는 것을 확인하게 되면 그 타입이 정의되어 있는 어셈블리에 대한 이름 정보(사용자 친화적인 어셈블리명, 버전번호, 컬쳐, 공개키)를 얻는다. 이 참조 어셈블리에 이름 정보가 곳이 메너페스트(Menifest)이다. 참조 어셈블리의 이름정보를 얻은 후에 해당 어셈블리 로딩을 담당 시스템에 요청한다. 이렇게 어셈블리가 로딩되면 해당 타입에 대한 정보를 얻은 후에 계속 컴파일을 진행해간다.

메너페스트에는 이렇게 다른 모든 참조 어셈블리들에 대한 이름 정보를 가지고 있다. 그리고 자신의 이름 정보도 이곳에 두고 있다. 즉 다른 어셈블리에서 자신의 이름 정보를 참조할때도 이 메너페스트 부분만 검색하면 된다.  

개발자는 어셈블리 어트리뷰트를 사용하여 어셈블리의 메너페이스 정보를 수정할 수 있다. 프로젝트를 생성하면 자동적으로 추가되는 AssemblyInfo.cs 파일을 보면 기본적으로 여러개의 어셈블리 어트리뷰트가 추가되어 있는 것을 볼 수 있다. 이것들이 모두 어셈블리의 메너페스트 정보를 수정하게 된다.

[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("")]
[assembly: AssemblyKeyName("")]
[assembly: AssemblyTitle("")]
[assembly: AssemblyDescription("")]

메너페스트가 자신과 다른 어셈블리에 대한 이름 정보를 가지고 있다는 사실을 기억해놓기 바란다. 나중에 설명할 어셈블리 바인딩(assembly binding)이라는 과정을 설명할 때 중요한 개념이기 때문이다. 어셈블리 바인딩은 또한 어셈블리의 배포와 버전 관리에 중요한 개념이며 바인딩은 또한 스마트클라이언트 배포(NTD 배포)의 개념이기도 하다.

그림은 복수개의 파일로 존재할 수 있는 어셈블리도 보여주고 있다.. 이 단위 파일을 모듈(module)이라고 부른다. 어셈블리는 이처럼 모듈 단위로 분리되어 각각 물리적인 파일로 따로 존재할 수 있다. 그리고 리소스(이미지, 문자열 등) 파일도 따로 모듈로 분리되어 있을 수도 있다. 이런 파일들이 합쳐져서 독립된 기능 즉 배포와 버전관리의 단위가 되는 어셈블리가 되는 것이다.

복수개의 모듈로 구성된 어셈블리에도 메너페스트 정보가 있는 모듈은 반드시 있어야 한다. 그래서 다른 어셈블리에서 참조 요청이 들어왔을 때 전체 모듈들을 다 확인하는 것이 아니라 메너페스트만 확인하면 된다.

어셈블리에는 리소스가 포함될 수도 있다. 리소스라는 것은 애플리케이션에서 사용되는 이미지나 문자열 같은 비실행 데이터를 말한다. 어셈블리는 관련된 리소스를 자체 가질 수 있어서 배포시도 함께 배포될 수 있다. 그리고 그림에는 없지만 리소스로만 구성된 어셈블리도 있다. 이런 어셈블리를 위성 어셈블리(satellite assembly)라 하는데, 어셈블리 로딩과 다운로드 시 이 위성 어셈블리 때문에 성능에 문제가 있을 수 있다. 이런 문제에 대해서는 HTTP 핸들러를 제작하면서 이야기했지만 자세한 내용은 뒤의 리소소와 그리고 어셈블리 다운로드하는 곳에서 자세히 설명한다.

지금까지의 설명을 간단히 말하면 어셈블리에는 “자신이 정의하고 있는 타입에 대한 정보와 그리고 자신을 구성하는 물리적 파일들에 대한 힌트, 그리고 자신이 참조하고 있는 다른 어셈블리들에 대한 정보 등 자신이 필요한 모든 정보를 스스로 가지고 있다”는 것이다. 어셈블리의 이런 속성을 "자기 설명적(self-describing)"이라는 말로 표현하고 있다. 자기 설명적 속성 때문에 어셈블리의 정보를 레지스트리에 등록하는 인스톨과정이 필요없게 된다. 자신의 메타데이터 정보를 읽고 해석할 수 있는 CLR이 있는 환경이라면 어느 곳이든 가서 CLR에 의해 로딩되고 제 할 일을 할 수 있는 것이다. 즉 어셈블리는 배포와 재사용의 단위가 되는 것이다.

원칙적으로는 하나의 어셈블리가 여러 파일로 구성될 수도 있긴 하지만 Visual Studio.NET을 이용하면 기본적으로 모든 어셈블리는 하나의 모듈 파일로 구성된다. 실전 프로젝트에서도 하나의 어셈블리는 하나의 모듈로 구성되는 것이 대부분이다.

어셈블리명(full qualified name)

이제 어셈블리의 메너페스트에 정의되어 있다는 어셈블리의 이름에 대해서 알아본다. 앞에서 사용자 친화적 이름(어셈블리 파일명), 버전, 컬쳐, 공개키(또는 공개키토큰)으로 구성되어 있다고 했다. 이 네가지 정보를 어셈블리 파일명, 버전, 컬쳐, 공개키 순으로 콤마(,)로 연결된 문자열을 만들면 어셈블리의 완전한 어셈블리명이 된다. XML 환경 설정 파일에 어셈블리 이름을 직접 입력해야 할 경우 또는 코드상에서 이런 4부분으로 구성된 문자열을 개발자가 직접 입력해야 하는 경우도 있을 수 있다.
1306610722

어셈블리의 완전한 이름

만약 이 네 부분이 생략되지 않고 모두 기록된다면 완전한 이름( fully qualified, fully specified assembly)이라고 한다.

Visual Studio.NET을 사용해서 어셈블리를 작성할때는 다음과 같은 어트리뷰트를 AssemblyInfo.cs에 넣게 되면 앞의 네 정보가 모두 메너페스트에 생성된다.

Using System.Reflection

[assembly: AssemblyVersion(“1,2.3.4”) ]
[assembly: AssemblyCulture(“ko-KR”) ]
[assembly: AssemblyKeyFile(“mycorp.snk”) ]

사용자 친화적 어셈블명(user-friendly name)

.NET이 제공하는 기본 클래스인 System.Reflection.AssemblyName에는 Name 속성이 있다. 실행중 이 속성값은 메너페스트 데이터를 가지고 있는 어셈블리 파일의 확장자없는 이름을 리턴한다. 이것은 CLR이 어셈블리 로딩시, 필수적으로 있어야 하는 부분으로 Visual Studio.NET으로 빌드를 하면 컴파일러가 자동으로 부여하는 속성이다. 코드상에서 동적으로 어셈블리를 생성할수도 있는데 이때는 어셈블리 이름을 개발자가 직접 부여할 수 있다.

"사용자 친화적 어셈블리명"이라는 길지만 좀 어색한 이름으로 소개한 이유는 그냥 부르는 "어셈블리명"은 흔히 완전한 이름을 나타내는 경우가 많기 때문이다. 어셈블리의 간략한 이름을 지칭할때는 우리뿐만 아니라 영어를 쓰는 사람들도 통일된 용어는 없는듯하다. 그래서 어떤 사람은 "간략한 어셈블리명(simple assembly name)"이라는 용어를 사용하기도 하고 또 어떤 사람은 보통 이 이름이 어셈블리의 확장자 없는 파일명과 동일하다고 해서 그냥 "어셈블리 파일명"이라는 용어를 사용하기도 한다. 그러나 정확히는 "어셈블리 파일명"과는 다른 의미이다. 즉 하나의 어셈블리를 복사해서 파일명을 다르게 부여해도 같은 어셈블리로 인식하는 경우도 있다. 

이 포스트에서는 사실 "간략한 어셈블리명"에 대해서 정해놓은 용어가 없다. "완전한 이름"과 구분이 필요한 경우는 상황에 맞게 구분하고 있다. 때로는 "완전한 이름"이라는 대신에 "어셈블리 이름에 관한 정보"라는 의미로 "이름 정보"라는 말을 사용하기도 한다.


 버전 번호(Version numbers)

모든 어셈블리는 4개의 숫자로 된 버전 번호를 갖는다. 버전번호는 “메이저번호.마이너번호.빌드번호.리비전번호”로 구성된다. System.Reflection.AssemblyName 클래스의 Version 속성으로 버전 번호 정보에 접근할 수 있다. C#코드에서는 System.Reflection.AssemblyVerion 어트리뷰트를 통해서 버전을 설정할 수 있다.

[assembly:AssemblyVersion("1.2.3.4")]

만약 버전 번호를 명시적으로 설정되지 않으면 기본값 “0.0.0.0”으로 설정된다. 버전번호를 설정하려고 한다면, 메이저번호는 반드시 명시되어야 한다. 생략되는 번호는 0으로 간주한다.

어트리뷰트                      실제버전
1                                   1.0.0.0
1.2                                 1.2.0.0
1.2.*                              1.2.d.s
1.2.3.*                           1.2.3.s
<설정않음>                    0.0.0.0
* d : 2000년01월01일부터 경과 일수
* s : 자정부터 빌드시까지의 경과 초수

빌드번호는 *로 표시될 수 있는데, 이렇게 되면 메너페스트에 삽입되는 빌드번호는 2000년 1월 1일부터 빌드한 날까지 지나간 날수가 된다. 그리고 리비전번호도 *로 표시될 수 있는데, 그날 자정에서부터 빌드시까지의 흐른 초수를 사용한다. 메이저번호와 마이너 번호는 *를 사용할 수 없다. 이 버전 번호는 어셈블리 확인기(assembly resolver)에 의해 중요하게 사용된다는 것을 뒤에서 설명한다.

컬쳐(culture)

컬쳐 또한 어셈블리 버전 번호와 함께 어셈블리를 구분짓는 속성의 일부로서 포함된다. 컬쳐는 어셈블리가 어느 언어, 어느 나라를 위해 만들어졌는지를 나타낸다. 디폴트로 현재 컬쳐는 사용자의 머신에 설정된 것을 따른다. 만약 프로그램상에서 특정 컬쳐에 대한 정보를 를 변경하고 싶다면 CultureInfo 인스턴스를 생성할 필요가 있다.

Thread.CurrentThread.CurrentCulture = new CultureInfo("ko-KR");

CultureInfo 인스턴스를 생성하기 위해서는 원하는 컬쳐이름을 필요하다. 컬쳐명은 RFC 1766에서 설명하고 있는 방법에 따라 언어와 국가(지역) 코드의 하이픈(-) 연결을 하면 된다.
<언어코드>-<국가코드>

예를 들면, "en-US"는 U.S 영어 컬쳐를 "en-AU"는 오스트레일이아의 영어 컬쳐를 말한다. 코드는 보통 2글자이다.

특정 컬쳐의 어셈블리를 만들고 싶다면 개발자는 보통 System.Reflection.AssemblyCulture 어트리뷰트를 사용해서 지정할 수 있다. Visual Studio.NET을 사용하는 개발자라면 AssemblyInfo.cs에 다음과 같은 코드를 넣으면 된다.

[assembly: AssemblyCulture ("ko-KR”)]

일반적으로 코드가 있는 어셈블리에는 컬쳐를 지정하지 않는다. 보통 그런 어셈블리에는 보통 컬쳐 종속적인 요소가 없기때문이다. int i=0 같은 코드는 특정 컬쳐에서만 사용하는 것은 아니다.

어셈블리에 특정 컬쳐 어트리뷰트를 사용하는 것은 리소스 사용과 밀접한 관계가 있다. 이 부분은 어셈블리 바인딩시 거치게 되는 경로 검색 과정과도 관련이 있으며 따라서 특정 컬쳐와 관련된 리소스를 다루는 부분을 이해해두면 스마트클라이언트 애플리케이션의 거동을 이해하는데 많은 도움이 될 것이다. 특정 컬쳐와 관련된 리소스를 관리하는 방법은 뒤에 리소스를 설명하는 부분을 참고하라.

공개키(public key)

어셈블리명을 구성하는 요소중에서 마지막으로 공개키(public key)에 대해서 알아보자. 공개키는 어셈블리의 개발자(배포 회사)를 확인하기 위해 사용하는 고유한 숫자들이다. 어셈블리는 128바이트의 완전한 공개키를 사용할 수도 있지만 간단히 공개키의 해시값인 8 바이트의 공개키토큰(public key token) 사용하여 동일한 효과를 나타낼 수도 있다. 어셈블리에 저장된 공개키 토큰은 sn.exe 커맨드 툴을 사용하면 볼 수 있다.
1319376030

공개키(토큰)출력

sn.exe –T myApp.dll   , sn.exe –Tp myApp.dll

-T 옵션을 사용하면 해당 어셈블리의 공개키 토큰을 출력해주고, -Tp를 사용하면 그림처럼 공개키를 함께 출력해준다.

새로운 공개키를 생성하기 위해서도 sn.exe 툴을 사용할 수 있다. 옵션으로 –k를 사용하면 공개키와 전용키(private key)의 쌍을 갖는 파일을 얻을 수 있다.

sn.exe –k publicprivate.snk

이 명령을 실행하게 되면 publicprivate.snk(확장자는 임의로 줄 수 있다.)파일에는 공개키와 전용키가 쌍으로 생성된다.
공개키(토큰)는 우연히 서로 다른 배포회사가 동일한 어셈블리 이름을 사용해서 파일 이름간에 충돌이 일어날 수 있는 경우를 해결할 수 있도록 해준다. 우연히 두 회사에서 어셈블리명.버전번호.컬쳐가 같은 어셈블리를 배포했다고 하더라도 공개키는 각각 고유한 값이므로 전체 어셈블리명은 다른 이름을 갖게 되어서 충돌을 피할 수가 있는 것이다.

공개키는 어셈블리에 고유 이름을 부여할 수 있는 역할도 하지만 어셈블리가 외부로부터 악의적으로 수정이 가해졌는지 체크하는데도 사용할 수 있다. 어셈블리에 고유한 디지털 사인을 추가하게 되면 CLR은 어셈블리가 외부로부터 악의적인 코드 수정이 일어났는가를 체크할 수 있게 된다. 어셈블리에 추가되는 디지털 사인 생성에 전용키(private key)가 사용되고,  디지털 사인이 된 어셈블리를 참조하는 호출 어셈블리쪽에서는 대상 어셈블리가 가지고 있는 전용키와 함께 생성된 공개키를 제시함으로써 사인으로 잠겨진 대상 어셈블리의 로딩이 가능해지게 된다. 공개키가 열쇠라면 전용키는 자물쇠라고나 할까.

using System.Reflection;
[assembly: AssemblyKeyFile(“publicprivate.snk”)]

우리가 Visual Studio.NET을 사용해서 어셈블리를 개발할 때 assemblyinfo.cs 파일에 흔히 추가하는 코드이다. 이 코드를 넣으면 어셈블리에 디지털 사인이 추가된다. 어셈블리 사인과 관련된 내용은 좀 더 전문적인 서적을 참고하기 바란다.

한번쯤은 강력한 이름(strong name)이라는 말을 들어본 적이 있을 것이다. 앞서 방법처럼 고유한 이름을 가지고 있는 어셈블리를 강력한 이름으로 서명한 어셈블리(strongly named assembly)라 한다. 어셈블리에 강력한 이름을 부여한다는 것은 고유한 이름을 부여한다는 것이다. 코드의 보안이 강화되며 어셈블리에 버전 정책을 사용할 수 있다. 그리고 강력한 이름의 어셈블리는 인터넷을 통한 안전한 배포가 가능하다( 강력한 이름이 없다고 인터넷을 통한 배포가 안되는 것은 아니다. )

어셈블리는 이 4부분의 요소를 모두 가질 수도 있고, 그렇지 않을 수도 있다. 강력한 이름의 어셈블리에 버전 번호가 없을 수 있다. 컬쳐 정보가 제공되지 않을 수 있다. 강력한 이름으로 서명되지 않는 어셈블리에도 버전 번호을 추가할 수도 그렇지 않을 수도 있다. 어셈블리명의 4부분중에서 공개키와 버전 번호는 어셈블리의 배포와 바인딩에 있어서 중요한 요소가 된다. 지금까지 어셈블리의 완전한 이름을 구성하는 각 요소들을 정리했다.

강력한 이름의 어셈블리와 약한 이름의 어셈블리에 대한 비교 설명은 다른 포스트에 있다.

Posted by dalbong2

GAC(Global Assembly Cache)

GAC(Global Assembly Cache)은 머신 차원의 공용 저장소로 이곳에 등록된 어셈블리는 머신에 설치된 모든 애플리케이션에서 같이 사용할 수 있다. 여러 애플리케이션에서 어셈블리에 접근하려면 그 어셈블리는 CLR이 인식할 수 있는 디렉토리에 있어야 한다. 참조하는 어셈블리를 애플리케이션이 로딩하려고 하면 CLR은 자동적으로 미리 정해진 그 디렉토리 구조를 따라가며 검색할 것이다.

GAC은 CLR이 이해할 수 있는 디렉토리 구조를 갖는다. GAC은 그러나 단순한 디렉토리가 아니다. 어셈블리의 버전닝 정책 즉 파일명은 같지만 버전번호가 다른 어셈블리가 동시에 존재할 수 있는 디렉토리 구조이며, 그리고 우연히 두 회사에서 출시한 어셈블리의 파일명이 같더라도 회사의 기밀 사항인 공개키/전용키 값이 다른 어셈블리가 동시에 존재할 수 있는 구조이다. 또한 어셈블리의 충돌을 피하기 위한 수단으로 GAC에 등록하려는 어셈블리는 반드시 디지털 사인과 공개키를 갖고 있어야 한다.

GAC의 내부 저장 구조는 어셈블리의 버전 정책을 이해하는데 도움이 된다.  GAC에 등록된 어셈블리의 내용을 보려면 다음을 수행한다.

시작->실행 창에서 "assembly"

이렇게 하면 윈도우 탐색기가 뜨지만 일반 디렉토리 구조와는 다른 내용이 출력된다.
1301365642

assembly 디렉토리 내용

GAC에 어셈블리를 등록시키기 위해서는 해당 어셈블리는 반드시 strongly named assembly이어야 한다. VS.NET으로 개발한다면 assemblyinfo.cs 파일에는 반드시 다음처럼 키파일(key file)을 지정하는 코드가 있어야 한다[각주:1]

[assembly: AssemblyKeyFile("..\\..\\mykey.snk")]

이렇게 생성된 어셈블리는 gacutil.exe 커맨드 툴을 사용해서 GAC에 등록할 수 있다. GAC에 등록하고 GAC에서 삭제하는 명령어는 다음과 같다.

gacutil.exe /i myassembly.dll
gacutil.exe /u myassembly
gacutil.exe /u myassembly, Version=1.1.0.0, Culture=ko-KR, PublicKeyToken=123456789012

옵션 “/i 어셈블리파일명”을 사용하면 어셈블리를 GAC에 등록할 수 있다. GAC에서 삭제할때는 /u 옵션을 사용하는데, “/u 어셈블리명” 명령어를 사용하면 버전, 컬쳐, 공개키토큰이 다를지라도 동일한 어셈블리명 파일명을 갖는 모든 어셈블리를 GAC에서 삭제한다. 동일한 파일명의 어셈블리가 동시에 있을 경우, 특정 어셈블리만 삭제하고 싶은 경우는 완전한 어셈블리명을 인자로 주면 된다.

윈도우 탐색기를 이용해서 assembly 폴더를 보면 하나의 폴더안에 모든 어셈블리가 저장되어 있는 것처럼 보이지만 실제로는 그렇지 않다. 단순한 단일 계층의 구조로 보이지만 GAC내부는 실제로 동일한 파일명을 가지고 있더라도 어셈블리명의 4가지 구성요소에 따라 다른 곳에 저장될 수 있는 디렉토리 계층 구조를 갖는다. 다음은 달봉이의 로컬 머신에서 명령 프롬프트 창을 실행해서 C:\Windows\Assembly\GAC 디렉토리 내용을 본 모습이다.
1070794810

GAC내부#1

C:\Windows\Assembly\GAC에는 GAC에 등록된 어셈블리별로 하나씩 하위 디렉토리가 있다. 이 디렉토리중에서 하나의 하위로 들어가면 또 하나 이상의 하위 디렉토리가 있다.
1311574477

GAC내부#2

System 디렉토리의 하위에는 다시 “1.0.5000.0__b77a5c561934e089” 디렉토리가 있다. 이 디렉토리명은  “버전_컬쳐_공개키토큰”으로 구성된다. 달봉의 머신에는 System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken= b77a5c561934e089”의 어셈블리가 인스톨되어 있다. 다시 이 디렉토리를 들어가보면 이 어셈블리파일이 있다. 만약 다른 버전의 System.dll 등록되어 있다면 다른 그 어셈블리는 다른 디렉토리에 저장되어 있을 것이고 그 디렉토리 경로는 다음과 같은 형식을 취할 것이다.

\GAC\System\버전번호__b77a5c561934e089

GAC의 내부 구조가 이러하므로 GAC에는 동일한 파일명을 갖는 어셈블리가 여러 버전으로 등록될 수 있다. 또한 이러한 구조 때문에 GAC에 등록된 어셈블리와 바인딩을 할때는 CLR은 어셈블리의 완전한 이름을 검색한다. 어셈블리 바인딩과 로딩에 대해서는 뒤의 Assembly Resolver, Assembly Loader에서 다룬다. 

GAC에 어셈블리를 등록할때는 단순히 어셈블리에 해당하는 디렉토리를 구성하고 그곳에 어셈블리 복사본을 저장하는 일만 일어나는 것은 아니다.  GAC에 등록될 때, CLR은 공개키, 디지털 사인을 이용하여 해킹에 의한 코드가 변경되었는지도 체크하게 된다. 사인 연기(delayed signing)을 이용해서 코드 변경 체크를 건너뛸 수도 있다.

  1. VS.NET 2005버전에서는 키파일을 지정하는 방법이 직접 assembly.cs 파일을 수정하는 대신에 키파일을 선택하는 속성 폼이 따로 있다. 하지만 역시 내부적으로 assembly.cs를 우리 대신에 수정해 준다. [본문으로]
Posted by dalbong2
TAG GAC

애플리케이션 도메인(AppDomain)과 환경 속성들

이 포스트에서는 애플리케이션 도메인과 그와 관련된 도메인 속성들에 대해서 알아본다. 이 포스트에서 중요한 개념은 애플리케이션의 베이스 디렉토리와 환경 설정 파일 .config이다. 애플리케이션의 베이스 디렉토리의 개념을 이해하는 것은 특히 스마트클라이언트 애플리케이션에서의 어셈블리 바인딩과 배포(특히 NTD배포)를 이해하는데 있어서 중요한 개념이다.

애플리케이션 도메인은 AppDomain이라는 클래스로 구현되어 있다. AppDomain에는 여러가지 환경 정보들을 가지고 있고, 이런 정보들은 퓨전을 제어하는 중요한 정보들이다. AppDomain의 환경 속성값들은 퓨전이 어셈블리를 검색할 때 이용하게 되는 중요한 정보들이다. 이런 환경 속성값들은 애플리케이션의 설정 파일 .config에 설정된다. 애플리케이션이 실행이 되면 CLR은 우선 .config 파일을 읽어들여서 이런 속성값들을 채우게 되다.

우선 애플리케이션이 실행되는 논리적인 공간이라고 한 AppDomain에 대해서 개념적으로 이해해보자. 코드의 실행과 리소스의 소유 범위를 결정하는 모델은 프로그래밍 모델, 기술에 따라서 항상 있어왔다. OS에서는 프로세스(process)가 애플리케이션의 경계가 되었고, IIS와 ASP에서는 가상 디렉토리를 기준으로 애플리케이션이 구분되었고 그리고 .NET 실행환경에서는 실행공간을 나누기 위해서 애플리케이션 도메인 개념을 사용하고 있는 것이다. 애플리케이션 도메인은 하나의 프로세스에 여러 개 있을 수 있다.
1029095727

Process 공간과 AppDomain 공간
좀더 상세한 그림은 CLR via C#- Jeffrey Richter( Chapter 21: CLR Hosting and AppDomains를 참조한다.)

.NET 애플리케이션이 시작되면, CLR은 기본적으로 하나의 애플리케이션 도메인을 생성하고 그 안에서 애플리케이션을 실행시킨다. IE 기반의 스마트클라이언트 애플리케이션에서도 브라우저가 최초의 어셈블리를 호출할 때 클라이언트에 애플리케이션이 하나 생성된다.

또한 프로그램적으로도 애플리케이션 도메인을 생성하고 그 속에서 또 다른 애플리케이션을 실행시킬 수도 있다. 그래서 그림처럼 하나의 OS 프로세스에 복수개의 애플리케이션 도메인이 존재할 수 있다.

두 애플리케이션이 동일한 어셈블리를 참조하고 있다면 어떻게 될까? 애플리케이션은 각자의 애플리케이션 도메인으로 어셈블리를 로딩하고 객체도 각각의 도메인 안에서 따로 생성한다. 그래서 그림처럼 각 애플리케이션 도메인마다 동일한 타입의 객체들이 독립적으로 존재하게 된다. 애플리케이션 도메인간에는 객체의 인스턴스 멤버뿐만 아니라 정적 멤버도 공유가 되지 않는다. 독립된 실행 공간이다.

독립된 공간이긴 하지만 두 공간 사이의 객체를 서로 접근 못하는 것은 아니다. 그러나 이것은 이 책의 범위를 벗어나는 주제이다. 뒤의 레퍼런스에서 소개하는 서적이나 아니면 다른 좀더 전문적인 문서를 참고하기 바란다. 여기서 말하고 싶은 요점은 하나의 .NET 애플리케이션은 애플리케이션 도메인이라는 하나의 독립된 공간에서 실행된다는 것이다.

앞에서 말한 애플리케이션 도메인의 환경 속성은 System.AppDomainSetup이라는 타입의 데이터 구조에 저장되어 있다. 이런 환경 설정값들은 AppDomain의 SetupInformation이라는 속성으로 사용자들에게 노출되고 있다.

AppDomain의 SetupInformation 속성을 이용하여 이런 환경 속성에 접근할 수도 있지만, 사용자는 GetData, SetData 메소드를 사용할 수도 있다. 환경 속성들은 이미 정해진 문자열 속성 이름을 가지고 있는데, 이 메소드들은 그 속성 문자열을 인자로 사용한다. 즉 다음과 같은 코드는 동일한 값을 반환한다.

string appbase = AppDomain.CurrentDomain.SetInformation.ApplicationBase;
string appbase = AppDomain.CurrentDomain.GetData("APPBASE");

이런 환경 속성들을 숙지하고 적절히 설정함으로써 애플리케이션의 개발, 배포, 운영상에서 발행할 수 있는 이슈들을 매끄럽게 해결할 수 있게 된다. 다음 표는 환경 속성을 나타내는 AppDomainSetup의 속성들과 그에 대응하는 Get/SetData의 속성 문자열을 몇 가지 보여주고 있다.
1364111840

애플리케이션 도메인 속성값 접근법 #1

이런 환경 속성중에서 어떤 것은 AppDomain에서도 직접 접근할 수 있도록 하고 있다.
1217830451

애플리케이션 도메인 속성값 접근법 #2



환경 설정값에 접근할 수 있도록 사용자에게 노출된 방법들이 이와 같으니 어떤 경우는 동일한 값에 접근하기 위하여 몇 가지 방법을 사용할 수 있다.
using System;
static void Main()
{
// 현재 AppDomain 인스턴스를 구한다.
  AppDomain current = AppDomain.CurrentDomain;
  //APPBASE 디렉토리를 구하는 3가지 방법
  string base1 = current.BaseDirectory;
  string base2 = current.SetupInformation.ApplicationBase;
  string base3 = (string)current.GetData(“APPBASE”);
  Console.WriteLine("base1:" + base1);
  Console.WriteLine("base2:" + base2);
  Console.WriteLine("base3:" + base3);

이 프로그램을 실행시키면, APPBASE 값은 프로그램이 실행되는 위치의 경로로 설정된다.
1252845535

애플리케이션 디렉토리 출력

그러나 이런 설정값들은 AppDomain 생성 후에는 변경이 불가하다.

AppDomain.CurrentDomain.ApplicationName = "MyApplication";

.NET 애플리케이션이 실행되면 이미 기본 AppDomain이 생성되고 그곳에서 애플리케이션이 실행된다. 따라서 이와 같은 코드는 적용되지 않는다. 따라서 이런 설정들이 나타내는 개념들을 이용하기위해서는 기본 AppDomain에서 새로운 AppDomain을 생성하는 프로그램을 작성할 필요가 있다. AppDomain을 이용한 프로그램이 그다지 어려운 것은 아니지만 이것은 이 책에서 벗어나는 주제이므로 이 책에서는 다루지 않고 있다. 관심있는 독자는 좀더 .NET 프로그래밍 위주의 서적을 참고하기 바란다. 다만 여기서는 애플리케이션 도메인의 속성들이 무엇을 말하는지 코드없이 개념 중심으로 알아본다.

애플리케이션 베이스 디렉토리 결정

애플리케이션 기본 디렉토리 Appbase값은 실제적인 파일 시스템상의 경로로서 참조되는 다른 어셈블리를 검색할 때 기준이 되는 디렉토리값이다. 애플리케이션 도메인이 애플리케이션이 실행되는 논리적인 공간이라면 그것을 실제 물리적인 경로와 연결시켜주는 것이 애플리케이션 기본 디렉토리 값이라고 볼 수 있다.
1164115792

애플리케이션 도메인, 베이스 디렉토리

AppDomain의 Appbase값은 설정 파일이 있는 경우는 그 파일이 로딩된 디렉토리값이 된다. 그러나 설정 파일이 없다면 .exe 파일이 로딩된 위치가 된다. 예를 들어 "c:\App" 또는 "http://localhost/MyApp/"가 Appbase 값이 될 수 있다. 그리고 웹 페이지 aspx에서 <object>태그에 의해 호출되는 스마트클라이언트 애플리케이션의 Appbase값은 배포 서버의 주소가 된다. 비록 <object>에 의한 어셈블리가 http://localhost/MyApp/아래에 있다 하더라도 Appbase값은 "http://localhost/"이 된다.

스마트클라이언트 애플리케이션의 경우에서처럼 Appbase값은 반드시 애플리케이션이 실행되는 머신상의 경로를 나타내는 것은 아니다. 스마트클라이언트 애플리케이션은 클라이언트측에서 실행되지만 Appbase는 http://~로 시작하는 원격 서버의 경로를 가리킨다.

달봉이는 Appbase값을 처리하는 곳에서 .NET 프레임워크가 데이스크탑 애플리케이션과 웹 애플리케이션을 위한 통합 프레임워크임을 재삼 느끼곤한다. 개발자는 Appbase가 로컬 머신의 경로이든 다른 네트워크상의 머신의 경로이든 상관없이 사용할 수 있다는 것이다. 예를 들어 다음과 같은 코드가 있을 수 있다.

Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + "MyApp.exe");

개발자는 이런 단일 형태의 코드만 작성하면 된다. 어셈블리 로더가 애플리케이션의 Appbase값을 통해 어셈블리 검색의 시작 위치를 확인하고 그 경로 표시 형태에 따라 후속작업을 하게 된다.

AppDomain이 생성될 때 동적 디렉토리(Dynamic Directory)라 불리는 값이 설정될 수 있다.  .NET에서는 어셈블리를 코드상에서 프로그램적으로 생성할 수 있는데, 이렇게 동적으로 생성되는 어셈블리가 저장될 디렉토리가 동적 디렉토리의 값으로 설정된다. CLR은 어셈블리를 검색할 때 .config의 <probing>요소의 privateBin에 설정된 디렉토리를 검색하기전에 동적 디렉토리를 먼저 확인한다. 가장 자주 코드를 동적으로 생성하는 녀석이 ASP.NET이다. ASP.NET이 설치되어 있는 컴퓨터의 동적 디렉토리는 아래 경로의 하위에 있다. 이곳을 보면 웹 애플리케이션별로 동적으로 생성된 어셈블리가 저장되어 있다.

%windir%\Microsoft.NET\Framework\<.NETVersion#>\Temporary ASP.NET Files\

ASP.NET의 경우는 웹 애플케이션이 만드는 모든 어셈블리를 이곳에 저장한다. CLR은 어셈블리 검색 과정에서 항상 이곳을 먼저 들러 보기 때문에 ASP.NET에서는 Appbase를 검색하지 않도록 BINPATH_PROBE_ONLY 속성을 설정해놓고 있다. ASP.NET에서는 APPBASE 디렉토리 검색을 막고 대신에 BINPATH_PROBE_ONLY 값으로 "bin"을 설정해 놓고 있다. 때문에 ASP.NET에서는 필요한 어셈블리가 있다면 APPBASE가 아니라 bin 폴더에 넣어두어야 한다.

다음에 나오는 FORCE_CACHE_INSTALL, SHADOW_COPY_DIRS, CACHE_BASE는 섀도우 복사(Shadow copy)와 관련된 속성들이다. 섀도우 복사는 서버측 애플리케이션과 배포와 관련된 일반적인 문제와 관련있다. 서버측 환경(IIS, COM+)에서 DLL이 한번 로딩되면 그 어셈블리에 다른 수정을 가하지 못하다록 읽기 락(read lock)이 걸린다. 따라서 DLL이 IIS, COM+환경하에서 로딩되고 나면 새로운 버전으로 그것을 덮어쓸 수가 없게 된다. 결국 새로운 버전을 배포하려면 애플리케이션의 실행을 중지시키고 해당 DLL의 락을 해제한 후에나 가능하게 된다. 섀도우 복사는 하나의 AppDomain에 대하여 설정할 수 있는 설정이다.

현장의 프로젝트에서도 이것과 관련한 요구사항이 종종 있다. "배포를 하기위해서 꼭 서버를 내려야 하느냐? .NET에서는 동일한 어셈블리가 버전만 다르면 동시에 존재할 수 있다고도 하는데, 서버를 내리지 않고도 배포를 할 수 있는 방법이 있지 않겠느냐? "는 것이다.

섀도우 카피는 이런 문제를 해결하기위해 사용될 수 있는 개념이다. CLR이 섀도우 복사를 이용하여 어셈블리를 로딩할때는 일단 임시 디렉토리로 어셈블리를 복사하고 나서 원본 대신에 그 복사본을 로딩하는 것이다. 따라서 복사본이 로딩되어 있는 동안에도 원래의 디렉토리로 배포가 가능하다.

섀도우 복사를 활성화하기 위해서는 3가지 설정이 필요하다.

1.       섀도우 복사를 활성화할지에 대한 여부
2.       어떤 디렉토리에 있는 어셈블리를 섀도우 복사할 것이지?
3.       어떤 디렉토리로 복사할 것인지?

따라서 섀도우 복사를 사용하려면 섀도우 복사의 대상이 디렉토리 즉 원본이 들어 있는 디렉토리(SHADOW_COPY_DIRS)와 복사본이 임시로 저장될 디렉토리(CACHE_BASE)에 대한 설정이 있어야 한다.

새도우 복사의 활성화 방법

- AppDomainSetup.ShadowCopyFiles 속성을 true로 설명하면 섀도우 복사가 활성화된다.

어떤 디렉토리에 있는 어셈블리를 섀도우 복사할 것인지?

- AppDomainSetup.ShadowCopyDirectories 속성은 섀도우 복사될 디렉토리를 나타낸다. 세미콜론(;)로 분리된 절대경로의 디렉토리 목록을 값으로 취한다. 이제 이 디렉토리에 있는 어셈블리가 로딩되면 섀도우 복사될 것이다. 만약 ShadowCopyDirectories값이 null이면 로딩되는 모든 어셈블리들이 섀도우 복사된다.

어디로 복사할 것인지.

- AppDomainSetup.CachePath + AppDomainSetup.ApplicationName 이 두 속성값의 연결문자열이 어셈블리가 어디로 섀도우 복사될 것인지를 결정한다. 만약 CachePath와 ApplicationName 속성 둘다 설정되었다면 CachePath\ApplicationName로 복사될 것이다. 그렇지 않다면 다운로드 캐시(%사용자계정명%\local settings\application data\assembly)로 복사된다.

CachePath+ApplicationName을 설정했다면 이곳의 어셈블리를 삭제하는 일은 직접 해야 한다. 그러나 다운로드 캐시에 복사된 어셈블리는 퓨전이 자동적으로 관리해준다. 퓨전과 다운로드 캐시는 조금 뒤에 나온다.

.NET 애플리케이션이 실행되면서 생성되는 기본 애플리케이션 도메인에서는 AppDomainSetup을 변경할 수 없다. 그러나 AppDomain의 API를 사용하면 속성을 변경할 수 있다. AppDomain의 관련 API는 다음과 같다: SetCachePath, SetShadowCopyFiles, SetShadowCopyPath. ApplicationName은 기본 애플리케이션 도메인의 애플리케이션 이름으로 설정된다.

AppDomain.CurrentDomain.SetShadowCopyFiles();
//AppDomain.CurrentDomain.SetCachePath(@"C:\cache");
AppDomain.CurrentDomain.SetShadowCopyPath(@"D:\SC_SourceCode\Hello_Solution\30_Test\TestWin\bin\Debug\dir");


애플리케이션 타입별 설정 파일( .config)

환경 설정 파일 .config이 없는 애플리케이션도 있을 수 있지만, 규모가 있는 대부분의 애플리케이션은 .config 파일을 이용해서 환경 설정을 하게 된다. CLR은 애플리케이션을 시작시키면서 환경 파일이 있는지를 확인하고, 설정 파일이 있는 애플리케이션인 경우는 설정 파일을 읽어들여 AppDomain의 환경 속성값을 초기화한다고 했다. CLR은 이 설정 파일을 어디서 찾을까? 애플리케이션 타입별로 설정 파일 위치가 다르다.

.exe 애플리케이션의 경우는 .exe 파일이 있는 위치에 .config 파일이 있어야 한다. .exe 파일을 로컬 PC 하드 드라이브에 있던 아니면 웹 서버에서 다운받던 상관없다. 그리고 그 파일명은 반드시 <애플리케이션명>.exe.config와 같은 형식으로 되어 있어야 한다.

ASP.NET 애플리케이션과 XML 웹 서비스 애플리케이션의 경우는 웹 애플리케이션의 가상 루트 디렉토리에 있어야 하며 이름은 web.config로 되어 있어야 한다. web.config 파일은 루트 디렉토리의 하위 디렉토리에도 있을 수 있는데, 이런 경우는 상위 디렉토리의 설정값을 상속받아서 상위 설정값을 오버라이딩할 수도 있고 또 설정을 추가할 수도 있다.

브라우저 기반의 스마트클라이언트 애플리케이션은 다음처럼  HTML의 <link> 태그를 사용한다.

rel 어트리뷰트는 "Configuration"으로 설정하고 href어트리뷰트에 .config 파일에 대한 URL을 설정하면 된다. 설정 파일의 이름과 확장자 조차도 임의로 작성할 수 있다. 설정 파일은 최소한 다음과 같은 모양을 하고 있어야 한다.

<configuration>
</configuration>

설정 파일에는 .NET에서 지정한 여러 XML 섹션이 있을 수 있다. 예를 들어서 흔히 사용되는 섹션중의 하나가 <appSettings>이다. 쉽게 변할 수 있는 값들을 여기에 설정해 놓고 코드의 변경없이 자주 값을 수정할 수 있도록 한다. 데이터베이스 연결 문자열을 이곳에 설정해 놓는 것이 대표적인 예이다.

<configuration>
  <appSettings>
       <add key="connectionstring"
                  value="server=203…..uid=…pwd=…database=…"/>
            <add key="number" value="1"/>
  </appSettings>
</configuration>

설정 파일의 각 섹션은 그 섹션의 값을 읽고 해석해낼 수 있는 섹션 리더(section reader)라는 것이 있다. 이런 섹션 리더는 애플리케이션의 .config 파일에 정의될 수도 있고, 머신 차원의 machine.config에 정의될 수 있다. <appSettings>요소의 섹션 리더는 machine.config에 다음과 같이 정의되어 있다.

<configuration>
  <configSections>
       …
       <section
                name="appSettings"
                type="System.Configuration.NameValueFileSectionHandler,
                System,
                Version=1.0.5000.0,
                Culture=neutral, PublicKeyToken=b77a5c561934e089"/>
            …

어셈블리 System.dll에 있는 NameValueFileSectionHandler 클래스는 <appSettings>요소내부의 섹션을 읽고 해석하여 NameValueCollection 객체를 반환한다는 내용을 정의하고 있다. 섹션 리더는 인터페이스 IConfiguraionSectionHandler를 구현해야 하고 <configSections>요소에 추가하면 등록이 된다. 

NameValueFileSectionHandler 클래스는 .NET 프레임워크 내부에서만 사용되고 대신에 사용자에게는 ConfigurationSettings, AppSettingsReader 클래스를 제공하고 있다.

<AppSettings> 자주 사용되는 요소이므로 여기서 ConfigurationSettings, AppSettingsReader 클래스들의 사용법을 간단히 소개한다.

using System.Configuration;
using System.Collections.Specialized;

NameValueCollection settings =
(NameValueCollection)ConfigurationSettings.GetConfig("appSettings");
...

ConfigurationSettings 클래스는 "appSettings"에 해당하는 섹션 핸들러를 구하고 다시 해당 섹션 핸들러는 현재 애플리케이션의 .config 파일에서 <appSettings>섹션에 있는 데이터를 파싱해서 NameValueCollection 객체를 만들어서 반환한다.

<appSettings> 섹션은 자주 사용된다는 이유로 ConfigurationSettings 클래스는 AppSettings라는 속성을 만들어 놓고 있다. 이것은 반환값이 NameValueCollection 타입이어서 타입 변환이 필요없다. 적당한 key 값을 인덱서 인자로 사용하면 반환된 NameValueCollection에서 필요한 값을 얻을 수 있다.

NameValueCollection settings = ConfigurationSettings.AppSettings;
string connectionstring = settings["connectionstring"];

ConfigurationSettins 클래스대신에 AppSettingsReader 클래스를 사용할 수도 있다.

AppSettingsReader reader = new AppSettingsReader();
int number = (int)reader.GetValue("number", typeof(int));

환경 설정 파일은 어셈블리 바인딩을 제어하는데도 중요하게 사용된다.

참조문서

Essential .NET Volume 1 - Don Box with Chris Sells
CLR via C# - Jeffrey Richter

Posted by dalbong2
1. 어셈블리의 일치 비교(assembly identity comparison)

어셈블리의 일치 비교는 어셈블리가 바인딩이 되고 로딩이 일어나려 할때 수행되고, 또한 어셈블리가 로딩되면, 로딩된 어셈블리의 캐시( LoadContext, LoadFromContext)에 캐싱이 되는데 새로운 어셈블리를 로딩하려고 할때 이 어셈블리가 이미 로딩되어 캐시에 있는지를 확인할때도 어셈블리 일치에 대한 비교 작업이 수행된다. 이런 어셈블리 일치에 대한 판정은 강력한 이름의 어셈블리과 약한 이름의 어셈블리가 다른 비교 로직을 거치게 된다.

강력한 이름의 어셈블리와 약한 이름의 어셈블리는 내부 구조에 있어서는 동일하다. 구조적인 면에서의 차이점은 강력한 이름의 어셈블리는 디지털 사인을 추가했다는 것이다. 디지털 사인이 추가된다는 것은 어셈블리에 고유한 이름이 추가되고 동일한 이름(fully qualified assembly name)의 다른 어셈블리가 없다고 보장할 수 있다는 것이다.

어셈블리 바인딩에 대한 예를 들어본다. 이제 어셈블리 파일에 대한 위치를 확인한 후라고 하자. CLR은 호출하는 어셈블리가 가지고 있는 메너페스트의 참조 정보를 통해서 대상 어셈블리가 강력한 이름의 어셈블리라고 판단되면, 단순히 어셈블리 파일명으로만 원하는 어셈블리라고 결정하는 것은 아니다. 즉 목표로 하는 어셈블리명과 동일한 이름을 갖는 파일을 찾기는 했지만 그것이 원하는 어셈블리인지 확인하는 방법은 어셈블리의 완전한 이름(fully qualified name)의 비교를 통해서이다. 예를 들어 어셈블리 파일명이 component.dll이고, 이것의 완전한 이름이 다음과 같다고 해보자.

“component, version=1.2.3.4, culture=neutral, publickeytoken=123456789012”

강력한 이름의 어셈블리는 공용 저장소 GAC에 다른 버전의 어셈블리가 동시에 등록될 수 있다. 공용저장소 GAC의 구조를 알고 나면 이해가 갈 것이다. GAC은 공용저장소로도 사용되지만 버전 정책이 실행될 수 있는 구조를 가지고 있다. 그래서 GAC에는 다음과 같은 어셈블리가 앞의 버전의 어셈블리와 동시에 저장되어 있을 수 있다.

“component, version=1.2.3.5, culture=neutral, publickeytoken=123456789012”

이 둘중의 어떤 어셈블리를 선택할지는 호출하는 어셈블리가 가지고 있는 어셈블리의 완전한 정보를 서버 비교해서 결정을 내리게 된다. 만약 버전 정책을 가지고 있는 config 파일이 있다면 그 내용을 적용한 후 결정되는 어셈블리의 완전한 이름과 대상 어셈블리의 완전한 이름이 비교될 것이다.

약한 이름의 어셈블리인 경우는 어셈블리 정보중에서 호출하는 어셈블리와 대상 어셈블리의 어셈블리 이름만 같은면 된다. 어셈블리 일치 비교에 버전과 컬쳐 정보는 포함되지 않는다. 그러나 공개키 비교는 포함된다.
예를 들어 빌드할때는 대상 어셈블리에 공개키가 없는 약한 이름이었다고 하자. 근데 대상 어셈블리를 다시 강력한 이름으로 만들어서 약한 이름의 어셈블리가 있는 곳에 강력한 이름의 버전의 파일로 덮어쓰기를 했다고 하자. 호출하는 어셈블리는 정상적으로 파일을 찾아서 로딩시킬수 있을까? 파일 검색은 할 수 있지만 어셈블리 로딩에서 에러가 발생한다. fuslogvw.exe 툴은 공개키가 맞지 않는다는 로그를 남긴다.

2. 어셈블리별 배포

약한 이름의 어셈블리는 공용 저장소 GAC에 등록될 수 없으므로 결국 애플리케이션마다 복사되어 배포되어야 한다.  그러나 강력한 이름의 어셈블리는 GAC에 등록되어 여러 애플리케이션에서 공유할 수 있도록 배포될 수 있으면서 그리고 동시에 애플리케이션 전용으로 애플리케이션 디렉토리에 복사될 수도 있다.

약한 이름의 어셈블리
- 전용 배포 가능
- 공용 배포 불가
강력한 이름의 어셈블리
- 전용 배포 가능
- 공용 배포 가능

3. 보안 체크

약한 이름의 어셈블리와 강력한 이름의 어셈블리는 어셈블리의 로딩시 보안 체크 과정을 거치느냐에도 차이가 있다. 약한 이름의 어셈블리는 공개키, 디지털 사인이 없다. 이런 경우는 호출하는 어셈블리와 대상 어셈블리의 로딩시 코드 변경 체크가 수행되지 않는다. 그러나 강력한 이름의 어셈블리는 코드 변경 체크를 거치게 된다. 다만 GAC으로 배포되는 경우는 GAC에 등록될 때만 코드 변경 여부를 체크하고 GAC이외의 디렉토리로 전용 배포된 경우는 로딩될때마다 체크를 하게 된다.

Posted by dalbong2
.NET의 애플리케이션 도메인과 관련해서 괜찮은 FAQ이다.

http://www.gotdotnet.com/team/clr/AppdomainFAQ.aspx
Posted by dalbong2

기본 AppDomain 생성자를 변경하는 작업이 왜 필요한지, 어디에 이용하는지를 묻는 사람이 있어서 달봉이가 어떻게 해서 여기까지 오게 되었는가를 잠시 덧붙인다.
달봉이가 현재 참여하고 있는 프로젝트에서 IE 페이지 하나에 스마트클라이언트 컨트롤 하나씩을 임베딩한다는 이야기가 있었다(현재는 EXE 컨테이너를 사용하는 방식으로 변경되긴 했지만). 그때 달봉이의 머리에 스친 의문을 해결하는 과정에서 AppDomain 생성이라는 것에까지 관심을 갖게 되었다. 달봉이가 가졌던 구체적인 의문은 지난 포스트(
IE 임베딩 방식 스마트클라이언트 애플리케이션의 도메인 중복 생성??)에서 볼 수 있다.
달봉이의 의문을 간단히 요약을 하면 다음과 같다.
『만약 <object> 태그로 스마트클라이언트 컨트롤을 로딩할때마다 클라이언트 PC에 AppDomain이 생성된다면 어떻게 될까? 그래서 같은 메뉴를 클릭할때마다 다른 AppDomain이 생성된다면 어떻게 될까? 』끔찍한 일이다. 답은 애플리케이션 도메인은 계속 생성되지 않는다는 것이다. 구체적인 내용은
포스트를 참조한다.
즉 달봉이는 AppDomain을 생성할때마다 로그를 남기는 작업이 필요했던 것이다. 해서 여기까지 오게 된 것이다.



이전 포스트에서 OS 프로세스와 Application Domain의 관계를 설명한 적이 있다.  이제 이 포스트에서는 OS 프로세스가 Application Domain을 생성할 때 그 과정에 참여할 수 있는 방법에 대해서 알아본다.

1236297958 

AppDomain 생성 과정

그림처럼 하나의 OS 프로세스는 CLR을 호스팅하고 CLR은 여러 개의 AppDomain 인스턴스를 생성할 수 있게 된다. CLR이 AppDomain 인스턴스를 생성할 때 System.AppDomainManager를 사용하게 되는데, AppDomainManager 확장을 통해서Application Domain 인스턴스 생성에 참여할 수 있게 된다.

■ AppDomainManager 확장

다음은 기본 AppDomainManager을 확장하는 코드이다. 상세한 코드는 참조 문서에서 소개하는 shawnfa 블로그를 참조한다.

public class MyAppDomainManager : AppDomainManager
  {

       public MyAppDomainManager() : base() ;
       public override AppDomain CreateDomain(…) 메소드
       //
       // .... 다른 메소드와 속성들
       //
  }

■ 확장된 AppDomainManager 정보 등록

1) 확장된 AppDomainManager 정보를 가지고 있는 어셈블리를 GAC에 등록한다.
2) 확장한 MyAppDomainManager에 대한 정보를 다음처럼 명령창을 이용해서 환경 변수에 설정한다. 레지스트리에 등록할 수도 있다. shawnfa 블로그를 참조한다.

C:\>set APPDOMAIN_MANAGER_TYPE
= MyNameSpace.MyAppDomainManager

C:\>set APPDOMAIN_MANAGER_ASM
= AppDomainManagerAssembly
, Version=1.0.0.0
, Culture=neutral
, PublicKeyToken=f1368f7b12a08d72

이제 PC에서 CLR이 AppDomain을 생성할 때 사용하는 생성자는 MyAppDomainManager(), CreateDomain()을 사용한다. 기본 클래스 AppDomainManager에서 제공하는 메소드 및 속성이 어떤 의미를 갖는지 정리한다. 우선 AppDomainManager 인스턴스 생성시 참여할 수 방법을 제공하는 메소드이다.
1263551021

기타 커스터마이징이 가능한 속성 및 메소드들이다.

1305858498

좀 더 자세한 내용을 위해서는 다음 링크 페이지를 참조한다.

참조문서

AppDomainManager 클래스 - MSDN
http://msdn2.microsoft.com/en-us/library/system.appdomainmanager.aspx

The Managed Hosting API ? shawnfa 블로그
http://blogs.msdn.com/shawnfa/archive/2004/11/12/256550.aspx
Customizing the AppDomain Creation Process ? shawnfa 블로그
http://blogs.msdn.com/shawnfa/archive/2004/11/17/259105.aspx

Posted by dalbong2
애플리케이션단에서 처리되지 않은 예외를 CLR에서 처리하는 방식이 .NET의 버전에 따라서 약간 다르다. 참조 문서는 v1.1과 v2.0에서의 Global 예외 처리를 비교하고 있다.

"CLR의 Global 예외처리"라는 것은 달봉이가 사용하는 용어로서, .NET 애플리케이션에서 처리하지 못하고 결국은 CLR까지 전달되어온 예외를 처리하는 방식을 말하고 있다.

v1.1의 방식은 문서를 참고하고, v2.0을 기준으로 해서 요약하겠다. 처리되지 않은 예외가 전파될때 최종적으로 작동하는 예외 처리기는 애플리케이션의 타입에 따라 다르다.


■ Windows Forms 애플리케이션이 아닌 경우

최종 예외 처리기  : AppDomain.UnhandledException 처리기
사용 예
AppDomain.CurrentDomain.UnhandledException += delegate(object sender, UnhandledExceptionEventArgs e)  
{
    //이곳에서 예외 처리한다.
     Console.WriteLine( "예외발생했씸더~~");
};


■ Windows Forms 애플리케이션인 경우

최종 예외 처리기 : Application.ThreadException 처리기
사용예
void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{
  //이곳에서 예외 처리한다.

  try
  {
     // Call user override
     if (this.threadExceptionHandler != null)
     {
        this.threadExceptionHandler(Thread.CurrentThread, new ThreadExceptionEventArgs(e.Exception));
     }
     else
     {
        using (ThreadExceptionDialog excptDlg = new ThreadExceptionDialog(e.Exception))
        {
           DialogResult result = excptDlg.ShowDialog();
           if (result == DialogResult.Abort)
              Application.Exit();
        }
     }
  }
  catch
  {
  }
}


Windows Forms 애플리케이션에서 처리되지 않은 예외중에서 Primary 쓰레드에서 발생한 예외는 Application.ThreadException 처리기로 보내진다. 그러나 만약 Secondary 쓰레드에서 예외가 발생하면 AppDomain.UnhandledException으로 보내지게 된다.

애플리케이션 타입에 따라 이렇게 예외 핸들링을 해야 하는 것이 불편하다면 다음과 같은 방법을 사용해서 AppDomain.UnhandledException 에서 일괄적으로 처리할 수 있다.

■  모든 애플리케이션에서 AppDomain.UnhandledException 사용하기

[STAThread]
static void Main()
{
  Application.SetUnhandledExceptionMode(UnhandledExceptionMode.ThrowException);
  Application.Run(new Form1());
}

Windows Forms 애플리케이션에서 Application.SetUnhandledExceptionMode()에서 이렇게 UnhandledExceptionMode.ThrowException 설정을 하면, 예외를 Application.ThreadException 처리기로 보내지 않고 다시 throw시킨다. 결국 AppDomain의 UnhandledException 처리기가 받게 된다.

참조 문서

Handling "Unhandled Exceptions" in .NET 2.0
Posted by dalbong2
닷넷 애플리케이션의 시작 시간을 줄이는 것에 대해서 이야기하고 있습니다.

http://blogs.msdn.com/shawnfa/archive/2006/06/23/644648.aspx
Posted by dalbong2

얼마전에 달봉이 여자 친구가 업체로부터 질문을 하나 받았다는 것이다. 내용을 보면 .NETv1.?에서 개발한 스마트클라이언트 애플리케이션을 .NETv1.? .NETv2.0이 설치된 클라이언트 PC에서 다운해서 구동시켰더니 이전과는 다른 거동을 하더라는 것이다.

그 다른 내용이 무엇이든지 간에 이렇게 다른 버전의 CLR이 설치되어 있는 PC에서 특정 버전의 CLR을 사용하려면 어떻게 해야 하는지 그 동안 달봉이는 궁금했다.

1. CLR Facade : MSCOREE.DLL

CLR
의 실제 DLL MSCORWKS.DLL 이다. 만약 멀티 프로세서를 사용하는 PC에서라면 MSCORSVR.DLL이 사용된다. 그리고 하나의 PC에는 여러 버전의 CLR이 설치될 수 있다. .NET 프레임워크가 설치된 PC에는 MSCOREE.DLL도 설치되어 있는데, 이것은 특정 PC에서 어떤 DLL의 CLR과 어떤 버전의 CLR을 사용할 것인가를 결정하는 일을 한다. DLL 파일을  "shim"이라고 부른다.

"shim"은 우리나라 말로도 그냥 ""이라고 한다. 영한 사전을 찾아보면 이렇게 나와있다. "틈새를 메우거나 물건을 수평으로 하기 위해 사용하는 나뭇조각이나 금속 조각 등". 파티션으로 사무실 공간을 나눠서 사용하는데, 그 파티션과 파티션을 연결하는 얇은 줄기같은 것들을 본 일이 있을 것이다. 이것을 ""인 것이다.

MSCOREE.DLL은 외부의 세계에서 CLR에 접근하려고 할때 제일 먼저 통과하는 관문(Facade)역할을 한다고 해서 CLR Facade라고 그림에도 표현되어 있다.

1352473588

MSCOREE 와 그의 친구들


MSCorEE.dll(shim)

이 하는 Facade 역할을 그림으로 그려보면 다음과 같다.

1106181055

Facade로서의 MSCorEE.dll

MSCorEE.dll이 하는 일은 로딩될 CLR의 버전을 결정하는 일이다. 앞에서 말한대로 MSCorEE.dll 자체는 CLR을 구현하고 있지는 않다.

하나의 머신에는 여러 버전의 CLR이 설치되어 있을 수 있지만, MSCorEE.dll은 가장 마지막 버전의 CLR과 함께 배포된 버전 하나만 설치된다. 따라서 최종 버전의 MSCorEE.dll은 이전에 설치된 CLR 버전을 알 수 있게 되는 것이다.

MSCorEE.dll은 호출한 호스팅 애플리케이션이 어떤 버전의 CLR로 빌드되었는지를 먼저 확인해서, 그 애플리케이션을 빌드한 CLR을 자동 로딩시킨다.

( 따라서 달봉이 여자 친구가 말한 것처럼,  애플리케이션을 빌드한 CLR 버전을 일부러 로딩시키려고 할 필요가 없다. 근데 이 기능이 제대로 작동하지 않는다는 얘기가 있다. )


그러나 MSCorEE.dll이 로딩할 CLR 버전을 결정할때 사용자가 참여할 수 있는 방법이 있다. 아래에 그 방법을 설명하고 있다.


2. 로딩될 CLR 버전 결정

사용자는 어떤 버전의 CLR을 로딩시켜야 할지에 대한 정보을 레지스트리, 환경 설정 정보를 통해서 MSCORE.DLL에게 전달할 수 있다.

1) 레지스트리 값 참조

[HKEY_LOCAL_MACHINE\Software\Microsoft\.NETFramework]
Version=v1.0.3215
InstallRoot=C:\windows\Microsoft.NET\Framework\



MSCOREE.DLL
이 로딩할 CLR DLL을 찾기위해서, 단순히 두 값을 결합한 값을 이용한다.
"C:\windows\Microsoft.NET\Framework\v1.0.3215"
디렉토리에 있는 CLR DLL을 로딩한다.

2) 애플리케이션의 환경 설정 파일(.config) 이용

만약 실행되는 애플리케이션의 설정 파일에 CLR의 버전에 대한 설정이 있다면 레지스트리 값을 대신해서 사용된다. 다음은 MSDN에서 참조한 내용이다.

<!-- 1.0 버전의 CLR을 사용할때 -->
<configuration>
  <startup>
     <requiredRuntime version="v1.0.3705" safemode="true"/>
  </startup>
</configuration>

<!-- 1.1( 또는 이후) 버전의 CLR을 사용할때  -->
<configuration>
  <startup>
     <supportedRuntime version="v1.1.4322"/>
     <supportedRuntime version="v1.0.3705"/>
  </startup>
</configuration>

<startup> 요소의 자식으로는 다음 두가지 올 수 있다.

<requiredRuntime>
1.0
대의 CLR을 사용해서 개발된 애플리케이션에서만 사용할 수 있다만약 1.1 버전 이후의 프레임워크로 만드어진 애플리케이션은 <supportedRuntime> 요소를 사용해야 한다.

<supportedRuntime>
앞의 예제 코드에서는 애플리케이션이 v1.1.4322 v1.0.3705을 지원한다는 것을 나타낸다. 애플리케이션이 지원하는 CLR 버전을 지정할때 사용한다. <supportedRuntime> 요소는 1.1 이후의 버전의 CLR을 사용하여 빌드한 애플리케이션에 대해서 사용할 수 있다. 1.0대 버전으로 빌드한 애플리케이션은 <requiredRuntime> 요소를 사용해야 한다. 애플리케이션의 설정 파일에 <supportedRuntime> 요소가 없다면 애플리케이션을 빌드할때 사용된 버전이 사용된다.

만약 앞의 예제처럼 여러 버전이 설정되어 있다면 처음 설정된 버전의 CLR이 우선적으로 로딩될 기회를 갖는다. 만약 그 버전이 없다면 다음으로 명시된 버전의 CLR이 로딩된다.

IE
브라우저의 호스팅을 받아 시작하는 애플리케이션은 <startup>요소와 그  자식 요소들 <requiredRuntime>, <supportedRuntime>을 무시한다.


참조 문서

Essential .NET Volume 1
The Common Language Runtime
- Don Box with Chris Sells

CLR via C#
- Jeffrey Richter

Posted by dalbong2

지금까지 CLR을 로딩하고 AppDomin을 생성하는 것에 대해 필요에 따라 여기 저기서 산발적으로 다뤘다.

윈도우 프로세스와 AppDomain의 관계
- 애플리케이션 도메인과 속성들(베이스 디렉토리)
IE와 AppDomain 그리고 MIME 타입 및 MIME 필터
- IE 임베딩 방식 스마트클라이언트 애플리케이션의 도메인 중복 생성??
- NTD 배포 및 어셈블리 로딩 그리고 IIS 설정
- IE에서 어셈블리가 로딩되는 과정

이제 이곳에서 호스트 애플리케이션별 CLR 인스턴스가 생성되는 차이점 및 호스트가 어떻게 AppDomain을 관리하는지에 대한 깔끔한 정리를 시도해 볼 생각이다. 다룰 호스트 애플리케이션 타입으로는 다음과 같다.

- 컨솔 및 윈도우 폼 애플리케이션
- Internet Explorer
- MS ASP.NET , XML 웹 서비스 애플리케이션
- MS SQL 서버 2005

■  컨솔 및 윈도우 폼 애플리케이션

관리형 실행 파일(컨솔, 윈도우 폼 애플리케이션)이 구동되면, shim(MSCorEE.dll)이 가동된다는 것에 대해서 이전 포스트( http://dalbong2.net/88 ) 에서 설명했다. shim은 일단 애플리케이션의 시작 어셈블리에 포함된 헤더 정보를 조사해서 그 애플리케이션이 어떤 버전의 CLR로 빌드가 되었는지를 확인한다. 그래서 그 버전의 CLR을 애플리케이션 프로세스로 로딩한다. CLR은 기본 AppDomain을 생성한 다음, 애플리케이션의 시작 어셈블리를 AppDomain으로 로딩한 다음 시작 포인트가 되는 메소드(Main())를 호출해서 애플리케이션을 그때부터 실행시키게 된다.

코드가 실행되면서, 다른 타입을 접근하게 되면, CLR(정확히 말하면 Fusion)은 참조되는 타입을 포함하고 있는 어셈블리의 위치를 결정하고 같은 AppDomain으로 어셈블리를 로딩한다.

애플리케이션의 Main() 메소드가 리턴되면, 애플리케이션을 위한 윈도우 프로세스는 종료된다. 이때 기본 AppDomain 및 추가된 모든 AppDomain들도 제거된다.

애플리케이션은 CLR에게 같은 애플리케이션 프로세스내에서 추가적인 AppDomain을 생성하도록 지시할 수도 있다.

■  Internet Explorer

머신에 .NET 프레임워크를 인스톨시키게 되면, MIME 필터(MSCorIE.dll)도 같이 인스톨된다. 인스톨된 이 MIME 필터는 IE 5.01 이후의 버전부터 작동하게 된다.

이 MIME 필터가 하는 일은 이렇다. IE를 통해서 다운되는 컨텐트에는 MIME 타입이 마킹되어 내려오게 되는데, 여러 MIME 타입 중에서 "application/octet-stream"이나 "application/x-msdownload" 타입의 컨텐트는 이 MIME 필터가 처리하게 된다.

이런 MIME 타입의 컨텐트를 확인하게 되면 MIME 필터는 먼저 CLR을 로딩하게 된다. 이렇게 해서, IE가 CLR를 호스팅하는 프로세스가 되는 것이다.

MIME 필터는 "동일한 웹 사이트"에서 다운된 어셈블리는 동일한 AppDomain으로 로딩되도록 한다. "동일한 웹 사이트"란 정확히 말하면 애플리케이션 베이스 디렉토리가 같은 웹 사이트를 말한다. 지난 포스트(IE 임베딩 방식 스마트클라이언트 애플리케이션의 도메인 중복 생성??)를 참조한다.

기본 AppDomain은 그것의 호스팅 프로세스 여기서는 IE 프로세스가 끝나지 않고서는 언로딩될 수 없다. 따라서 어떤 웹 사이트에서 다운되어 실행되는 코드가 사용자가 다른 웹 사이트로 서핑을 떠난다 해도 언로딩되지 않는다. 이것은 어셈블리가 이미 클라이언트측에 로딩되어 있다면, 사용자가 F5(새로고침)을 한다고 해서 서버에서 수정된 새로운 버전의 어셈블리를 다운받을 수 없는 이유이기도 하다. 수정된 새로운 어셈블리를 다운받으려면 IE 프로세스를 종료해야 한다.

■  MS ASP.NET , XML 웹 서비스 애플리케이션

ASP.NET은 ISAPI DLL로서 ASPNET_ISAPI.dll에 구현되어 있다. 클라이언트에서 처음으로 ASP.NET ISAPI DLL로 요청을 보낼때 ASP.NET은 CLR을 로딩하게 된다.

그런 다음 클라이언트에서 어떤 웹 애플리케이션에 대한 요청이 올라올때 마다, ASP.NET은 그것이 해당 웹 애플리케이션에 대한 첫번째 요청인지를 체크하고 만약 그렇다면 ASP.NET은 CLR에게 그 웹 애플리케이션을 위한 AppDomain을 생성하도록 한다.

각각의 웹 애플리케이션은 그것의 가상 루트 디렉토리(virtual root directory)로 아이덴터티가 구분되어 진다. ASP.NET은 CLR에게 웹 애플리케이션이 노출시킨 타입(.aspx 페이지)이 있는 어셈블리를 AppDomain으로 로딩시켜 이 타입의 인스턴스를 생성하고 클라이언트의 웹 요청을 처리하도록 지시한다. 만약 그 코드가 다른 타입을 참조하고 있다면 CLR은 필요한 어셈블리를 그 웹 애플리케이션의 AppDomain으로 로딩시킬 것이다.

만약 클라이언트에서 다른 웹 애플리케이션에 있는 페이지를 요청하게 되면, ASP.NET은 CLR에게 새로운 AppDomain을 생성하도록 한다. 이 새로운 AppDomain은 이미 다른 AppDomain들이 생성되어 있는 동일한 worker  프로세스내에 생성된다. 즉 많은 웹 애플리케이션이 하나의 윈도우 프로세스내에 생성되고 이것은 시스템의 성능을 향상시켜준다.

■  MS SQL 서버 2005

MS SQL 서버 2005 자체는 비관리형 C++코드로 작성되어 있는 비관리형 애플리케이션이다. 그러나 SQL 서버 2005에서는 관리형 코드를 사용해서 저장 프로시져(stored procedures)를 작성할 수 있다.

관리형 저장 프로시져 실행에 대한 요청이 처음으로 데이터베이스에 들어오면 그때 SQL 서버는 CLR을 로딩한다. 저장 프로시져는 자기만의 AppDomain에서 실행된다. 이 AppDomain은 sandboxing되어 있어서 그 프로시져가 데이터베이스 서버에 해를 입히는 일을 수행하는 것을 막을 수 있다.

이상 CLR의 호스트가 될 수 있는 애플리케이션 타입별로 어떻게 CLR이 로딩되고 어떻게 AppDomain이 생성되는지에 대해서 알아봤다. 이런 과정을 이해하는 것은 .NET 애플리케이션의 내면을 이해함에 있어서 안개를 한층 더 걷어 주는 역할을 할 것이다. 

Posted by dalbong2

Windows Vista니 .NET3.0이니 정말 난리도 아니다. 무슨 일인가 눈만 멀뚱거리다 드디어 달봉이도 무슨 일인지 함 알아보고 싶었다. 간단하게 3.0의 물결에 살짝 올라타 볼 생각으로 우선 SmartClient 애플리케이션과 관련한 얘기를 정리해 볼 것이다.  해서 카테고리 이름도 SmartClient 3.0이라고 했다.  우선 웹 브라우저 기반의 애플리케이션인 XAML Browser Application 부터 알아볼 것이다.

1. XAML Browser Application 개요

2. How-To : XAML Browser Application 만들기

3. How-To : 웹 서버로 게시하기

4. 클라이언트로 배포하기
  - 3.0에서는 브라우저 임베딩 타입의 스마트클라이언트 애플리케이션에서도 ClickOnce 배포를 사용할 수 있다. 이것에 대해서 알아볼 것이다.

5. 기타 사항
  - 기존의 ClickOnce에서의 주요 주제를 다시 한번 XAML Browser Application 관점에서 살펴본다.

            - 권한 elevate 하기( 기본 권한 : Internet Zone 권한을 갖는다)

            - 업데이트하기

            - 부트스트래퍼 사용하기

달봉이가 이미 올렸던 ClickOnce 카테고리의 포스트를 읽어본다면 이 연재는 쉽게 이해될 것이다. 그렇길 바란다.

마지막으로 WPF 샘플 애플리케이션 모음 동영상 을 소개합니다.  WPF로 이렇게까지 할 수 있다고 합니다. 즐감!
Posted by dalbong2

출근해서 컴을 켜자 RSS리더기의 알림창이 쑤욱 올라온다. 누가 블로그에 포스트를 새로 하나 올린 모양이다. 제목을 클릭해서 해당 블로그로 가 보니 다음과 같은 내용이 있다. 시스템을 시작하거나 또는 사용자가 로그인하면 자동으로 어떤 일을 할 수 있도록 설정할 수 있다는 것인데, 유용한 팁이 될 것 같아 이곳에 캐시해둔다.
원본 주소는 이곳(http://blogs.msdn.com/junfeng/archive/2006/09/19/761765.aspx)을 참조하면 된다.

참고로 달봉이 노트북의 레지스트리 키 HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run 을 보면 다음처럼 컴이 부팅될때 시작되는 프로그램들이 등록되어 있는 것을 볼 수 있다.
1395199481


Run/RunOnce/RunOnceEx Registry Key
Run\RunOnce\RunOnceEx are documented ways to automatically run certain tasks when system startup or a user logs in. 

Run\RunOnce keys are documented here:

Run and RunOnce Registry Keys
http://windowssdk.msdn.microsoft.com/en-us/library/ms723554.aspx

RunOnceEx is documented here:

Syntax for the RunOnceEx Registry Key
http://support.microsoft.com/default.aspx?kbid=232509

Description of the RunOnceEx Registry Key
http://support.microsoft.com/kb/310593

You can also specify RunOnce when install device drivers

KB 281820: INFO: Specifying RunOnce in Device INF Files
http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q281820  

Be careful about RunOnce registry key: It may run before system restart.  

According to KB 281829, RunOnce registry key will run in the following cases:  

1. At the end of installation through InstallFromHinfSection even in the cases where you must restart the computer.

2. After the default processing of DIF_INSTALLDEVICE even if you must restart the computer (with the exception of server-side processing).

3. After the system has been restarted.  

This means, RunOnce registry key entries may be executed immediately after a device driver installation, which may happen at any given time.  

If your RunOnce entry needs to be executed after certain event, make sure you write the RunOnce registry key after the event, or if you cannot do so, use RunOnceEx. Otherwise you may experience random failures.

Posted by dalbong2

http://blogs.msdn.com/junfeng/archive/2004/02/05/67815.aspx
이 링크를 보면 .NET 프레임웤이 설치되었는지를 확인해볼 있는 레지스트리 키에 대해 나와 있다.

How to detect .NET Framework installed or not

If you search google how to detect .Net Framework, this(http://support.microsoft.com/default.aspx?scid=kb;%5BLN%5D;315291) page ranks very high.

Ignore all the details in the KB articles. It basically asks you to check this reg keys & value properties.

For the .NET Framework 1.0:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\policy\v1.0
Value property : "3705" 

For the .NET Framework 1.1:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\policy\v1.1
Value property : "4322" 

For the .NET Framework 2.0:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\policy\v2.0
Value property : "50727

But wait, there is another not-so-well known documented way to detech existence of .Net framework v1.1. And it is not even in the first 10 pages of google hits.

Redistributing the .Net framework 1.1

It tells you how to detect .Net framework 1.1. And more! It also tells you how to detect the language pack, and J# package!

Should I say this is a failure of google, or Microsoft?:)

Posted by dalbong2

 

기존의 애플리케이션이 다른 버전의 CLR로 제작된 경우, 때로는 애플리케이션이 실행될 CLR 버전을 변경시킬 필요가 있다. 이런 경우 애플리케이션의 config 파일에 다음 설정을 추가함으로써 원하는 CLR 버전을 선택할 수 있다.

Forces the v1.0 CLR to be run. If the v1.0 CLR is not installed, the app will fail to run.

<?xml version ="1.0"?>
<configuration>
    <startup>
         <requiredRuntime version="v1.0.3705"/>
         <supportedRuntime version="v1.0.3705"/>
     </startup>
</configuration>

현재 실행되는 어셈블리가, 현재 설치되어 있는 CLR 버전보다 최신 버전에서 개발된 경우에는 "BadImageFormatException" 예외를 발생시킬 수 있다. 자세한 내용은 New Assembly, Old .NET (and Vice-Versa)를 참고한다.

다음은 바인딩시 다른 버전으로 어셈블리 바인딩을 유도하는 설정이다. 바인딩에 대해서는 지난 포스트를 참고한다.

Redirects “assemblyName, Version=1.0.0.0, Culture=neutral, PublicKeyToken=25283151a234958d“ to version 2.0.0.0 of that assembly. This is only useful for strongly-named assemblies, since versions don't matter for those that are simply-named.

<?xml version ="1.0"?>
<configuration>
<runtime>

        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

                <dependentAssembly>
                        <assemblyIdentity name="assemblyName" culture="" publicKeyToken="25283151a234958d"/>
                        <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0"/>

                </dependentAssembly>
        </assemblyBinding>

</runtime>
</configuration>

다음은 어셈블리의 코드 베이스값을 변경하는 설정이다.

Redirects “assemblyName, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8968ee41e78ce97a“ to codebase “http://www.yourwebsite.com/filename.dll“. 'Href' can also be set to something like “file:///c:/localfile/filename.dll“. Note that redirecting to a codebase causes a System.Net.WebPermission or System.IO.FileIOPermissionAccess.Read + PathDiscovery demand when loading that assembly.

<?xml version ="1.0"?>
<configuration>
<runtime>

        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

                <dependentAssembly>
                        <assemblyIdentity name="assemblyName" culture="" publicKeyToken="8968ee41e78ce97a"/>
                        <codeBase version="1.0.0.0" href="http://www.yourwebsite.com/filename.dll"/>

                </dependentAssembly>
        </assemblyBinding>

</runtime>
</configuration>

다음 내용은 블로그 Suzanne Cook's .NET CLR Notes 의 포스트의 내용을 그대로 옮겨놨다.

Posted by dalbong2

닷넷 코드로 직접 IIS7.0을  제어할 수 있게 되었다고 합니다.

웹사이트 생성

ServerManager iisManager = new ServerManager();

iisManager.Sites.Add("NewSite", "http", "*:8080:", "d:\\MySite");

iisManager.Update();

응용프로그램 디렉토리 생성

ServerManager iisManager = new ServerManager();

iisManager.Sites["NewSite"].Applications.Add("/Sales", "d:\\MyApp");

iisManager.Update();

가상디렉토리 생성

ServerManager iisManager = new ServerManager();

Application app = iisManager.Sites["NewSite"].Applications["/Sales"];

app.VirtualDirectories.Add("/VDir", "d:\\MyVDir");

iisManager.Update();

실행 상태 및 관리

ServerManager iisManager = new ServerManager();

iisManager.Sites["NewSite"].Stop();

응용프로그램풀 재생

ServerManager iisManager = new ServerManager();

iisManager.ApplicationPools["DefaultAppPool"].Recycle();

실행요청 목록

ServerManager iisManager = new ServerManager();

foreach(WorkerProcess w3wp in iisManager.WorkerProcesses) {

    Console.WriteLine("W3WP ({0})", w3wp.ProcessId);

 

    foreach (Request request in w3wp.GetRequests(0)) {

        Console.WriteLine("{0} - {1},{2},{3}",

                    request.Url,

                    request.ClientIPAddr,

                    request.TimeElapsed,

                    request.TimeInState);

    }

}

스크랩원본글: http://www.serverinfo.pe.kr/TipnTech.aspx?Mode=View&Seq=239

출처웹사이트: 서버주무르기[Serverinfo.pe.kr], http://www.serverinfo.pe.kr/
Windows Server, IIS, SQL Server, Exchagne 서버운영자를 위한 팁앤테크, QnA 게시판

Posted by dalbong2

Why Vista Matters to Developers

A good article on the importance of Vista for developers. I particularly like this article because you hear from customers and partners directly on what Windows Vista means to them and their business.  A few good quotes:

"Perhaps even more profound, if somewhat less groundbreaking, is that the .Net Framework 2.0—on which 3.0 sits—is also included, thus making Vista the first version of Windows that ships ready-to-run .Net ClickOnce Smart Client applications and any other .Net application," Brust said. "That's big news. Since .Net apps, when shipped without the framework, can be extremely small, the release of Vista makes it possible for .Net apps to ship as casually as Visual Basic apps could years ago once the VB run-time started shipping as part of Windows."

Jim Arrowood, an enterprise architect with Dollar Thrifty Automotive Group, in Tulsa, Okla., said, "One major benefit gained out of the box from Windows Communication Foundation for Dollar Thrifty was the ability to offer different Web service endpoints built upon a single code base." Arrowood is working on a new car rental system for Dollar Rent A Car and Thrifty Car Rental that will be based on Vista.

The New York Times' Times Reader, a Vista-based beta application, is one such [killer] application, he said. "It's the visuals and the underlying capabilities made manifest in killer applications that will convince consumers to upgrade," Schadler said. "It took only one application to convince me: the Times Reader."

You can read the full article over on eWeek.

Posted by dalbong2

메모리 관리를 설명하는 부분을 보면 항상 나오는 말들인데도, 처음 개발에 들어선 개발자들에겐 돌아서면 헛갈리는 부분이다. 이 포스트에서는 이것들의 차이점을 정리해 본다. 

■ null !

이것의 의미는 분명하다. 메모리상에 존재하는 객체에 대한 모든 참조의 끈을 끊는다는 의미이다. 만약 메모리상의 객체를 참조하는 변수가 모두 null로 되면 메모리상의 객체는 가비지 컬렉트 (Garbage Collect)후보가 된다. 이때 가비지 컬렉팅이 일어나면 해제된다.

null의 원래 의미는 이렇지만, .NET 프레임워크에서는 디버깅모드와 릴리스 모드( + 최적화모드)에 따라서 다르게 해석할 수 있다. 다음 코드를 보자.

[STAThread]

static void Main()

{

    Timer t = new Timer();



    // 필요한 작업을 한다.


    Console.Read();


    t = null;

}

필요한 작업을 하고 코드는 사용자로부터 키 입력을 기다릴것이다. Main 메소드가 호출될때 이 메소드는 컴파일(JIT 컴파일)되고 Timer 객체 t가 메모리에 생성될 것이다. 그러나 Timer 객체를 참조하는 t에 null을 설정함으로써 이제 이 객체를 가리키는 참조가 하나도 없게 된다. 만약 최적화 옵션(그런 것이 있단다-_-;;)이 활성화된 상태의 JIT 컴파일러가 컴파일을 하게 되면 이 경우 Timer객체를 참조하는 변수가 하나도 없다고 판단한다.

그래서 사용자로부터 키 입력을 기다리고 있는 동안 가비지 컬렉팅이 수행되면  이 Timer 객체는 가비지 컬렉션의 후보가 될 것이다.  메소드가 아직 실행중이라는 것은 객체의 가비지 컬렉팅을 막아주지 못한다는 사실을 염두에 둘 필요가 있다.

그러나 디버깅 모드에서는 메소드(여기서는 Main)도 하나의 가상 참조(참조 그래프상의 "Root"노드가 되는 것이다)로 여긴다. 무슨 말인가 하면 Main 메소드라는 참조가 Timer 객체를 참조하고 있다고 해석하는 것이다. 결과적으로 디버깅모드에서는 가비지 컬렉팅이 일어나도 Timer객체는 살아남게 되는 것이다.

그러나 어플리케이션이 디버깅 모드에서는 잘 돌아가다가 릴리스 모드에서는 에러가 발생한다면 난처한 일이 아닐 수 없다. 이것을 해결하는 방법으로 객체를 해제할때 null대신에 Dispose 메소드를 사용하면 된다.

■ Finalize 메소드

.NET에서 Finalize 메소드는 일반 메소드와 달리 클래스명 앞에 틸드(~)가 붙어서 "~클래스명() "형태를 갖는 메소드를 말한다. 어떤 타입에 대해서 Finalize 메소드를 정의해놓게 되면 가비지 컬렉팅 작업에 의해서 객체가 수거되려고 할때 GC는 그 타입의 Finalize 메소드를 호출해서 정의된 메모리 해제 작업을 수행한다.

■ Dispose 메소드

Finalize 메소드는 GC가 호출하는 메소드인 반면에 Dispose 메소드는 객체의 클라이언트가 직접 호출할 수 있는 메소드이다. null은 객체의 메모리 해제 시기를 결정할 수 없다. GC(Garbage Collector)가 컬렉팅을 수행할때까지 메모리에 남게 된다. 해서 객체를 사용하는 클라이언트측에서 직접 객체가 차지하는 자원을 해제할 수 있는 방법을 제공해줄 수 있는 방법이 Dispose 메소드이다. 앞의 코드를 Dispose()를 이용해서 변경하면 다음과 같다.

[STAThread]

static void Main()

{

    Timer t = new Timer();



    // 필요한 작업을 한다.


    Console.Read();

    t.Dispose(); // t = null;

}

이런 식으로 코딩을 하면 디버깅 모드이든, 최적화된 릴리스 모드이간에 Timer 객체에 대한 참조가 여전히 유효하게 되어 Timer 객체는 가비지 대상이 되지 않는다.

■ Dispose 패턴

IDisposable 인터페이스를 구현하는 .NET 프레임워크 제공의 모든 객체들은 Dispose() 메소드를 호출해서 클라이언트가 직접 원하는 시기에 해제할 수 있도록 하고 있다. 만약 Dispose()를 지원하는 타입을 사용자가 직접 정의한다면 Dispose()에서 자원을 해제하는 코드를 원하는 대로 넣으면 된다.

Dispose()를 직접 정의할때 주의할 점은 클라이언트가 직접 Dispose()를 호출해서 자원을 해제했다면 그 자원 해제 사실을 GC가 알 수 있도록 표시를 해 줘야 한다. 그래야  이미 해제 했던 자원을 GC가 또 해제하지 않을 수 있게 된다. 만약 또 해제하려고 한다면 에러가 발생한다. 따라서 Dispose 패턴 이라는 것이 나오게 된다. 이 패턴은 객체의 클라이언트와 GC가 자원 해제를 중복되게 시도하지 않고 안전하게 자원을 해제할 수 있는 코딩 패턴을 말해준다. 실제 Dispose 코딩 패턴에 대해서는 "dipose pattern"이라는 검색어로 구글링해보면 자세히 알아 볼 수 있을 것이다.

Posted by dalbong2

오늘은 특별한 팁이나 해결책을 적으려는 것은 아니다. 다만 현재 진행하고 있는 프로젝트에서 논의되었던 얘기에 대해서 고민한 내용을 정리해 보려고 한다.

■상황

현재 달봉이가 참여하고 있는 L기업의 프로젝트는 일반 SI 프로젝트이기는 하지만 좋다는 솔루션은 다 투입된 것 같다. 그래서 그것을 통합하고 테스트하는데 상당한 시간이 투입되고 있다.
업무 ERP 시스템은 스마트클라이언트로 개발하고 있는데, 달봉이가 하는 주요 역할은 스마트클라이언트 어플리케이션 프레임웤과 그리고 어플리케이션 배포다. 스마트클라이언트의 Shell 형태는 윈도우 폼 실행 프로그램이고 배포 방식은 ClickOnce를 사용하고 있다. 어플리케이션이 클라이언트에 설치되는 모드는 "인스톨 모드" 즉 어플리케이션 실행이 온라인으로도 가능하고 "시작-> 모든 프로그램"의 바로가기를 통해서도 가능하다.
현재 EP 사이트가 별도로 존재하고 있고 최종 사용자들중 많은 사람들이 EP 사이트에 있는 링크를 통해서 스마트클라이언트 어플리케이션을 실행시키게 된다. 뿐만 아니라 EP쪽에 있는 어떤 링크들은 스마트클라이언트가 시작되면서 로딩할 최초 페이지에 대한 ID도 넘겨줄 수 있도록 되어 있다.  시작메뉴ID가 넘어오는 경우 스마트클라이언트의 프레임워크에서는 그 ID를 인식하고 ID에 해당하는 페이지를 동적으로 로딩시켜주게 된다.

■요구 사항

요구 사항은 이렇다. 스마트클라이언트 어플리케이션이 이미 실행되고 있는 상황에서 EP나 기타 웹 페이지에서 페이지 ID를 가지고 있는 링크가 클릭되면 새로운 스마트클라이언트 어플리케이션 프로세스를 생성하지 않고 기존에 실행되고 있는 스마트클라이언트 어플리케이션에서 링크에 추가된 페이지만을 새로 로딩할 수 있겠느냐는 것이다.

1047923727

[그림] 요구사항


■ 방안

1216033509

[그림] 방안

두가지 방안이 논의되었다. 첫번째 논의된 방안은 웹 페이지와 스마트클라이언트에서 공유할 수 있는 저장 공간을 이용하는 방법이었다. 웹 페이지에서는 컨트롤(액티브 X 컨트롤이던 임베딩된 스마트클라이언트 컨트롤이던)에 의해서 공용 저장소에 페이지ID를 저장해두고 스마트클라이언트 어플리케이션에서는 공용 저장소를 계속 폴링하는 구조로 만들어서 새로운 페이지 ID가 저장소에서 발견되면 스마트클라이언트는 그 ID에 해당하는 페이지를 로딩하도록 하자는 것이었다.  두번째 방안은 웹 페이지의 컨트롤과 스마트클라이언트간에 IPC 통신을 통해서 직접 페이지ID를 전달하자는 것이었다. 

둘 다 가능한 방법들이었다. 그러나 적용되지는 않았다. 왜? 그렇게 중요한 이슈도 아니었고, 시간도 없었다. 쿵야!!

■세번째 방안

근데, 달봉이가 얼른 떠올린 것은 AppDomain 생성을 직접 제어할 수 있으면 어떻게 될까 하는 것이었다. AppDomain생성에 개발자가 참여할 수 있다는 것까지는 알고 있었기 때문이었다. AppDomain의 CreateDomain()을 이용하면, 원하는 이름의 AppDomain이 이미 생성되어 있을 경우  AppDomain을 새로 생성하지 않고 기존의 것을 반환하는 방법을 사용하면 어플리케이션을 중복해서 생성하지 않을 수 있을 것이라 생각이 얼른 들었다.

어플리케이션 도메인 관리자(AppDomainManager)는 하나의 프로세스에 생성되는 어플리케이션 도메인을 관리하는 클래스로서 이 관리자를 확장하면 CLR이 어플리케이션 도메인을 생성하는 과정에 직접 참여할 수 있게 된다. 기본 도메인 생성자를 변경하는 이야기는 지난 포스트(http://dalbong2.net/80)를 참고할 수 있다. 어플리케이션 도메인과 프로세스에 대한 관계도 지난 포스트(http://dalbong2.net/18)에 있다. 기타 "AppDomain"이라는 검색어로 검색을 해 보면 몇가지 포스트가 검색된다. 참고할 수 있을 것이다.

그러나 생각해보니 힘들다는 것을 알게 되었다.

어플리케이션 도메인은 프로세스내에 생성된다. 따라서 어플리케이션 생성을 커스터마이징하기 전에 그 도메인이 포함되어 있는 프로세스를 알 필요가 있다. 즉 프로세스 정보를 읽고 관리하는 호스팅 프로그램이 있어야 한다. 이런 작업은 AppDomainManager가 할 수 없다. 현재 생성되어 있는 스마트클라이언트 어플리케이션 도메인이 포함된 프로세스를 선택해서 그 프로세스에 다시 어플리케이션 도메인을 생성하는 작업은 어플리케이션 도메인 관리자(AppDomainManager)에서는 불가능하다.

이렇게 프로세스를 관리해서 하나의 프로세스에 복수개의 어플리케이션 도메인을 생성하기 위해서는 Hosting Process 프로그램을 작성해야 한다. 이것은 하나의 프로세스에 여러 개의 어플리케이션을 구동할 수 있는 프로그램이다. ASP.NET도 일종의 Hosting Process 프로그램이고 개발자가 흔히 사용하는 Visual Studio 2005도 Hosting Process 프로그램이다.

이것을 만든다는 것은 벼룩 잡으려다 초간 삼간 태우는 격이다. 현업과 업무적으로 해결하는 것이 빠르다. 크윽!!

■부연

Hosting Process 을 구현하는 방법을 알아보다 다음과 같은 흥미로운 아티클을 찾게 되었다.

Hosting the ASP.NET runtime in your own application( http://www.microsoft.com/belux/msdn/nl/community/c... )

ASP.NET 런타임을 클라이언트측의 프로그램에서 호스팅하는 방법에 대한 이야기이다.


써놓고 보니까 왜 썼는지 모르겠다-_-;;

Posted by dalbong2

.NET 프레임워크의 버전이 계속 올라가면서, 프로젝트를 진행하다 보면 프레임워크 버전간의 호환성 문제가 이슈가 되는 경우가 있습니다.

지금 참여하고 있는 프로젝트에서도 프레임워크 버전 문제가 잠깐 제기된 적이 있었습니다.

■ 상황

기존의 한 시스템이 .NET 1.1된 윈폼 어플리케이션이 있었습니다. 그리고 새로 만들게 될 시스템은 .NET2.0으로 만들 다른 윈폼 어플리케이션입니다.

문제는 .NET1.1 어플리케이션에서 .NET2.0의 어셈블리로 된 화면을 로딩시킬 수 있는가입니다.

얼른 생각해봐도 v1.1 어플리케이션에서 다이렉트로 v2.0 어셈블리를 로딩하는 것은 안될 것으로 추측했습니다.

그러나 다음 경우처럼 웹 페이지를 통해서 호출하면 어떨까하는 의문이 들었습니다.

그림처럼 호출하는 1.1 어플리케이션의 윈폼에서 webbrowser컨트롤을 사용하고 그 webbrowser 컨트롤에서는 웹 페이지(asp.net)를 호출합니다.

다시 그 웹 페이지에서는 <object> 태그를 이용해서 v2.0으로된 윈폼 화면을 로딩합니다.


1016599251

[그림] v1.1 어플리케이션에서 웹 페이지를 이용해서 v2.0 어셈블리를 로딩하려는 경우


■ 추측

처음에 얼른 생각해 봤을때, 될 것 같기도 하고 안될 것 같기도 했습니다.

- 될 것 같다는 느낌 :  "AppDomain이 각각 버전 별로 하나씩 생성되면 되지 않을까?"

- 안될 것 같다는 느낌 :  "근데.. CLR 인스턴스가 버전별로 여러개 생성될 수 있나?...."

결론을 먼저 말하면 안될것 같은 느낌이 맞았던 것입니다.

■ 결론

만약 호출되는 어셈블리도 v1.1로 만들어졌다면 앞에서 설명한 상황의 윈폼 로딩은 정상적으로 작동합니다. AppDomain이 두개 생성되는 것도 맞습니다. 

그러나 AppDomain은 생성하는 CLR 인스턴스가 하나의 OS 프로세스에서는 하나만 존재할 수 있다는 것을 조금 생각 후에 상기하게 되었습니다. v1.1 어플리케이션이 시작할때 이미 그 프로세스에는 v1.1 버전의 CLR 인스턴스가 이미 하나 생성되어 있습니다. 그런 상태에서 다시 v2.0 CLR을 로딩시킬 수는 없습니다. OS 프로세스-CLR-AppDomain에 대한 관계는 지난 포스트 "기본 AppDomain 생성자 변경하기(http://dalbong2.net/80)"에서 알아본적이 있습니다. 따라서 v1.1 어플리케이션용 OS 프로세스에서 v2.0용 AppDomin을 생성할 수가 없게 됩니다.

만약 v1.1용 AppDomain에서 v2.0 어셈블리를 호스팅할 수 있다면 그렇다면 이야기가 달라질 수 있을 것입니다. 그러나 이것도 Microsoft 호환성 정책에 의해서 지원되지 않고 있습니다. Microsostrk 지원하는 호환성 정책은 다음과 같습니다.

Side-by-side 호환성 정책 : 여러 버전의 프레임워크가 설치된 머신에서, 어플리케이션들은 자신이 만들어진 어플리케이션을 이용해서 실행될 수 있습니다. 즉 v1.1 어플리케이션은 v1.1 CLR을 사용하고 v2.0 어플리케이션은 v2.0 CLR을 사용할 수 있습니다.

Backward 호환성 정책 : 이전 버전(v1.1)의 프레임워크를 이용해서 만들어진 어플리케이션은 이후 버전(v2.0)의 프레임워크에서도 잘 작동하도록 지원하고 있습니다.

Forward 호환성 정책 : 그러나 Microsoft는 이후 버전(v2.0)의 어플리케이션이 이전 버전(v1.1)의 프레임워크에서는 정상 작동하도록 하는 것은 포기하고 있습니다.

.NET 프레임워크는 Forward 호환성 정책을 지원하지 않는다는 것은 v1.1용 AppDomain에서 v2.0 어셈블리를 로딩할 수 없다는 이야기입니다.


너무 당연한 이야긴가?  쉬운 얘기를 어렵게 만드는데에 무슨 재주 있다니까...-_-;;

Posted by dalbong2

혹시 우리가 아무 걱정없이 사용해왔던 이벤트 핸들러가 어플리케이션의 메모리 증가의 원인이 될 수 있다는 것을 아시나요.

다음 블로그 포스트(http://blogs.msdn.com/tess/archive/2006/01/23/516139.aspx )를 보면 자세한 실험 내용이 나와 있습니다.
이 블로그에서 실험했던 내용을 간단히 정리해보도록 하겠습니다.
 
여기 우리가 흔히 대수롭지 않게 생각했던 코드가 있습니다. 

public class WebForm1 : System.Web.UI.Page
{
  public static MyClassThatHasEvents MyStaticObject = new MyClassThatHasEvents();
 
  private void InitializeComponent()
  {   
   this.Load += new System.EventHandler(this.Page_Load);
   MyStaticObject.StuffHappened += new StuffHappenedEventHandler(this.MyStaticObject_StuffHappened);

  }
...

정적 객체( MyStaticObject)의 이벤트(StuffHappened)에 웹 페이지(WebForm1)에 정의되어 있는 메소드(MyStaticObject_StuffHappened)를 핸들러로 등록하는 코드가 있습니다.
페이지에 정의되어 있는 이벤트 핸들러가 정적 객체의 이벤트 멤버에 의해서 참조되고 있습니다.
따라서 클라이언트의 요청에 대해서 웹 페이지 실행되고 나서도 정적 객체가 여전히 페이지를 참조하고 있기 때문에 페이지 객체는 메모리에 그대로 상주하게 된다는 것입니다.
계속 페이지를 요청하게 되면 메모리는 계속 증가하게 됩니다.
이 상태에서 Garbage collection을 수행해도 페이지 객체들은 여전히 정적 객체의 참조를 받고 있어서 메모리는 완전히 회복되지 않습니다.
만약 이런 종류의 메모리 증가를 막으려면 페이지의 라이프 사이클의 적절한 단계에서 이벤트 핸들러를 명시적으로 제외하는(-=) 코드를 넣어야 합니다.

이런 이벤트 핸들러에 의한 메모리 증가는 많은 윈폼 페이지를 가지고 있는 스마트클라이언트 어플리케이션에서는 더욱더 흔히 일어날 수 있는 일입니다.

요는 이렇다는 것입니다.
페이지보다 오래 남는 객체에 대한 이벤트 핸들러를 등록할때는 주의하라.

Posted by dalbong2

Visual Studio 2008을 통해서 .NET Framework( v3.5 )의 일부 라이브러리들의 소스 코드를 볼 수 있다는 소식이 발표되었습니다.

.NET Framework Library Source Code now available
http://weblogs.asp.net/scottgu/archive/2008/01/16/net-framework-library-source-code-now-available.aspx

VS 2008을 이용해서 .NET Framework 디버깅하기 위한 자세한 설정 내용은 다음 포스트를 참조할 수 있습니다.

Configuring Visual Studio to Debug .NET Framework Source Code
http://blogs.msdn.com/sburke/archive/2008/01/16/configuring-visual-studio-to-debug-net-framework-source-code.aspx

Posted by dalbong2
유경상 수석의 WCF MSDN 세미나 동영상이 포스팅되었습니다.
유경상 수석 특유의 치밀하고 합리적인 설명이 돋보이는 세미나입니다.

http://www.simpleisbest.net/archive/2007/12/03/1978.aspx
Posted by dalbong2
TAG WCF

트랜잭션 NTFS

개발/.NET 2009/04/23 13:58

TxF( 트랜잭션 NTFS ) = 트랜잭션 + NTFS

파일 시스템을 트랜잭션 바운더리에 포함시킬 수 있게 되었다는 소식은 풍문으로 들어 알고 있었습니다. MSDN 매거진을 읽다가 이에 대한 구체적인 문서가 있어서 메모해 둡니다.

파일 시스템 트랜잭션으로 응용 프로그램 향상
http://msdn.microsoft.com/msdnmag/issues/07/07/NTFS/default.aspx?loc=ko

Posted by dalbong2

달봉이가 가끔 부딪치고 있는 두 개념 LoadContextLoadFromContext에 대해서 정리해 볼까 한다.

Loaded Assembly Cache

바인딩 컨텍스트(binding context)라는 것이 있는데, Junfeng Zhang(이 사람 이름은 어떻게 발음해야 할지 달봉이는 항상 고민이다)에 의하면 로딩된 어셈블리의 캐시(loaded assembly cache)로 말하고 있다. 우리가 익히 알고 있는 GAC(Global assembly cache)도 바인딩과 프로빙 과정을 거치지 않도록 할 수 있다는 점에 어떻게 보면 캐시라고 볼 수도 있을 것이다. 그리고 NTD에 의해 배포된 어셈블리가 캐싱되는 어셈블리 다운로드 캐시(assembly download cache)도 있다. 허나 이런 캐시들은 파일 시스템 상의 캐시로서 로딩되지 않은 어셈블리를 위한 것이다. 이미 로딩되어 있는 어셈블리는 다시 로딩하지 않고 바인딩 컨텍스트에 있는 것을 사용하게 되는 것이다. LoadFrom(경로)처럼 직접 어셈블리의 경로를 통해서 로딩하는 경우도 loaded assembly cache를 먼저 확인하고 나서 없다면 경로의 어셈블리를 로딩한다.

When to load assembly


애플리케이션 실행되고 나서 새로운 타입(클래스 포함)이 나타나면 그 타입이 포함된 어셈블리를 로딩하게 되는데, 호출하는 어셈블리는 참조되는 대상 어셈블리 및 그 타입에 대한 모든 정보를 어셈블리의 일부인 메너페스트라는 곳에 가지고 있다. 이 정보를 통해서 대상 어셈블리를 결정하고 로딩하게 되는 것이다. 자세한 것은 어셈블리 구조를 설명하는 포스트를 참조하기 바란다.
많은 경우가 이렇게 정적인 참조에 의해 어셈블리를 로딩하지만 또한 어셈블리를 Load() LoadFrom()등을 사용해서 동적으로 로딩하는 경우도 많다.

LoadContext vs. LoadFromContext


다시 바인딩 컨텍스트(binding context) 얘기로 돌아가보면... "바인딩 컨텍스트"란 로딩된 어셈블리의 캐시라고 했는데, "바인딩 컨텍스트"라는 용어 자체에는 "캐시"를 암시하는 것은 없다. 대신에 도메인처럼 어떤 영역을 구분짓는데 사용하는 컨텍스트(context)라는 단어가 사용되고 있다. 그렇다. 우리의 추측에 부합하는 용어의 선택이다. 바인딩 컨텍스트라는 것은 단순히 로딩된 어셈블리의 캐시라는 원래의 의미에 추가하여 어셈블리 바인딩(assembly binding)과 관련된 의미를 가지고 있다. 즉 다른 바인딩 방법에 의해 로딩된 어셈블리는 다른 캐시에 저장되고, 이것을 구분하여 두 바인딩을 대표하는 메소드 Load LoadFrom의 이름을 따서 LoadContext LoadFromContext로 구분해서 사용하고 있다.

GAC, AppDomain의 ApplicationBase 또는 PrivateBinPath상에 있는 디렉토리 등에 있는 어셈블리는 LoadContext로 로딩된다.

어셈블리를 로딩하는데, 파일에 대한 경로를 이용하는 경우, 해당 어셈블리가 LoadContext에서 발견되지 못하면 결국 해당 경로에 있는 어셈블리를 가지고 와서 LoadFromContext에 캐싱한다. 이렇게 경로를 이용해서 어셈블리를 로딩하는 방법은 LoadFrom() 메소드외에도 CreateInstanceFrom(), ExecuteAssembly() 등이 있다. 그리고 codebase 값을 사용하여 어셈블리를 로딩하는 경우도 LoadFromContext로 로딩된다.

참고로 위치 기반의 어셈블리 로딩시는 바인딩이 두번 일어난다는 것을 상기하자. 즉 LoadFrom()을 호출하면, 경로에 있는 어셈블리 파일로 부터 이름 정보를 얻어서 다시 내부적으로 Load()가 호출된다.
참조에 의해 로딩된 어셈블리와 Load() 메소드 및 그 자식 어셈블리들(Load() 메소드에 의해서 로딩된 어셈블리에서 참조하는 다른 어셈블리들)로 로딩된 어셈블리는 같은 LoadContext에 저장된다. 그리고 LoadFrom()또는 CreateInstanceFrom()처럼 경로를 기반으로 해서 로딩된 어셈블리와 그 자식 어셈블리들은 LoadFromContext에 저장된다.

공유 불가


두 캐시의 컨텍스트가 다르다함은 그 용어에서 느껴지는 것처럼 서로 공유되지 않는 다른 특징이 있다. 동일한 어셈블리가 다른 바인딩 컨텍스트에 캐싱되어 있을 수도 있다는 것이다.

LoadContext의 어셈블리가 참조하는 다른 어셈블리가 LoadFromContext에 캐싱되어 있다면 그 어셈블리는 사용할 수 없다. 그 어셈블리를 다시 LoadContext로 로딩시키게 된다.

그러나 LoadFromContext에서는 LoadContext에 있는 어셈블리를 사용할 수 있게 있다. 이것은 LoadFrom() 메소드의 두번째 바인딩때 LoadContext를 검색하기 때문이다. LoadFrom()의 두번의 바인딩에 대해서는 지난 포스트를 참조한다.

버전 정책 사용 여부


LoadContext
에서는 버전 정책을 사용해서 소위 “DLL Hell”을 피할 수 있다. 그러나 LoadFromContext로 로딩된 어셈블리에 대해서는 버전 정책을 사용할 수 없다.



참조 문서

기타 자세한 내용은 다음 블로그를 참조할 수 있다. Binding ContextLoadContext, LoadFromContext에 대한 설명이 자세히 나와있다.

Binding Context and LoadFrom by Junfeng Zhang
LoadFile vs. LoadFrom by Suzanne Cook
Choosing a Binding Context by Suzanne Cook

Reflection Only Assembly Loading

다음 블로그를 보면 V2.0 이전에서는 LoadFrom()에서도 바인딩이 두번 일어난다는 것을 알수 있다.
LoadFrom's Second Bind-Suzanne Cook's blog
http://blogs.msdn.com/suzcook/archive/2003/09/16/57247.aspx

Posted by dalbong2