Environment: DirectX, Windows

DirectX Programming in C#

A little background info:

DirectX projects require more processing power from the computer and they could become really complex because of all the math that is required.

Most DirectX projects are games, but it isn't only limited to games. So for these types of projects it is necessary to keep everything simple and get everything that is not used out of the actual program. Performance is also another issue but only is required for larger, more complex programs.

Requirements

This project was created using Microsoft Visual Studio 2005, if you don't have it, you could try using earlier versions but I don't know if it will work the same way. If you don't have Microsoft Visual Studio 2005 or it is just too expensive you can get a free version on http://msdn.microsoft.com/vstudio/express/visualcsharp/. You will also need the DirectX SDK which can be found on http://msdn.microsoft.com/directx/sdk/.

Programming

This article is intended to be a sort of step by step tutorial to start writing a program for DirectX using C#.

First open Microsoft Visual Studio 2005 and create an empty C# project. Insert a new class into your project. Next you can delete or comment the following 2 lines of code:

using System.Collections.Generic;
using System.Text;

You can also remove the following references from your project:

System.Data System.Xml

You have to specify that the program is a windows program so go to Project-> Project Properties. Put the output type as Windows Application.

Next we have to add the necessary DirectX resources for this project. Put the namespaces in the beginning of the file:

using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

Now go to the add reference dialog and click browse. Go to the directory you installed the DXSDK into and go to the "developer runtime" folder then go to x86 folder then go to the "DirectX for Managed Code" folder. Select the Microsoft.DirectX.Direct3D.DLL, Microsoft.DirectX.Direct3DX.DLL and Microsoft.DirectX.DLL references and click ok. Next go to add reference again and select System.Drawing and System.Windows.Forms reference. Now you have the project set up for DirectX.

You start off with:

Device DX_Device = null; // Drawing device

to make the drawing device. Next you have to put the information for your window into the constructor:

public UsingDirectX()
{
this.ClientSize = new Size(256,256); // Specify the client size
this.Text = "My First DirectX Program"; // Specify the title
}

Next you must tell the device how to render to the screen and handle any exceptions.

public bool InitializeDirect3D()
{
try
{
PresentParameters pps = new PresentParameters();
pps.Windowed = true; // Specify that it will be in a window
pps.SwapEffect = SwapEffect.Discard; // After the current screen is drawn, it will be automatically // deleted from memory
DX_Device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, pps); // Put everything into the device
return true;
}
catch(DirectXException e)
{
MessageBox.Show(e.ToString(), "Error"); // Handle all the exceptions
return false;
}
}

The next function handles the main rendering that is done all in one neat package.

private void Render()
{
if(DX_Device == null) // If the device is empty don't bother rendering
{
return;
}

DX_Device.Clear(ClearFlags.Target, Color.White, 1.0f, 0); // Clear the window to white
DX_Device.BeginScene();

// Rendering is done here

DX_Device.EndScene();
DX_Device.Present();
}

The main function is what its name implies. It is the main function were everything in our program takes place, including the message loop.

static void Main()
{

UsingDirectX form = new UsingDirectX(); // Create the form
if (form.InitializeDirect3D() == false) // Check if D3D could be initialized
{
MessageBox.Show("Could not initialize Direct3D.", "Error");
return;
}
form.Show(); // When everything is initialized, show the form
while(form.Created) // This is our message loop
{
form.Render(); // Keep rendering until the program terminates
Application.DoEvents(); // Process the events, like keyboard and mouse input
}
}

I hope you learned the basics of creating a simple directx program. All this program does is basically set up a loop and keep rendering, or in this case just clearing the screen to white since there is no rendering being done. Full code to this project can be found here.


Attachments

Project Files First DirectX Sample

1

윈도우 PHP Web Programming (14) - PHP 문자열 함수

1. string addslashes(string str)

문자열에 가 있으면 역 스래시(\)를 붙여준다. 반대는 stripslashes이다.

2. string chop(string str)

문자열의 뒷 부분의 공백을 제거한다. ltrim은 앞 부분의 공백을 제거하며, trim은 앞과 뒤의 공백을 제거한다.

3. string chr(int ascii)

ASCII에 해당하는 문자를 반환한다.

4. string crypt(string str, string [salt])

주어진 문자열을 암호화 한다. 두 번째 인자가 생략되면 시스템이 임의로 암호화 방식을 결정한다. 암호화 방식은 DES, MD5 등을 사용한다. 암호를 해독하는 함수는 없다.

5. echo(string arg1, string [argn])

하나 이상의 문자열을 출력한다. Print 함수와 같은 기능을 수행한다.

6. int ereg(string pattern, string str, array [regs])

두 번째 문자열(str)에서 첫 번째 문자열(pattern)을 찾는다. 두 번째 문자열에 ()가 포함되어 있으면, 세 번째 인자가 필요하다. ereg 함수는 대소문자를 구별하지만, eregi 함수는 대소문자를 구별하지 않는다.

7. string ereg_replace(string pattern, string replacement, string str)

세 번째 문자열(str)에서 첫 번째 문자열(pattern)을 찾아 두 번째 문자열(replacement)로 바꾼 후 그 값을 반환한다. 일치하는 문자가 없으면 원래의 문자열을 반환한다. ereg_replace 함수는 대소문자를 구분한다. eregi_replace 함수는 대소문자를 구분하지 않는다.

8. array explode(string separator, string str)

첫 번째 인자 separator를 구분자로 하여 두 번째 문자열 str을 나누어 배열로 저장한다. split 함수와 유사한 기능이다. implode 함수는 반대 기능을 한다. 구분자 문자는 배열에 저장하지 않는다.

9. string htmlentities(string str)

문자열의 특정 문자들을 HTML 개체들로 변환한 결과 값을 반환한다.

10. string htmlspecialchars(string str)

게시판이나 방명록 등에서 사용자가 입력한 &, , <, >와 같은 특수 문자를 화면에 출력 가능하도록 &amp, &quot, &lt, &gt와 같은 HTML 코드로 변경한다. 기타 다른 특수 문자를 변환하기 위해서는 htmlentities 함수를 사용한다.

11. string implode(string glue, array pieces)

배열로 나누어진 문자열을 하나의 문자열로 합친다. glue는 합친 문자열의 구분자이다. Explode 함수의 반대 기능이며, join 함수와 같은 기능을 수행한다.

12. string join(array pieces, string glue)

implode 함수와 같은 기능을 수행한다.

13. string ltrim(string str)

문자열의 앞 부분의 공백을 제거한다. chop 함수는 뒷 부분의 공백을 제거하며, trim 함수는 앞 뒤 부분의 공백을 제거한다.

14. string nl2br(string str)

문자열에 포함된 개행 문자를 <BR> 태그로 바꾼다. 사용자가 enter 키를 입력한 곳을 찾아 그곳에 <BR> 태그를 삽입하여 저장하기 위해 사용된다.

15. into rd(string str)

주어진 문자를 ASCII 값으로 반환한다. chr 함수의 반대 기능이다.

16. int preg_match(string pattern, string subject, array [matches])

문자열 subject에서 문자열 pattern과 일치하는 패턴이 있는지 찾아서, 있다면 그 패턴을 array에 저장하고 1(TRUE)를 반환한다.

Pattern 유형은 다음과 같다.

- /he/I he와 일치하는 패턴

- /[abc]/ a, b, c 중 어느 하나와 일치하는 패턴

- /[a-z]/ a부터 z까지 문자 중 어느 한 문자와 일치하는 패턴

- /[a-zA-Z]/ a부터 z, A부터 Z까지 문자 중 어느 한 문자와 일치하는 패턴

- /[^0-9]/ 숫자가 아닌 패턴

- /[\w]{3}/ 일반 문자가 3번 이상 연속으로 반복되는 패턴

- /[\d]{1,5}/ 숫자가 1번 이상 5번 이하 연속으로 반복되는 패턴

- /[\W]{3,}/ 일반 문자가 아닌 문자가 3번 이상 연속으로 반복되는 패턴

- /?/ 문자가 없거나 임의의 한 문자인 패턴

- /./ 줄 바꿈 문자가 아닌 임의의 문자인 패턴

- /+/ 선행 패턴을 만족하는 문자() 1번 이상 반복되는 패턴

- /*/ 선행 패턴을 만족하는 문자() 1번 이상 반복되는 패턴

17. print(string arg)

문자열을 출력한다.

18. int printf(string format, mixed [args])

문자열을 포맷하여 출력한다.

19. int quotemeta(string str)

Meta 문자 앞에 \문자를 삽입한다.

20. string sprintf(string format, mixed [args] )

문자열을 포맷하여 출력한다. Printf 함수와 동일한 기능이지만, sprintf 함수는 수행 결과를 호출한 곳에 반환한다.

21. array split(string pattern, string str, int [limit])

문자열 str pattern 구분자를 사용하여 나누어 배열에 저장한다. 오류가 발생하면 FALSE를 반환하고, limit은 나누어 저장할 문자열의 개수로 생략 가능하다.

22. int strcasecmp(string str1, string str2)

대소문자를 구분하지 않고 두 문자열을 비교한다.

23. string strchr(string haystack, string needle)

문자열 haystack을 검사하여 문자열 needle이 처음 나타난 위치부터 끝까지 값을 반환한다. 찾는 문자열이 없는 경우 FALSE를 반환한다.

24. int strcmp(string str1, string str2)

두 문자열을 배교하여 같으면 0, str1 str2보다 작으면 - 값을, 크면 + 값을 반환한다.

25. string stripslashes(string str)

문자열에서 역 슬래시 문자를 제거하여 반환한다.

26. string stristr(string str, string, find_str)

문자열 str에서 문자열 find_str을 찾아서 일치하는 부분부터 끝까지 반환한다. 대소문자를 구분하지 않는다. 찾는 문자열이 없으면 FALSE를 반환한다.

27. int strlen(string str)

문자열의 길이를 반환한다.

28. int strpos(string haystack, string needle, int [offset])

문자열 haystack을 검사하여 문자열 needle이 처음 나타난 위치 값을 숫자로 반환한다. 찾지 못하면 FALSE를 반환한다. Offset은 검사를 시작할 위치이다.

29. string strrchr(string hatstack, string needle)

문자열 haystack을 검사하여 문자열 needle이 마지막으로 나타난 위치부터 끝까지의 문자열을 반환한다.

30. string str_replace(string str1, string str2, string str3)

문자열 str3에서 문자열 str1을 찾아서 문자열 str2로 바꾼다. ereg_replace 함수보다 처리 속도가 빠르다.

31. string strrev(string str)

문자열의 글자 위치를 정반대로 바꾸어 반환한다.

32. int strrpos(string haystack, string needle)

문자열 haystack을 검사하여 문자열 needle이 마지막으로 나타난 위치의 값을 숫자로 반환한다.

33. string strstr(string str, string find_str)

문자열 str에서 문자열 find_str을 찾아서 일치하는 부분부터 끝까지 문자열을 반환한다. 대소문자를 구별한다.

34. string strtolower(string str)

문자열을 소문자로 변환하여 반환한다.

35. string strtoupper(string str)

문자열을 대문자로 변환하여 반환한다.

36. string substr(string str, int start, int length)

문자열 str start 위치부터 length 만큼의 문자를 반환한다. Length가 생략되면 끝까지 반환된다.

37. string trim(string str)

문자열의 앞과 뒤의 공백을 제거하고 반환한다.

38. string ucfirst(string str)

문자열의 첫 문자가 영문자이면 대문자로 변경한 후 문자열을 반환한다.

39. string ucwords(string str)

문자열 내의 모든 단어들의 첫 문자를 대문자로 변환하고, 문자열을 반환한다.

주의 : cpu가 virtualization을 지원하지 않으면 안된다.

1. virtual box를 설치한다.(2.1.2)
2. 새로운 vm을 만든다. 이때 주의할 것은 반드시 VT-x / AMD-v가 사용 가능한 상태가 되어야 한다.
3. os/2 warp 4 iso 이미지를 cd에 마운트 상태로 한다.
4. iso 이미지를 열어보면 BOOTIMGS 디렉토리 아래에 DISK_0_1.IMG, DISK_2.IMG 파일이 있다. 이 중 DISK_0_1.IMG를 플로피 디스크로 마운트한다.
5. vm을 시작한다. 부트 메뉴에서 2. removable media를 선택한다. 그럼 아래의 화면을 볼 수 있다.


6. 로고가 지나고 나면 대충 다음 다음을 눌러서 진행한다(무책임한 게시글)
7. 설치가 완료되면 cd와 플로피의 마운트를 해제하고 enter를 눌러서 재부팅한다.
8. 부팅이 되면 시커먼 화면만 나온다 .ALT+F2를 누르면 부팅시 어떤 일이 진행되는 지를 확인할 수 있다.
9. 그 다음부터는 익숙한 GUI 환경에서의 시스템 설정이다. 입맛대로..... 진행이 잘 되면 열심히 파일을 복사하는 화면을 확인할 수 있다.


10. 설정과 설치가 끝나면 다시 재부팅이다.
11. 어쨋든 이놈은 부팅할때 아무것도 안뜨고 시커먼 화면이다. 역시 ALT+F2를 눌러서 무슨 일을 꾸미는지 확인하자. Virtual box에서 network설정을 어떻게 하는지 몰라서 host랑 똑같이 했더니 에러를 뱉는다. 그냥 무시했다. 나중에 고치지 뭐....
12. 재부팅 되고 나면 또 열심히 복사한다.
13. 설치 과정간의 광고를 보면 "Lotus가 짱입니다" 따위의 글을 볼 수 있다. 하긴 1994년에 나온 녀석이니........-_-;
14. 아무튼 설치 과정은 무지하게 길다.....
15. 다 끝나고 나면 이런 화면이 뜬다.


16. 윈도우 3.x 정도의 느낌........ 프리쉘은 없나 -_-;
Posted by soulreaver

MySQL 함수 2-1 날짜 및 시간 관련 칼럼 함수 첫번째

아노..함수 입니다..

갠적으로 싫어하는 게 함수라..

양은 양대로 많고..이해한다고 해도..

놓치지 쉬운 부분이라..

실제로 사용하는 거 몇가지 빼고는..

거의 모르고 살게 되지요..

예전 웹마스터 교육 받고..

그 당시 인기던..

pure lite dream 아이콘 방명록..

이름이 맞나 모르겠네..

그거 제작자한테 허락맞고 cgi 였던거를

php 로 컨버팅한적 있는데..

웹프로그래밍 만들때는 MySQL 보다 php 함수가 더 큰 영향을 받아서..

MySQL 기분 문법 select insert update delete 밖에 안 썻었지..

지금 보니..

최종 수정 시간 입력할때..

단지..MySQL 함수 하나로 끝날 수 있었네..

php 로 할때..

일일히 시간 구해서 업데이트 방식 썻었는데..

머 이로서..

짜기전에 함수 한번 정도 알아 두면 좋다 ㅇㅅㅇ

라는 거지요..

그럼 시작 하겠습니다.

----------------------------------------------------------------------------------------

- 날짜 및 시간 관련 함수

날짜 시간 관련 칼럼 타입

- 날짜 관련 타입은 문자 칼럼 타입 (char, varchar 등), 숫자 칼럼 타입 (int, smallint 등)

- 과 함깨 가장 많이 사용되는 칼럼이다.

- DATE

- 날짜 타입이다. '1000 - 01 - 01' 에서 '9999 - 12 - 31' 까지 나타낼 수 있다.

- 기본적으로 지원 하는 형태는 'YYYY - MM - DD' 이다.

- DATETIME

- 날짜와 시간이 합쳐진 형태이다. '1000 - 01 - 01 00:00:00' 에서 '9999 - 12 - 31 23:59:59'

- 까지 나타낼 수 있다.

- 기본적으로 지원하는 형태는 'YYYY - MM - DD HH:MM:SS' 이다.

- TIMESTAMP[(M)]

- 날짜 및 시간 타입이다. '1970 - 01 - 01 00:00:00' 에서 2037 년 어느 때까지 나타낸다.

- [(M)] 자리에는 출력될 길이를 나타내는 숫자를 쓸 수 있는데 14 나 12 나 8 혹은 6 을

- 쓸수 있다. 숫자를 쓰지 않으면 기본적으로 14 자리로 나타낸다.

- TIMESTAMP 의 특징은 자동 변경 칼럼 타입이라는 것이다. 이것은 INSERT 나 UPDATE

- 문을 사용할때 매우 유용하다.

mysql> desc customers;
+--------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+---------------+------+-----+---------+----------------+
| customers_id | int(5) | | PRI | NULL | auto_increment |
| gender | char(1) | | | | |
| name | varchar(32) | | | | |
| job | varchar(30) | | | | |
| email | varchar(35) | | | | |
| address | varchar(100) | | | | |
| telephone | varchar(14) | | | | |
| registdate | date | YES | | NULL | |
| modifydate | timestamp(14) | YES | | NULL | |
| password | varchar(12) | | | | |
+--------------+---------------+------+-----+---------+----------------+
10 rows in set (0.06 sec)

mysql>

- 현재 customers 테이블의 modifydate 칼럼은 TIMESTAMP(14) 로 정의되어 있다.

mysql> select name, telephone, modifydate from customers where name = '장영실';
+--------+--------------+----------------+
| name | telephone | modifydate |
+--------+--------------+----------------+
| 장영실 | 051-665-6789 | 20020903162508 |
+--------+--------------+----------------+
1 row in set (0.00 sec)

mysql>

- 장영실 이라는 고객에 대해 다음과 같은 데이터가 들어있다.

mysql> update customers set telephone = '051-665-1234' where name = '장영실';
Query OK, 1 row affected (0.45 sec)
일치하는 Rows : 1개 변경됨: 1개 경고: 0개

- 위와 같이 '장영실' 의 전화번호를 update 문으로 변경하여 보다.

mysql> select name, telephone, modifydate from customers where name = '장영실';
+--------+--------------+----------------+
| name | telephone | modifydate |
+--------+--------------+----------------+
| 장영실 | 051-665-1234 | 20061005162108 |
+--------+--------------+----------------+
1 row in set (0.00 sec)

mysql>

- 전화번호만 변경 했음에도 불구하고 자동으로 modifydate 칼럼의 값도 현재 날짜로

- 변경되는 것을 볼 수 있다. 즉 TIMESTAMP 칼럼은 데이터가 수정된 날짜를

- 자동으로 체크하는데 유용하게 사용할 수 있는 칼럼이다.

- TIME

- 시간 타입니다. '-838:59:59' 에서 '838:59:59' 까지 나타낼 수 있다.

- 기본적으로 지원하는 형태는 'HH:MM:SS' 이다.

- YEAR

- 연도를 나타내는 타입이다. 2자리 혹은 4자리로 나타낼 수 있으며 자리수를 정하지 않으면

- 기본적으로 4자리로 나타낸다. 4자리로 사용할 때는 1901에서 2155년까지 지원하며

- 2자리로 사용할 때는 1970에서 2069년짜지 지원한다. (70-69)

- 위의 것은 칼럼을 생성할 때 옵션 문으로 들어 간다.

날짜 및 시간 관련 함수

- MySQL 에서는 날짜 및 시간 관련하여 매우 다양하고 유용한 함수들을 지원한다.

mysql> select name, modifydate from customers;
+----------+----------------+
| name | modifydate |
+----------+----------------+
| 방정환 | 20020213150234 |
| 한용운 | 20011124201452 |
| 김정호 | 20011124230126 |
| 허난설헌 | 20000305092412 |
| 우장춘 | 20020312183521 |
| 장영실 | 20061005162108 |
| 안익태 | 20010824010354 |
| 신사임당 | 20020317224541 |
| 허준 | 20020816175657 |
| 황진이 | 20011230204703 |
+----------+----------------+
10 rows in set (0.00 sec)

mysql>

- 다음 예는 customers 테이블로부터 지난 30일간 변경된 적이 있는 로우룰 추출하는 예이다.

mysql> select name, modifydate from customers where to_days(now()) - to_days(modifydate) <= 30;
+--------+----------------+
| name | modifydate |
+--------+----------------+
| 장영실 | 20061005162108 |
+--------+----------------+
1 row in set (0.47 sec)

mysql>

- 지금부터 TO_DAYS(), NOW() 와 같은 날짜 및 시간 관련 함수들을 하나씩 알아 보겠다.

- to_days(now()) - to_days(modifydate) <= 30;

- 현재 날짜 시간 - 각 로우의 최종수정된 시간 의 값이 30 보다 적을 것 을 나타냄

- DAYOFWEEK(date)

- 지정한 날짜가 그 주의 몇번째 요일인가를 가져온다.(1=일요일, 2=월요일, ... 7=토요일)

- 2006년 10월 5일은 목요일이므로 5번째 요일이다.

mysql> select dayofweek('2006-10-5');
+------------------------+
| dayofweek('2006-10-5') |
+------------------------+
| 5 |
+------------------------+
1 row in set (0.00 sec)

mysql>

- WEEKDAY(date)

- DAYOFWEEK(date) 와 같으나 0은 월요일, 1은 화요일 ... 6은 일요일을 의미한다.

mysql> select weekday('2006-10-5');
+----------------------+
| weekday('2006-10-5') |
+----------------------+
| 3 |
+----------------------+
1 row in set (0.00 sec)

mysql>


- DAYOFMONTH(date)

- 1에서 31까지 중 해당 달의 몇번째 날인지를 가져온다.

mysql> select dayofmonth('2006-10-5');
+-------------------------+
| dayofmonth('2006-10-5') |
+-------------------------+
| 5 |
+-------------------------+
1 row in set (0.00 sec)

mysql>

- DAYOFYEAR(date)

- 1에서 366까지 중 해당 연도의 몇번째 날인지를 가져온다.

mysql> select dayofyear('2006-10-5');
+------------------------+
| dayofyear('2006-10-5') |
+------------------------+
| 278 |
+------------------------+
1 row in set (0.00 sec)

mysql>

- MONTH(date)

- 1에서 12까지 중 해당 연도의 몇번째 달인지를 가져온다.

mysql> select month('2006-10-5');
+--------------------+
| month('2006-10-5') |
+--------------------+
| 10 |
+--------------------+
1 row in set (0.00 sec)

mysql>


- DAYNAME(date)

- 해당 날짜의 요일명을 가져온다.

mysql> select dayname('2006-10-5');
+----------------------+
| dayname('2006-10-5') |
+----------------------+
| Thursday |
+----------------------+
1 row in set (0.00 sec)

mysql>


- MONTHNAME(date)

- 해당 달의 이름을 가져온다.

mysql> select monthname('2006-10-5');
+------------------------+
| monthname('2006-10-5') |
+------------------------+
| October |
+------------------------+
1 row in set (0.00 sec)

mysql>

- QUARTER(date)

- 분기를 계산할 때 유용한 함수이다.

- 1년을 4분기로 나누어 1에서 4까지 중 해당 분기를 가져온다.

mysql> select quarter('2006-10-5');
+----------------------+
| quarter('2006-10-5') |
+----------------------+
| 4 |
+----------------------+
1 row in set (0.00 sec)

mysql>

- WEEK(date)

- 해당연도의 몇번째 주인가를 가져온다.

mysql> select week('2006-10-5');
+-------------------+
| week('2006-10-5') |
+-------------------+
| 40 |
+-------------------+
1 row in set (0.00 sec)

mysql>

- WEEK(date,first)

- week(date) 와 동일하나 한 주의 기준을 정할 수 있다.

- first 인자에 0을 쓰면 일요일로 시작되는 주를 기준으로 순서를 가져오며

- 1을 쓰면 월요일로 시작되는 주를 기준으로 순서를 가져온다.

- MySQL 4.0 에서 first 에 해당하는 인자가 0 이고 해당 날짜가 전년도의 마지막 주에 해당

- 하면 0을 가져온다.

mysql> select week('2006-01-01'), week('2006-01-01',1), week('2006-01-01',0);
+--------------------+----------------------+----------------------+
| week('2006-01-01') | week('2006-01-01',1) | week('2006-01-01',0) |
+--------------------+----------------------+----------------------+
| 1 | 0 | 1 |
+--------------------+----------------------+----------------------+
1 row in set (0.00 sec)

mysql>

- YEAR(date)

- 1000 에서 9999 까지의 연도를 가져온다.

mysql> select year('2006-10-5');
+-------------------+
| year('2006-10-5') |
+-------------------+
| 2006 |
+-------------------+
1 row in set (0.00 sec)

mysql>

- YEARWEEK(date)

- 연도와 몇번째 주인가를 동시에 가져온다.

mysql> select yearweek('2006-10-5');
+-----------------------+
| yearweek('2006-10-5') |
+-----------------------+
| 200640 |
+-----------------------+
1 row in set (0.00 sec)

mysql>

- YEARWEEK(date,first)

- yearweek(date) 와 동일하며 week(date,first) 에서 처럼 first에 한 주의 기준을

- 정할 수 있다. first에 해당하는 인자가 0이고 해당 날짜가 전년도의 마지막 주에

- 해당하면 전년도와 전년도의 마지막 주에 해당하는 순서를 가져온다.

mysql> select yearweek('2006-10-5',0), yearweek('2006-10-5',1);
+-------------------------+-------------------------+
| yearweek('2006-10-5',0) | yearweek('2006-10-5',1) |
+-------------------------+-------------------------+
| 200640 | 200640 |
+-------------------------+-------------------------+
1 row in set (0.00 sec)

mysql>

- HOUR(time)

- 0 에서 23 까지 중 해당 시간을 가져온다.

mysql> select hour('17:09:40');
+------------------+
| hour('17:09:40') |
+------------------+
| 17 |
+------------------+
1 row in set (0.00 sec)

mysql>

- MINUTE(time)

- 0 에서 59 까지의 숫자 중 해당 분을 가져온다.

mysql> select minute('17:09:40');
+--------------------+
| minute('17:09:40') |
+--------------------+
| 9 |
+--------------------+
1 row in set (0.00 sec)

mysql>

- SECOND(time)

- 0 에서 59 까지의 숫자 중 해당 초를 가져온다.

mysql> select second('17:09:40');
+--------------------+
| second('17:09:40') |
+--------------------+
| 40 |
+--------------------+
1 row in set (0.00 sec)

mysql>


- PERIOD_ADD(P,N)

- P 기간에 N 달 만큼 더한 결과를 YYYYMM 형태로 가져온다.

- 이때 P 는 YYMM 혹은 YYYYMM 형태일 뿐 date 값은 아님에 유의하시 바란다.

mysql> select period_add(9810,2);
+--------------------+
| period_add(9810,2) |
+--------------------+
| 199812 |
+--------------------+
1 row in set (0.00 sec)

mysql>

- PERIOD_DIFF(P1,P2)

- YYMM 혹은 YYYYMM 형태의 P1 과 P2 가 몇달 사이인지 가져온다.

- 이때 P1, P2 는 date 값이 아니다.

mysql> select period_diff(9802,199703);
+--------------------------+
| period_diff(9802,199703) |
+--------------------------+
| 11 |
+--------------------------+
1 row in set (0.00 sec)

mysql>

- DATE_ADD(date,INTERVAL expr type) 혹은 ADDDATE(date,INTERVAL expr type)

- date 로부터 expr 만큼 type 단위로 더한 결과를 가져온다.

- DATE_SUB(date,INTERVAL expr type) 혹은 SUBDATE(data,INTERVAL expr type)

- date 로부터 expr 만큼 type 단위로 뺀 견롸를 가져온다.

- expr 과 type 이 어떻게 관계를 가지는지 나타낸 표는 다음과 같다.

+----------------------------------------+

| type 값 | expr 형태 |

+----------------------------------------+

| SECOND | 초 |

| MINUTE | 분 |

| HOUR | 시 |

| DAY | 날 |

| MONTH | 달 |

| YEAR | 년 |

| MINUTE_SECOND | 분:초 |

| HOUR_MINUTE | 시:분 |

| DAY_HOUR | 날 시 |

| YEAR_MONTH | 년-달 |

| HOUR_SECOND | 시:분:초 |

| DAY_MINUTE | 날 시:분 |

| DAY_SECOND | 날 시:분:초 |

+----------------------+-----------------+

mysql> select date_add("1997-12-31 23:59:59", interval 1 day);
+-------------------------------------------------+
| date_add("1997-12-31 23:59:59", interval 1 day) |
+-------------------------------------------------+
| 1998-01-01 23:59:59 |
+-------------------------------------------------+
1 row in set (0.00 sec)

mysql>

- 1997-12-31 23:59:59 에 하루를 더해서 1998-01-01 23:59:59 값이 리턴된다.

mysql> select date_add("1997-12-31 23:59:59", interval "1:1" minute_second);
+---------------------------------------------------------------+
| date_add("1997-12-31 23:59:59", interval "1:1" minute_second) |
+---------------------------------------------------------------+
| 1998-01-01 00:01:00 |
+---------------------------------------------------------------+
1 row in set (0.00 sec)

mysql>

- 1997-12-31 23:59:59 에 1분 1초가 더해져서 1998-01-01 00:01:00 값이 리턴된다.

mysql> select date_add("1998-01-01 00:00:00", interval "-1 10" day_hour);
+------------------------------------------------------------+
| date_add("1998-01-01 00:00:00", interval "-1 10" day_hour) |
+------------------------------------------------------------+
| 1997-12-30 14:00:00 |
+------------------------------------------------------------+
1 row in set (0.00 sec)

mysql>

- 1998-01-01 00:00:00 에서 하루 10시간 이전 즉 34시간 이전 의 값인

- 1997-12-30 14:00:00 이 리턴된다.

mysql> select date_sub("1998-01-02", interval 31 day);
+-----------------------------------------+
| date_sub("1998-01-02", interval 31 day) |
+-----------------------------------------+
| 1997-12-02 |
+-----------------------------------------+
1 row in set (0.00 sec)

mysql>

- 1998-01-02 에서 31일이 빼어진 1997-12-02 가 리턴된다.

mysql> select date_add("1998-01-30", interval 1 month);
+------------------------------------------+
| date_add("1998-01-30", interval 1 month) |
+------------------------------------------+
| 1998-02-28 |
+------------------------------------------+
1 row in set (0.00 sec)

mysql>

- 위와 같이 30 일 간격의 날짜가 2 달을 넘어가게 될 경우 MONTH 간격은 30일 간격이

- 아니라 다음 달의 마지막 날까지로 계산한다.

- EXTRACT(type FROM date)

- date 로부터 type 형태로 결과를 추출하여 가져온다.

- 이때 type 은 DATE_ADD(), DATE_SUB() 에서 사용하는 type 과 같은 형태이다.

mysql> select extract(day_hour from "1998-12-31 23:33:33");
+----------------------------------------------+
| extract(day_hour from "1998-12-31 23:33:33") |
+----------------------------------------------+
| 3123 |
+----------------------------------------------+
1 row in set (0.00 sec)

mysql>

- TO_DAYS(date)

- 0 년부터 계산한 날짜수를 가져온다.

- 참고로 TO_DAYS()는 그레고리력(1582)을 따르지 않는다.

mysql> select to_days('2006-10-05');
+-----------------------+
| to_days('2006-10-05') |
+-----------------------+
| 732954 |
+-----------------------+
1 row in set (0.00 sec)

mysql>

- FROM_DAYS(N)

- 0 년부터 계산된 날짜수로부터 날짜를 가져온다.

- 참고로 FROM_DAYS()는 그레고리력(1582)을 따르지 않는다.

mysql> select from_days('732954');
+---------------------+
| from_days('732954') |
+---------------------+
| 2006-10-05 |
+---------------------+
1 row in set (0.00 sec)

mysql>

- DATE_FORMAT(date,format)

- 날짜를 원하는 문자열 형태로 바꿀 때 매우 유용하게 사용되는 함수이다.

- format 에는 특정한 문자들을 열거하여 원하는 문자열을 만든다.

- format 에 열거할 수 있는 문자와 그 설명은 다음 표와 같다.

+------+-------------------------------------------------------------------------------+

|열거자| 설명 |

+------+-------------------------------------------------------------------------------+

| %M | 달 이름 (January, December) |

| %W | 요일 이름 (Sunday, Saturday) |

| %D | 1st, 2nd 와 같은 영문법에 의한 해당 달의 날짜 |

| %Y | YYYY 형태의 연도 |

| %y | YY 형태의 연도 |

| %X | 해당 날짜에 대해 일요일로 시작하는 주를 기준으로 YYYY 형태의 연도를 가져온다. |

| | %v 와 함깨 쓰인다. |

| %x | 해당 날짜에 대해 월요일로 시작되는 주를 기준으로 YYYY 형태의 연도를 가져온다. |

| | %v 와 함깨 쓰인다. |

| %a | Sun, Sat 와 같은 요일의 약어 |

| %d | 00 에서 31 까지의 해당 달의 날짜 |

| %e | 0 에서 31 까지의 해당 달의 날짜 |

| %m | 01 에서 12 까지의 해당 년의 달 |

| %c | 1 에서 12 까지의 해당 년의 달 |

| %b | Jan, Dec 와 같은 달의 약어 |

| %j | 001 에서 366 까지의 해당 년의 날짜 |

| %H | 00 에서 23 까지의 시 |

| %k | 0 에서 23 까지의 시 |

| %h | 01 에서 12 까지의 시 |

| %l | 01 에서 12 까지의 시 |

| %L | 1 에서 12 까지의 시 |

| %i | 00 에서 59 까지의 분 |

| %r | 12 시간 기준으로 시간을 hh:mm:ss [AP]M 형태로 가져온다. |

| %T | 24 시간 기준으로 시간을 hh:mm:ss 형태로 가져온다. |

| %S | 00 에서 59 까지의 초 |

| %s | 00 에서 59 까지의 초 ( %S 와 같다. ) |

| %p | AM 혹은 PM |

| %w | 0 에서 6 까지 요일을 가져온다. ( 0=Sunday..6=Saturday ) |

| %U | 00 에서 53 까지 해당 년의 몇번째 주인가를 가져온다. 일요일이 시작인 주를 기준 |

| %u | 00 에서 53 까지 해당 년의 몇번째 주인가를 가져온다. 월요일이 시작인 주를 기준 |

| %V | 01 에서 53 까지 해당 년의 몇번째 주인가를 가져온다. 일요일이 시작인 주를 기준 |

| | ' %X ' 와 함깨 쓰인다. |

| %v | 01 에서 53 까지 해당 년의 몇번째 주인가를 가져온다. 월요일이 시작인 주를 기준 |

| | ' %x ' 와 함깨 쓰인다. |

| %% | ' % ' 문자를 그대로 가져온다. |

+-----+--------------------------------------------------------------------------------+

mysql> select date_format('1997-10-04 22:23:00', '%W %M %Y');
+------------------------------------------------+
| date_format('1997-10-04 22:23:00', '%W %M %Y') |
+------------------------------------------------+
| Saturday October 1997 |
+------------------------------------------------+
1 row in set (0.09 sec)

mysql>

- %W 는 요일 이름을 리턴, %M 은 달 이름을 리턴, %Y는 YYYY 형태의 연도를 리턴

mysql> select date_format('1997-10-04 22:23:00', '%H:%i:%s');
+------------------------------------------------+
| date_format('1997-10-04 22:23:00', '%H:%i:%s') |
+------------------------------------------------+
| 22:23:00 |
+------------------------------------------------+
1 row in set (0.00 sec)

mysql>


- %H는 00에서 23까지의 시를 리턴,%i 는 00에서 59까지의 분을 리턴,%s 00에서 59까지의 초 리턴

mysql> select date_format('1999-01-01', '%X %V');
+------------------------------------+
| date_format('1999-01-01', '%X %V') |
+------------------------------------+
| 1998 52 |
+------------------------------------+
1 row in set (0.00 sec)

mysql>

- %X 는 해당 날짜에 대해 일요일로 시작하는 주를 기준으로 YYYY 형태의 연도를 리턴

- %V 는 01 에서 53 까지의 해당 년의 몇번째 주인가를 리턴. 일요일이 시작인 주를 기준

- TIME_FORMAT(time, format)

- DATE_FORMAT() 과 같은 방법으로 사용되는 함수이다. 단지 time 에 시간 값이 들어가고

- format 에는 DATE_FORMAT() 에서 사용하는 열거자 중 시간이나 분, 초에 해당하는

- 열거자만 들어가야 한다는 차이다 있다.

- CURDATE() 혹은 CURRENT_DATE()

- 함수가 문자열처럼 사용되느냐 숫자처럼 사용되느냐에 따라 현재 날짜를 'YYYY-HH-DD'

- 혹은 'YYYYMMDD' 형태로 가져온다.

mysql> select curdate(), curdate()+0, curdate()+1;
+------------+-------------+-------------+
| curdate() | curdate()+0 | curdate()+1 |
+------------+-------------+-------------+
| 2006-10-06 | 20061006 | 20061007 |
+------------+-------------+-------------+
1 row in set (0.00 sec)

mysql>

- CURTIME() 혹은 CURRENT_TIME

- 함수가 문자열처럼 사용되느냐 숫자처럼 사용되느냐에 따라 현재 시간은 'HH:MM:SS'

- 혹은 'HHMMSS' 형태로 가져온다.

mysql> select curtime(), curtime()+0, curtime()+1;
+-----------+-------------+-------------+
| curtime() | curtime()+0 | curtime()+1 |
+-----------+-------------+-------------+
| 01:08:59 | 10859 | 10860 |
+-----------+-------------+-------------+
1 row in set (0.00 sec)

mysql>

- UNIX_TIMESTAMP 혹은 UNIX_TIMESTAMP(date)

- 인자가 없는 경우에는 현재의 Unix 시간 ('1970-01-01 00:00:00' 을 기준으로 초단위) 을

- 가져온다. date 인자가 있는 경우에는 date 의 Unix 시간 ('1970-01-01 00:00:00' 을

- 기준으로 초단위) 을 가져온다.

mysql> select unix_timestamp(), unix_timestamp('2002-05-04 22:23:00');
+------------------+---------------------------------------+
| unix_timestamp() | unix_timestamp('2002-05-04 22:23:00') |
+------------------+---------------------------------------+
| 1160066173 | 1020518580 |
+------------------+---------------------------------------+
1 row in set (0.00 sec)

mysql>

- FROM_UNIXTIME(unix_timestamp)

- 문자열처럼 사용되느냐 숫자처럼 사용되느냐에 따라 유닉스 시간을 'YYYY-MM-DD HH:MM:SS'

- 혹은 'YYYYMMDDHHMMSS' 형태로 가져온다.

- FROM_UNIXTIME(unix_timestamp,format)

- 유닉스 시간을 원하는 형태로 변환하여 가져온다. format 에 들어갈 열거자들은

- DATE_FORMAT() 함수에서 사용하는 열거자들을 사용할 수 있다.

mysql> select from_unixtime(unix_timestamp(), '%Y %D %M %h:%i:%s %x');
+-----------------------------------------------------------+
| from_unixtime(unix_timestamp(), '%Y %D %M %h:%i:%s %x') |
+-----------------------------------------------------------+
| 2006 6th October 01:40:17 2006 |
+-----------------------------------------------------------+
1 row in set (0.38 sec)

mysql>


- SEC_TO_TIME(seconds)

- 초를 시,분,초 형태로 변환한다. 함수가 문자열처럼 사용되느냐 숫자처럼 사용되느냐에 따라

- 'HH:MM:SS' 혹은 'HHMMSS' 형태로 가져온다.

mysql> select sec_to_time(2378);
+-------------------+
| sec_to_time(2378) |
+-------------------+
| 00:39:38 |
+-------------------+
1 row in set (0.36 sec)

mysql>

- TIME_TO_SEC(time)

- time 을 초로 변환한다.

mysql> select time_to_sec('22:23:00');
+-------------------------+
| time_to_sec('22:23:00') |
+-------------------------+
| 80580 |
+-------------------------+
1 row in set (0.00 sec)

mysql>


무선 USB: 초광대역(UWB) 기술을 사용한 다양한 접근 방식의 이해


개요
USB(Universal Serial Bus)는 현재 시장에서 가장 널리 사용되는 연결 기술 중 하나다. 초광대역(UWB)은 고속 데이터 전송 기술로, 실내 무선 환경에서 유선과 같은 수준의 성능을 제공한다. UWB 기술은 컴퓨터 주변기기, 캠코더, 휴대폰 및 기타 여러 가지 전자 제품을 연결하느라 복잡하게 얽힌 USB 케이블을 대체할 잠재력을 갖추고 있다. 이 문서에서는 무선 USB를 실현하기 위한 세 가지 접근방식을 검토한다.



무선 USB를 위한 접근방식
현재 가장 유용하고 널리 사용되는 연결 방법 중 하나는 USB(Universal Serial Bus)다. 시중의 카메라, 캠코더, 녹음기, 컴퓨터, 스캐너, 프린터, 휴대폰, 전화기, 키보드, 디스플레이를 비롯한 많은 전자 제품에서 USB 연결 기술이 사용된다. 실제로 컴퓨터에 있는 USB 포트의 수보다 더 많은 기기를 연결할 수 있게 해주는 허브라는 장치가 개발될 만큼 USB 지원 연결 방식은 광범위하게 사용되고 있다. 시장 조사 회사인 인스태트(In-Stat)에 따르면 현재 시장에서의 USB 지원 장치 규모는 7억 개 정도이며, 그 규모가 2009년까지 약 21억 개로 증가할 전망이다. 이러한 모든 기기를 연결하면 연결선도 당연히 많아질 것이다. 각각의 연결선은 소비자에게는 제약을 의미한다. 한 가지 예로, 기기 간에는 물리적 연결이 서로 일치해야 하는데, 현재 시중에는 물리적으로 다양한 유형의 USB 커넥터가 존재하고 있다. 가정과 직장에서 USB 장치의 사용이 더 증가하고 있기 때문에 기기를 더 적절히 배치하고 복잡함과 혼란을 덜기 위해서는 배선을 줄이는 것이 바람직하다. 연결선을 줄이는 한 가지 명확한 해결책은 무선 연결을 사용하는 것이다. 여러 시장의 다양한 업체로부터 많은 기기가 출시되고 있으며, USB 케이블을 제거하기 위한 다양한 해결책도 제시되고 있다.


USB 신호를 무선으로 전송하는 기본적인 세 가지 방법은 다음과 같다.
1. USB 직렬 데이터 스트림을 초광대역(UWB) 무선 신호로 직접 변환한다. 기기는 USB가 케이블을 통해 전송되는지, 무선 신호를 통해 전송되는지 알지 못한다.
2. USB 커넥터만 사용하되, 데이터를 다시 패키징하여 커넥터에서 다른 신호(일반적으로 TCP/IP)로 전송하도록 한다.
3. USB를 전자 시스템 내부적으로 재정의하고, 소프트웨어의 애플리케이션 계층에서만 호환성을 유지한다. 이 방법은 최소한의 하위 호환성을 유지하는 솔루션이다.


현재 시장에는 무선 USB를 위한 여러 가지 솔루션이 있으며, 2006년부터 새로운 솔루션들이 등장할 전망이다.


무선(Cable-Free)™ USB
프리스케일 반도체의 솔루션인 무선(Cable-Free)™ USB는 이미 사용되고 있는 광범위한 USB 장치를 이용해 진정한 USB 무선 연결을 구현한다. 이 솔루션은 DS-UWB(Direct Sequence Ultra-Wideband)라는 무선 기술을 사용하여 USB 신호를 전송한다. 이 무선 기술은 기본적으로 매우 낮은 전송 전력에서 상당히 높은 데이터 전송률을 제공하며, 아울러 시스템 소비전력이 극히 낮다는 장점을 갖고 있다. 프리스케일의 경험에서 도출된 최선의 소비전력은 시스템 소비전력 1 밀리와트 당 1 메가비트의 전송률이다.프리스케일은 한 걸음 더 나아가 "진정한 USB" 인터페이스를 고성능 DS-UWB 무선 시스템에 구현시켰다. USB 2.0 인터페이스는 다음과 같은 여러 가지 장점을 제공한다.


• 프리스케일 DS-UWB 지원 시스템은 USB 2.0 표준을 기반으로 하기 때문에 소프트웨어 업데이트 없이 USB 지원 시스템에 연결할 수 있다.
• 프리스케일 DS-UWB 지원 시스템은 미니 카드 규격에 배치할 수 있으며, 호스트 시스템을 수정하지 않고 직접 USB 연결을 사용할 수 있다.
이러한 접근방식은 현재 시중에 나와 있는 7억 개 이상의 USB 장치를 현재 상태 그대로, 즉 변경하지 않고 연결할 수 있도록 해주며 USB 무선 운영을 구현하기 위한 솔루션을 제공한다.


현재 프리스케일은 상용 UWB 솔루션을 공급 중이며 고객들은 USB 솔루션 설계에 이를 사용하고 있다(보다 자세한 정보는 www.freescale.com/uwb 사이트에서 확인할 수 있다).


허브 설계
두 번째 방법은 허브 및 이 허브와 짝을 이루는 호스트 동글을 통해 UWB 신호를 다시 패키징하는 것이다. 호스트 측(일반적으로 PC)에 동글이 연결되고 PC에 소프트웨어가 설치된다. 이렇게 하면 PC는 소프트웨어 수준에서 USB 통신을 가로챈 다음 이를 다시 구성하여 TCP/IP 신호 형태로 USB 커넥터를 거쳐 공중으로 전송할 수 있다. 이 신호는 다시 허브에 수신되는데, 이것이 실질적으로 TCP/IP-USB 컨버터 박스다. 이 방법은 기존 USB 커넥터를 사용하므로 일부 하위 호환성을 제공하지만 드라이버 업데이트가 필요하며 TCP/IP-USB 변환 계층을 추가해야 한다. TCP/IP 솔루션의 단점은 등시성 통신을 사용할 수 없다는 점인데, 이는 캠코더, 텔레비전, 스테레오 시스템 또는 디지털 카메라와 같은 시청각 시스템을 위한 원활한 성능을 구현하려면 시스템 엔지니어가 상당한 통신 성능 여유를 구축해야 하며, 이로써 추가적인 비용이 발생하게 됨을 의미한다.


프리스케일은 이 솔루션 유형을 시험하였다. 프리스케일은 기존 USB 커넥터의 사용이 시장 관점에서는 매우 유익한 일이지만, 셀 수 없이 많은 기기 유형과 수백 만의 코드 라인을 고려할 때 결국 최적의 시장 솔루션은 드라이버 수준에서 많은 소프트웨어 변경을 요구해서는 안 된다고 판단하였다. 이 솔루션의 또 다른 중요한 제약 사항은 TCP/IP가 전송되기 때문에 이 유형의 시스템은 다른 인증된 무선 USB 시스템과의 상호운용에 있어서 중간적인 솔루션밖에 될 수 없다는 점이다. 이 기능을 내장하거나 동글로 구현하는 제품은 새로운 무선 USB 표준과 호환되지 않기 때문에 소비자 입장에서는 또 하나의 "새로운 변종" USB가 생기는 것일 뿐이다.


USB를 무선으로 재정의
세 번째 솔루션은 USB를 무선으로 재정의하고 기기 간의 로컬 무선 연결을 허용하는 것을 목표로 두는 것이다. 본질적으로, 원래의 USB에서 그대로 유지되는 것은 애플리케이션 계층에서 소프트웨어 드라이버 계층으로의 소프트웨어 연결 뿐이며, 하드웨어, 드라이버, 시스템 아키텍처 등 다른 모든 부분은 변경된다. 어떤 면에서 보면 이와 같은 시스템 유형은 이름만 USB라고 할 수 있다.
이 솔루션은 MB-OFDM(멀티 밴드 직교 주파수 분할 다중화)이라고 하며, UWB와는 다른 접근방식이다. 이 접근방식의 장점은 시스템을 이 사양에 맞춰 설계하고 솔루션에 최적화할 수 있다는 것이다. 이러한 장점은 거의 모든 신규 시스템 설계에 적용된다. 그러면 수익을 내기 위해 시스템 공급업체가 해야 할 것은 무엇인가? 대부분의 공급업체는 미래에 맞춰 솔루션을 설계하고 판매되기를 기다리기보다는 유선 USB 솔루션과 무선 USB 솔루션을 모두 갖추고, 시스템에서 이 두 가지를 모두 적절히 다룰 수 있는 타당한 수준의 복잡성과 솔루션을 확보해야 한다.


예를 들어, PC에 미니 카드를 구현하려면 유선 USB 신호가 반드시 있어야 하는데, 이는 이 사양이 USB와 PCI Express 상호 연결 기술을 모두 사용할 수 있게 해주기 때문이다. 따라서 대부분의 시스템은 어떤 경우에도 USB 버스를 없애지 않을 것이다. 사실 USB 커넥터는 PC 시스템에서 제거할 수 있겠지만 USB 버스가 그대로 유지되면 소프트웨어 개발자는 여전히 새로운 소프트웨어를 작성해야 한다.


즉, 이러한 무선 USB 구현 방법은 대체 방식이 아니라 항상 시스템에 추가하는 방식이다. 마지막으로 소프트웨어 문제의 중요성을 강조하자면, 캠코더나 전화기의 시스템 등의 대부분의 임베디드 시스템이 Microsoft?? Windows?? 외의 운영 체제를 구동한다는 사실을 알아야 한다. 이는 USB를 위해 소프트웨어 드라이버를 재정의한다는 것은 수없이 많은 임베디드 소프트웨어 시스템에 영향을 미치게 됨을 의미한다.



ClickOnce, 사용자 정의 필수 구성 요소 포함하기

배경

ClickOnce는 닷넷 프레임웤 2.0에서 추가된 새로운 어플리케이션 배포방법입니다. 닷넷 프레임워크 1.X 버전에서 지원했던 NTD(No touch deployment)나 Windows Installer 방식에 더해(2.0에서도 여전히 지원합니다.) 두 배포방식의 장점을 취합하고 단점을 보완하여 소개한 것이 ClickOnce입니다. 하지만 여전히 NTD와 Windows Installer가 필요한 이유는 ClickOnce가 다른 두 개의 배포방식을 대체하는데 목적이 있는 것이 아니기 때문입니다. NTD는 Sandbox 내에서 배포되고 수행되는 어플리케이션을 위한 것이기 때문에(Smart Client Application) 강력한 보안을 지원하는 대신 많은 제약이 뒤따릅니다. 예를 들면 부트스트래퍼 미지원, GAC에 공용어셈블리 설치 불가, 설치위치 임의 변경 등이 되겠네요. 또한 NTD의 고질적인, 달리 말해 많은 개발자가 고민하는 문제가 보안설정변경(CAS – Code Access Security)이겠죠. CAS 변경을 위해 별도의 ActiveX에 의존하게 됩니다. 또한 클라이언트 머신에 닷넷 프레임워크가 설치되어 있어야 하는데 NTD는 부트스트래퍼를 지원하지 않으므로 닷넷 프레임워크를 설치과정에 포함시킬 수가 없죠. 반면에 Windows Installer는 굉장히 강력하죠. 자동업데이트 및 온라인실행 지원이 되지 않는 것을 제외하곤 상상가능한 모든 설치작업이 가능합니다. 그렇다면 ClickOnce는? 앞에서 거론한 NTD의 대표적인 단점인 부트스래퍼와 CAS 변경 자동화 기능을 지원하며 Windows Installer의 단점인 자동업데이트와 온라인/오프라인 실행 및 설치를 지원합니다. 물론 단점도 있죠. 이 문서에서 다루려는 내용도 ClickOnce의 단점 중에 하나를 보완하기 위한 방법을 알리기 위한 것입니다. 단점이라기 보다는 까다로운 점이라고 하는 게 정확할 듯 합니다. 구현 가능한 것이니까요.

주제

이 문서의 주제는 ClickOnce에 사용자 정의 필수 구성요소 추가하는 방법에 대한 것입니다.

사전지식

ClickOnce는 별도의 설정을 하지 않는 이상 어플리케이션이 참조하는 공용어셈블리에 대해 설치작업을 지원하지 않습니다. 즉, A라는 어플리케이션이 Ref.A라는 공용어셈블리(여기에서 언급하는 공용어셈블리는 써드파티 제품을 의미합니다. 즉, 로컬어셈블리로 변환 불가한 어셈블리입니다. – 추1 : 개발자가 생성한 공용어셈블리 배포에 대해서는 테스트가 필요합니다.)를 참조하는 프로젝트를 게시할 경우 Ref.A는 기본설정에 배포될 응용프로그램 파일리스트에서 제외됩니다. 임의로 포함되도록 변경하더라도 설치과정에서 오류가 발생합니다. (대표적인 것으로 Microsoft.mshtml.dll, Infragistics Winform controls이 있습니다.) 그렇다면 ClickOnce로는 방법이 없는걸까요? 그렇지는 않습니다. 게시하려는 프로젝트의 속성 창을 열고 게시 속성 탭을 열면 필수구성요소를 변경하는 버튼이 있습니다. 이 버튼을 클릭하면 다음과 같은 다이얼로그가 출력됩니다. (항목은 개발자 머신마다 다를 수 있습니다.)

사용자 삽입 이미지

설치할 필수 구성 요소 리스트를 살펴보면 .NET Framework, MDAC, Crystal Reports 등이 있음을 알 수 있습니다. 이 필수 구성 요소들은 ClickOnce 게시한 제품이 설치되기 전에 먼저 설치되어야 하는 요소들입니다. 대표적인 요소가 .NET Framework 2.0 이겠죠.

필수 구성 요소를 추가하는 방법은 다음과 같습니다.

  1. 프로젝트 속성 창에서 게시 속성을 엽니다.
  2. <설치 모드 및 설정> 그룹에서 <필수 구성 요소> 버튼을 클릭합니다.
  3. 필수 구성 요소 다이얼로그에서 <필수 구성 요소를 설치하기 위한 설치 프로그램 만들기> 체크박스를 체크합니다.
  4. <설치할 필수 구성 요소 선택> 리스트에서 필요한 구성 요소를 선택합니다.
  5. <필수 구성 요소의 설치 위치를 지정하십시오.> 라디오 버튼 그룹에서 다운로드 위치를 선택합니다.
  6. <확인> 버튼을 클릭하여 작업을 완료합니다.
  7. 게시합니다.

위의 작업을 수행하여 게시할 경우 필수 구성 요소들이 제품이 설치되기 전에 먼저 설치됩니다. 간단하죠. 하지만 문제가 있습니다. 위 절차 중 4. <설치할 필수 구성 요소 선택> 리스트에 없는 요소를 추가하려면 어떻게 해야 할까요? 위에서 언급했던 Microsoft.mshtml.dll 이나 써드파티 컴포넌트의 경우 말이죠. 이런 경우를 위해 MS에서는 사용자 정의 필수 구성 요소 선택하는 방법에 대해 친절히 설명하고 있습니다. 관련 MSDN URL은 참고문헌을 참고하세요.


하지만 위 문서에서 결정적으로 빠진 내용이 있습니다. 위 문서 중반 부분에 이런 내용이 있습니다.

새 구성 요소 패키지를 만들려면 다음을 제공해야 합니다.

  • EXE 또는 MSI 파일 형식의 재배포 가능 구성 요소
  • 패키지의 모든 언어 중립 메타데이터가 포함된 제품 매니페스트(product.xml). 이 매니페스트에는 모든 지역화된 재배포 가능 구성 요소 버전에 공통되는 메타데이터가 포함되어 있습니다.
  • 언어별 메타데이터가 포함된 패키지 매니페스트(package.xml). 이 매니페스트에는 일반적으로 지역화된 오류 메시지가 포함되어 있습니다. 구성 요소에는 지역화된 버전의 해당 구성 요소별로 최소한 하나의 패키지 매니페스트가 있어야 합니다.

첫 번째 항목은 Windows Installer로 설치파일을 만드는 것을 말합니다. Windows Installer로 설치파일을 만드는 방법은 문서 마지막 참고문헌을 참고하세요.
문제는 두 번째와 세 번째 항목입니다. 제품 매니페스트와 패키지 매니페스트 파일을 구성하는 것인데 구성하는 방법이 쉽지 않습니다. 자세한 내용은 제품 및 패키지 스키마 참조 문서에 있지만 살펴보면 이해하기가 만만치 않습니다. 무엇보다 우리가 원하는 것은 PackageFile이 msi(Windows Installer)일 경우 <InstallChecks> 작성하는 방법과 <Commands> 작성하는 방법인데 말이죠, 이와 관련해서는 예문이 없습니다. 그래서 이것 저것 장님 문고리 잡듯이 MSDN에 있는 샘플 매니페스트를 수정해가면서 게시하는데 3~4분 걸리는 프로젝트를 테스트해 보았습니다. 근 15여 차례 정도? 계속 Install error가 생겨서 install log를 살펴보니 product.xml(제품 매니페스트)의 <InstallChecks>와 <Commands>의 <InstallConditions>에서 오류가 생기더군요. 도저히 이런 방법으론 안되겠다 싶어 제품 및 패키지 스키마 참조를 다시 읽어 보았습니다. 살펴보니 <InstallChecks> 하위 요소에 <MsiProductCheck>란 놈이 있더군요. MsiProductCheck를 사용해서 구글에서 검색해 결국 답을 얻었습니다. MS 포럼에 질문이 올라와 있더군요. 링크는 다음과 같습니다. http://forums.microsoft.com/msdn/showpost.aspx?postid=5396&siteid=1 위 링크에서 얻은 내용을 토대로 MSI 설치 파일(Windows Installer 설치 파일)을 사용한 사용자 정의 필수 구성 요소를 구성하는 방법을 정리해 보았습니다.

솔루션

ClickOnce에 MSI 설치 파일 필수 구성 요소 추가하는 절차는 다음과 같습니다.
편의상 msi 파일은 미리 만들어져 있고 파일명은 PreInstall.msi로 합니다.

  1. \Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bootstrapper\Package 폴더로 이동합니다.
  2. 폴더를 만듭니다. (예: PreInstall)
  3. 새로 만든 폴더에 PreInstall.msi 파일을 복사합니다.
  4. product.xml 파일을 새로 만듭니다. (표-1)의 코드를 복사해 넣습니다.
  5. (표-1)의 코드에서 PreInstall 문자열을 적절하게 변경하시면 되고, 중요한 부분은 <InstallChecks><MsiProductCheck>의 Product 속성입니다. 이 곳에는 Visual Studio 2005의 설치 프로젝트 속성에 있는 ProdcutCode 값을 복사해 넣으면 됩니다. 중괄호({…})를 포함한 값입니다.
  6. PreInstall 폴더에 Ko폴더를 새로 만듭니다. 새로 만든 Ko폴더로 이동합니다.
  7. eula.txt 파일을 만들고 소프트웨어 사용권 내용을 입력합니다.(아무런 내용이든 상관없습니다.)
  8. package.xml 파일을 만들고 (표-2)의 내용을 복사해 넣습니다.
  9. PreInstall 문자열을 적절하게 변경시키면 됩니다.

(표-1)

<?xml version="1.0" encoding="utf-8" ?>

<Product

xmlns="http://schemas.microsoft.com/developer/2004/01/bootstrapper"

ProductCode="PreInstall">

<!-- Defines list of files to be copied on build -->

<PackageFiles>

<PackageFile Name="PreInstall.msi"/>

</PackageFiles>

<RelatedProducts>

<DependsOnProduct Code="Microsoft.Net.Framework.2.0" />

</RelatedProducts>

<InstallChecks>

<MsiProductCheck

Property="PreInstall"

Product="{XXXX…}"

/>

</InstallChecks>

<Commands Reboot="Defer">

<Command PackageFile="PreInstall.msi" Arguments="/quiet /qn" EstimatedInstallSeconds="10" EstimatedInstalledBytes="1000000">

<InstallConditions>

<BypassIf Property="PreInstall" Compare="ValueEqualTo" Value="5"/>

<!-- Block install if there is no .NET Framework -->

</InstallConditions>

<ExitCodes>

<ExitCode Value="0" Result="Success"/>

<DefaultExitCode Result="Fail" FormatMessageFromSystem="true" String="GeneralFailure"/>

</ExitCodes>

</Command>

</Commands>

</Product>


(표-2)

<?xml version="1.0" encoding="utf-8" ?>

<Package

xmlns="http://schemas.microsoft.com/developer/2004/01/bootstrapper"

Name="DisplayName"

Culture="Culture"

LicenseAgreement="eula.txt">

<PackageFiles>

<PackageFile Name="eula.txt"/>

</PackageFiles>

<!-- Defines a localizable string table for error messages-->

<Strings>

<String Name="DisplayName">PreInstall</String>

<String Name="Culture">ko</String>

<String Name="GeneralFailure">

A failure occurred attempting to install PreInstall

</String>

</Strings>

</Package>



위의 절차는 내가 만든 MSI 설치파일을 ClickOnce 게시 프로젝트에 추가하는 최소한의 절차입니다. 좀 더 자세한 내용은 MSDN을 참조하십시오.


참고문헌

1. MSDN : ClickOnce와 Windows Installer 비교
2. MSDN : 사용자 지정 필수 구성 요소 추가
3. MSDN : 제품 및 패키지 스키마 참조
4. MSDN : Use the Visual Studio 2005 Bootstrapper to Kick-Start Your Installation
5. MS Forums : MsiProductCheck element

Fusion 7 : Loading Assemblies Dynamically


오늘은 동적으로 어셈블리를 로드하는 기법에 대해 알아본다.Assembly.LoadFrom(), Assembly.Load()와 같은 Reflection 메서드를 사용하는데, 이게 내부적으로 동작하는게 만만치 않게 복잡한 편이다. 현업에선 주로 LoadFrom()/Load()를 사용해 어셈블리를 동적으로 로딩한 후, 이를 활성화 하여 사용하는데, 이렇게 하는 이유는, 어플리케이션의 재컴파일없이 구성정보 등를 통해 어플리케이션의 기능을 확장(메뉴를 추가하거나 화면을 추가함)할 수 있고, 또 NTD(No-Touch Deployment)가 가능하다는 점이라 생각한다.
일반적인, 정적으로 참조가 걸린 라이브러리 형태에서, 후에 새로운 라이브러리를 생성해 기존 어플리케이션에 기능 또는 화면으로 추가하고 싶다면, 기존 어플리케이션을 개발환경에서 열고, 새 라이브러리에 대한 참조를 추가 한후 다시 컴파일 해서 다시 클라이언트에 ClickOnce 등을 통해 배포해야 한다.
그러나 동적으로 어셈블리를 로딩하는 구조라면? 구성(파일 또는 DB)을 통해 로딩할 기능(화면)을 정의해 두고, 이를 어플리케이션 시작 시에 읽어들여, 필요한 기능(화면) 에 대한 라이브러리 어셈블리를 공용의 저장소로 부터 동적으로 로딩해 사용한다면, 기존 어플리케이션을 재 컴파일할 필요가 없고, 배포할 필요도 없어진다.
그러나 당연히 이 아키텍쳐도 단점이 있으며 모든 환경에 적합한 절대 "참"의 구조라곤 할 수 없다..(아래에 조금 설명됨)

즉 자주 수정될 수 있는 어플리케이션 모듈은 서버 상의 공용 저장소에 올려두고 클라이언트 어플리케이션은 이를 LoadFrom/Load 해서 동적으로 가져다 쓰게 되면, 모듈이 수정되더라도 클라이언트 측에 새로 배포할 부담이 (거의) 없다. 그러나 클라이언트에 내려와 있는 모듈이 아니므로 이 모듈에 대한 초기 로딩 시에 동적 로딩으로 인해 발생하는 지연은 감수해야만 한다. 한 번 로딩 후에는 클라이언트 (Internet Explorer) 캐시 상의 모듈이 사용되므로 초기 지연이 다시 발생하진 않지만, 캐시 상의 모듈이 언제 다시 refresh 될지는 ..... 예측 가능한 부분, 신뢰할 수 있는 부분이 아니다. 한가지 확실한 건, 모듈이 변경되지 않는 이상, 최소한 프로그램 실행 동안에는 캐시에 존재하는 모듈을 사용한다. 종료 후 다시 시작하면?.. 사용될 수도, 그렇지 않을 수도...-_-! .. 오늘 쫌 길다..맘 단디 무라...

* 용어 : 라이브러리 - Library 형태의 닷넷 어셈블리, 즉 *.dll

라이브러리가 정적으로 연결되었다 하더라도(static linked) 지연된 로딩이 발생하게 된다. 이는 라이브러리의 풀 네임으로 참조하는 어셈블리라 하더라도, 라이브러리는 처음 사용되기 직전에 로딩된다는 의미이다.
라이브러리가 strong name을 가지고 있지 않다면, 이는 어플리케이션 폴더나 그 하위의 폴더에 존재할 수 있다. 만일 strong name을 가지고 있다면, 어플리케이션 폴더, 로컬 디스크 상의 어떤 위치, GAC 또는 다른 머신 상에 위치할 수 있다.

.NET Framework은 코드 상에서 어셈블리를 로드할 수 있는 방법을 제공한다. 하지만 이는 컴파일 타임에 해당 라이브러리의 메타데이터를 알 수 없으므로 자주 사용되어 지는 방법은 아닐 것이다. 이 경우 타입과 멤버의 메터데이터 정보가 요청하는 어셈블리내에 저장되지 않는게 되는 것이다.그리고 new 키워드를 사용해 객체를 생성할 수도 없다. 또는 타입의 멤버를 직접 호출할 수도 없다. 대신, 객체를 생성하기 위해 Framework의 활성화(Activation) 클래스를 사용해야 하며, 객체의 멤버에 접근하기 위해 Reflection을 사용해야 한다. 이는 Visual Basic의 "late 바인딩"과 같은 개념이면서 동일한 문제를 내포하고 있다.
Late 바인딩은 타입 검사가 런타임 시에 코드의 사용자, 즉 고객에 의해 수행된다. 가급적 late binding은 지양해야 할 방법이라 할 수 있다.
라이브러리는 어플리케이션 도메인 상에 로드되는데 라이브러리 로드를 위한 메커니즘은 존재하지만 언로드를 위한 메커니즘은 존재하지 않는다. 하지만 라이브러리들이 적재되어 있는 전체 어플리케이션 도메인을 언로드 하는 메커니즘은 존재한다. 라이브러리를 동적으로 로드할 때 발생할 수 있는 몇가지 흥미로운 이슈들이 존재한다.


■ Reflection을 이용해서 어셈블리 로드하고, 메서드 호출하기

아래의 strong name을 가지고 버전이 1.0.0.0인 lib.cs를 사용한다.
만일 프로세스 어셈블리(lib을 참조하고 있는 어셈블리) 코드가 아래와 같다면,


using System;
using System.Reflection;
class App
{
static void Main()
{
try
{
object obj = FromAssembly("lib");
InvokeObject(obj);
}
catch(Exception e1)
{
Console.WriteLine(e1.Message);
}
}
static object FromAssembly(string shortName)
{
Console.WriteLine("Use Assembly.Load");
AssemblyName name = new AssemblyName();
name.Name = shortName;
Assembly a = Assembly.Load(name);
Type type = a.GetType("LibraryCode");
ConstructorInfo ctor = type.GetConstructor(Type.EmptyTypes);
return ctor.Invoke(null);
}
static void InvokeObject(object obj)
{
Type type = obj.GetType();
MethodInfo mi = type.GetMethod("GetVersion");
string version = (string)mi.Invoke(obj, null);
Console.WriteLine(version);
}
}

위 코드에서 FromAssembly 메서드는 객체를 로드하고, InvokeOjbect 메서드는 객체의 GetVersion 메서드를 호출한다.
이 코드 내에서 FromAssembly는 어셈블리 명을 생성한 후 Assembly.Load() 를 이용해서 어셈블리를 로드한다.
컴파일 하고 실행; 라이브러리가 로컬 폴더에서 로드되는 것을 볼 수 있다. Private 어셈블리를 위해 Fusion은 오직 Short Name만을 확인한다.

라이브러리의 public key 토큰을 추출한다.

C:\TestFolder>sn -T lib.dll
Microsoft (R) .NET Framework Strong Name Utility Version 1.1.4322.573
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Public key token is 3bf941bb1f722efe


이제 라이브러리를 GAC에 추가하고 로컬 폴더에 있는 라이브러리 어셈블리(lib.dll)를 제거한다. 그리고 실행한다.
FileNotFoundException이 발생한다. 이유는 Assembly.Load()을 이용해서 GAC으로부터 어셈블리를 로드할 때, Fusion은 어셈블리의 Full Name을 요구하기 때문이다.

그러므로 코드를 변경하여 Full Name을 명시하도록 한다.

static object FromAssembly(string shortName)
{
Console.WriteLine("Use Assembly.Load");
AssemblyName name = new AssemblyName();
name.Name = shortName;
name.Version = new Version("1.0.0.0");
name.CultureInfo = new CultureInfo("");
byte[] pkt
= new byte[] {0x3b,0xf9,0x41,0xbb,0x1f,0x72,0x2e,0xfe};
name.SetPublicKeyToken(pkt);
Assembly a = Assembly.Load(name);
Type type = a.GetType("LibraryCode");
ConstructorInfo ctor = type.GetConstructor(Type.EmptyTypes);
return ctor.Invoke(null);
}


배열 pkt는 public key 토큰의 바이트 배열이다. 이 경우 어셈블리가 culture 정보를 가지지 않는다. 하지만 neutral culture로 간주되어야 하므로 CultureInfo 객체가 공백문자열로서 초기화 되어 할당되어 진다.(CultureInfo 를 사용하기 위해서 System.Globalization 네임스페이스를 사용해야 함)
다시 컴파일 하고 실행 이제 GAC으로 부터 라이브러리가 로드되는 것을 알 수 있다.

Assembly.Load()에는 스트링을 인자로 받는 overload된 메서드를 제공한다.

어셈블리의 short name을 제공하여 호출하는 경우, 라이브러리가 private assembly 인 경우에만 로드가 된다. Short name을 제공했지만 private assembly가 아닌 경우 로드는 실패하게 된다.

□ 호출 : Assembly a = Assembly.Load("lib");

lib.dll : 어플리케이션 폴더에 존재하고 GAC에도 등록
결과 : GAC으로 부터 로드됨

l ib.dll:GAC에만 존재
결과 : 예외 발생 : Could not load file or assembly "lib" or one of its dependencies.

어셈블리에 대한 full name을 알고 있다면 아래 처럼 full name을 인자로 전달함으로써 GAC에서 로드할 수 있다.

호출 : Assembly a = Assembly.Load("lib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3bf941bb1f722efe");

lib.dll : 어플리케이션 폴더에 존재하고 GAC에도 등록
결과 : GAC으로 부터 로드됨

l ib.dll:GAC에만 존재
결과 : GAC으로 부터 로드됨

큰 그림 보려면 클릭하세요!(돋보기도 같이 활용)

결론:

Assembly.Load를 호출 시,

short name 을 사용해서 호출할 경우 반드시 어플리케이션 폴더에 참조하는 라이브러리 어셈블리가 존재해야 함.
이때 동일한 short name의 어셈블리가 GAC에도 등록되어 있다면, GAC에서 로드함. (그러나 어플리케이션 폴더에 참조하는 어셈블리가 없다면 예외 발생함.)
이때, 어플리케이션 폴더에 있는 라이브러리 어셈블리의 버전과 동일한 버전이 GAC에 존재하면 GAC에서 로드하지만, 존재하지 않는 경우(다른 버전만 GAC에 존재하는 경우) 어플리케이션 폴더 내의 라이브러리를 로드한다.

어플리케이션 폴더에는 short name과 일치하는 어셈블리가 존재하다면, 어떤 버전이든 상관없이 로드하게 된다.
full name을 사용해서 호출할 경우 반드시 일치하는(버전, public key 토큰) 라이브러리가 GAC또는 어플리케이션 폴더에 존재해야 함.
Full name을 사용해서 호출할 경우 GAC에 full name에 일치하는(버전, public key 토큰)의 라이브러리가 존재하면, GAC에서 로드함. 그러나 GAC에 존재하지 않으면 어플리케이션 폴더에서 full name에 일치하는(버전, public key 토큰)의 라이브러리를 로드함.어떤 경우든 full name과 버전, public key 토큰이 일치하는 게 없다면 예외 발생함.

어셈블리를 로드하는 다른 방법도 존재한다.
Assembly a1 = Assembly.LoadFile(@"c:\TestFolder\lib.dll");
Assembly a2 = Assembly.LoadFrom("lib.dll");

LoadFile은 Fusion을 사용해 파일을 찾지 않는다. 대신 이 메서드에는 인자로서 어셈블리에 대한 전체 경로를 전달해야 한다. 이는 어떤 위치에 있는 어셈블리도 로드할 수 있다는 의미가 된다. 예를 들면, 파일의 win32 폴더(GAC의 물리적 위치)로 파일에 대한 경로를 지정한다면 GAC으로 부터의 로드도 가능하다는 의미이다.
LoadFrom 역시 전체 경로를 인자로 받지만, 상대 경로를 허용한다.
이들 메서드의 문제는 어셈블리가 반드시 private assembly 여야 한다는 것이다. GAC으로 부터 로드할 수 있는 충분한 정보를 제공할 수 없기 때문이다.
추가로, Load 메서드는 또한 byte[] 배열 인자를 취하는 overload 도 제공한다. 이 배열은 어셈블리의 실제 바이트들을 포함해야 한다.

static object FromFile(string fileName)
{
System.IO.FileStream fs = System.IO.File.OpenRead(fileName);
byte[] data = new byte[fs.Length];
fs.Read(data, 0, data.Length);
fs.Close();
Assembly a = Assembly.Load(data);
Type type = a.GetType("LibraryCode");
ConstructorInfo ctor = type.GetConstructor(Type.EmptyTypes);
return ctor.Invoke(null);
}


위의 Load(byte() 사용해 라이브러리를 로드하면 해당 경로에 반드시 어셈블리 파일이 존재해야 한다.
그러나, GAC에 동일한 어셈블리가 등록된 상태라면? 위의 Load(shortname) 처럼 GAC에서 로드할까? 그러하다(?)..
큰 그림 보려면 클릭하세요!(돋보기도 같이 활용)
.NET Framework 3.0에서는 두 개의 새로운 메서드 ReflectionOnlyLoad(), ReflectionOnlyLoadFrom()을 제공한다. 이들은 코드 검사만을 위한 어셈블리를 리턴하므로 코드를 실행할 수는 없다.

■ 객체 활성화(Activating Objects)

Assembly 함수들을 사용하는 데 있어서의 다른 주요한 문제는 reflection을 통해 객체를 활성화 해야 하다는 점이다.
객체를 활성화 하는 두 개의 다른 방법이 존재한다. : 아래 함수들은 내부적으로 Assembly.Load()에 의해 수행되는 어셈블리 로딩 작업을 포함하므로 Assembly.Load() 대신 사용해도 된다.

1. Activator 사용

static object UseActivator(string name)
{
ObjectHandle oh
= Activator.CreateInstance(name, "LibraryCode");
return oh.Unwrap();
}

Activator는 리모팅에 의해 사용되며 ObjectHandle은 객체 참조를 어플레케이션 도메인 간에 전달하는 메커니즘을 제공한다.
위에서 보듯이 객체를 사용하기 전에 반드시 ObjectHandler 을 unwap 해야 하는 것을 볼 수 있다. AppDomain은 객체를 활성화 하고 핸들을 unwrap 하는 메서드를 제공한다.
다른 하나의 코드이다.

2.AppDomain 사용
static object UseAppDomain(string name)
{
AppDomain ad = AppDomain.CurrentDomain;
return ad.CreateInstanceAndUnwrap(name, "LibraryCode");
}

1 번 방법보다 간단하게 호출할 수 있다.


■ 로컬 어셈블리와 GAC 어셈블리

메인 메서드를 아래와 같이 변경해서 실행한다고 가정한다.

static void Main()
{
object obj = null;
try
{
obj = UseAppDomain("lib, Version=1.0.0.0, "
+ "Culture=neutral, PublicKeyToken=3bf941bb1f722efe");
InvokeObject(obj);
}
catch(Exception e1)
{
Console.WriteLine(e1.GetType().ToString());
}
try
{
obj = UseAppDomain("lib");
InvokeObject(obj);
}
catch(Exception e2)
{
Console.WriteLine(e2.GetType().ToString());
}

static object UseAppDomain(string name)
{
AppDomain ad = AppDomain.CurrentDomain;
return ad.CreateInstanceAndUnwrap(name, "LibraryCode");
}
static void InvokeObject(object obj)
{
Type type = obj.GetType();
MethodInfo mi = type.GetMethod("GetVersion");
string version = (string)mi.Invoke(obj, null);
Console.WriteLine(version);
}
}


GAC에는 lib 어셈블리 등록된게 없다.(gacutil -u lib)
위 코드 상의 두 호출 모두 lib.dll을 어플리케이션 폴더에서 로드할 것이다.
이제 GAC에 lib.dll을 등록하고 로컬 파일 명을 lib.old.dll로 변경한다.
그러면, full name을 이용한 호출은 성공할 것이지만, short name 호출은 실패(FileNotFoundException)할 것이다. 이유는 위에서 설명되었다.
이제 다시 lib.old.dll을 lib.dll로 원상복구 한다.
이렇게 한 후 호출을 하면, 예상컨데, 위의 full name호출은 GAC으로 부터, 아래의 short name 호출은 어플리케이션 폴더(로컬)로 부터 로드될 것이다. 그러나. 결과는 두 호출 모두 GAC으로 부터 로드된다.
이는 Fusion은 주어진 short name으로 로컬 버전을 찾고, 존재하면 로컬 버전의 정보를 이용해 full name을 얻는다 그리고 이를 가지고 GAC으로부터 어셈블리를 로드하려고 하기 때문이다.

결론적으로 Load() 의 작동메카니즘은,

먼저 (검색할) 어플리케이션 폴더를 확인한다. 이를 위해 구성 파일의 codeBase 항목을 확인하여 존재하는 경우 그 하위 폴더도 포함된다.
어셈블리에 Culture 정보가 명시된 경우, culture 명으로 된 하위 폴더를 검색함. Culture 정보가 명시되지 않은 경우 어플리케이션 폴더 내에서 검색됨.

1. 기본 어플리케이션 폴더 검색

i. Short name을 가진 DLL을 어플리케이션 폴더에서 검색. --> /appFolder/lib.dll
ii. 존재하지 않는다면,short name 명을 가진 하위 폴더를 검색함. --> /appFolder/lib/lib.dll
iii. 존재하지 않는다면, EXE 가 검색됨.(어플리케이션 폴더, short name 명을 가진 하위 폴더)

--> /appFolder/lib.exe
--> /appFolder/lib/lib.exe

2. Private Path 검색
그런 후 Fusion은 <probing> 요소 내에 privatePath가 존재하는지 확인함. 존재하는 경우 명시된 그 폴더와 short name 명을 가지 그 하위폴더들에 대해 DLL/EXE 검색을 수행함.

i. /appFolder/private/lib.dll
ii. /appFolder/private/lib/lib.dll
iii. /appFolder/private/lib.exe
iv. /appFolder/private/lib/lib.exe

현재 테스트 중인 라이브러리는 strong name을 가진다. 그리고 GAC과 어플케이션 폴더 모두에 존재한다.
위의 어셈블리 찾기 과정은 private assembly을 로드 할 것이다. 하지만 Load는 찾은 라이브러리가 strong name을 가지는지 확인 후, 가지는 경우, 그 정보를 이용해 다시 어셈블리를 검색하게 된다.

3. Binding policy 확인(Version Redirection 확인)
어플리케이션 구성, publisher policy, machine 구성을 확인하여 버전 redirect가 있는지 확인한다.

4. 그리고 어셈블리가 이미 로드되었는지를 확인한다.
5. 이미 로드되지 않았다면, GAC을 확인한다.
6. 만일 GAC에서 어셈블리가 발견되면 그것을 리턴하게 된다. --> GAC 버전
7. 그렇지 않으면 어플리케이션 폴더의 로컬 버전이 리턴된다. --> 어플리케이션 폴더 버전

■ Partial Names

Assembly 클래스는 LoadWithPartialName 메서드를 제공한다. 이 방법은 권장되지 않는다.
이 메서드를 사용할 때 완전한 full name을 전달하지 않으면 Fusion은 가장 먼저 찾은(가장 높은 버전의) 어셈블리을 리턴하게 된다.

static object UsePartialName(string name)
{
Assembly a = Assembly.LoadWithPartialName(name);
Type type = a.GetType("LibraryCode");
ConstructorInfo ctor = type.GetConstructor(Type.EmptyTypes);
return ctor.Invoke(null);
}

■ <qualifyAssembly>

Partial name을 사용하는 다른 방법으로서 구성 파일 설정을 포함하는 방법을 제공한다.
<assemblyBinding> 내의 <qualifyAssembly> 요소를 추가할 수 있는데 이는 partial name을 해당하는 full name으로 매치시켜 준다.
이를 위해 직접 구성파일을 편집해야 한다.
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<qualifyAssembly
partialName="lib"
fullName="lib,version=1.0.0.0,culture=neutral, ¶
publicKeyToken=3bf941bb1f722efe"
/>
</assemblyBinding>
</runtime>
</configuration>

호출을 아래와 같이 partial name(short name) 으로 하더라도 매치되는 full name으로 어셈블리가 로드된다.
object obj = UsePartialName("lib");

■ AssemblyResolve Event

어셈블리를 로드 시 Fusion은 특정 어셈블리를 찾지 못하면 AssemblyResolve 이벤트를 현재 AppDomain 상에 발생시킨다. 이 이벤트는 다른 .NET 이벤트와 달리 값을 리턴한다. 그러므로 오직 하나의 델리게이트 만이 이벤트를 처리해야 한다.
하나 이상의 이벤트 핸들러를 가진다면 가장 마지막 이벤트 핸들러로 부터의 리턴 값이 사용된다. 다른 이벤트 핸들러는 무의미 해진다.
AssemblyResolve 이벤트는ResolveEventHandler 델리게이트 이다.:
public delegate Assembly ResolveEventHandler( object sender, ResolveEventArgs args);

델리게이트는 하나의 ResolveEventArgs 인자를 취하며, 이 ResolveEventArgs 는 Name이라는 string 속성을 가진다. 이 속성은 요청된 어셈블리의 full name 값을 가진다. 이벤트 핸들러는 이 속성(Name)을 이용해 요청된 어셈블리를 찾고 로드하게 된다.
이 시점에서 Fusion이 어셈블리를 찾을 수 없는 경우에 임의로 어셈블리를 로드하기 위해, Fusion을 내부적으로 이용하는 특정 메서드를 사용해서는 안된다.
예를 들어, 아래와 같이 샘플의 main 메서드를 변경하고,

static void Main()
{
AppDomain.CurrentDomain.AssemblyResolve
+= new ResolveEventHandler(ResolveAssembly);
try
{
object obj = FromAssembly("lib");
InvokeObject(obj);
}
catch(Exception e)
{
Console.WriteLine(e.GetType().ToString());
}
}
static object FromAssembly(string shortName)
{
Console.WriteLine("Use Assembly.Load");
AssemblyName name = new AssemblyName();
name.Name = shortName;
name.Version = new Version("1.0.0.0");
name.CultureInfo = new CultureInfo("");
byte[] pkt
= new byte[] {0x3b,0xf9,0x41,0xbb,0x1f,0x72,0x2e,0xfe};
name.SetPublicKeyToken(pkt);
Assembly a = Assembly.Load(name);
Type type = a.GetType("LibraryCode");
ConstructorInfo ctor = type.GetConstructor(Type.EmptyTypes);
return ctor.Invoke(null);
}


이벤트 핸들러가 어셈블리를 로드하도록 시도하게 할 것이다. 일단 아무 일도 하지 않도록 하고 테스트 한다.
static Assembly ResolveAssembly(object sender, ResolveEventArgs args)
{
return null;
}


(GAC 에 등록된 lib 이 없는 상태에서)
컴파일 후 lib.dll을 lib.old로 rename 하고 실행을 하면, FileNotFoundException 예외가 발생할 것이다. Short name을 가진 어셈블리가 어플리케이션 폴더에 존재하지 않고, full name을 가진 어셈블리가 GAC에 존재하지 않기 때문이다. 하지만 우리는 요청하는 어셈블리가 로컬 폴더에 존재한다는 것을 알고 있다. 단지 이름만 변경되었다는 것을 알고 있다. 그래서 우리는 이벤트 핸들러를 이용해서 실제의 이름으로 로드 해보기로 한다.

static Assembly ResolveAssembly(
object sender, ResolveEventArgs args)
{
string[] name = args.Name.Split();
string assem = name[0].Substring(0, name[0].Length-1);
return Assembly.LoadFrom(assem + ".old");
}


Name 속성이 full name을 전달할 것이므로 이를 파싱하여 파일명을 얻는 과정이 필요하다.
실행을 하면, 이름 변경된 lib.old가 로드된다.

큰 그림 보려면 클릭하세요!(돋보기도 같이 활용)

끝.

RegFreeCOM : 등록(regsvr32)이 필요없도록 COM 참조하기

원문 : http://msdn.microsoft.com/msdnmag/issues/05/04/RegFreeCOM/

닷넷 개발환경에서 개발을 하더라도 항상 managed 코드나 어셈블리만을 다루는 것은 아니다.

아직까지 COM에 대한 의존도를 무시할 수 없다. 더군다나 우리가 꼭 사용해야 하는 윈도우/오피스 관련 컴포넌트들은 거의 COM 기반 인 경우가 많다. 이들을 가지고 개발을 하기 위해서 우리는 Interop 과정을 통해 interop.*.dll 과 같이 COM을 닷넷 환경에서 사용할 수 있도록 wrapping한 모듈을 생성해서 이를 참조 사용한다. (MS에서는 주요 COM 컴포넌트에 대해서는 PIA라고 해서 개발자들을 위해 미리 Interop 된 어셈블리를 제공하기도 한다.)
물론 우리의 고마운 Visual Studio는 이 작업을 대신 해 주므로 우리는 그냥 참조 추가만 해 주면 된다.
요컨대 아직 우리의 닷넷 기반 개발은 COM을 사용해야 할 때가 많다는 것이고, 그렇다면 우리가 이전에 “DLL Hell”이라 부르던 문제(?)에 대해 아직 완전하게 자유로울 수 없다는 이야기 되겠다. 그렇다면 닷넷 어셈블리처럼 자기 완결적인 정보를 스스로 가지고 있어서 XCOPY 만으로 배포가 가능한 그러한 COM을 만들 수는 없을까? 그러면 DLL Hell을 피하면서 맘 푹 놓고 COM을 참조해 개발을 하고, 클라이언트에 배포할 수 있을 텐데…
우연히 발견한 msdn 매거진의 아티클 하나가 참으로 가려운 곳을 긁어 주었다. 또한 이 기술인 이미 Windows XP와 함께 소개되었다는 사실은 나에게 한 소리한다. “너…시..공부 안할래?.... 남들 다 아는 사실을 니만 모르고 있다..무슨 노다지라도 캔 양…”, “알았다… 지금이라도 알았으니까 모른척 알고 있었던 척 하면 데잖아……-_-!”.

이 내용을 아시는 분은 그저 썩소 한방씩 보내기 바란다…..이번 글은 번역 수준으로 글을 올린다.
naive assembly 부분은 역자의 소질이 미약하여 번역이 어색할 것이다. 이해 바란다.

우리가 VB6나 ATL을 이용해 작성한 사용자컨트롤이나 또는 다른 객체를 작성하는 것은 COM을 이용하는 것이다. 닷넷 이전에 COM은 윈도우 상의 어플리케이션들을 위한 기본적인 컴포넌트 모델이었다.

그러나 COM 컴포넌트를 배포하는 것은 그것을 개발하는 것보다 힘이 들때가 많았다. COM 컴포넌트들은 머신내의 모든 어플리케이션들이 사용할 수 잇도록 전역적으로 등록되어진다. 불행히도, 이 전역적인 등록은 아주 주의 깊게 관리되지 않으면 이들을 사용하는 어플리케이션에 원치않는 부작용을 야기시키게 된다. 이러한 부작용을 우리는 DLL Hell이라 부른다.

이러한 요소들은 닷넷 환경에서 문제가 되지 않는다. 왜냐하면 컴포넌트는 등록되어질 필요가 없으며, 어플리케이션에 분리된 형태로 존재하고, 또한 GAC에 도움으로 잘 정의된 side-by-side 방식에 의해 관리되어 지기 때문이다.

다양한 마이그레이션 마법사는 우리의 코드를 닷넷 프레임웍로 변환하게끔 도와줄 수 있지만, 반드시 실행되는 것은 아니다.

윈도우 XP는 registration-free COM(Reg-Free COM) 이라 부르는 COM 액티베이션 모델을 소개한다. Ref-Free COM은 COM 컴포넌트를 위한 레지스트리에 대한 대안으로서 등장했다. 이는 일반적으로 레지스트리에 설치되는 모든 표준 컴포넌트 등록 정보를 파일로서 표현하도록 한다. 그리고 이 파일은 어플리케이션과 동일한 폴더에 저장되어 진다.

이 문서는 VS2005의 새로운 Reg-Free COM 지원 기능을 이용해서 기존 COM 컴포넌트를 이용하는 어플리케이션을 어떻게 배포하는지 보여줄 것이다. 이 기능은 우리가 XCOPY나 ClickOnce와 같은 간단한 배포 모델로서 이러한 어플리케이션을 배포할 수 있도록 해준다.

Reg-Free COM Overview

전통적으로, 어플리케이션이 COM을 사용하는 경우, 컴포넌트는 작동되기 위핸 등록되어져야만 한다. Figure 1에 보여지는 것 처럼.
이는 어플리케이션이 네이티브 코드이거나 또는 닷넷 프레임웍 기반이냐에 상관 없이 적용되는 규칙이다.

Figure 1 Traditional COM Activation

Figure 1 Traditional COM Activation

시스템 레지스트리에 컴포넌트 정보를 등록하는 것의 이점은 여러 어플리케이션에 의해 공유되어 질 수 있다는 것이다. 또한 컴포넌트를 네트웍의 다른 컴퓨터 상에 설치하고 이를 활성화 하는 것 역시 가능하다. 만일 우리의 어플리케이션이 이러한 기능이 전혀 필요없다면, 즉 오로지 특정 어플리케이션 만이 해당 컴포넌트를 사용하는 상황이라면, 이 머신에 대해 적역적으로 등록하는(시스템 레지스트리에 등록하는) 과정 자체는 불필요한 부담이 된다.

먼저, 모든 컴포넌트가 적절하게 등록되었음을 확인하는 것은 대게 별도의 MSI 또는 EXE 인스톨러와 같은 셋업 프로그램이 필요하다는 것을 의미하게 된다. 또한, 컴포넌트 등록은 12개에서 수천개 까지 방대한 양의 레지스트리 키를 등록시킬수 있다. 만일 이중의 하나라도 잘못되면, 어플리케이션은 작동을 하지 않는다. 이런식으로 등록정보가 머신에 산재하게 된다면 설치제거나 업데이트를 매우 어렵게 한다. 특히나 컴포넌트가 여러 어플리케이션에 의해 공유된다면 더욱 그럴것이다.

Ref-Free COM은 전통적인 COM과는 달리, 시스템 레지스트리에 별도로 컴포넌트가 등록될 필요가 없다. 따라서 이전에 언급된 문제들을 피할 수 있다. 이는 모든 정보를 시스템 레지스트리가 아닌 XML manifest에 표현한다. 이 XML은 단지 어플리케이션 폴더 상에 존재하는 하나의 파일일 뿐이다.

컴포넌트가 manifest를 가지고 있다면, 컴포넌트의 Activation 과정에서 운영체제는 레지스트리를 보기 전에 먼저 manifest를 보게 된다.

Figure 2 Isolated COM Activation

Figure 2 Isolated COM Activation

Reg-Free COM 컴포넌트 모델을 활성화하기 위해서 프로그램(WindowsApplication1.exe)의 일부로서 어플리케이션 manifest(WindowsApplication1.exe.manifest)가 존재하기만 하면 된다.

이는 프로그램이 실행될 때 마다, Native Assembly Loader는 실행파일의 이름에 기반한 manifest 파일을 찾기 때문에 가능하다.

닷넷 어셈블리를 위한 컴포넌트 모델과 같이, 어플리케이션 폴더 외부에 다른 별도의 추가 정보가 존재할 필요가 없다. 윈도우는 컴포넌트 파일과 일치하는 각 식별자(CLSID)에 맵핑 정보 테이블을 내부적으로 구축한다. 이는 어플리케이션 코드가 실행되기 전인, CreateProcess 과정에서 수행된다. 그 후에, 어플리케이션이 컴포넌트를 인스턴스화(메모리내에 객체로서 생성) 할때 CoCreateInstance는 는 시스템 레지스트리를 보기 전에 이 내부의 맵핑 테이블을 먼저 보게 된다. 만일 모든 컴포넌트가 하나 또는 그 이상의 manifest에 의해 완전하게 표현되어 있다면, 어떠한 컴포넌트 등록도 필요치 않게 된다.!!

이 모델의 주요한 이점은 COM 컴포넌트가 완전하게 어플리케이션으로부터 분리되어 진다는 것이다. 다른 어플리케이션이 동일한 COM 컴포넌트( 혹은 다른 버전의 동일 컴포넌트)가 등록되어 지기를 요구한다 할 지라도, 이 어플리케이션은 어떤 방식으로도 간섭을 받지 않는다. 다른 어플리케이션과 완전히 독립적으로 컴포넌트를 사용하게 되는 것이다.

그래서 다른 한편으로는 이 어플리케이션을 위한 COM 컴포넌트를 다른 어플리케이션이 접근할 수 있는 방법도 없다. 그러므로, 버전 충돌 이나 컴포넌트의 GUID 변경으로 인해 야기되는 문제들은 Reg-Free COM를 명시적으로 사용하므로써 피할 수 있게 되는 것이다. (그러나 이러한 점이 컴포넌트의 versioning 전략을 포함한 분배의 회피 수단으로서 사용될 수 없다 할지라도)

결국 업데이트와 설치제거는 어플리케이션 폴더를 제거하거나 대체하는 것이 가장 간단하다.

덧붙이면, Reg_Free COM의 이점을 얻기 위해 기존 코드를 수정할 필요는 없다.적합한 manifest 구성을 설정하는 것만으로 충분하다. 사실, Reg-Free COM의 원래 의도는 기존 VB6, C++ 과 같은 언어로 작성된 네이티브 어플리케이션을 위한 것이었다. 이 글은 닷넷 기반 어플리케이션에서 Reg-Free COM을 사용하는 것에 초점을 두고 있다.

언급했듯이 Native Assembly Loader가 COM 컴포넌트를 manifest를 이용해 바인딩하는데 실패하면, 이전 방식대로 시스템 레지스트리에서 컴포넌트 등록정보를 찾게 된다. 이러한 행동은 Reg-Free COM 어플리케이션으로 적절하게 구성되지 않은 어플리케이션도 작동될 수 있도록 한다. 그러므로, 어플리케이션이 Reg-Fress COM을 위해 올바르게 구성되었는지를 검증하기 이해서 Clean 머신에서 테스트를 하는 것이 가장 좋다.

Isolation COM Components

Visual Studio 2005는 간단한 스위치 기능으로서 COM 컴포넌트를 격리시키도록 하는 새로운 기능을 제공한다. 이는 컴포넌트의 Type Library와 등록 정보로부터 manifest를 자동으로 생성해 준다.

그래서, 개발자 머신 상에 해당 COM 컴포넌트가 등록되어 져야만 COM 컴포넌트를 격리시키는 것이 가능하다. 그러나 이러한 개발자 머신의 헌신 덕분에 우리는 엔드 유저의 머신 상에서 컴포넌트가 등록 과정을 제거할 수 있게 되는 것이다.

Visual Studio 2005 상에서 모든 COM 인터페이스는 Isolated 라 불리는 새로운 속성을 가지고 있다. 기본적으로, 이 속성은 false 값을 가진다. False 값은 이 컴포넌트는 정상적인, 등록된 COM 참조로 취급되어야 한다는 것을 의미한다. 즉 전통적인 COM 참조가 되겠다.

이 값이 True이면, 빌드 타임에 컴포넌트에 대한 manifest가 생성되게 한다. 이는 또한 해당하는 컴포넌트 파일이 어플리케이션 폴더로 복사되어 지게끔 한다. Manifest 생성기가 격리된 COM 참조를 발견하게 되면, 이는 컴포넌트의 Type Library의 모든 CoClass 항목을 열거하게 되고, 각 항목을 일치하는 등록 데이터와 연관시키게 된다. 이 과정 내에서, manifest 는 컴포넌트 파일내에 존재하는 모든 COM 클래스에 대한 정의를 자동으로 가지게 된다.

COM 컴포넌트를 격리하는 기능은 VB6.0 COM 컴포넌트를 VB.NET 또는 C# 어플리케이션 내에 Reg-Free COM으로 배포하기 위해 고안되었다. 이 기능은 VB6.0 컴포넌트에 국한되지 않는다. 그러나 Reg-Free COM의 한계가 존재한다. 이는 "The Limitations of Reg-Free COM" 를 참조 바란다.

다음의 두가지 시나리오에서 어떻게 작동하는지를 보여줄 것이다. 먼저 간단한 VB6.0으로 COM 컴포넌트를 생성하고, 이것이 어떻게 Reg-Free COM을 이용해 배포되는지를 보여줄 것이다. 그리고, 조금 더 복잡한 예제로서 ActiveX 컨트롤이 Reg-Free COM 하에서 어떻게 작동하는지를 볼 것이다.

Isolating a Simple COM Component

VB6.0으로 간단한 COM 컴포넌트를 만드는 것으로 시작한다.
VB6.0에서 ActiveX DLL 형태로 프로제트 생성하고 Class1 코드 모듈에 아래의 코드를 넣는다. 컴포넌트 이름은 VB6Hello로 한다.
VB6가 설치되지 않은 독자는 그냥 첨부된 VB6Hello.dll을 사용해도 된다.
컴파일 한 후 VB6Hello.dll 을 regsvr32로 등록한다.(개발자 머신에는 등록해야 한다.)

	Public Sub SayHello()    	MsgBox "Hello from Visual Basic 6.0"	End Sub

주의 할 점은 ActiveX DLL, ActiveX Control 프로젝트 형식만이 Reg-Free COM 을 지원한다. ActiveX EXE , ActiveX Document 프로젝트 타입은 지원하지 않는다. ("The Limitations of Reg-Free COM"에서 언급되어 진다.)

다음 단계로, 이 COM 컴포넌트를 닷네 윈 폼 어플리케이션에서 참조를 한다.
Visual Studio 2005를 시작하고, 새로운 “RegFreeComDemo_1” 라는 이름의 윈 폼 프로젝트를 생성한다.
“VB6Hello.dll” 에 대한 참조를 추가한다.
“Say Hello” 라는버튼을 추가한다. 버튼의 클릭 이벤트 핸들러에 아래의 코드를 넣어준다.

  	private void button1_Click(object sender, EventArgs e)	{		VB6Hello.Class1Class cls = new VB6Hello.Class1Class();		cls.SayHello();	}

F5를 눌러 실행을 하게 되면 아래의 그림과 같은 실행화면을 만날 수 있다.

Figure 3 RegFreeComDemo1

Figure 3 RegFreeComDemo_1

Reg-Free COM 부분으로 넘어가기 전에 몇가지 살펴보도록 한다.
먼저 빌드 과정에서 생성된 파일을 보면,

Interop.VB6Hello.dll, RegFreeComDemo_1.exe, RegFreeComDemo_1.exe.config 이렇게 존재한다.

여기서 Interop.VB6Hello.dll은 빌드과정에서 자동으로 생성된 COM Interop 어셈블리이다.
그러나 VB6Hello.dll은 폴더 내에 존재하지 않는다는 점을 주목한다.

COM 컴포넌트를 등록 해제 한다. (regsvr32 /u VBHello.dll)

그리고 다시 Visual Studio 2005 IDE 외부에서 RegFreeComDemo_1.exe를 실행한다.(실행파일을 그냥 실행) 그러면 버튼을 눌렀을 때 어플리케이션이 실패하는 것을 확인할 수 있다.

확인 했으면 다시 COM 컴포넌트를 등록( regsvr32) 한다.

이제 COM 컴포넌트를 격리해보자.(Reg-Free COM으로 만들어 보자)
참조 목록이 있는 Reference 노드에서 VB6Hello를 선택하고, Isolated 속성을 True로 변경한다.

다시 실행(F5) 하게되면 정상적으로 작동하게 된다. 이전과 별반 차이가 없이 보이지만 내부적으로 이는 현재 Reg-Free COM 하에서 작동을 하는 것이다.

이를 입증하기 위해, VB6Hello.dll을 다시 등록해제 한 후 Visual Studio 2005 개발환경 외부에서 (실행파일을 그냥 실행) RegFreeComDemo_1.exe를 다시 실행해 본다. 버튼을 클릭하면 잘 작동하는 것을 확인 할 수 있다.!!!!
(앞의 과정에서 regsvr32 /u VB6Hello.dll 한 후 실행 했을 때 오류가 났던 것과는 다르다.)

만일 임의로 manifest 파일의 이름을 다른 이름으로 변경하게 되면 다시 어플리케이션을 실패하게 된다.
빌드 과정에서 생성된 5개의 파일을 확인한다.

  • Interop.VB6Hello.dll,
  • RegFreeComDemo_1.exe,
  • RegFreeComDemo_1.exe. con fig,
  • RegFreeComDemo_1.exe.manifest,
  • VB6Hello.dll

마지막 두개의 파일 COM 컴포넌트를 격리(Isolated=True) 한 후 새롭게 나타난 파일이다.

어플리케이션 manifest 파일, RegFreeComDemo_1.exe.manifest가 바로 Reg-Free COM을 가능하게 하는 바로 그 manifest 파일이다. 위 파일들을 .NET Framework 2.0과 Windows XP가 설치된 다른 머신에 XCOPY 복사하여 실행하여도 잘 작동을 하게 된다.(어떠한 등록 과정(regsvr32) 없이도)
Windows XP는 VB 런타임 라이브러리를 포함하고 있으므로, MSVBVM60.dll, OLEAUT32.dll 등과 같은 라이브러리를 설치해야 할 필요는 없다.
(그러나 어떤 머신에서는 설치가 제거되었거나 어떤 이유에서든지 VB 런타임이 설치되지 않은 상황이 반드시 발생한다… 이럴 때는 MS 다운로드 사이트 가서 VB6 런타임 설치파일을 다운 받아서 설치해주어야 한다.)

어플리케이션 manifest는 실제로 XML 문서이다. 이 파일의 내용은 Figure 5에 있다.

Figure 5 Reg-Free COM Application Manifest

<?xml version="1.0" encoding="utf-8"?>

<assembly xsi:schemaLocation="urn:schemas-microsoft-com:asm.v1 assembly.adaptive.xsd" manifestVersion="1.0" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

<assemblyIdentity name="RegFreeCOMDemo_1.exe" version="1.0.0.0" type="win32" />

<file name="VB6Hello.dll" asmv2:size="20480">

<hash xmlns="urn:schemas-microsoft-com:asm.v2">

<dsig:Transforms>

<dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />

</dsig:Transforms>

<dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />

<dsig:DigestValue>by35ekL4xX8qNvjy6IdVbVYnv9Q=</dsig:DigestValue>

</hash>

<typelib tlbid="{479e93bd-102c-40c9-931f-4baad33b0bbb}" version="1.0" helpdir="" resourceid="0" flags="HASDISKIMAGE" />

<comClass clsid="{48245372-4586-436b-a715-8bfd07e742eb}" threadingModel="Apartment" tlbid="{479e93bd-102c-40c9-931f-4baad33b0bbb}" progid="VB6Hello.Class1" />

</file>
</assembly>


(Hash와 GUID 식별자는 다를 수 있다.)

<file> 요소를 보면 <comClass>, <typelib> 요소를 포함하고 있다. 보는 바와 같이 클래스와 Type Library의 GUID는 manifest에 선언되어 있다. 이는 전통적인 방식에서 시스템 레지스트리에 등록되는 정보와 동일한 정보이다.

ClickOnce를 사용해 본적이 있다면, ClickOnce 어플리케이션 manifest가 존재한다는 것을 알 수 있으며 이 파일이 곧 바로 위에서 말한 manifest 파일이다. 즉 이는 ClickOnce 와 RegFree COM 정의는 동일한 파일 내에 존재할 수 있다는 것을 의미 한다.
그럼 ClickOnce로 배포 되어 질 때 동일한 어플리케이션에 어떤 변화가 일어나는지 보도록 한다.
Visual Studio 2005 에서 프로젝트를 선택한 후 Publish 명령을 실행한다. 그러면 Publish 마법사가 뜬다. 여기서 ClickOnce 구성을 한다.
(ClickOnce에 대해서는 다른 문서 참조 바람)

* ClickOnce 배포를 하기 전에, VB6Hello를 등록해제 했다면, 다시 등록하기 바란다. 언급했듯이 개발자 머신에는 해당 COM 컴포넌트가 반드시 등록되어야 한다. 그렇지 않으면 컴파일이 실패한다.

ClickOnce로 배포를 통해 실행하더라도 잘 작동을 한다. 물론, 실행하는 클라이언트에서 VB6Hello.dll을 등록(regsvr32)한 적이 없다.그러면 ClickOnce의해 배포되어져 내려온 어셈블리를 확인하면, 당연히 아래와 같이 예상대로 어셈블리와 COM 컴포넌트가 존재할 것이다.

ClickOnce 버전의 manifest 파일은 이 문서의 첨부에 포함되어 있으며, 조금 확인해 보면,

이전과 동일하게 <file>, <comClass>, <typeLib>를 포함하지만, 전체 어플리케이션의 모든 파일에 대한 정의를 포함하고 있다. 추가적으로 어플리케이션의 무결성을 보증하기 위한 해시정보가 모든 파일과 어셈블리에 요구된다.

또한 ClickOnce 어플리케이션은 반드시 Sign이 필요하다 XCOPY로 배포하는 (이전의 것) 경우는 이러한 Sign이 필요치 않으며, 이들의 어플리케이션 보안은 Full Trust로 정의되어 진다. 네이티브 코드를 직접 참조하는 모든 어플리케이션(COM 컴포넌트를 포함한)은 Full Trust 보안 레벨로 정의된다. Visual Studio 2005는 만일 우리가 하나 이상의 COM 참조에 Partial trust를 이용하고 있는 경우, 빌드 시 경고를 표시하게 된다.

확인할 사실은 Manifest 내에는 ClickOnce 배포를 위한 정보와 우리가 하고 있는 RegFreeCOM을 위한 정보가 공존할 수 있고, 이 작업은 Visual Studio 2005에 의해 자동으로 이루어 진다는 것이다.

생뚱맞은 이야기지만, Reg-Free COM을 사용하는 경우 최소한의 요구되는 OS는 Windows XP이다.

이번 샘플은 Reg-Free COM을 이용해 간단한 컴포넌트를 호출하는 방법을 보았다. 이는 비즈니스 객체와 같은 훨씬 복잡한 컴포넌트에도 쉽게 적용할 수 있다.

A More Complex Example

이번 샘플에서는 미리 만들어 놓은 VB6ControlTest” 컴포넌트를 이용할 것이다. 이는 VB6 개발환경과 함께 설치되는다른 ActiveX 컨트롤을 사용하고 있는 VB6 사용자 컨트롤이다.

VB6ControlTest.ocx를 로컬 컴퓨터로 복사한 후 등록한다.

Visual Studio 2005를 시작하여 새로운 윈 폼 프로젝트를 생성하고 RegFreeComDemo_2로 이름을 정한다.

도구상자(toolbox)에 VB6ControlTest 컴포넌트를 추가한다.

VB6ControlTest 컴포넌트를 도구상자에서 윈 폼 위로 Drag & Drop으로 추가한다.

Figure 6 RegFreeComDemo2 Program

Figure 6RegFreeComDemo_2 Program

이제 ActiveX 컨트롤을 격리해 보자. 참조 노드에서 VB6ControlTest 항목과 AxVB6ControlTest 항목을 선택하고 Isolated 속성을 True로 변경한다.

우리는 사용자 컨트롤(VB6ControlTest)을 격리했다. 그러나 우리는 사용자 컨트롤에 의해 참조되는 컨트롤들도(VB6 개발환경과 함께 설치된 ActiveX 컨트롤들) 격리하고 싶다.

그렇게 하기 위해서 우리는 단지 이들 컨트롤들을 프로젝트에서 참조 추가하고 이들의 Isolated 속성을 True로 변경한다.

· Microsoft Common Dialog Control 6.0 (SP6)
· Microsoft Rich Textbox Control 6.0 (SP6)
· Microsoft Tabbed Dialog Control 6.0 (SP6)
· Microsoft Windows Common Controls 6.0 (SP6)
· Microsoft Windows Common Controls-2 6.0 (SP6)

주의할 점은 순수하게 UI 위젯 형태의 컨트롤들은 Reg-Free COM 하에서 대부분 잘 작동하지만 반드시 테스트 해 보아야 한다.

Publish 마법사를 통해 ClickOnce 게시를 한다. 그리고 .NET Framework 2.0만 설치된 다른 머신으로 가서 게시된 ClickOnce 어플리케이션을 설치/실행해 보면, ActiveX 컨트롤들이 자연스럽게 배포되어 지는 것을 확인할 수 있다.

격리시킨 OCX 들이 배포되어져 있다.

Native Assemblies

Isolated 속성은 기존 COM 컴포넌트를 정말 쉽게 격리시키고 배포할 수 있게 해 준다. 하지만 컴포넌트가 어떤 vender나 컴포넌트 소유자로부터 manifest와 함께 제공되어 졌다면, 우리는 native assembly를 직접 참조할 수 있다. 이 경우, 사실 컴포넌트 제작자에 의해 제공된 manifest를 이용하는 것이 훨씬 편하고 선호될 것이다.

Visual Studio 2005에서는 native assembly 에 대한 참조를 지원한다. native 참조는 참조하는 파일의 Type 속성이 native로 설정된 경우 생성된다. native 참조를 추가하기 위해 프로젝트에서 참조 추가를 하고 manifest의 위치로 이동한다. 어떤 컴포넌트는 DLL 내에 manifest를 포함시킨다. 이 경우, DLL 자체를 선택할 수 있으며, 그러면 Visual Studio는 해당 dll이 embedded manifest를 포함하고 잇다는 것을 감지하게 되면 이를 native 참조로 추가하게 된다.

native 어셈블리는 컴포넌트와 연관된 추가적인 파일들을 정의할 수 있다. 그리고 다른 독립적인 native 어셈블리들을 참조할 수 도 있다.
임의의 복잡한 native 어셈블리들과 파일들을 수동으로 생성 시킨 manifest 내에 임의로 캡슐화 하는 것도 가능하다.
Visual Stuido 2005는 참조하는 컴포넌트와 동일한 폴더에 존재하는 어떤 파일 또는 독립적인 native 어셈블리들을 자동으로 manifest에 포함시킬 것이다.
Manifest는 xml이므로 컴포넌트 제작자는 자신의 컴포넌트내에 native 어셈블리를 포함시키고 싶다면 manifest 파일을 수동으로 생성할 수도 있다.
Platform SDK에 포함된 MT 툴을 이용할수도 있다. 물론, VB.NET 또는 C# 클래스 라이브러리 내에서 native 참조와 Reg-Free COM을 사용할 수 있다.

흥미롭게도, 결과 컴포넌트는 managed 어셈블리(.dll)와 native 어셈블리(.manifest) 두 개로 이루어 진다. 다른 프로젝트에서 이 컴포넌틀 참조할 때, 두 참조를 모두 추가해야만 컴포넌트를 제대로 사용할 수 있다. 프로젝트 참조를 사용할 때, 두 어셈블리는 자동으로 참조되어 진다. 프로젝트 참조 형태로 추가하지 않으면, 참조 추가를 두 번 해주어야 한다. 참조는 DLL에 대한 참조 한번과 manifest에 대한 참조, 이렇게 두 번의 작업이 필요하다.

위의 이야기는 조금은 어려운 감이 있지만, 요지는 우리가 *.manifest 파일을 참조로 추가할 수 있어 그 manifest 내에 포함된 native 어셈블리들을 참조로 추가할 수 있다는 것이다.
간단한 테스트를 위해 NativeTestClient.dll을 참조 추가하고, 이 COM 컴포넌트 제작자가 배포한 NativeTestClient.dll.manifest 파일을 참조 추가해 본다. 이 manifest 내에서는 해당 컴포넌트 제작자가 자신의 컴포넌트 내에서 참조하는 다른 native 어셈블리(NativeCOM.dll)에 대한 참조 정보가 있을 것이다.(아래의 예에서 NativeTestClient.dll 과 NativeTestClient.dll.manifest, NativeCOM.dll은 모두 동일한 폴더 내에 존재해야 한다.)

아래처럼 NativeTestClient.dll.manifest 파일에 포함된 native assembly 에 대한 참조 정보로 인해 NativeCOM.dll이 자동으로 추가되었음을 확인할 수 있다.

Conclusion

Reg-Free COM 은 COM 컴포넌트를 사용하는 어플리케이션 배포에 있어 어려웠던 점을 해소하는 훌륭한 방법이다. 이는 ActiveX 컨트롤에 대해 작동을 한다. 그러나 이는 UI가 없는 비즈니스 객체에 대해서도 작동을 한다. 이는 native 와 COM 컴포넌트를 어플리케이션 격리와 배포의 용이성 측면에서 manged 컴포넌트 수준으로 향상시키는 기술이라 할 수 있다.
유일한 주요 결점은 최소한의 플랫폼으로서 Windows XP가 필요하다는 점이다.
그러나 데스크탑 환경이 날로 향상되는 환경에서 이 새로운 기술이 제공하는 이점은 이러한 제약을 충분히 극복하고 있다고 여겨진다.



첨부파일 : RegFreeCOM.zip (1614Kbytes)
06년경부터 외국사이트등에서 Mac osx를 해킹해서 x86 일반 PC에 설치하는 방법등이 알려지고

있습니다. 애플 社주관으로 PC에 Mac os를 설치하는 대회같은것도 있기도 했죠. 일단 Mac 자체가

일반PC와는 달리 바이오스를 사용하지 않고 EFI라는 것을 사용하기 때문에 그냥 DVD만 넣는다고

해서 설치가 되진 않고 가장 문제는 하드웨어적 문제인데 지금은 꽤 많은 부분이 해킨토시 이용자

들을 통해 패치가 이뤄져서 설치가 잘 되는 편입니다. 아무래도 Mac os의 편리함이나 디자인등에

매료되서 PC사용자들이 최근에 많이 시도해보는것 같은데 저 또한 인터페이스나 디자인이 마음에

들어 꽤전에 시도해보다가 특정 하드웨어 몇개가 죽어도 잡히질 않아 때려쳤던 기억이 나네요.

몇일전에 윈도우XP용 테마를 찾다가 osx 레오파드를 보고 다시한번 시도해 보기로 마음을

먹었습니다. 대개 많은 해킨토시 이용자분들이 XP나 비스타와 osx를 멀티부팅 하는 방법이나

Vmware로 설치해서 사용하는것으로 아는데 여기서는 가장 부담없는 Vmware를 통해 설치하는

방법을 알아보겠습니다. (사실은..귀찮아서...;)

1. VMware 셋팅

사용자 삽입 이미지

VMware를 설치하기전 자신의 하드웨어가 다운로드한 Mac os에서 지원하는지 확인해야합니다.

과거 해킨토시는 AMD CPU에서 설치하기 매우 힘든경우가 많았지만 최근에는 AMD CPU도

잘 되는것으로 알고있습니다. 인텔 CPU의 경우 SSE3을 지원해야 설치가능합니다. 위와같이

CPU-Z같은 프로그램으로 자신의 CPU를 확인합니다. 왠만한 사양의 CPU를 쓰고있는 분들은

SSE3을 지원합니다.

사용자 삽입 이미지

설치가 이뤄질 VMware의 가상PC를 설정합니다. File→new→Virtual Machine을 선택해서

다음과 같이 Custom을 선택해줍니다.

사용자 삽입 이미지

여기서는 Vmware 6.5를 사용했습니다. 6.0을 사용해도 무방합니다. Hardware Compatibility에서

5와 호환되게 하면 많은 제약이 따르므로 기본값으로 놔두고 Next를 눌러줍니다.

사용자 삽입 이미지

Vmware 6.0에서와 달리 6.5에서는 OS를 인스톨할 CD나 DVD,ISO이미지 여부를 묻습니다.

추후에 수정가능하므로 맨 아래를 선택하고 Next를 눌러줍니다.

사용자 삽입 이미지

어떤 OS가 설치될것인지 묻게되는데 위와같이 설정해주고 Next를 눌러줍니다.

사용자 삽입 이미지

버추얼머신의 이름을 써주는 칸에 맘에드는 이름을 아무렇게나 입력해주고 머신이 저장되는

파일이 저장될 디렉토리를 지정해줍니다.

사용자 삽입 이미지

코어가 두개이상인 CPU의 경우 선택해주는 부분입니다. 쿼드코어 사용자도 Two로 선택하면됩니다.

저의 경우는 P4 프레스캇 싱글코어라 One을 선택했습니다.

사용자 삽입 이미지

메모리 설정 부분입니다. 글을 쓰기전 미리 설치해본결과 2Gb나 1Gb나 가벼운 작업시 별로 큰

차이는 없었습니다. 1Gb정도만 설정해주고 부족하다 싶으면 나중에 수정해주면 됩니다.

사용자 삽입 이미지

네트워크 설정 부분입니다. 공유기를 쓰신다면 Bridge 네트워크를 이용해야 될수 있습니다만

NAT가 가능하다면 NAT로 설정하는것이 편합니다.

사용자 삽입 이미지
어댑터 타입을 물어보는데 LSI Logic을 사용합니다. Bus Logic을 사용해도 Vmware상에서

성능상 많은 차이를 보이진 않습니다.

사용자 삽입 이미지

이제 버추얼머신에서 HDD처럼 쓰일 파일을 만들게 됩니다. 맨 아래를 선택하면 실제 HDD를

사용하게 되는데 여유로운 HDD를 가지고 계신분이라면 더욱 나은 성능을 위해 실제 HDD를

사용하시는것도 괜찮은 방법입니다. 다만 이경우 실제 HDD의 전체를 사용하게 되므로

반드시 남는 HDD를 사용하시기 바랍니다.

사용자 삽입 이미지

디스크 타입을 묻는데 SCSI로 설정시 인식하지 못할때가 있습니다. 여기서는 IDE로 설정하지만

SCSI로 설정시 이상 없으신 분들은 SCSI로 설정하시기 바랍니다.

사용자 삽입 이미지

디스크의 전체 용량을 지정해 줍니다. osx 10.4.9를 설치하면 대략 6.8Gb정도의 용량이 소요됩니다.

실제로 osx를 활용하실분은 20~30Gb정도 설정해 주셔야 여러 프로그램도 설치하고 편하게 사용

하실수 있을듯 합니다. 지금 호스트PC에서 디스크 파티션 타입을 FAT32를 사용하시는분은 거의

없을것이라 생각하지만 만약 FAT32를 사용하는 디스크에서 지정한다면 맨 아래를 선택하셔서

디스크를 2Gb씩 나눠 사용하도록 하시기 바랍니다. 물론 실제 이용시에 디스크 용량은 똑같습니다.

사용자 삽입 이미지

디스크 파일이 저장될 위치를 지정해줍니다. 아까 위에서 Vmware Location을 지정해준 곳과

동일하게 해놓는것이 편합니다.

사용자 삽입 이미지

Customize Hardware를 눌러 약간 수정을 하겠습니다.

사용자 삽입 이미지

혹은 Finish를 눌렀다면 초기화면에서 좌측의 Edit virtual machine settings를 통해 수정을 합니다.

여기서는 플로피 디스크 드라이브를 삭제하겠습니다. 메뉴가 직관적이라 add / remove 버튼을

통해 손쉽게 삭제 가능합니다. Mac에서는 플로피 드라이브를 사용하지 않기때문에 삭제를 해줍니다.

사용자 삽입 이미지

이제 Vmware에서 osx를 설치할 준비가 모두 끝났습니다.

2. Mac OSX 설치

인텔과 AMD CPU모두 왠만한 배포판에서 패치를 지원하는데 개인이 만든 배포판이 많아서 어떤

것은 설치가 안되기도 합니다. 아래의 토런트 파일을 다운로드 하면 세개의 토런트 파일이 있는데

각각 10.4.7, 10.4.8, 10.4.9 버전이 있습니다. 저의 경우는 10.4.9버전을 사용했습니다.

Mac_OS_X_10_4_9_Intel_SSE3_[JaS_10_4_8_AMD_Intel_SSE2_SS.torrent
JaS_Mac_OS_X_10_4_8_AMD_Intel_SSE2_SSE3_PPF_1_Defiant_di.torrent
Mac_OS_X_10_4_7_AMD_Intel_(JaS)_ISO_Repack-(4283690[1][1].2454.torrent


위는 파일 풀네임으로 어떤 패치가 이뤄져있는지 표시되있습니다.


토런트를 잘 모르시는 분들은 간단히 http://utorrent.com 에서 프로그램을 다운로드 받아

다운로드 받은 위의 파일을 클릭하시면 됩니다. 위의 세파일들은 그럭저럭 괜찮은 속도를 보입니다

당나귀나 이뮬처럼 사용자들이 모여있지 않으면 속도가 느리므로 빛의 속도는 기대하지 마시길..

모두 다운로드 하셨다면 Daemon툴등의 가상DVD 에뮬레이터 등을 사용해서 ISO이미지를 넣고

사용하셔도 되고 혹은 Vmware에서 ISO이미지를 직접 사용해서 설치 하시면 됩니다.

(Edit virtual machine settings에서 가능) 이제 Vmware의 상단의 플레이 버튼을 눌러

머신을 가동합니다.

사용자 삽입 이미지

가동시 F2를 눌러 Boot 메뉴에서 CD-ROM으로 부팅가능하게 우선순위를 변경해줍니다.

그뒤 F10을 눌러 저장하고 빠져나옵니다.

사용자 삽입 이미지

다음과 같이 설치 화면이 뜨면 10초가 다 지나기전 esc를 눌러줘야하는데 누르지 않으면

에러가 뜹니다,

사용자 삽입 이미지

이와 같은 에러가 생깁니다. 물론 별 문제 없으니 ctrl+del+insert를 눌러 재부팅 하거나 멈춤버튼을

눌러 껐다 켠뒤 다시 esc를 눌러줍니다.

사용자 삽입 이미지

성공하면 위와 같이 여러가지 읽어들이는 글귀들이 좌라락 뜹니다.

사용자 삽입 이미지

위와같이 설치 초기화면이 떠야됩니다. 당연한 말이지만 만약 뜨지 않고 멈춘다면 커널패닉일수

있는데 이경우 CPU가 설치 ISO에서 지원하지 못해서 일겁니다. 주언어로 한글 사용을 선택합니다.

사용자 삽입 이미지

설치를 준비화는 화면이 뜹니다.

사용자 삽입 이미지

인스톨러 메인화면입니다 컨티뉴 해줍니다.

사용자 삽입 이미지

설치할 디스크를 묻는데 아무것도 안뜹니다 당황할것 없습니다. Vmware의 디스크가 파티션

설정이 되있지 않아서 그럽니다.

사용자 삽입 이미지

위와 같이 상단메뉴에서 선택해줍니다.

사용자 삽입 이미지

위와 같이 처음에 만들었던 VM디스크를 선택해줍니다. 그뒤 오른쪽에서 Partition을 선택해주면

다음과 같은 화면이 뜹니다.

사용자 삽입 이미지

Volume Scheme에서 몇개로 파티션을 나눠줄지 선택하는 메뉴가 뜹니다. 최대 16개의 파티션으로

분할할수 있습니다. 여기서는 단일 파티션으로 1개를 선택했고 Name은 볼륨명이니 마음에 드는대로

써줍니다. 그아래 파티션 유형을 묻는데 맨 처음것을 선택합니다. 그뒤 좌측 하단 부위 Partition을

눌러주면 파티션 설정하면 디스크가 날라간다라고 하는데 알겠어 해줍니다. 모두 끝났으면

창의 빨간 버튼을 눌러 닫습니다.

사용자 삽입 이미지

파티션 생성을 마치고 나면 다음과 같이 볼륨을 선택할수 있게되는데 HDD그림을 누르고

컨티뉴 합니다.

사용자 삽입 이미지

이제 설치할 몇몇 가지를 체크해줍니다 X11은 나중에 설치 가능하지만 그냥 체크해주고 아래에

Jas... Intel이나 AMD둘중 자신의 CPU에 맞게 체크해줍니다. 그리고 대다수의 하드웨어를 지원

가능하도록 Support..을 체크해주고 Nvidia,ATI그래픽 카드를 지원하는 Titan을 체크해줍니다.

인스톨 버튼을 누르면 설치원본을 검증하게 되는데 시간도 오래걸리고 잘못된 이미지라면

어짜피 다시 받는수밖에 없으니 Skip해줍니다.

사용자 삽입 이미지

인스톨을 시작하게 됩니다. 베이스 시스템을 설치하는데에 예상시간 44분이지만 모두 설치하고나면

저의 경우 두시간 넘게 소요됐습니다. 가능하면 성능 좋은 시스템에서 사용바랍니다.;;

설치하는도중 멀티부팅용으로 사용하는 거의 수명이 다되서 골골대던 HDD가 또 말썽을 일으켰

습니다. 최근엔 상태가 더 심각해져서 사용중에 전원을 껏다켠것처럼 힘을 못받다 뻗어버리네요.

결국 이 다음화면부터는 스샷을 찍지 못했지만 인스톨이 끝나면 재부팅 되고 설치 완료입니다.

처음 부팅화면에서 몇가지 설정을 거친후 본 메인화면으로 넘어가는데 이 또한 어려운것이

전혀 없기때문에 넘어가도 괜찮을듯 합니다.

사용자 삽입 이미지

OSX의 부팅화면 역시 깔끔하네요.

사용자 삽입 이미지

꿩대신 닭으로.. 미리 설치해뒀던 레오파드 메인화면입니다.

(내용추가 : 이 VMware이미지는 외국에서 꽤 알려진 pcwiz판 레오파드 VMware이미지를
받은것으로 제가 직접 설치했다는 의미는 아닙니다. 물론 레오파드 플랫이미지로 제가따로
설치는 했었습니다만 지금은 쓰지 않고있네요. 따로 말씀하신분이 있어서 내용을 추가합니다.)


OSX를 Vmware를 통해 설치해서 활용까지 하고 싶으신 분들은 최소한 듀얼코어 CPU를 사용해야

할듯 합니다. 제 시스템에서 설치를 끝마치고 Vmware로 이것저것 해보려고 하면 너무 더뎌서

사용가치가 없었습니다. (P4 2.66Ghz) 그래도 해킨토시를 설치해보고 잠깐 사용해보니 편하긴

편하더군요. 하지만 십수년간 윈도우에 익숙해져서 파일 관리등에 있어서는 리눅스가 익숙하신

분들이 osx를 쓰시면 편할듯 합니다. 실제 시스템 상에서 설치하면 하드웨어가 제각각이라

설치가 실패하는 경우도 생길듯 합니다만 Vmware를 사용한다면 왠만해선 설치에 성공하리라

봅니다. 애플에서는 OSX의 해킹에 대해 하려면 해라식으로 놔두고 있는데 이에 대해서

사용자 의견이 분분합니다. 제 생각에는 뭔짓을 해서든 한번 어렵게 써보고 osx의 편리함을

느껴봐라 맥에서 안쓰니까 하드웨어 설치 잘 안되고 짜증나 죽겠지? 그러니까 맥 사서 써라 라고

하는듯 싶네요. 뭐 어찌됐건 많은 해킨토시 사용자들에 의해 패치가 이뤄지고 있으니

이렇게 한번씩 설치 해보시면 좋은 경험이 될듯 싶습니다.

I2C protocol

전에 유영창님이 마소에 쓴 칼럼을 포스팅하긴 했는데 내용이 리눅스 디바이스 드라이버 쪽이라 이번에는 protocol관점에서 간단하게 포스팅 하기로 했다.

I2C는 일종의 protocol이라 디바이스마다 동작이 상이할 수도 있지만 기본 개념만 알고있으면 스펙을 보고 쉽게 제어할 수 있을 것이다. 이제 부터 시작~!

I²C 는 필립스에서 개발한 직렬 컴퓨터 버스이며 마더보드, 임베디드 시스템, 휴대폰등에 저속의 주변 기기를 연결하기 위해 사용된다. I²C 라는 이름은 Inter-Integrated Circuit의 약자이며 "eye-squared-see[아이-스퀘어-씨]" 라고 발음한다.

I2C의 장점은 단지 2개의 wire로만 통신이 가능하다는데 있다.
1개의 wire는 SCL로 uni-direction이며 Master에서만 출력한다.
나머지 하나의 wire는 SDA로 bi-direction이며 Master와 Slave에서 In/out으로 동작한다.
(물론 Multi-master환경에서는 SCL역시 bi-direction으로 동작하나 여기서는 Single-master임을 가정한다.)

DIAGRAM


위의 그림과 같이 Master에는 여러개의 Slave가 SDA, SCL의 wire로 연결되어 있으며,
SDA, SCL에는 pull-up register가 달려있다.
각 Slave마다 Slave address가 존재하며 Slave에 따라 10bit또는 7bit으로 이루어져있다.
Slave Bit수에 의해 동일한 버스에 1024개 또는 128개의 Slave가 존재할 수 있으나 이중 16개는 예약되어있다.
(즉, 1008개와 112개가 최대이나 프로토콜일 뿐이므로 굳이 예약된 것에 대한 것이 잊어버리자-_ -b)

WAVEFORM


신호는 크게 START bit , DATA bits, STOP bit의 3가지로 나뉜다.

START와 STOP은 SCL이 HIGH일 때 변하며, DATA는 SCL이 LOW일 때 변한다.
(protocol이 진행 중이지 않을 경우에는 pull-up register에 의해 SDA, SCL모두 HIGH상태이다.)

START는 위의 파형과 같이 SCL이 HIGH일 때 SDA가 Falling하는 경우로 slave에게 protocol이 시작됨을 알린다.

STOP은 SCL이 HIGH일 때 SDA가 Rising하는 경우로 slave에게 protocol이 종료 됨을 알린다.

DATA는 SCL이 LOW일 때 SDA가 변하며 SCL이 HIGH일 때 SDA값이 valid하다.
또한 DATA bits는 ACK와 R/W신호를 포함한다.(R/W신호는 HIGH일 경우 READ, LOW일 경우 WRITE이다.)

I2C 전송은 마스터 입장에서 크게 2가지 경우로 나뉘다. Slave에 data를 write하는 Transfer와 Slave로 부터 read하는 receive의 경우이다.

START와 STOP, R/W 신호는 무조건 Master에서 생성하는 것이나 data와 ack는 Transfer인지 Receive인지에 따라 Master 혹은 Slave에서 생성한다.

I2C Transfer
다음은 0x33의 Slave address를 가지는 장치의 0x12번 register에 data 0x21를 write하는 예이다.
(노란색 부분은 Master가 발생시킨 신호이며 푸른색은 Slave가 발생시킨 신호임에 유의한다.)

  • Start+Slave Address :
    Master에서 start signal을 발생시킨 후 slave address를 버스에 던져 준다.
    각 slave device는 master가 던진 address를 자신의 address와 비교하여 맞지 않으면 아무 동작도 하지 않고(SDA는 pull-up에 의해 HIGH상태유지), 맞으면 ACK타이밍에 SDA를 LOW로 만들어 준다.
    (R/W signal은 write인 '0'임에 유의한다.)
  • Register Address :
    Master는 Slave address단계에서 ACK를 확인한 후 해당하는 register address를 다시 버스에 던지고 ACK신호를 기다린다.
  • DATA+Stop :
    Register Address단계에서 ACK가 발생했으면 Master는 다시 쓰고자 하는 Data값을 bus로 던지면 Slave는 ACK신호를 발생시키게 되고, Master는 그 후 Stop signal을 발생시켜 통신을 종료한다.

I2C Receive
다음은 0x33 Slave address의 0x12번 register를 read하는 예이다.

  • Start+Slave Address+Register Address :
    이 구간의 동작은 앞서의 Transger동작과 같다. R/W sinal이 우선 write인 '0'임에 유의한다.
  • Restart+Slave Address :
    Master는 Re-start signal을 발생 시키고 다시 Slave address를 READ signal('HIGH')와 함께 발생시키고,
    Slave는 ACK signal을 발생시킨다.
  • DATA + Stop :
    Slave는 해당하는 register의 값을 SDA를 통해 발생시키게 되고 Master는 ACK를 날린 후 Stop signal과 함께 통신을 종료한다.

위의 Transfer와 Receive의 예는 Texas Instruments의 TPS65010이라는 칩을 예로 든 것이다.
I2C를 사용하는 Slave device마다 대동소이한 형태의 통신을 하기때문에 I2C의 기본 개념과 해당 장치의 Spec만 잘 보면 I2C를 통하여 쉽게 장치를 컨트롤할 수 있을 것이다.

<참고자료 : wikipedia i2c, 유영창님의 "임베디드 리눅스로 I2C버스에 접근하자", tps65010.pdf>

사용자 삽입 이미지

최근 하는 삽질입니다. 아직 진행중이구요.
오늘 너무 달렸나 뒷골이 땡기네요. 날씨가 많이 쌀쌀해졌지요? ^^;


경로는 Adium.app/Contents/Resources/ko.lproj 이런식으로 넣어 주시고 아디움을 재시작 하시면 한글로 적용됩니다.
아직 다소 한글화 덜 되어 있는 부분과 어색한 부분 등이나 추가할 부분은 댓글로 알려 주시기 바라며 많은 테스트를 부탁드립니다.

추가: 캐티탈님이 알려 주신 몇가지에 대해 수정되었습니다.
+설치전 안내
아래의 포스팅을 보시고 osx 이미지까지 받으셨다면.. 이제 슬슬 달려 보겠습니다.
md5 체크를 해야 하나-_- 일단 달립니다.
빈 dvd를 한장 준비하시고 네로(nero 6이상)에서 가장 느린 속도로 레코딩을 해주세요.
보통 4기가 이상이라면 20분 이상 걸립니다. 만약 dvd-rw가 없다면.. 다음으로 패쓰!
dvd-r보다는 dvd-rw로 레코딩을 권장합니다. 행여 이미지가 잘못된 경우 설치중에 멈추는 증상이 발생하니까요.
레코딩이 완료되면 이미지 osx를 설치할 파티션을 만들어 줍니다.

미리 준비한 하드를 fat32로 포맷합니다. 포맷이 완료 되면 이제 레코딩한 dvd를 넣고 윈도우를 재시작 합니다.
단, 주의할점은 하드가 하나있고 파티션을 만들어서 하는경우라면 각별한 주위를 요구합니다. 또한 마우스와 키보드가 둘 다 usb여야 설치가 원할합니다.
먼저 xp가 설치되어 있고 백업 하드가 하나 있을경우 남은 파티션에 osx를 설치하면 되나, 파티션이 3개라면 문제는 달라집니다.
파티션의 순서에 따라 나중에 듀얼부팅이 되기도 하고 안되기도 합니다. 파티션이 3개인 경우 첫번째 파티션에 xp, 두번째 파티션에 osx, 마지막 파티션에 백업하드가 적당합니다. 또 파티션의 순서에 해당 문자열이 맞나 확인하세요. 현재 d드라이브지만 매일 나중에 만든 파티션이라면.. d드라이브가 백업 파티션이 되고 e드라이브가 osx파티션이 되야 합니다.
이 부분에서 꼭 신중을 기해 주세요. 잘못하면-_- xp까지 날리게 됩니다. 자신의 하드의 파티션 정보를 분명히 알고 가셔야 합니다. 또한 만약을 위해 xp를 ghost나 true imaes로 백업해 두세요.
물론 다른 하드에 설치하는경우라면 크게 걱정할 필요없습니다.
저는 다른 하드에 설치하는걸 권장하고 싶습니다-_-;;

-듀얼 부팅을 위한 작업?;;
우선 레코딩 씨디를 읽으면 chain0 이라는 파일이 있습니다. 이 파일을 C:\로 카피합니다. 그리고 C:\boot.ini 파일을 열어 주세요.
그리고 맨 아랫줄에 아래와 같이 추가해 주세요.
c:\chain0="Mac OS X 10.4.7(여기는 마음데로 적으시면 됩니다.)"
그리고 저장합니다. 0은 숫자영입니다.


-하드개 2개 이상인 경우
osx설치전에 메인 하드 즉(xp가 설치된 하드)를 제거합니다. 케이블을 빼어 두세요. 그리고 설치할 하드만 연결합니다. 설치가 완료 된 후에 다시 케이블을 연결하면 됩니다. 여기서 2하드의 마스터와 슬래이브 설정을 잘 해두세요. 만약 나중에 연결시에 osx하드가 마스터로 된다면-_-;; 컴퓨터를 끄고 xp가 설치된 하드만 연결하셔서 부팅하셨다가 다시 끄고 나중에 osx하드를 연결하시면 내츄럴하게 슬래이브로 연결되겠습니다.

-하드가 1개인 경우(각별히 주의를 요망) 만약을 대비해 위에서 말했듯이 acronis ture images 같은 녀석과 acronis disk suit 이라는 2개 유틸을 설치해 두세요. 첫번째 녀석은 백업을 하는 녀석이구요. 2번째 디스크를 관리/포맷/삭제 등을 합니다. 물론 2번째 유틸을 설치하시고 부팅가능한 CD를 만들어 주세요. 만드는 방법은 유틸을 실행하시고 공 CD한장을 넣고 bootable media? 던가 하는 메뉴를 누르면 만들어 줍니다. 물론 트루 이미지도 나중에 백업한걸 복원하기 위해서 한장 만들어 두세요.

+설치 시작

부팅하면 cmos설정에서 dvd을 첫번째로 부팅되게 지정하고 저장한 뒤에 나오세요.
그리고 dvd로 부팅이 시작 된다면.. 처음 다윈?이라 부르는 도스처럼 생긴 녀석이 등장합니다.
(8)부터 숫자를 카운트 다운합니다. 긴장하지 마시고 esc를 누르시면 구워둔 dvd 이름(jas 10.4.10 os x 어쩌고 이런식)과 하드가 보일겁니다. 여기서 dvd를 선택하시고 -v를 입력 후에 엔터를 눌러 주세요.
그럼 한참 영어가 올라오다가 mac의 기초 화면이 나올겁니다. 만약 나오지 않는다면 첫번째 포스팅에서 강조한 wiki 리스트에서 자신의 컴퓨터 사양을 확인하세요-ㅅ-;;


설치 언어를 고르는 부분이 나왔다면 한국어를 선택합니다. 그리고 설치를 진행하시고 하드를 선택하는 부분이 나옵니다. 물론 하드나 파티션이 보이지 않을 수 있습니다. 윈도우에서 작업 표시줄이라 부르는곳에서 디스크 유틸리티를 선택합니다.

사용자 삽입 이미지


사용자 삽입 이미지


파티션이나 혹은 하드의 이름을 입력하시고 지우기를 클릭하세요. 확장 저글링으로 포맷이 제대로 됬다면 디스크 유틸리티를 닫아 주세요. 그럼 설치화면 설치가능한 디스크,파티션이 보입니다. 그 뒤 설치 옵션에서 바로 뒤로 하지 마시고 자세히 보셔서 자신의 시스템에 맞게 패치를 선택해야 합니다. intel cpu이면 인텔 패치를, amd cpu면 amd cpu를 선택하시고, 그래픽 카드의 경우 역시 자신에 딱 맞는 드라이버가 있을 경우에만 선택하세요. ati라던지 nati, titan등 잘 보르시면 해당 패치를 누르면 아래 설명이 나오니 참고하시기 바랍니다. 프린터 드라이버는 자신의 보유 기종만을 선택하시고 랭귀지는 한국어만 선택합니다. 또 x11은 나중에 설치해도 되니 설치하지 마세요.

그럼 계속 설치합니다. 설치가 다 되셨으면 재시작을 하시면 xp부팅화면에서 아까 추가한 mac osx항목이 있을꺼에요. 만약 이 화면이 나오지 않고 mac 설치시에 나온 부트로더가 나온다면 esc를 누르시고 xp를 고르시면 기존 윈도우xp로 가실 수 있습니다. 허나 설치후에 재시작이 되지 않으면-_- 삽질을 시작해야 합니다. 오늘은 여기까지만 달립니다 :)

+ Recent posts