Resources and Satellite Assemblies 2

Fusion 9 : Resources and Satellite Assemblies 2

오늘은 자원과 위성 어셈블리 그 두번째 시간 되겠다.

(바이너리 리소스를 위한) XML Compiled Resources

지금 까지의 리소스 포함은 우리가 어플리케이션 내에 문자열 리소스를 포함하는 경우 ( 예를들면, 에러 메시지, 다이얼로그 박스 상의 캡션 정보와 같은)  적합한 형태이다. 하지만 아이콘, 비트맵과 같은 바이너리 리소스를 포함하고 싶은 경우,
바이너리 리소스는 문자열에서 했던 방식의 메커니즘을 제공하지 않는다. 바이너리 리소스를 위해서는 텍스트 인코딩을 사용해 바이너리 값을 텍스트 값으로 변환해야 한다.Convert 클래스는 두 개의 메서드를 제공하는데, ToBase64String과 FromBase64String이 그것이다.

관리되는(Managed)   XML리소스는 일반적으로 .resx 파일로 알려져 있다.
(결국, resx 파일 내에 우리는 원하는  바이너리 리소스를 포함할 있으며 이를 Compiled 리소스로 컴파일 생성되는 .resources 파일을 리소스로 포함시켜 프로세스를 실행하면 되는 것이다.)

Raw 형식의 XML 리소스(.resx)  파일 생성 –> (resgen 툴을 이용해 컴파일) –> 컴파일 된 xml 리소스 파일(*.resources) –>실행 프로그램의 리소스로 포함시켜 컴파일

이 파일은 ResxResourceWriter 클래스를 사용해 쓸 수 있으며,  ResxResourceREader 클래스를 이용해서 .resx 파일의 데이터를 읽을 수 있다.  하지만 ResxResourceWriter  클래스를 사용하는데 익숙치 않으므로 이전처럼 그냥 .resx로 컴파일하고 ResourceManager로 읽을 수 있다면 편할 것이다.
Resgen 툴은 .txt 또는 .resx 파일을 입력을 받아들일 수 있으며, 흥미롭게도 .resources 파일을 입력을 받아 들일수도 있다. 이 경우 툴은 .resources 파일을 디컴파일하여 .txt 또는 .resx파일로 결과는 생성한다.
Resx 파일은 엄격한 스키마를 가지고 있으며 이 스키마를 지키지 않으면 컴파일이 되지 않는다. Visual Studio를 사용하면 프로젝트에 윈 폼을 추가할 때 마다 자동으로 .resx 파일을 생성해 준다. Visual Studio를 사용하지 않으면 우리가 직접 resx 파일을 생성해야만 한다.

□ 수동으로
.resx 만들기(without Visual Studio)

그렇다고 resx파일을 만드는 것이 매우 어려운 작업은 아니다 resgen 툴을 이용해서 .resources 파일을 디컴파일 할수 있기 때문이다.
그 생성 단계는 간단하다. 먼저 빈 텍스트 파일을 생성한다. 그리고 리소스 파일로 컴파일 한다. 마지막으로 .resx 파일로 디컴파일 한다.

일반
raw 리소스 파일 생성 –> (resgen 툴을 이용해 컴파일) –> 컴파일된 리소스 파일(.resources)
–> (resgen 툴을 이용해 resx로 디컴파일) –> 디컴파일된 raw  XML 리소스 파일(.resx)

Resgen test.txt
Resgen  test.resource  test.resx

이렇게 하면 test.resx 라는 이름의 XML 파일이 생성된다. 이 파일은 매우 긴 주석과 스키마, 그리고 기본값을 포함하고 있다. 주석을 제거하고,  스키마는 유지하는게 낳을 것이다. 결론적으로 아래는 내용은 resx 파일 내에 존재해야 한다.

<?xml version=”1.0″ encoding=”utf-8″?>
<root>
<resheader name=”resmimetype“>
<value>text/microsoft-resx</value>
</resheader>
<resheader name=”version”>
<value>2.0</value>
</resheader>
  <resheader name=”reader”>
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name=”writer”>
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
</root>

마지막 두 항목은 .resx 파일을 읽고 쓰는데 사용되는 클래스를 나타낸다. 더 중요한 것은 resmimetype 이다. 이는 데이터의 기본적인 형식을 나타내면 위 예시에서는 text를 포함하고 있음을 나타낸다.(text/microsoft-resx)
리소스 파일에 데이터를 추가하는 것은 쉽다. 각 <data> 노드는 <value> 노드를 포함한다. <data> 노드는 name 속성을 가지며 추가적으로 type 속성을 가질 수도 있다. <value> 노드의 내부 텍스트에 값이 들어가게 된다.
만일 mimetype을 명시하지 않으면 <data> 노드는 기본 resmimetype이 사용되며 이는 값이 다음 중 하나의 타입이어야 한다는 것을 의미한다.
String, Byte, SByte, Int16, Int32, Int64, UInt16, UInt32, UInt64, Single, Double, DateTime, TimeSpan, Decimal
ResourceManager는 type과 value을 읽는다 그리고 그 값을 통해 타입을 생성한다. ResourceManager.GetObject를 이용해서  컴파일된 리소스로부터 항목을 가져올 수 있다. 이때 리소스 명을 인자로 전달해야 한다.GetObject는 Object  객체를 리턴하므로 적당한 타입으로 변환해야 한다.
예를 들어 아래의 텍스트를 test.resx 파일에 추가한다.

 <data name=”Data” type=”System.Int32,
mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089″>
<value>42</value>
</data>
</root>

Data 는 32 비트 정수라는 것을 나타낸다.

이 리소스를 읽는 코드는 간단하다.

class App
{
static void Main()
{
Assembly a = Assembly.GetExecutingAssembly();
ResourceManager resources = new ResourceManager(“test”, a);
object obj = resources.GetObject(“Data”);
int data = (int)resources.GetObject(“Data”);
Console.WriteLine(“value is {0}”, data);
}
}

그리고 .resx를 compiled 리소스로 컴파일 하고, 프로세스 컴파일 , 실행
큰 그림 보려면 클하세요!(돋보기도 같이 활용)

□ 바이너리
리소스 데이터 추가하기

위 에 언급된 기본 타입 외의 타입을 사용하고 싶다면, 타입을 직렬화한 후(BinaryFormatter 또는 SoapFormatter를 이용해서) 그 결과를 base64로 인코딩 해야 한다.이렇게 하는 가장 간단한 방법은 ResxResourceWriter를 이용하는 것이다.
예를 들어, .cur 파일을 가지고 있고 이 커서 파일을 폼의 커서로 이용하고 싶다고 가정한다.
genCursor.cs라는 이름의 콘솔 어플리케니션을 생성하고 코드는 아래와 같다

// First parameter is the name of the icon in the .resx file
// Second parameter is the name of the cursor file
using System; using System.IO;
using System.Resources;
using System.Windows.Forms;
class App
{
static void Main(string[] args)
{
if (args.Length < 2) return;
using (ResXResourceWriter writer
= new ResXResourceWriter(Console.Out))
{
using (FileStream fs = File.OpenRead(args[1]))
{
Cursor cursor = new Cursor(fs);
writer.AddResource(args[0], cursor);
cursor.Dispose();
writer.Generate();
}
}
}
}

위 코드는 ResXResourceWriter 의 출력을 콘솔로 재지정(redirect)한다. 그래서 그 출력을 다시 다시 파일로 연결하거나  콘솔에서 그냥 복사해도 된다.

genCursor Cursor test.cur > test.resx

Test.resx 파일을 열면 아래와 같이 리소스가 추가되어 있다.

<data name=”Cursor” type=”System.Windows.Forms.Cursor, System.Windows.Forms” mimetype=”application/x-microsoft.net.object.bytearray.base64“>
<value>
AAACAAEAICAAAA8ADwAwAQAAFgAAACgAAAAgAAAAQAAAAAEAAQAAAAAAgAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAGBgAACAEAABDwgAAjDEAARAggAEg
QIACIIxAAkESQAJCIkACREJAAkiCQAIxBEABIgSAAQQIgACMMQAAQ8IAACAEAAAYGAAAB+AAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA//////////////////////////////////gf///gB///wAP//4AB//8D
wP/+D8B//h+Af/wfAD/8Pgw//DwcP/w4PD/8MHw//AD4P/4B+H/+A/B//wPA//+AAf//wAP//+AH///4
H/////////////////////////////////8=
</value>
</data>

Mimetype이 application/x-microsoft.net.object-.bytearray.base64으로 주어진다. 이는 Cursor 객체를 직렬화하는데 BinaryFormatter가 사용되었다는 의미이다.

□ 바이너리
리소스 사용하기

결과 리소스 파일(test.resx)을 컴파일 한다.

Resgen test.resx

윈폼 어플리케이션을 하나 만들고 리소스로 포함된 커서를 이용하도록 한다.

using System;
using System.Resources;
using System.Windows.Forms;
using System.Reflection;
class MainForm : Form
{
MainForm()
{
Assembly a = Assembly.GetExecutingAssembly();
 ResourceManager resources = new ResourceManager(“test”, a);
      this.Cursor = (Cursor)resources.GetObject(“Cursor”);
}
static void Main()
{
Application.Run(new MainForm());
}
}

윈폼 어플리케이션 컴파일
큰 그림 보려면 클하세요!(돋보기도 같이 활용)
결과


□ Visual Studio
를 이용해 리소스 활용하기

매우 작업이 간단해 진다. 거의 모든 작업을 Visual Studio가 처리해 준다.
바이너리 파일을 리소스화(resx 파일에 리소스로 추가),  리소스 파일에 대한 컴파일,  리소스를 어셈블리에 embed 하는 작업 등

프로젝트 상에서 리소스 파일 추가

추가된 리소스 파일(Resource1.resx)를 디자인 모드로 열면,
문자열/이미지/아이콘/… 등의 카테고리 별 리소스를 확인할 수 있다.

문자열 리소스 하나 추가 : a=1

바이너리 리소스 하나 추가 : 디스크 상의 기존 이미지 파일을 리소스로 추가

이미지 파일을 추가했으므로 왼쪽 카테고리를 “이미지”로 변경하면 추가된 파일이 보인다.

리소스 사용하기

코드도 매우 간단하다.
 private void Form1_Load(object sender, EventArgs e)
{
//문자열 리소스 사용
MessageBox.Show(Resource1.a);
//바이너리 리소스 사용
this.BackgroundImage = Resource1.KKansoon;
}

실행


특별하게 신경 쓰지 않아도 Visual Studio가 embedded 리소스 형태로 이미지를 실행 어셈블리에 포함시켜 준다.
잘 보면, managed XML 리소스 파일(resx)이 포함(embedded)  리소스로 어셈블리에 포함됨을 속성으로 지정하고 있다.(기본값)

이 의미는 곧 Resource1.resx 파일 안에 바이너리 데이터(base64 인코딩된 텍스트)가 포함될 것이고, 이 Resource1.resx가 실행 어셈블리(exe)에 포함될 것이므로, 프로젝트 상에 추가되어 있는 실제 이미지 파일(깐순.jpg)는 굳이 다시 실행 어셈블리에 포함시킬 필요가 없다.
곧 이미지 파일은 Visual Studio가 빌드과정에서,  Resource1.resx을 컴파일 하여 Resource1.resources 를 생성할 때 그 바이너리 데이터를 제공하기 위해 사용되기 위한 것이라 짐작이 가능하다.

즉,  디자인 타임에만 사용될 녀석이라는…실행시간에는 필요가 없다는..
해당 이미지 파일의 빌드작업 속성을 찍은 “없음” 값이 기본이다. (이것 때문에 많이 헷갈려 했었는데…)



이미지 파일을 “포함리소스”로 변경하면? 이미 리소스 파일에 포함된 이미지 데이터가 다시 따로 실행 어셈블리 속으로 포함되므로, 쓸데없이 실행 어셈블리 사이즈만 증가시키는 결과를 낳는다.
 리소스로 포함된 다른 형태의 파일들도 마찬가지 일 것이다.

■ 커서파일을 리소스로

Visual Studio 이용해서 커서 파일을 리소스로 추가하는 것은 조금 애로사항이 있다.(*.cur) 아래를 참조 바람.

http://edndoc.esri.com/arcobjects/9.0/ArcGISDevHelp/DevelopmentEnvs/DotNet/WorkingWithResources.htm

*.cur
리소스로 추가될 그냥파일형태로 추가된다. (커서라는 카테고리 없음, 그렇다고 이미지도 아님)



그래서
아래 처럼 폼의 커서 속성에 바이너리 데이터를 우겨 넣으면 에러.

this.Cursor = (Cursor)Resouce1.TestCursor;

Visual Studio를 쓰지 않는 방식대로 resx를 수동으로 만들어서 해당 커서 바이너리 데이터를 넣어서 그걸 붙여 넣기 할 수 있다.
그래서 수동으로 임의의 resx를 만들어 그 안에 커서 바이너리를 추가한 다음 해당 <data> 항목(리소스)를 클립보드로 복사하여,
Resource1.resx 파일 내용에 집어 넣는다.

예)
<data name=”TestCursor” type=”System.Windows.Forms.Cursor, System.Windows.Forms” mimetype=”application/x-microsoft.net.object.bytearray.base64″>
<value>
AAACAAEAICAAAA8ADwAwAQAAFgAAACgAAAAgAAAAQAAAAAEAAQAAAAAAgAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAGBgAACAEAABDwgAAjDEAARAggAEg
QIACIIxAAkESQAJCIkACREJAAkiCQAIxBEABIgSAAQQIgACMMQAAQ8IAACAEAAAYGAAAB+AAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA//////////////////////////////////gf///gB///wAP//4AB//8D
wP/+D8B//h+Af/wfAD/8Pgw//DwcP/w4PD/8MHw//AD4P/4B+H/+A/B//wPA//+AAf//wAP//+AH///4
H/////////////////////////////////8=
</value>
</data>

그럼 “기타 ” 카테고리에 항목이 생기면서 아래 처럼 표시된다.

그런 후 에

this.Cursor = (Cursor)Resouce1.TestCursor;

하게 되면 커서는 바뀐다.  그러나 좀 번거롭다… resx를 수동으로 만드는 작업을 해야 하니….

또다른 두번째 방법은 소발에 쥐잡기로 알아낸 방법이다. CursorConverter를 이용해도 된다. 그냥 “파일” 카테고리로 커서 파일을 리소스에 추가해 놓고, 아래처럼 리소스 바이너리 데이터를 커서로 변환한다.

 CursorConverter cv = new CursorConverter();
this.Cursor = (Cursor)cv.ConvertFrom(Resource1.Cursor1);


두번째가 조금 간단해 보인다.. 참고) 더 나은 방법이 존재할 거라 본다.

오늘은 요까이

Advertisements
Categories: C#

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중