본문 바로가기

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

어셈블리 바인딩 2

어셈블리 바인딩 관련 최종 버전은 아래 링크로 바로 갈 수 있다.

2015/10/15 - [04.기술-APP/.NET InDepth] - 어셈블리 바인딩(최종)


어셈블리 바인딩 2

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

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

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

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

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

■ 이름풀이 관련 설정들

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

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

"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 코드도 함께 본다. 그림은 어셈블리 이름풀이와 로딩의 전체 과정을 표시하고 있다.

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

( 그림이 애매하다. <codeBase> 힌트로 어셈블리를 찾지 못하면 바로 probing을 하지 않고, 파일을 발견하지 못했다는 에러가 발생한다. )

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

업데이트 - 2013.03.29

 

( 최종이길 바라며...)

 

 

 



■ 이름정보 모으기

참조되는 어셈블리에 대한 요청이 있거나(이 요청은 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 프레임워크용 애플리케이션에는 적용되지 않는다.

버전 정책의 오버라이딩

세 종류의 정책이 있으므로 어떤 어셈블리에 대한 버전 정책이 각 레벨별로 중복이 될 수 있다. 이런 경우 버전 정책이 적용되는 순서를 그림에서 보여주고 있다. 만약 이름풀이 대상의 어셈블리가 최초에는 버전 "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>)로 여러 개 존재할 수 있다.

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

버전 정책 적용 결과

이 그림은 이름풀이가 참조에 의해서 시작되었음을 보여준다. 제일 위쪽의 그림은 어떤 어셈블리를 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 디렉토리 외부의 다른 디렉토리에 대한 상대 경로를 지정할 수 없다.

어셈블리 프로빙 대상 경로

그림처럼 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. 이 경우는 이름 기반의 바인딩인 경우만 거치는 단계이다. [본문으로]

'IT 살이 > 04. 기술 - 프로그래밍' 카테고리의 다른 글

어셈블리 바인딩 1  (3) 2009.04.23
어셈블리 구조  (0) 2009.04.23
GAC은 어떻게 생겼을까  (0) 2009.04.23