Silverlight를 설치하려면 여기를 클릭합니다.*
Korea 대한민국변경|Microsoft 전체 사이트
MSDN
|개발자 센터
MSDN Home   MSDN Home
MSDN 홈 > MSDN Magazine > 2001년 기사 > .NET Framework: 응용 프로그램 및 유형 빌드, 패키징, 구축, 관리

.NET Framework: 응용 프로그램 및 유형 빌드, 패키징, 구축, 관리

Jeffrey Richter 저
이 문서는 .NET 및 COM+를 잘 알고 있다는 전제 하에 작성되었습니다.
요약? Common Language Runtime용으로 빌드된 유형은 사용한 .NET 언어의 종류에 관계 없이 Microsoft .NET Framework의 응용 프로그램에서 공유될 수 있습니다. 이것은 개발자에게 커다란 장점입니다.

이 문서는 Private 및 기타 어셈블리의 역할과 중요성을 비롯하여 .NET Framework에서 사용하는 응용 프로그램 및 유형의 빌드, 패키징, 구축에 대해 설명합니다. 또한 메타데이터와 어셈블리가 버전간 충돌, DLL 문제 등의 오래된 문제점을 해결하고 시스템 안정성을 향상시키는 방법에 대해서도 살펴봅니다.

오늘날의 응용 프로그램은 하나의 엔티티로 만들어지는 것이 아니라 여러 개체 유형으로 구성됩니다. 또한 응용 프로그램을 개발한 회사에서 만든 유형도 사용하지만 Microsoft나 다른 공급업체의 유형을 사용할 수도 있습니다. Microsoft® .NET CLR(Common Language Runtime)을 대상으로 하는 언어로 이러한 유형을 개발할 경우, 유형이 개발된 언어 종류에 관계 없이 다른 유형을 기본 클래스로 사용할 수 있을 정도로 모든 유형을 완벽하게 함께 사용할 수 있습니다.

이 문서에서는 이러한 유형을 빌드하고 구축 파일에 패키징하는 방법과 .NET Framework로 해결할 수 있는 문제들을 살펴보겠습니다.

맨 위로


.NET Framework 구축 목표

Windows®는 간혹 불안정하고 복잡하다는 평을 들어 왔습니다. 이에 대한 원인은 여러 가지가 있습니다. 예를 들어, 거의 모든 응용 프로그램은 Microsoft나 다른 공급업체의 DLL(dynamic link libraries)을 사용합니다. 응용 프로그램에서 다른 공급업체의 코드를 실행할 경우 코드 개발자는 해당 코드가 어떻게 사용될지를 일일이 예측할 수 없습니다. 따라서 문제의 발생 가능 소지가 있으나 응용 프로그램은 사전에 테스트 및 디버깅 작업을 거치므로 단순히 이것 때문에 문제가 발생하지는 않습니다.

문제는 공급업체에서 코드를 업데이트할 경우 발생합니다. 업데이트 시 새로운 파일은 이전 파일과의 호환성이 있어야 하지만 공급업체에서 코드를 업데이트할 때마다 모든 응용 프로그램을 다시 테스트하는 것은 거의 불가능합니다.

여러분도 새로운 응용 프로그램을 설치하여 이전에 설치한 다른 프로그램이 손상된 경험이 있을 것입니다. 이러한 불안정성 때문에 사용자들은 시스템에 새 소프트웨어를 설치할 때 매우 신중하게 됩니다. 제 경우도 다른 프로그램의 작동에 영향을 줄지 몰라 일부 응용 프로그램을 설치하지 않기로 했습니다.

Windows에 소프트웨어를 설치하는 것은 매우 복잡합니다. 응용 프로그램의 설치 작업은 시스템의 모든 부분에 영향을 줍니다. 예를 들어, 설치 시 여러 디렉터리에 파일이 복사되고, 레지스트리 설정이 업데이트되며, 바탕 화면에 바로 가기와 시작 메뉴, 빠른 실행 도구 모음이 만들어집니다. 문제는 응용 프로그램이 하나의 엔티티로 완전하게 분리되어 있지 않다는 것입니다. 응용 프로그램을 백업하려면 응용 프로그램 파일뿐 아니라 관련 레지스트리 부분을 몽땅 복사해야 합니다. 응용 프로그램은 다른 시스템으로 쉽게 옮길 수도 없으며 이런 경우 모든 파일과 레지스트리 설정이 제대로 되도록 설치 프로그램을 다시 실행해야 합니다. 또한 설치 제거를 해도 일부 파일은 삭제되지 않은 채 시스템에 남아 있습니다.

Windows에 대한 불평의 세 번째 이유는 보안입니다. 응용 프로그램을 설치할 때는 여러 종류의 파일이 복사되는데 그 중 대부분은 다른 회사에서 만들어진 것입니다. 또한 네트워크를 통해 코드가 전송되는 웹 응용 프로그램의 경우, 정작 사용자 자신은 시스템에 코드가 설치 중인지조차 알 수 없습니다. 이러한 코드는 파일 삭제, 전자 메일 전송을 비롯한 모든 작업을 수행할 수 있으므로 이러한 프로그램이 발생시킬 수 있는 문제 때문에 사용자가 새 응용 프로그램 설치를 꺼리는 것도 당연합니다. 따라서 다른 회사에서 개발된 코드가 시스템 리소스를 액세스하려는 경우 사용자가 이를 허용하거나 거부할 수 있는 보안 기능이 시스템에 내장되어야 합니다.

맨 위로


유형을 모듈로 빌드

이 단원에서는 여러 유형이 포함된 소스 파일을 구축할 수 있는 파일로 변환하는 방법을 설명합니다. 먼저 아래의 간단한 응용 프로그램을 살펴봅시다.

public class App {
   static public void Main(System.String[] args) {
      System.Console.WriteLine("Hi");
   }
}
이 응용 프로그램은 Main이라는 static, public 메서드를 가진 App라는 유형을 정의합니다. Main 내부에는 System.Console이라는 다른 유형에 대한 참조가 있습니다. System.Console은 Microsoft에서 구현된 유형으로서 이런 유형의 메서드를 구현하는 MSIL(Microsoft intermediate language) 코드가 MSCorLib.dll 파일에 있습니다. 따라서 이 응용 프로그램은 유형을 정의하고 다른 회사의 유형도 사용합니다.

예제 프로그램을 빌드하기 위해 위의 코드를 소스 코드 파일(App.cs)에 저장한 후 다음 명령줄을 실행합니다.

csc.exe /out:App.exe /t:exe /r:MSCorLib.dll App.cs
이 명령줄은 C# 컴파일러에서 App.exe라는 실행 파일을 만들게 합니다(/out:App.exe). 여기서 생성된 파일의 유형은 Win32® 콘솔 응용 프로그램입니다(/t[arget]:exe).

C# 컴파일러는 소스 파일 처리 시 이 코드에서 System.Console 유형의 WriteLine 메서드를 참조함을 발견합니다. 이 때 컴파일러는 해당 유형과 WriteLine 메서드가 존재하고, WriteLine에서 기대하는 인수 유형과 프로그램에서 제공하는 유형이 일치함을 확인할 수 있어야 합니다. 이를 위해 C# 컴파일러에서 외부 유형에 대한 참조를 해결할 때 사용할 수 있도록 어셈블리 집합을 제공하는 방법을 사용했습니다. 위 명령줄의 경우, 컴파일러가 MSCorLib.dll 파일에 지정된 어셈블리에서 외부 유형을 찾도록 /r[eference]:MSCorLib.dll 스위치를 포함시켰습니다.

MSCorLib.dll은 byte, integer, character, string 등의 주요 유형이 모두 포함된 특수 파일입니다. 실제로 이러한 유형이 매우 자주 사용되기 때문에 C# 컴파일러는 자동으로 이 어셈블리를 참조합니다. 즉, 다음 명령줄은 /r 스위치가 없지만 위의 명령줄과 똑같은 기능을 합니다.

>
csc.exe /out:App.exe /t:exe App.cs
특별한 이유 때문에 C# 컴파일러에서 MSCorLib.dll 어셈블리를 참조하지 않게 하려면 /nostdlib 스위치를 사용합니다. 예를 들어, App.cs 파일을 컴파일할 때 다음 명령줄을 사용하면 오류가 발생합니다.
csc.exe /out:App.exe /t:exe /nostdlib App.cs
이제 C# 컴파일러에서 만들어진 App.exe 파일이 정확히 어떤 파일인지 자세히 살펴보기로 합시다. 먼저 이것은 표준 PE(Portable Executable) 파일입니다. 이것은 32비트나 64비트 Windows를 실행하는 시스템에서 이 파일을 로드하여 뭔가를 할 수 있음을 의미합니다. Windows는 두 종류의 응용 프로그램을 지원하는데 그 중 하나는 CUI(Console User Interface) 응용 프로그램이고 다른 하나는 GUI(Graphical User Interface)입니다. 지금은 /t:exe 스위치를 지정했기 때문에 CUI 응용 프로그램이 생성됬으나, /t:winexe 스위치를 사용하면 GUI 응용 프로그램이 생성됩니다.

이제 어떤 PE 파일이 생성되었는지를 알았고, 그럼 App.exe 파일은 무엇인가? 관리 PE 파일에는 CLR 헤더, 메타데이터, MSIL이라는 세 가지 주요 구성 요소가 있습니다. CLR 헤더는 CLR(관리 모듈)이 필요한 모듈에만 있는 짧은 정보 블록입니다. 여기에는 모듈이 사용될 CLR의 주 버전 및 부 버전 번호, 플래그, 모듈의 진입점 방법(CUI 또는 GUI)을 나타내는 MethodDef 토큰, 디지털 서명이 포함됩니다. 또한 헤더의 끝부분에는 모듈에 포함된 특정 메타데이터 테이블의 크기와 오프셋이 나옵니다.

메타데이터는 여러 개의 테이블로 구성된 이진 데이터의 블록입니다. 그림?1은 모듈의 메타데이터 블록에 나타나는 일반적인 테이블을 보여 줍니다.

그림?1의 메타데이터 테이블은 해당 모듈 내에 구현된 항목을 정의합니다. 또한 메타데이터 테이블에는 다른 어셈블리에서 참조 중인 항목도 나타납니다. 그림?2는 더 일반적인 참조 메타데이터 테이블을 보여 줍니다.

그림?1그림?2에 나타난 것보다 훨씬 더 많은 테이블이 있지만 여기서는 컴파일러에서 생성된 메타데이터 정보가 어떤 것인지만 설명하겠습니다.
관리 PE 파일 내의 메타데이터를 검사할 수 있는 도구는 여러 가지가 있으나 저는 MSIL 디스어셈블러인 ILDasm.exe를 주로 사용합니다. 메타데이터 테이블을 보려면 다음 명령줄을 실행합니다.

ILDasm /Adv App.exe
ILDasm.exe가 실행되어 App.exe 어셈블리를 로드합니다. /Adv 스위치는 보기 메뉴의 고급 메뉴 항목을 사용할 수 있도록 지정합니다. 메타데이터를 인간이 읽을 수 있는 형식으로 보려면 View.MetaInfo.Show! 메뉴 항목을 선택합니다. 이렇게 하면 그림?3과 같은 정보가 나타납니다.

다행히 ILDasm은 메타데이터 테이블을 처리하여 정보를 적절하게 결합시키므로 원시 테이블 정보를 직접 분석할 필요가 없습니다. 예를 들어, 그림?3을 보면 ILDasm에서 TypeDef 항목을 표시할 때 첫째 TypeRef 항목이 나타나기 전에 해당 구성원 정의 정보가 함께 표시됩니다.

여기에 나타난 내용을 완벽하게 이해할 필요는 없으며 중요한 것은 App.exe에 App라는 TypeDef가 포함되어 있음을 아는 것입니다. 이 유형은 System.Object(다른 어셈블리에서 참조되는 유형)에서 파생된 public 클래스입니다. 또한 App 유형은 Main과 .ctor(생성자)이라는 두 메서드를 정의합니다.

Main은 x86 같은 원시 CPU 코드가 아니라 MSIL 코드를 사용하는 static, public 메서드입니다. Main의 반환 유형은 void이고, args라는 하나의 문자열 배열 인수를 취합니다. 생성자 메서드(이름이 항상 .ctor로 표시됨)는 public이고 역시 MSIL 코드를 사용합니다. 생성자의 반환 유형은 void이고, 인수는 없으나 메서드가 호출될 때 개체의 메모리를 참조하는 포인터가 생성되도록 합니다.

ILDasm은 다양한 정보를 보여 주므로 꼭 한 번 사용해 보길 권합니다. ILDasm에서 보여 주는 내용을 많이 이해할 수 있을수록 CLR을 더 잘 이해할 수 있습니다.

흥미 차원에서 잠시 App.exe 어셈블리에 관한 통계를 살펴봅시다. ILDasm의 View.Statistics 메뉴 항목을 선택하면 그림?4와 같은 정보가 표시됩니다.

여기에는 파일 크기(단위: 바이트)와 파일을 구성하는 여러 다른 부분의 크기(단위: 바이트 및 백분율)가 나와 있습니다. 재미있는 것은 이렇게 작은 App.cs 응용 프로그램에서 실제로 MSIL 코드의 크기는 18바이트밖에 되지 않고, PE 헤더와 메타데이터가 파일 크기의 대부분을 차지한다는 것입니다. 물론 응용 프로그램이 커지면서 대부분의 유형과 참조가 다른 유형과 어셈블리에 재활용되므로 전체 파일 크기에 비해 메타데이터와 헤더 정보의 크기가 작아집니다.

맨 위로


유형을 어셈블리로 빌드

앞 단원에서 설명한 App.exe 파일은 메타데이터가 있는 단순한 PE 파일이 아니라 어셈블리이기도 합니다. .NET 플랫폼에서 어셈블리는 재활용, 버전, 보안, 구축의 단위입니다. 따라서 유형을 패키징하고 구축하기 위해서는 어셈블리에 포함되는 모듈에 유형을 저장해야 합니다. 대부분의 경우 어셈블리는 App.exe 예제의 경우와 같이 하나의 파일로 구성됩니다. 그러나 메타데이터가 포함된 PE 파일, 리소스 파일(예: .gif, .jpg ) 등의 여러 파일로 이루어지는 어셈블리도 있습니다. 선택은 여러분의 자유입니다. 어셈블리는 논리적 EXE 또는 DLL이라고 생각하면 편리합니다.

Microsoft가 새로운 어셈블리 개념을 도입하게 된 이유를 궁금해 하는 분들이 많으리라 생각됩니다. 이것은 어셈블리가 재활용이 가능한 유형의 논리적 개념과 물리적 개념을 분리할 수 있기 때문입니다. 예를 들어, 어셈블리는 여러 유형으로 구성되므로 한 파일에는 자주 사용하는 유형을 두고 다른 파일에는 자주 사용하지 않는 유형을 저장할 수 있습니다. 따라서 인터넷을 통해 다운로드하는 방식으로 어셈블리를 구축할 경우, 자주 사용하지 않는 유형이 있는 파일은 클라이언트가 해당 유형을 액세스할 필요가 없으면 다운로드하지 않아도 됩니다. 예를 들어, UI 컨트롤을 개발하는 ISV가 Active Accessibility 유형을 별도의 모듈로 구현했다고 합시다. 이 모듈은 내게 필요한 옵션 기능이 필요한 사용자만 다운로드하고 다른 사용자는 이 모듈을 다운로드할 필요가 없습니다. 다시 말해, 파일이나 모듈이 다운로드 및 배포 단위라면 어셈블리는 재활용 및 버전 관리 단위입니다.

사용자의 관점에서 어셈블리는 내보낸 유형 및 리소스의 컬렉션으로 이름과 버전이 지정됩니다. 개발자의 관점에서 어셈블리는 유형 및 리소스를 구현하는 하나 이상의 파일의 컬렉션으로서 한 개의 PE 파일이나 PE 파일의 컬렉션, 리소스 파일, HTML 페이지, GIF 등이 포함됩니다.

어셈블리를 빌드하려면 PE 파일 중 하나를 목록(manifest) 관리자로 선택해야 합니다. 또는 목록만 포함된 별도의 PE 파일을 만드는 방법도 있습니다. 목록이란 어셈블리의 ID, 작동, 파일, 내보낸 공용 유형, 어셈블리를 구성하는 모든 파일을 설명하는 테이블 집합입니다. 또한 목록에는 해당 어셈블리에서 참조하는 다른 어셈블리도 나타납니다. 어셈블리의 목록을 포함하는 PE 파일은 메타 데이터에 별도의 테이블이 추가됩니다. 그림?5는 PE 파일의 목록 메타데이터에 포함된 메타데이터 테이블을 보여 줍니다.

목록은 어셈블리 사용자에게 간접적으로 어셈블리 구현 세부 정보를 제공하고 어셈블리를 자체 독립적으로 만듭니다. 목록을 포함하고 있는 파일은 어셈블리에 어떤 파일들이 속하는지를 알고 있지만 나머지 각 파일들은 어셈블리에 속해 있음을 인식하지 못합니다.

/t[arget]:exe, /t[arget]:winexe, /t[arget]:library 중 하나의 명령줄 스위치를 지정하면 C# 컴파일러는 어셈블리를 만듭니다. 이 스위치들은 모두 목록 메타데이터 테이블을 포함하는 한 개의 PE 파일을 생성하며, 그 결과 CUI나 GUI, DLL 실행 파일이 각각 생성됩니다.

위의 스위치 외에도 C# 컴파일러는 /t[arget]:module 스위치를 지원합니다. 이 스위치는 목록 메타데이터 테이블이 포함되지 않은 PE 파일을 만듭니다. 이렇게 생성된 PE 파일은 항상 DLL PE 파일이며, 이 파일은 어셈블리에 추가해야만 내부에 포함된 유형을 사용할 수 있습니다.

어셈블리에 모듈을 추가하는 방법은 여러 가지가 있습니다. C# 컴파일러를 사용하여 목록이 있는 PE 파일을 빌드할 경우 /addmodule 스위치를 사용할 수 있습니다. 여러 파일이 포함된 어셈블리를 빌드하는 방법을 이해하기 위해 여기 두 개의 소스 코드 파일이 있다고 가정합시다. 하나는 자주 사용하지 않는 유형이 포함된 RUT.cs이고 다른 하나는 자주 사용하는 유형이 포함된 FUT.cs입니다.

RUT 파일은 자주 사용되지 않으므로 어셈블리 사용자가 해당 유형을 사용하지 않을 경우 이 모듈이 필요 없도록 각 파일을 별도의 모듈로 컴파일합니다.

csc /out:RUT.mod /t:module RUT.cs
RUT.mod 파일은 표준 DLL PE 파일입니다. 해당 모듈의 유형을 사용하려면 어셈블리에 모듈을 추가해야 함을 나타내기 위해 확장명을 .mod로 지정했습니다.
이번에는 자주 사용하는 유형을 별도의 모듈로 컴파일합니다. 이 유형이 보다 자주 사용되므로 이 모듈에 어셈블리의 목록을 포함시킵니다. 실제로 이제부터는 이 모듈이 전체 어셈블리를 대표하게 되므로 출력 파일의 이름을 FUT.dll 대신 JeffTypes.dll로 변경합니다.

csc /out:JeffTypes.dll /t:library /addmodule:RUT.mod FUT.cs
위의 행을 실행하면 C# 컴파일러에서 FUT.cs 파일을 컴파일하여 JeffTypes.dll 파일이 생성됩니다. /t:library를 지정했으므로 목록 메타데이터 테이블이 포함된 DLL PE 파일이 JeffTypes.dll 파일로 생성됩니다. /addmodule:RUT.mod 스위치의 역할은 RUT.mod 파일도 어셈블리에 포함되도록 컴파일러에게 알려 주는 것입니다. 구체적으로는 FileDef 테이블에 RUT.mod 파일을 추가하고, RUT.mod의 공용 유형을 ExportedTypesDef 테이블에 추가합니다. 컴파일러에서 이 모든 작업을 끝내면 그림?6과 같이 두 개의 파일이 생깁니다.


그림 6 RUT 모듈과 JeffTypes.dll
그림 6 RUT 모듈과 JeffTypes.dll

맨 위로


RUT.mod 모듈에는 RUT.cs를 컴파일하여 생성된 MSIL 코드가 포함됩니다. 또한 RUT.cs에 정의된 유형, 메서드, 필드, 속성, 이벤트 등을 설명하는 메타데이터 테이블이 포함되어 있습니다. 메타데이터 테이블은 RUT.cs에서 참조하는 유형, 메서드 등을 설명해 놓은 것입니다. JeffTypes.dll은 별도의 파일로서, RUT.mod처럼 FUT.cs를 컴파일하여 생성된 MSIL 코드와 비슷한 정의 및 참조 메타데이터 테이블을 포함합니다. 하지만 JeffTypes.dll에는 JeffTypes.dll을 어셈블리로 만들어 주는 또 다른 목록 메타데이터 테이블이 있습니다. 이 목록 메타데이터 테이블은 어셈블리(JeffTypes.dll 파일과 RUT.mod 파일)를 구성하는 모든 파일을 설명하며, 여기에는 JeffType.dll과 RUT.mod에서 내보낸 모든 공용 유형이 포함됩니다.

실제로 목록 메타데이터 테이블에는 목록이 포함된 PE 파일에서 내보낸 유형이 포함되어 있지 않습니다. 이것은 PE 파일에서 목록 정보가 차지하는 용량을 줄이기 위한 방법입니다.

때문에 메타데이터 테이블에 JeffType.dll 및 RUT.mod에서 내보낸 모든 공용 유형이 포함되어 있다는 것은 엄밀히 말하면 정확하지 않으나, 목록의 논리적인 역할을 설명해 주는 것입니다.

JeffTypes.dll 어셈블리가 빌드되면 ILDasm.exe를 사용하여 메타데이터의 목록 테이블에서 어셈블리 파일이 실제로 RUT.mod 파일의 유형을 참조하는지 확인할 수 있습니다. 이 문서에 포함된 예제 코드에는 RUT.cs와 FUT.cs 파일을 사용하여 JeffTypes.dll 어셈블리를 생성하는 프로젝트가 있습니다. 이 프로젝트를 빌드한 후 ILDasm으로 메타데이터를 검사하면 출력 결과에 FileDef 및 ExportedTypesDef 테이블이 포함되어 있을 것입니다. 그림?7에 이러한 테이블의 예가 나와 있습니다.

여기에서 RUT.mod 파일이 어셈블리의 일부임을 알 수 있습니다. ComType 테이블(Beta 2에서 이름이 바뀜)을 보면 ARarelyUsedType이라는 공용 유형이 있습니다. 이 유형의 구현 토큰은 0x26000001이며 이것은 이 유형의 MSIL 코드가 RUT.mod 파일에 포함됨을 나타냅니다.

토큰은 4바이트 값이며 최상위 바이트가 토큰 유형을 나타냅니다(0x01=TypeRef, 0x02=TypeDef, 0x26=FileRef, 0x27=ExportedType). 전체 목록을 보려면 .NET Framework SDK에 포함된 CorHdr.h 파일에서 CorTokenType 열거형을 참조하십시오. 토큰의 하위 3바이트는 해당 메타데이터 테이블의 행을 나타냅니다. 따라서 0x26000001은 FileDef 테이블의 둘째 행을 참조합니다.

JeffTypes.dll 어셈블리의 유형을 사용하는 클라이언트 코드를 빌드하려면 /r[eference]:JeffTypes.dll 컴파일러 스위치를 사용해야 합니다. 이 클라이언트 코드를 실행하기 위해 CLR(Common Language Runtime)은 JeffTypes.dll을 사용할 수 있어야 합니다. 하지만 RUT.mod 파일은 클라이언트 코드에서 이 파일에 포함된 유형을 사용할 때만 필요합니다.

C# 컴파일러 대신 어셈블리 링커 유틸리티인 AL.exe를 사용할 수도 있습니다. AL.exe 유틸리티는 다른 종류의 컴파일러로 빌드된 모듈로 어셈블리를 만들 때(컴파일러에서 C# /addmodule 스위치의 기능을 지원하지 않을 경우)나 빌드 시 어셈블리 패키징 요구 사항을 모를 경우 유용합니다. 또한 AL.exe를 사용하면 소프트웨어 로컬라이제이션 시 많이 사용되는 리소스만 있는 어셈블리(위성 어셈블리)를 빌드할 수도 있습니다.

AL.exe 유틸리티는 다른 모듈에 있는 유형을 설명하는 목록만 포함된 EXE나 DLL PE 파일을 만들 수 있습니다. AL.exe가 어떻게 작동하는지를 이해하기 위해 JeffType.dll 어셈블리를 빌드하는 방법을 바꿔 봅시다.

csc /out:RUT.mod /t:module RUT.cs
csc /out:FUT.mod /t:module FUT.cs
al  /out:JeffTypes.dll /t:lib FUT.mod RUT.mod
그림?8은 위의 문을 실행하여 생성된 파일입니다. 그림?8에는 RUT.mod와 FUT.mod라는 두 개의 모듈이 있고, 이 모듈은 그 자체만으로는 어셈블리가 아닙니다. 세 번째 파일인 JeffTypes.dll은 MSIL 코드는 없고 목록 메타데이터 테이블만 포함된 작은 DLL PE 파일(/t[ype]:lib 스위치 사용)로서, 이 목록은 RUT.mod와 FUT.mod가 어셈블리에 포함됨을 설명합니다.

그림 8 RUT 및 FUT 모듈과 JeffTypes.dll
그림 8 RUT 및 FUT 모듈과 JeffTypes.dll

맨 위로


AL.exe 유틸리티로 CUI 및 GUI PE 파일(/t[ype]:exe 또는 /t[ype]:win 스위치 사용)을 만들 수도 있으나 이렇게 하면 다른 모듈의 메서드를 호출하는 MSIL 코드가 잔뜩 포함된 EXE PE 파일을 만들게 되므로 사용하지 않습니다. AL.exe 유틸리티를 호출할 때 /main 명령줄 스위치를 사용하면 이러한 MSIL 코드가 생성됩니다.

   csc /out:App.mod /t:module /r:JeffTypes.dll App.cs
   al  /out:App.exe /t:exe /main:App.Main app.mod
여기서 첫째 행은 App.cs 파일을 모듈로 빌드합니다. 그런 다음 둘째 행은 목록 메타데이터 테이블이 포함된 작은 App.exe PE 파일을 만듭니다. 이 때 /main:App.Main 스위치 때문에 간단한 global 함수도 생성됩니다. __EntryPoint라는 이 함수는 다음 MSIL 코드를 포함합니다.

.method privatescope static void __EntryPoint() il managed
{
  .entrypoint
  // Code size       8 (0x8)
  .maxstack  8
  IL_0000:  tail.
  IL_0002:  call       void [.module 'App.mod']App::Main()
  IL_0007:  ret
} // end of method 'Global 
  // Functions::__EntryPoint'
위에서 보듯이 이 코드는 단순히 App.mod 파일에 정의된 App 유형에 포함된 Main 메서드를 호출합니다.

목록 메타데이터 테이블이 포함된 PE 파일에 응용 프로그램의 진입점이 없는 어셈블리를 만들 리가 없으므로 Al.exe의 /main 스위치는 사용할 일이 없으며, 여기서는 단지 이러한 스위치가 있음을 알려 주기 위해 설명한 것입니다.
AL.exe로 어셈블리를 만들 때 /embed[resource] 스위치를 사용하면 리소스 파일(PE 파일이 아닌)을 어셈블리에 추가할 수 있습니다. 이 스위치는 리소스 정보가 포함된 파일을 가져와 해당 파일의 내용을 결과 PE 파일에 삽입합니다. 이 때 리소스의 존재를 알리기 위해 목록 테이블이 업데이트됩니다. 또 /link[resource] 스위치가 있는데 이것은 목록 테이블을 업데이트할 뿐 어셈블리 PE 파일에 리소스 파일을 삽입하지는 않습니다. 따라서 리소스 파일은 별도로 다른 어셈블리 파일과 함께 패키징됩니다.

CSC.exe도 AL.exe처럼 C# 컴파일러에서 생성된 어셈블리에 리소스를 결합할 수 있습니다. C# 컴파일러의 /res[ource] 스위치가 지정된 리소스 파일을 어셈블리 PE 파일에 삽입하는 반면, 이 컴파일러의 /linkres[ource] 스위치는 독립 실행형 리소스 파일을 참조하도록 목록 테이블을 업데이트합니다.

끝으로, 표준 Win32 리소스를 어셈블리에 삽입할 수 있습니다. AL.exe나 CSC.exe를 사용할 때 /win32res 스위치와 함께 .res 파일의 경로명을 지정하기만 하면 됩니다. 또한 AL.exe나 CSC.exe를 사용할 때 /win32icon 스위치와 함께 .ico 파일의 경로명을 지정하면 표준 Win32 아이콘 리소스를 어셈블리 파일에 손쉽게 삽입할 수 있습니다.

맨 위로


어셈블리 버전 리소스 정보

AL.exe나 CSC.exe에서 PE 파일 어셈블리를 만들 때 표준 Win32 버전 리소스가 PE 파일에 삽입됩니다. 사용자는 파일의 등록 정보를 통해 이 리소스를 확인할 수 있습니다(그림?9 참조). 또한 Microsoft Visual Studio.NET 리소스 편집기를 사용하여 버전 리소스 필드를 보거나 수정할 수 있습니다(그림?10 참조).


그림 9 등록 정보 보기
그림 9 등록 정보 보기

어셈블리를 빌드할 때는 코드 내 어셈블리 수준에서 적용되는 사용자 지정 특성을 사용하여 리소스 필드를 설정합니다. 그림?10에 표시된 버전 정보를 생성하는 데 필요한 코드 예제가 그림?11에 나와 있습니다.

그림 10 버전 정보
그림 10 버전 정보

그림?12는 버전 리소스 필드와 해당 사용자 지정 특성을 보여 줍니다. AL.exe로 어셈블리를 빌드할 경우 사용자 지정 특성 대신 명령줄 스위치를 사용하여 이 정보를 설정할 수도 있습니다. 그림?12에는 각 리소스 필드에 해당하는 AL.exe 명령줄 스위치가 나와 있습니다. C# 컴파일러는 이러한 스위치를 제공하지 않으며 일반적으로 사용자 지정 특성을 사용하는 것이 더 나은 방법입니다.

버전 리소스 특성 외에, AL.exe의 /proc[essor] 및 /os 스위치를 사용하여 AssemblyProcessorDef 및 AssemblyOSDef 목록 메타데이터 테이블의 항목을 각각 설정할 수 있습니다. 또한 이 정보는 AssemblyOperatingSystemAttribute 및 AssemblyProcessorAttribute를 사용하여 설정할 수도 있습니다. 이 두 가지 특성은 모두 System.Reflection 이름 공간에 정의됩니다. 그러나 앞에서 언급한 것처럼 AssemblyProcessorDef 및 AssemblyOSDef 목록 메타데이터 테이블은 현재 CLR에서 무시되므로 이 특성은 잘 사용하지 않습니다.

맨 위로


간단한 응용 프로그램 구축(Private 어셈블리)

앞 단원에서 모듈을 빌드하는 방법과 이러한 모듈들을 어셈블리로 결합하는 방법을 살펴보았습니다. 이제 사용자가 응용 프로그램을 실행할 수 있도록 모든 어셈블리를 패키징 및 구축할 준비가 되었습니다.

어셈블리는 특별한 패키징 수단이 필요 없습니다. 어셈블리 집합을 패키징하는 가장 쉬운 방법은 모든 파일을 직접 복사하는 것입니다. 예를 들어, 모든 어셈블리 파일을 CD-ROM에 넣어 CD에서 사용자의 하드 디스크에 있는 디렉터리로 파일을 복사하는 배치 파일 설치 프로그램을 사용자에게 제공할 수 있습니다. 어셈블리에 필요한 어셈블리 참조 및 유형이 모두 포함되어 있으므로 사용자가 응용 프로그램을 실행하기만 하면 런타임이 응용 프로그램의 디렉터리에서 참조되는 어셈블리를 찾습니다. 응용 프로그램을 실행하기 위해 레지스트리나 Active Directory™를 수정할 필요가 전혀 없으며, 응용 프로그램을 설치 제거할 때도 모든 파일을 삭제하면 됩니다.

물론, .cab 파일(인터넷 다운로드 시 파일 압축 및 다운로드 시간 감소를 위해 사용) 등의 다른 방법을 사용하여 어셈블리 파일을 패키징 및 설치할 수도 있습니다. Windows Installer 서비스(MSIExec.exe 1.5 이상)에서 사용할 수 있도록 어셈블리 파일을 MSI 파일로 패키징할 수 있습니다.

이처럼 배치 파일이나 다른 간단한 설치 소프트웨어를 사용해도 사용자의 시스템에 응용 프로그램을 설치할 수 있습니다. 하지만 바탕 화면에 바로 가기와 시작 메뉴, 빠른 실행 도구 모음을 만들려면 더 복잡한 설치 소프트웨어가 필요합니다. 또한 응용 프로그램을 쉽게 백업 및 복원하거나 다른 시스템으로 옮길 수는 있지만 여러 가지 바로 가기 링크를 만들려면 특별한 처리가 필요합니다.

응용 프로그램과 같은 디렉터리에 구축되는 어셈블리는 다른 응용 프로그램(같은 디렉터리에 구축되지 않는 한)과 어셈블리 파일을 공유하지 않으므로 Private 어셈블리라고 합니다. Private 어셈블리는 레지스트리 설정이나 Active Directory를 업데이트할 필요가 전혀 없으므로 개발자와 사용자, 관리자 모두에게 편리합니다. 즉, 어셈블리에서 참조하는 모든 유형은 런타임에서 해당 유형이 있는 어셈블리를 찾을 수 있는 메타데이터를 갖습니다. 또한 모든 유형은 참조하는 어셈블리에 의해 분류됩니다. 응용 프로그램은 항상 빌드 및 테스트할 때과 완전히 동일한 유형에 바인딩됩니다. 런타임은 우연히 이름이 같은 유형이 포함된 다른 어셈블리를 로드할 수 없습니다. 이것은 시스템에서 실행되는 모든 응용 프로그램에서 사용할 수 있도록 유형을 레지스트리에 기록하는 비관리 COM과 다른 점입니다.


맨 위로


간단한 관리 제어(구성)

응용 프로그램 실행과 관련하여 사용자나 관리자가 결정해야 할 몇 가지 문제가 있습니다. 예를 들어, 관리자는 어셈블리 파일을 사용자의 하드 디스크로 옮기거나 어셈블리 목록에 포함된 정보를 수정하도록 결정할 수 있습니다. 또한 버전 관리 및 원격 문제도 해결해야 합니다. 버전 관리에 대해서는 2부에서 설명하도록 하겠습니다.

응용 프로그램을 관리 제어하기 위해서는 응용 프로그램 디렉터리에 구성 파일을 만들어야 합니다. 구성 파일은 관리자에 의해 작성 및 관리되며 응용 프로그램 실행에 적용되는 정책을 변경하기 위해 런타임에서 사용됩니다. 이러한 구성 파일은 XML 파일이며 특정 응용 프로그램이나 사용자, 시스템과 연결할 수 있습니다. 레지스트리 설정이 아니라 별도의 파일을 사용하므로 파일을 쉽게 백업할 수 있고, 필요한 파일만 복사하면 관리 정책도 복사되므로 관리자가 응용 프로그램을 다른 시스템으로 복사할 수 있습니다.

관리자가 예제 응용 프로그램을 구축할 때 JeffTypes 어셈블리 파일을 응용 프로그램의 어셈블리 파일과 다른 디렉터리에 저장하려는 경우를 생각해 봅시다. 디렉터리 구조는 다음과 같습니다.

AppDir 디렉터리(응용 프로그램의 어셈블리 파일 포함)
   AuxFiles 하위 디렉터리(JeffTypes의 어셈블리 파일 포함) 
파일을 이동한 후 런타임은 JeffTypes 어셈블리 파일을 찾을 수 없게 됩니다. 이 문제를 수정하기 위해 관리자는 XML 구성 파일을 만듭니다. 이 파일의 이름은 응용 프로그램의 주 어셈블리 파일과 같고 .cfg 확장명을 갖습니다(이 예제의 경우 App.cfg). 구성 파일의 내용은 다음과 같습니다.

<?xml version ="1.0"?>
<Configuration>
   <AppDomain PrivatePath="AuxFiles"/>
</Configuration>
이제 런타임은 어셈블리 파일을 찾을 때마다 먼저 응용 프로그램 디렉터리를 찾은 후 해당 파일이 없으면 AuxFiles 하위 디렉터리를 찾게 됩니다. PrivatePath 속성에는 세미콜론으로 구분된 여러 개의 경로를 지정할 수 있습니다. 모든 경로는 응용 프로그램의 시작 디렉터리에 대한 상대 경로여야 합니다. 절대 경로는 사용할 수 없으며, 모든 어셈블리는 응용 프로그램의 시작 디렉터리나 하위 디렉터리에 있어야 합니다.

맨 위로


결론

Windows는 소프트웨어 설치를 관리하기 어렵다는 평을 들어왔습니다. 수년 간 이 문제를 해결하기 위해 Microsoft는 Windows에 여러 가지 응급책(예: 버전 리소스, 비교적 새로운 Microsoft Installer 엔진 MSI)을 만들었는데, 이러한 기술이 도움이 되기는 했지만 문제가 완전히 해결되지는 않았습니다. 개인적으로 이 문제가 완전히 해결될 수 있다고는 생각하지 않으나 메타데이터와 어셈블리는 시스템 안정성 향상에 있어서 커다란 성과입니다. 또한, 레지스트리 설정이 필요 없는 Private 어셈블리는 관리 상의 문제를 크게 해결해 줍니다.
2부에서는 여러 응용 프로그램에서 공유할 수 있는 어셈블리를 빌드하는 방법과 CLR이 참조 유형을 포함하는 어셈블리를 찾는 방법에 대해 살펴보겠습니다.

Jeffrey Richter( http://www.jeffreyrichter.com/)는 Programming Applications for Microsoft Windows(Microsoft Press, 1999)의 저자이며 소프트웨어 교육 및 디버깅, 컨설팅 회사인 Wintellect(http://www.Wintellect.com)의 공동 설립자입니다. 그의 전문 분야는 .NET 및 Win32 프로그래밍/디자인입니다. 현재 Microsoft .NET Framework 프로그래밍 설명서를 집필 중이며 .NET 기술 세미나를 주재합니다.

? 최종수정일: 2001년 2월 19일

Top of Page Top of Page


Microsoft