본문 바로가기

프로그래밍/C#

CLR via C# 2장 빌드, 패키징, 배포, 응용프로그램과 타입의 관리

.NET Framework 배포 목표

 

지난 수년간, Windows는 불안정하고 복잡한 운영체제라는 인상이 있었다.

 

1. DLL지옥

  • 실행되는 응용프로그램들이 여러 제조사가 만든 코드를 실행하기 때문에, 만들어 놓은 코드 조각이 누구에 의하여 어떻게 사용될 것인지 확실히 알수 없음.
  • 사용자들은 프로그램을 업데이트하고 새로운 파일을 배포받을 때마다 문제를 겪을 수 있다. 시스템 DLL의 응용프로그램을 설치할 경우 다른 버전의 DLL로 덮어 씌워진다던지, 하는 문제.
  • (Windows 2000 이후 Windows File Protection 기능을 통해 대부분 해결되었다고 함.)

2. Windows 설치가 복잡하다고 여겨진다. (Windows에서의 응용프로그램 설치)

  • 대다수의 응용프로그램들은 설치 후에 시스템의 여러 영역에 다양한 영향을 주도록 되어 있음.
  • 여러 곳에 디렉터리가 만들어지고, 파일이 생성되고, 레지스트리 설정이 변경 됨.
  • 다른 컴퓨터로 프로그램을 옮기거나, 백업을 하거나, 깨끗하게 제거하기가 쉽지 않음.

3. 보안이 좋지 않다.

  • 응용프로그램이 설치되면 여러 다른 제조사들이 만든 파일이나 정보들이 함께 유입된다.
  • 이렇게 배포된 코드들은 파일을 삭제하거나 사용자 몰래 메일을 보내는 등 무엇이든 할 수 있다.

 

.NET Framework는 위의 문제들에 대한 해결책을 제시한다.

 

 


모듈 안에 타입 만들기

 

소스 코드 안에 들어있는 여러 타입들을 어떻게 배포할 수 있는 파일로 만들까?

 

public sealed class Program {
    public static void Main() {
        System.Console.WriteLine("Hi");
    }
}

 

이 프로그램은 우리가 정의한 Program이라는 타입, Main이라는 정적 메서드로 구성되었고, Main 메서드 안의 System.Console이라는 타입에 대한 참조로 이루어져있다.

 

 System.Console은 MS에 의해서 구현되었고, IL이 이 타입을 구현하기 위해서 MSCORLIB.DLL 파일 안에 쓰여져 있다.

위의 코드를 실행시키기 위해서 커맨드 라인을 입력한다.

 

csc.exe /out:Program.exe /t:exe /r:MSCorLib.dll Program.cs

 

 C# 컴파일러가 소스 파일을 처리하는 동안, 컴파일러는 System.Console 타입의 WriteLine 메서드를 참조하고 있다는 것을 확인한다. 이 시점에서 컴파일러는 이 타입이 어딘가에 존재한다는 것, 타입이 WriteLine(String) 시그니처의 메서드를 가지고 있을 것이라는 것을 기대한다.

 

 이러한 외부 타입들을 포함하는 어셈블리들이 어디에 있는지 알려주어 외부 종속성을 파악할 수 있도록 레퍼런스 인자를 통해 MSCORLIB.DLL을 찾아보도록 지시해주었다. 다만, MSCORLIB.DLL 파일은 Byte, Char, String, Int32 등의 기본적인 타입을 포함한 특별한 파일이기 때문에, /r:MSCorLib.dll을 지정하지 않았어도 C# 컴파일러가 알아서 찾아본다.

 

csc.exe Program.cs

 

 

만약 C# 컴파일러가 자동으로 탐색하는 것을 막고 싶다면 /nostdlib 인자를 사용하면 된다.

 

C# 컴파일러가 만든 Program.exe 파일을 살펴보면.

  • 표준형(Standard)의 이식 가능한 실행 파일 - 32bit, 64bit 버전의 Windows에서는 이 파일을 로드하여 실행할 수 있다.
  • CUI 프로그램으로 만들기 위해서는 /t:exe 스위치를, GUI 프로그램으로 만들기 위해서는 /t:winexe 스위치를, Windows Store 앱으로 만들기 위해서는 /t:appcontainerexe 스위치를 지정해야한다.

지시 파일(Response File)

 

지시 파일은 컴파일러 명령 줄 스위치들로 내용이 이루어진 텍스트 파일이다.

CSC.EXE를 실행할 때, 컴파일러는 지시 파일을 열어서 파일 안에 들어있는 스위치 내용들을 읽어들인다.

예를 들어 MYPROJECT.RSP라는 파일 내용이 아래와 같다면,

 

 

/out:MyProject.exe
/target:winexe

 

 

CSC.EXT가 이 지시 파일을 사용하도록 하기 위해 다음과 같이 @접두사를 붙여 명령 줄을 실행하면 된다.

 

csc.exe @MyProject.rsp CodeFile1.cs CodeFile2.cs

 

 C# 컴파일러는 여러 지시 파일들을 읽을 수 있다. 우리가 명령 줄에 명시적으로 입력하게 될 내용들보다 앞서 컴파일러는 CSC.RSP라는 지시 파일을 자동으로 확인한다. 우리가 만드는 프로젝트에 전역적으로 반영하려는 설정이 있다면, CSC.RSP 파일을 수정하여 모든 프로젝트에 반영시킬 수 있다. 컴파일러는 이 지시 파일들의 내용들을 통합하여 설정을 하나로 묶어서 사용하게 된다. 만약 로컬 스위치와 글로벌 지시 파일 간에 충돌이 있다면, 로컬 스위치의 설정이 더 우선시 된다.

 

 전역 CSC.RSP 파일은 거의 모든 자주 쓰이는 어셈블리들을 이미 참조하기 때문에, 앞의 상황처럼 따로 MSCORLIB.DLL 파일을 명시적으로 레퍼런스 해주지 않아도 되는 것이다. 이런 어셈블리들을 매번 참조하도록 하는 것이 컴파일러를 느리게 할 수는 있지만, 소스 코드에서 실제로 참조하지 않는 어셈블리는 실제 어셈블리 파일이나 코드의 실행에 영향을 주지 않는다.

 

 /reference 스위치를 사용할 경우에는, 특정 파일의 전체 경로를 지정할 수 있다. 하지만 전체 경로를 지정하지 않고 파일의 이름만 지정한다면 아래의 목록의 순서대로 파일을 검사하여 먼저 발견되는 파일을 실제 참조에 넣어서 사용한다.

  • 워킹 디렉토리
  • CSC.EXE 파일이 들어있는 디렉터리.
  • /lib 컴파일러 스위치로 설정된 임의의 디렉터리들
  • LIB 환경 변수상에 설정된 임의의 디렉터리들

메타데이터에 대해 잠시 살펴보기

PROGRAM.EXE 파일 안의 내용에 대해서 알아보자.

관리 PE 파일은 네 가지의 주요 파트로 구성되어있다.

  1. PE32 / PE32+ 헤더 - Windows 운영체제가 기대하는 표준 정보들
  2. CLR 헤더 - 프로그램 실행을 위해서 CLR이 필요하다는 정보 블록. CLR의 버전 번호, 플래그, 모듈의 CUI / GUI명, Windows Store 실행 파일인 경우 사용할 진입점 메서드 정보 토큰(ModuleDef), 감력한 이름 디지털 서명.
  3. 메타데이터 헤더 - 정의 테이블, 참조 테이블, 매니페스트 테이블의 세가지 카테고리로 구분되는 이진 데이터.
  4. IL

자주 쓰이는 메타데이터 블록 내에 존재하는 정의테이블에 관한 설명.

  • ModuleDef : 어셈블리 내부의 모듈당 한 개의 항목이 포함. 모듈의 파일 이름과 확장명, 버전 GUID 포함.
  • TypeDef : 어셈블리 내부의 각 타입당 한 개씩 포함된다. 타입의 이름, 기본 타입, Public / Private 등의 플래그.
  • MethodDef : 메서드의 이름, Private, public 등의 각종 플래그, 원형, 실제 구형에 대한 IL 코드 위치 (오프셋 주소)의 정보. 각 테이블에는 메서드 호출 시 필요한 매개변수들의 인자에 대한 정보 테이블인 ParamDef 테이블에 대한 인덱스 정보들도 가지고 있다.
  • FieldDef : 필드에 대한 플래그, 타입, 이름을 포함.
  • ParamDef : 매개변수의 In, Out, Retval 등의 플래그, 타입, 이름 포함.
  • PropertyDef : 속성의 플래그, 타입, 이름 정보.
  • EventDef : 이벤트의 플래그, 이름 정보.

 

 컴파일러가 소스 코드를 컴파일할 때마다 위의 테이블들을 기준으로 소스 코드를 정의하게 된다. 또한 메타데이터 테이블 항목들은 컴파일러가 감지하는 소스 코드의 참조 항목들에 대해서도 추가로 만들어지게 된다. 이에 따라 메타데이터 안에는 참조된 항목들에 대한 기록을 보관하는 참조 테이블들에 대한 정보도 같이 만들어진다.

 

자주 쓰이는 메타데이터 참조 테이블의 종류와 설명

  • AssemblyRef : 모듈 내에서 참조하는 각 어셈블리마다 만들어진다. 어셈블리 바인딩에 필요한 정보 (어셈블리 이름, 버전, 문화권 정보, 공개 키 토큰 값)가 포함되어 있다.
  • ModuleRef : 모듈에 의하여 참조되는 타입들을 구현하는 PE 모듈마다 만들어진다. 각 항목에는 모듈의 파일 이름과 확장명이 들어있다. 현재 실행 중인 어셈블리 모듈에 의해 참조되는 다른 모듈들을 바인딩하기 위해 사용된다.
  • TypeRef : 참조되는 타입당 하나씩 항목이 생성된다. 타입의 이름과 타입을 어느 위치에서 찾을 수 있는지에 대한 힌트가 들어있다.
  • MemberRef : 모듈에 의해 참조되는 필드/메서드/속성/이벤트 등의 각 멤버들마다 항목이 만들어진다. 각 항목들은 멤버의 이름과 원형, 각 멤버들을 정의하는 타입에 대한 참조로 TypeRef 항목을 가리키게 된다.

 

 위의 항목 말고도 더 많은 항목들이 있지만, 컴파일러가 어떤 종류의 메타데이터를 만들고 기록하는지에 대한 이해를 위한 것이다. 매니페스트 테이블을 후반에서 설명한다.

 

 관리 PE 파일 안에 들어있는 메타데이터 파일을 보여주는 도구들이 많이 있다. IL 역어셈블러라고 하는 ILDASM.EXE를 사용하기 위해서는 아래와 같이 커맨드를 입력하면 된다.

 

ILDASM PROGRAM.EXE

 

 이렇게 하여 ILDASM.EXE가 PROGRAM.EXE 어셈블리를 실행하면서 불러오도록 할 수 있다. 이 프로그램의 메타데이터를 보고서 형태로 보기 위해, 프로그램의 상단 메뉴에서 보기 -> 메타 정보 -> 표시 순으로 메뉴를 클릭하거나, 단축키 Ctrl + M을 눌러 기능을 실행할 수 있다.

 

 

 

 


 

여러 모듈을 하나의 어셈블리로 통합하기.

 

 PROGRAM.EXE 파일을 살펴보면서, 단순히 메타데이터가 들어있는 PE 파일이 아닌, 하나의 어셈블리라는 것을 보았다. 어셈블리는 타입에 대한 정의가 담겨있는 파일과, 리소스 파일을 묶은 컬렉션이다. 어셈블리가 포함하는 파일들 중에서는 매니페스트도 있다.

 매니페스트는 어셈블리의 일부로 포함되는 여러 다양한 파일들의 이름을 저장하는 메타데이터 테이블들의 또 다른 집합이다. 어셈블리의 버전, 문화권(Culture), 게시자, 외부에서 사용할 수 있도록 공개된 타입들, 어셈블리를 이루고 있는 파일들에 대한 정보를 담고있다.

 

 CLR은 어셈블리 파일들을 다루고 사용하면서 그 안에 들어있는 매니페스트 메타데이터 테이블을 먼저 읽어들이고, 어셈블리 내부에 존재하는 다른 파일들을 사용하기 위해 매니페스트를 사용하여 이름을 확인한다.

 

알아둬야 할 어셈블리의 주요한 특징

  1. 어셈블리 안에는 재사용 가능한 타입들을 정의하고 있다.
  2. 어셈블리는 버전 번호가 기록되어있다.
  3. 어셈블리는 관련된 보안 정보들을 가지고 있을 수 있다.

 어셈블리의 개별 파일들은 매니페스트 메타 데이터 테이블이 가리키는 어셈블리 내의 파일들을 제외하고는 위의 특성이 부여되지 않는다.

 패키지로 만들고, 버전 번호를 부여하고, 보안을 유지하며, 타입을 재사용할 수 있도록 하기 위해서는 (위의 특성을 지키기 위해서) 어셈블리의 일부가 될 수 있도록 모듈들을 구성해야 한다. 대부분의 경우, 어셈블리는 한 개의 파일로 구성된다. (물론 여러 개의 파일로도 구성될 수 있고, 메타 데이터를 포함하는 PE 파일, 이미지 리소스 파일들도 해당된다.)

 

 왜 MS가 어셈블리라는 개념을 소개했을까?

 어셈블리는 논리적인 개념과 물리적인 개념의 재사용 가능한 타입을 분리해서 생각할 수 있도록 도와준다. 예를 들어 자주 사용되는 타입들과 그렇지 않은 타입들을 구분해서 분리된 파일로 나누어 담을 수 있다. 자주 사용하는 어셈블리는 필수적으로 배포하고, 그렇지 않은 어셈블리는 전혀 다운로드하지 않게 할 수 있다.

 

 음용프로그램을 구성할 떄 구성 파일 안의 codeBase 요소에 다운로드할 어셈블리 파일을 지정할 수 있다. 어셈블리 파일에 대한 URL을 파악하도록 되어있어서, 어셈블리를 로드할 때 기재된 URL에 따라 캐싱 / 파일을 로드할 수 있다. (만약 없다면 FileNotFoundException 발생)

 

다중 파일 어셈블리를 사용하려는 이유는 크게 세가지이다.

  1. 배포하려는 타입을 효율적으로 분할 배포할 수 있다.
  2. 리소스나 데이터 파일들을 어셈블리에 포함시킬 수도 있다. 이후에 배울 AL.EXE(어셈블리 링커)같은 도구를 이용하여 가능.
  3. 모듈들을 각각 다른 언어로 만들고, 하나의 어셈블리로 통합할 수 있다. (근데 이게 왜 다중 파일 어셈블리...?)

 

 결국 어셈블리란 : 재사용 가능하고, 버전을 관리할 수 있으며, 보안을 준수할 수 있는 기본 단위이다. 타입이나 리소스들을 몇 개로 분할하여 배포할 것인지, 어떤 패키지를 이용하여 배포할 것인지 결정하게 할 수 있다. CLR이 매니페스트를 포함한 파일을 로드할 때 현재 로드한 어셈블리가 참조하는 다른 어셈블리를 로드할 수 있을지 파악하게 할 수 있다. 어셈블리는 로드하는 어떤 프로그램이든 파일을 포함하는 매니페스트의 이름만 정확히 알 수 있다면, 분할 배포한 파일의 경계는 없어지고, 응용프로그램의 동작이나 기능을 깨뜨리는 일 없이 안전하게 기능을 업그레이드 할 수 있다.

 

 ※ 만약 단일 버전 번호와 보안 설정을 공유하는 여러 타입이라면, 단일 어셈블리로 배포하는 것이 성능적으로 좋다. CLR과 Windows가 어셈블리를 찾고 로드할 시간을 단축시킨다. 주소 공간의 단편화도 완화시켜주며 NGEN의 최적화 성능 또한 좋아진다.

 

 어셈블리를 만들기 위해서는, PE 파일들 중의 하나를 선택해서 매니페스트를 대표하도록 만들어야 한다. 또는, 여러 개의 PE 파일을 만들되 매니페스트만을 포함하도록 만들 수도 있다. (??? 뭐라는 거야)

 

 관리 모듈을 어셈블리로 변경하기 위해서 필요한 메니페스트 메타데이터 테이블의 종류

  • AssemblyDef : 어셈블리는 대표하는 모듈에 대해 하나의 항목이 포함된다. 어셈블리의 이름, 버전 정보, 문화권 정보, 플래그, 해시 알고리즘 공개 키등이 포함된다.
  • FileDef : PE 파일이나 리소스 파일들 중 AssemblyDef 항목에 표시되는 매니페스트를 포함하는 파일을 제외한 모든 항목들당 한 개씩 생선된다. 파일의 이름과 확장명, 해시 값, 플래그가 포함된다.
  • ManifestResourceDef : 어셈블리의 일부가 되는 리소스마다 한 개씩 항목이 만들어진다. 리소스 이름, 플래그, FileDef의 인덱스 번호가 포함된다. 인덱스 번호를 통해서 리소스의 실제 내용이 담긴 파일이나 스트림을 찾을 수 있다.
  • ExportedTypesDef : 어셈블리의 PE 모듈들에 포함되는 공개된 타입들당 한 개씩 이 항목이 만들어진다. 타입의 이름, 타입에 대한 구현을 담당하는 어셈블리 파일에 대한 FileDef 테이블 인덱스 번호, TypeDef 테이블 인덱스 번호에 대한 정보가 포함된다. 매니페스트를 포함하는 파일 안에 들어있는 타입들은 메타데이터의 TypeDef 테이블에 포함되어 있기 때문에 이곳에 기록되지 않는다.

 

 매니페스트를 사용함으로써, 어셈블리를 사용하는 프로그램과 어셈블리의 분할 사이에 간접 계층을 제공하고, 어셈블리가 최대한 자신의 정보를 자기 설명적인 상태로 유지할 수 있도록 해준다. 매니페스트를 포함하는 파일은 어셈블리를 구성하는 파일들이 어떤 것인지에 대해서도 메타데이터 정보를 포함하지만, 이에 해당되는 개별 파일들은 별도로 자신이 어셈블리의 일부라는 것을 설명하는 메타데이터를 가지고 있진 않다.

 

 C# 컴파일러를 아래의 스위치를 지정하여 실행하면 어셈블리를 생성한다.

 

/t[arget]:exe, winexe, appcontainerexe, library, winmdobj

 

 위의 스위치 모두 컴파일러가 단일 PE 파일을 만들고, 그 안에 메니페스트 메타 데이터 테이블을 넣도록 한다. 그러나 /t[arget] : module 스위치를 사용할 경우, 컴파일러는 PE 파일을 만들지만, 매니페스트 메타데이터 테이블을 포함하지 않는다. PE 파일은 항상 DLL로 만들어지고, 만들어진 파일은 반드시 그 안에 들어있는 타입이 CLR에서 로드되기 이전에 어셈블리에 포함되어야만 한다. C# 컴파일러는 이 경우 파일 이름 뒤에 확장명으로 .netmodule이라는 확장명을 붙인다.

 

 어셈블리 안에 모듈을 추가하는 방법은 여러 가지 있다. C# 컴파일러를 통해서 만든다면 /addmodule 스위치를 사용할 수 있다. 다음의 두 소스 코드 파일이 있다고 가정하자.

 

  • 거의 사용되지 않는 타입들만을 모아둔 RUT.CS (Rarely Used Types) 파일
  • 자주 사용되는 타입들만을 모아둔 FUT.CS (Frequently Used Types) 파일

 

 거의 사용되지 않는 타입들만을 포함한 파일을 컴파일해서 사용자에게 필요 없는 부분을 전달하지 않도록 만들기 위해서는 다음과 같이 명령 줄을 실행한다.

 

csc /t:module RUT.cs

 

 이 명령을 실행하면 C# 컴파일러는 RUT.NETMODULE 파일을 만들어낸다. 이 파일은 표준 DLL PE 파일이지만 CLR은 직접 이 파일을 로드할 수 없다. 이제 자주 사용되는 타입들을 모아둔 소스 코드 파일을 하나의 모듈로 컴파일한다. 이 모듈은 자주 사용되는 타입이 주로 포함되기 때문에 매니페스트를 포함하는 대표 모듈로 만들 것이다. 그렇기 때문에 만들어지는 파일의 이름을 FUT.DLL 대신 MULTIFILELIBRARY.DLL로 변경할 것이다.

 

csc /out:MultiFileLibrary.dll /t:library /addmodule:RUT.netmodule FUT.cs

 

 /addmodule 스위치를 이용하여 컴파일러에 FileDef 매니페스트 메타데이터 테이블에 RUT.netmodule 의 존재를 등록한다. 이와 동시에 RUT.netmodule 파일에서 개방한 공개 타입들에 대한 정보를 ExportedTypesDef 매니페스트 메타 데이터 테이블에 등록하도록 만들게 된다.

 

 

 

 RUT.netmodule 파일은 RUT.CS 파일을 컴파일하여 만든 IL 코드를 포함하고 있다. 또한 이 파일은 RUT.CS에 의하여 정의/참조된 타입, 메서드, 필드, 속성, 이벤트 등을 설명하는 메타데이터 테이블을 가지고 있다. MultiFileLibrary.dll 파일은 이와 유사하지만, 어셈블리 매니페스트 메타데이터를 가지고 있다. 이 때문에 MultiFileLibrary.dll은 이 어셈블리를 대표하는 모듈이자, 그 자신이 어셈블리가 된다.

 추가로 들어가는 매니페스트 메타데이터 테이블에는 MultiFileLibrary.dll 그 자신과 RUT.netmodule 파일 등 어셈블리를 이루는 다른 모든 파일들에 대한 정보들이 포함된다.

 

 

 

 MultiFileLibrary.dll 어셈블리가 만들어진 이후 ILDASM.EXE 도구로 메타데이터를 확인해본 결과이다. 

 

 MultiFileLibrary.dll을 참조하는 클라이언트 코드들은 모두 /r:MultiFileLibrary.dll 스위치를 컴파일러에 지정해주어야 한다. 이 스위치는 컴파일러에 dll 어셈블리를 로드하도록 하여 FileDef 테이블 안에 모든 파일들을 검색, 이후 참조하는 외부 타입의 실제 위치를 파악하도록 지시한다. 만약 참조하는 외부 타입이 없다면 (RUT.netmodule) 컴파일러는 당연히 오류를 나타낸다.

 즉, 새로운 어셈블리를 만들기 위해선 기존에 참조하는 어셈블리의 파일들은 모두 사용 가능한 상태여야 한다.

 

 클라이언트의 코드가 실행될 때, 메서드를 호출하게 된다. 메서드를 처음 호출할 때 (아마도 JIT 컴파일러가 네이티브 코드를 생성할 때) CLR은 메서드가 참조하고 있는 타입들을 파악한다(매개변수, 반환값, 지역변수). 그 다음 CLR은 참조되는 어셈블리에서 매니페스트를 포함하는 파일을 로드한다.

 만약 이 파일 내에서 해당되는 타입을 직접 가져올 수 있다면, CLR이 내부적으로 이 정보를 기록해두어 필요할 때 사용할 수 있도록 준비해둔다. 만약 매니페스트가 다른 파일에 들어있는 타입이고 그 타입에 접근할 수 있다면, 다시 해당하는 파일을 로드하여 같은 절차를 반복한다.

 CLR은 실제 어셈블리를 메서드가 참조하는 타입이 들어있는 어셈블리가 로드되지 않았을 때에만 로드한다. 즉, 프로그램을 실행할 때, 컴파일 할 때와는 다르게 어셈블리에 연관된 모든 파일들이 존재하지 않아도 된다.

 


 

Visual Studio에서 어셈블리를 프로젝트에 추가하기.

 

생략

 


 

어셈블리 링커 사용하기.

 

 앞서 말했듯이, C# 컴파일러를 사용하지 않는 대신, 어셈블리 링커 유틸리티인 AL.EXE를 사용하여 직접 어셈블리를 만들 수도 있다. CLR기반의 다른 컴파일러는 C# 컴파일러의 /addmodule 같은 스위치를 제공하지 않을 수도 있다. 이런 경우 AL.EXE를 이용하여 어셈블리를 만들어 낼 수도 있고, 어셈블리 패키징을 할 때 필요한 종속성이 무엇인지 알기 어려운 때에나, 리소스 전용 어셈블리를 만들기 위해서 사용할 수도 있다(이런 유형의 어셈블리를 위성 어셈블리라고 한다).

 

 AL.EXE 유틸리티는 다른 모듈들에 들어있는 타입을 설명하는 매니페스트만 포함하는 EXE 또는 DLL PE 파일을 생성할 수 있다. 

 

csc /t:module RUT.cs
csc /t:module FUT.cs
al /out:MultiFileLibrary.dll /t:library FUT.netmodule RUT.netmodule

 

 

 


 

어셈블리에 리소스 파일 추가하기

 

 AL.EXE.에 /embed[resource] 스위치를 이용하여 어셈블리에 리소스를 파일로 추가할 수 있다.

 이 스위치를 어떤 종류의 파일이든 모두 PE 파일에 포함되도록 해준다. 그리고 ManifestResourceDef 테이블에는 추가한 리소스의 존재를 반영하기 위해 항목이 새로 추가된다.

 /link[resource] 스위치도 제공하는데, 이는 ManifestResourceDef 테이블과 FileDef 테이블만을 수정하며, PE 파일 안에 리소스가 포함되지 않고, 배포될 때 같이 배포되어야 하는 별도의 파일이 된다.

 

 


 

어셈블리 버전 리소스 정보

 

 AL.EXE와 CSC.EXE를 사용하여 PE 파일 어셈블리를 만들면, PE 파일 내부에 표준 Win32 버전 리소스를 추가하게 된다. 파일 속성을 통해서 이 내용을 확인하거나, 프로그램상에서 System.Diagnostics.FileVersionInfo 클래스의 GetVersionInfo를 호출하여 정보를 가져올 수 있다.

 

 

Properties/AssemblyInfo.cs

 

 이하 생략

 


 

단순 응용프로그램 배포 (개별적으로 배포된 어셈블리)

 

Windows Store앱 

 이제 사용자가 응용프로그램을 실행할 수 있도록 어떻게 패키지로 만들고 모든 어셈블리들을 배포할 수 있는지 살펴보자.

 Windows Store 앱은 어셈블리를 패키지화하는 것에 관하여 매우 엄격한 규칙을 가지고 있으며, Visual Studio에서는 응용프로그램에서 필요로 하는 모든 어셈블리들을 한 개의 .appx 파일로 만들어줘 Windows Store또는 테스트용으로 로드 할 수 있게 도와준다. 

 

 사용자가 .appx 파일을 설치할 때 그 안에 들어있는 모든 어셈블리들이 특정한 디렉토리 안에 설치되고, CLR이 그 어셈블리들을 로드하여 Windows가 사용자의 시작 화면에 응용프로그램 타일을 설치할 수 있도록 한다. 만약 기존에 설치된 어셈블리를 다른 사용자가 설치하려고 하는 경우에는 단순히 다른 사용자의 시작 회면에 새로운 타일을 보여주도록 만든다. 삭제의 경우 반대로 그 앱을 사용하는 계정이 하나라도 있다면 단순히 타일을 삭제하게 되고, 모든 사용자가 그 앱을 사용하지 않으면 어셈블리와 함께 응용프로그램 설치 디렉터리를 삭제하게 된다. 

 

 서로 다른 사용자들은 서로 다른 버전의 Windows Store 앱을 설치할 수 있으며, Windows에서는 서로 다른 버전의 앱을 분리된 디렉터리에 설치하여 동시에 한 컴퓨터에서 실행할 수 있도록 한다.

 

데스크톱 앱

 데스크톱 앱의 경우, 어셈블리를 배포하기 위하여 패키지화 하는 것에 신경쓸 필요가 없다. 가장 단순한 배포 방법은 관련된 모든 파일을 한꺼번에 복사하는 것이다. 배포하려는 모든 어셈블리 파일과 이 파일들을 사용자의 드라이브의 특정 위치에 일괄적으로 복사하는 설치 배치 파일을 만들어 사용자에게 전달하면 배포가 끝난다.

 

 어셈블리 안에 모든 종속 구성요소들에 대한 것이 포함되어 있기 때문에, 사용자가 프로그램을 실행하기만 하면 CLR이 배포된 디렉토리에서 참조하게 되는 모든 어셈블리들을 찾아준다. 레지스트리를 수정하는 일도 없고, 제거는 단순히 해당 디렉토리를 삭제하면 된다.

 

 응용프로그램의 일부로서 같은 디렉터리에 함께 복사된 어셈블리들을 개별적으로 배포된 어셈블리(Privately Deployed Assembly)라고 부른다. 서로 다른 응용프로그램들이 같은 디렉터리에 설치되지 않는 한 서로 어셈블리들을 공유하지 않기 때문에 이렇게 부른다. 응용프로그램의 기본 디렉터리에 누구든 파일을 복사하기만 하면 CLR이 파일을 찾아 읽고 코드를 실행할 수 있고, 삭제나 백업이 단순해진다.

 

 이와 같은 단순한 설치/이동/제거 시나리오는 각 어셈블리의 메타데이터들이 어떤 어셈블리가 반드시 필요한지를 분명히 명시하기 때문에 그런 것이고, 레지스트리의 설정이 전혀 필요하지 않게 되었기 때문이다. 또한 참조된 어셈블리는 모든 타입의 범위를 정하므로, CLR은 이름만 같게 만들어진 다른 어셈블리로 대체하여 로드할 수 없다. COM은 레지스트리 상에 타입에 대한 정보가 기록이 되기 때문에, 컴퓨터상의 어떤 응용프로그램도 해당 타입을 자유롭게 접근하였던 것과 대비된다.

 


 

단순한 관리와 설정

 

 응용프로그램을 관리할 수 있도록 하기 위해, 응용프로그램의 디렉토리에 설정 파일을 놓을 수 있다. 응용프로그램 제작시에 이 파일을 만들고 같이 배포할 수 있다. 컴퓨터의 관리자나 사용자가 스스로 이 파일을 만들거나 수정할 수도 있다. CLR은 이 파일의 내용을 해석하여 어셈블리 파일의 위치를 찾거나 로드하기 위한 정책을 수정할 수도 있다.

 

 이 설정 파일은 XML 파일로, 레지스트리와는 다르게 분리된 파일이기 때문에 쉽게 백업할 수 있더 관리자가 응용프로그램을 다른 컴퓨터로 배포하기 매우 편리하다. 단순히 필요한 파일과 관리 정책 파일을 복사하기만 하면 된다. (3장에서 이 설정 파일에 대해 상세히 살펴본다고 한다)

 


 

요약

 

어셈블리

 코드들의 논리적인 묶음이다. PE 파일의 형태로 존재한다. 한 개의 어셈블리에는 여러 개의 파일을 포함할 수 있다. 작성한 소스코드가 어셈블리로 묶여져 있지 않다면, 다른 응용프로그램에서는 이용할 수 없다. 어셈블리 파일엔 자체 정보를 가지고 있는 어셈블리 매니페스트가 존재한다.

 

 대부분의 어셈블리는 개별적으로 배포된 어셈블리이기 때문에, 동일한 이름을 사용하는 다른 버전의 응용프로그램이 있다고 하더라도 DLL 지옥이나 레지스트리에 얽메이지 않을 수 있음. 그렇지 않고 Shared Assembly라 하더라도 어셈블리에 기록된 버전을 이용하여 응용프로그램에서 어셈블리의 사용 요청이 왔을 경우에 Major/Minor 버전이 맞는 어셈블리중 가장 최근 revision의 어셈블리를 제공함으로서 해결한다.

 

 응용프로그램을 관리하기 용이하게 하기 위해 XML타입의 설정 파일을 제공한다. 응용프로그램은 설치/이동/제거 시에 레지스트리에 영향을 주지 않기 때문에 손쉽게 관리할 수 있음.