'모노'에 해당되는 글 39건

  1. 2013.10.20 Windows Azure Linux Virtual Machine과 docker를 이용한 Linux 기반의 C# 개발 환경 구축
  2. 2012.10.29 Mono 3.0 출시
  3. 2011.04.02 응용프로그램 가상화 종결자 - roozz
  4. 2010.12.30 Windows Azure BLOB Storage에 올린 파일을 Silverlight와 Flash에서 활용하려면?
  5. 2010.11.30 Delphi Prism, .NET, 그리고 iPhone App 개발
  6. 2009.01.16 Mono 2.2 출시
  7. 2008.07.20 리눅스에서 닷넷하기: MonoDevelop에서 만드는 Hello World
  8. 2007.12.08 xPlatform works in Linux!
  9. 2006.01.04 [팁] Verbatim String에 관해 알아둘 몇 가지
  10. 2005.10.18 .NET 프레임워크의 종류와 근황
  11. 2005.10.03 [C# 고급] 이벤트 처리기 작성하기
  12. 2005.07.17 [팁] Mono 1.1.x 빌드에서 C# 2.0 기능 활용하기
  13. 2005.07.17 C# 2.0의 구문 살펴보기 Part 3: Generic 클래스
  14. 2005.07.17 C# 2.0의 구문 살펴보기 Part 2: 익명 대리자
  15. 2005.07.16 C# 2.0의 구문 살펴보기 Part 1: partial 키워드
  16. 2005.06.17 [팁] using 구문의 또 다른 용법
  17. 2005.02.23 [ASP .NET] 세션을 편리하고 빠르게 사용하는 노하우
  18. 2005.02.11 OR 연산으로 조립이 가능한 옵션 매개 변수 만들기
  19. 2005.02.11 Boxing과 Unboxing의 모든 것
  20. 2005.02.07 주민등록번호 검사 메서드
  21. 2005.02.07 [팁] C#에서 이스케이프 시퀀스를 좀 더 편리하게 쓰는 방법
  22. 2005.02.05 SQL-Injection을 비즈니스 계층에서 해결하기
  23. 2005.02.01 .PLA 형식의 MP3 Playlist 작성
  24. 2005.01.09 C/C++ #define 정의문 내용 검색
  25. 2004.12.13 System.Object를 제대로 구현하는 클래스 만들기
  26. 2004.11.26 [중요] 리플렉션 강좌 #2
  27. 2004.11.22 [중요] 리플렉션 강좌 #1-1 (확장 변환과 손실 변환)
  28. 2004.11.21 [중요] 리플렉션 강좌 #1
  29. 2004.11.20 [팁] pinvoke.net 보다 중요한 프로그램 하나
  30. 2004.11.19 Windows에서 자주 쓰이는 프로그램 하나
IaaS2013. 10. 20. 22:30

Windows Azure의 Virtual Machine 서비스는 Windows와 Linux를 Guest OS로 지원하는 전형적인 IaaS 플랫폼입니다. 그리고 Linux는 다양한 오픈 소스 소프트웨어와 결합할 수 있는 매우 이상적인 소프트웨어 개발 환경이기도 합니다.

Linux에서 사용할 수 있는 매력적인 소프트웨어들 중 최근 큰 주목을 받고 있는 프로젝트로 docker 프로젝트가 있는데, 이 프로젝트는 기본적으로 Hypervisor 없이 Linux 실행 환경을 가상화하고 서로 격리된 상태로 실행할 수 있도록 도와주는 Linux Container (LXC)를 조금 더 실용적이고 사용하기 편리하게 만들어주는 컴패니언 소프트웨어로, 이미지를 공유하거나, 이미지로부터 생성하는 컨테이너를 만들기 위해 필요한 명령을 자동으로 등록하거나, 컨테이너를 이미지로 변환하는 등의 작업을 단순화합니다.

docker 프로젝트를 설치하고 사용하는 방법에 대한 가이드는 인터넷 상에 이미 다양한 자료로 게시된 적이 있고, 최근에 열린 NAVER 개발자 행사인 DeVIEW 2013에서도 다루어진 적이 있습니다. (http://www.slideshare.net/modestjude/docker-in-deview-2013)

오늘 살펴보려는 내용은 docker 프로젝트를 Windows Azure Virtual Machine 상에서 설치하고 이용하는 방법과 함께, docker Index에 게시되어있는 최신 버전의 Mono 이미지를 다운로드하고 활용하는 방법을 간단히 살펴보려고 합니다. 지금 소개하는 방법을 통해서 쉽고 빠르게 리눅스 기반의 다중 개발 환경을 손쉽고 빠르게 프로토타이핑할 수 있습니다.

시작하기 전에 - Hypervisor 안에서 또다른 가상 환경을 만드는 것은 불가능하지 않습니까?

docker를 일반적인 PC나 서버 환경이 아닌 곳에서 사용할 때 가장 먼저 드는 의문점이 바로 이것입니다. 기본적으로 Hypervisor 위에서 실행하는 OS는 또 다시 가상 환경을 만들어낼 능력이나 여건이 되지 못합니다. 가능하도록 설정했다고 해도 결국 물리적인 한계에 부딪힐 가능성이 커집니다.

사실, docker는 Hypervisor가 아니기 때문에 Hypervisor 고유의 CPU 기능을 활용하는 일은 거의 없고, 호스트가 되는 리눅스의 커널의 재량에 따라 그 안에서 실행되는 독립적인 프로세스일 뿐입니다. 그러나, 제아무리 docker가 유용하다고 해도, 시스템이나 VM에 할당된 자원의 밀도나 품질을 생각해보았을 때 docker가 실제 처리할 수 있는 작업의 양이 항상 효율적이라고는 할 수 없습니다. 따라서, docker를 사용한 시스템 구축은 전적으로 충분한 시나리오 테스트와 QA를 거쳐야만 함을 염두에 두어야 할 것입니다.

docker로 할 수 있는 일

docker는 단순히 프로세스만을 격리하는 것이 아니라 일정 수준의 가상 환경을 다룹니다. 즉, 자체적으로 이용할 수 있는 파일 시스템, 네트워크 어댑터, 가용 메모리 크기 등이 있고, 상황에 따라 이들 자원의 크기가 동적으로 변화하게 됩니다. 그러나 우리가 일반적으로 사용하는 완전한 Hypervisor와는 다르게 자원을 명시적으로 제한할 수 있는 방법이 2013년 10월 현재 기준으로 특별히 없습니다. 그리고 네트워크는 사설 IP로만 할당되기 때문에, docker가 자체적으로 구성하는 NAT를 이용하여 외부로부터 들어오는 네트워크 요청을 특정 컨테이너 앞으로 도착하도록 연결해주는 것이 꼭 필요합니다.

그럼에도 불구하고, docker는 리눅스 기반의 환경에서 항상 있을 수 있는 파일 시스템, 커널, 잘못된 라이브러리나 패키지 설치로 인한 시스템의 중단으로부터 안전하게 지켜줄 수 있고, 신뢰할 수 있을만한 수준을 제공하면서도, 가볍게 다룰 수 있는 가상 환경을 제공한다는 점에서 큰 주목을 받고 있으며, 심지어 Hypervisor 기반의 실행 환경 위에서 전혀 다른 Linux Guest를 추가 실행할 수 있을만큼 유연합니다.

docker Index (https://index.docker.io/)에 게시된 이미지들을 살펴보면 알겠지만 호스트의 Linux OS와 아무 관련이 없는 busybox 같은 응급 이미지도 존재하고, Ubuntu가 호스트인 시스템에서 CentOS를 컨테이너 OS로도 택하는 것이 가능합니다. 즉, docker 환경에 맞추어 개발된 OS이기만하면 docker와의 상호작용을 전제로 호스트 OS와 완전히 분리된 환경에서 실행이 가능하므로 독립적인 환경 구성이 가능함을 뜻합니다.

docker를 Azure Linux VM 위에 설치하기

docker는 LXC 프로젝트를 기반으로 하기 때문에, 커널 버전에 대한 의존성이 크고, 사용할 수 있는 Host OS의 종류에도 제한이 있습니다. 현 시점에서는 Ubuntu Linux 13.04에서의 실행이 가장 안정적이기 때문에, Windows Azure Virtual Machine 갤러리에서 다음 그림과 같이 Ubuntu Linux 13.04 기반의 VM을 하나 추가해서 시작하셔야 합니다. 만약 Ubuntu Linux 12.04나 12.10 버전을 실행 중인 경우 Secure Shell 터미널 환경을 통하여 원격으로 13.04로 업그레이드하는 명령을 실행할 수도 있지만, libcurse 기반의 UI 실행이 일부 필요하기 때문에 putty 등에서는 정상적으로 보이지 않을 수 있고, 또한 잘못 선택할 경우 VM에 원격 접속이 불가능한 상황이 올 수 있으므로 추천하지 않습니다.

http://manage.windowsazure.com/ 으로 접속하여 새 VM을 갤러리로부터 생성하도록 시작합니다. 아래와 같은 대화 상자가 나타나는지 확인한 후 Ubuntu Linux 13.04를 선택합니다.

 

다음 버튼을 클릭한 다음 기본 VM 설정을 입력합니다.

 

PuttyGen을 이용하여 키 체인을 만들어 업로드하거나, 사용자 암호를 지정하고 다음 버튼을 클릭합니다.

 

클라우드 서비스는 네트워크 연결 및 중재를 위한 기본 단위이자 실행 환경을 정의하는 단위 환경입니다. docker 환경과 연결하려는 클라우드 서비스나 다른 VM이 있을 경우 같이 소속되도록 설정해주시고, 가용성 설정 등을 확인한 다음 다음 버튼을 클릭합니다.

 

기본적으로 SSH 포트가 열려있습니다. 그러나 docker container와의 연결을 허용하고 외부에서 접속을 받아들이려면 Windows Azure의 방화벽 설정도 수정해야 합니다. 외부에서 docker container 서비스로의 접속이 안된다면 이쪽의 방화벽 설정을 꼭 확인하셔야 합니다. 마침 버튼을 클릭하여 VM 생성을 시작합니다.

 

VM의 외부 DNS 주소를 확인하고, putty로 접속을 시작합니다.

 

접속이 완료되었다면 이제부터 명령어 입력을 진행합니다. 설치 가이드의 내용은 http://docs.docker.io/en/latest/installation/ubuntulinux/#ubuntu-raring-13-04-64-bit 에서 발췌하였습니다.

우선 최신 패키지 목록을 가져오기 위하여 다음 명령을 수행합니다. 최신 릴리즈를 사용하여 VM을 만들었다면 별 다른 업데이트는 발생하지 않을 것입니다.

sudo apt-get update

그리고 Ubuntu 13.04 시스템들 중 일부 릴리즈에서 누락되어있을 수 있는 AUFS 파일 시스템에 대한 지원 (LXC 실행을 위해 꼭 필요합니다.)을 추가하기 위하여 아래 명령어를 수행합니다. 참고로 Azure Virtual Machine을 통하여 최신 릴리즈를 사용하도록 VM을 생성했다면 역시 AUFS에 대한 지원이 이미 포함되어있어서 아래 명령어로 시스템에 변화가 발생하지는 않을 것입니다.

sudo apt-get install linux-image-extra-`uname -r`

이제 docker 리포지터리에 접근하여 패키지를 설치할 차례입니다. 우선 키 체인을 다운로드하여 시스템에 등록하여 docker 패키지를 인터넷으로부터 다운로드할 수 있도록 허가합니다.

sudo sh -c "wget -qO- https://get.docker.io/gpg | apt-key add -"

OK라는 메시지를 확인하였으면 이어서 리포지터리 목록에 docker 리포지터리를 추가하기 위하여 다음 명령어를 실행합니다.

sudo sh -c "echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list"

이제 패키지 업데이트를 다시 수행하여 사용 가능한 패키지 목록을 갱신합니다.

sudo apt-get update

출력 중에서 다음과 비슷한 메시지가 들어있는지 확인합니다.

Get:3 http://get.docker.io docker/main amd64 Packages [1,395 B]

이제 패키지 목록을 업데이트하였으므로 lxc-docker 패키지가 사용 가능한 상태가 되었을 것입니다. lxc-docker 패키지를 아래 명령으로 설치합니다.

sudo apt-get install lxc-docker

종속 패키지들을 다수 설치해야 함을 알리는 메시지가 나타나면 y 키를 눌러 진행합니다.

설치가 잘 되었는지 확인해보기 위하여 docker Index 사이트로부터 ubuntu 게스트 OS 이미지를 다운로드해보겠습니다. 아래 명령어를 실행합니다.

sudo docker run -i -t ubuntu /bin/bash

아래와 같이 다운로드 메시지가 나타나고 콘솔이 바뀌는 것을 확인하기 바랍니다.

rkttu@dockertest:~$ sudo docker run -i -t ubuntu /bin/bash
Unable to find image 'ubuntu' (tag: latest) locally
Pulling repository ubuntu
8dbd9e392a96: Download complete
b750fe79269d: Download complete
27cf78414709: Download complete
root@f45ee37cf476:/#

호스트 이름이 Azure VM의 dockertest가 아니라 임의로 작명한 f45ee37cf476이라는 이름으로 바뀐 것을 볼 수 있습니다. 그리고 IP 주소 대역도 다르다는 것을 쉽게 파악할 수 있는데, ifconfig을 비롯한 네트워킹 도구가 이 버전의 이미지에는 들어있지 않기 때문에 다음 명령어를 실행하여 net-tools 패키지를 설치합니다.

apt-get install net-tools

격리 환경 상에서 자동으로 root 권한을 얻었으므로 sudo를 덧붙일 필요가 없습니다. 설치가 끝난 다음, ifconfig eth0 명령을 실행하면 다음과 같이 나타납니다.

eth0      Link encap:Ethernet  HWaddr fa:b2:4f:b9:15:84
          inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0
          inet6 addr: fe80::f8b2:4fff:feb9:1584/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:192 errors:0 dropped:0 overruns:0 frame:0
          TX packets:81 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:267507 (267.5 KB)  TX bytes:5591 (5.5 KB)

굵게 강조 표시한대로 사설 IPv4 주소가 할당되어있습니다. 그리고 패키지 업데이트와 업그레이드를 진행하여 인터넷 연결 상태를 다시 한 번 확인해봅니다.

apt-get update
apt-get upgrade

가상 환경에서 나가기 위하여 exit 명령을 입력하면 다음과 같이 원래의 Host OS로 프롬프트가 바뀌는 것을 볼 수 있습니다.

root@f45ee37cf476:/# exit
exit
rkttu@dockertest:~$

이제 방화벽 설정을 확인합니다. 기본적으로 Ubuntu는 ufw라는 방화벽 프로그램을 이용합니다. 그리고 Windows Azure 기본 구성 이미지에서는 ufw가 비활성화되어있고, Windows Azure의 방화벽이 외부로부터의 연결을 차단하며, Windows Azure 간 네트워크에는 제한이 없습니다. 아래 명령어를 입력하여 방화벽 상태를 확인합니다.

rkttu@dockertest:~$ sudo ufw status
Status: inactive
rkttu@dockertest:~$

그리고 방금 전 테스트를 위하여 받은 이미지와 그 이미지를 기반으로 실행한 컨테이너가 잘 등록되었는지 확인하여 설치 프로세스를 마무리합니다.

rkttu@dockertest:~$ sudo docker images -a
REPOSITORY          TAG                 ID                  CREATED             SIZE
ubuntu              12.04               8dbd9e392a96        6 months ago        131.5 MB (virtual 131.5 MB)
ubuntu              latest              8dbd9e392a96        6 months ago        131.5 MB (virtual 131.5 MB)
ubuntu              precise             8dbd9e392a96        6 months ago        131.5 MB (virtual 131.5 MB)
ubuntu              12.10               b750fe79269d        7 months ago        24.65 kB (virtual 180.1 MB)
ubuntu              quantal             b750fe79269d        7 months ago        24.65 kB (virtual 180.1 MB)
<none>              <none>              27cf78414709        7 months ago        180.1 MB (virtual 180.1 MB)

rkttu@dockertest:~$ sudo docker ps -a
ID                  IMAGE               COMMAND             CREATED             STATUS              PORTS
f45ee37cf476        ubuntu:12.04        /bin/bash           8 minutes ago       Exit 0

예상한 대로 이미지가 다운로드되어있고, 해당 이미지를 기반으로 /bin/bash 앱에 대한 컨테이너가 생성 후 실행되었으며 종료 코드 0으로 종료되었다는 결과 표가 보입니다.

mono 개발 환경 빠르게 구축하기

mono 개발 환경을 입맛에 맞게 구축하는 방법은 여러가지가 있습니다. 방금 전처럼 base image를 받아서 수작업으로 설치하거나, 그 과정을 서술하는 Dockerfile을 만들어 한 번에 일괄 실행하여 시스템의 설치를 전개하는 방식도 있을 수 있습니다. 그렇지만 docker를 개발 환경으로 이용하기 위해서 취할 수 있는 가장 좋은 방법은 docker Index 사이트에 게시된 최신 이미지를 확인하여 해당 이미지를 직접 로컬 시스템으로 Pull 하는 것입니다.

편의를 위하여 잠시 리눅스 콘솔에서 윈도 화면으로 되돌아온 다음 https://index.docker.io/ 로 접속하여 키워드로 mono를 지정하고 검색합니다.

 

 

Ubuntu 이미지를 이용하여 Mono 3.2.3을 설치하여 배포하는 이미지가 Docker Index에 올라와있습니다. 그 외에도, 닷넷 기반의 SOA 실행 및 개발 환경 구축을 편리하게 할 수 있도록 Service Stack과 연계한 리눅스 이미지도 보입니다. 우리가 사용하려는 것은 rwentzel/ubuntu-mono 이미지이므로 이 이미지의 이름을 기록합니다.

이제 다시 리눅스 콘솔로 되돌아가서 해당 이미지를 로컬 리포지터리로 다운로드하겠습니다. 다음 명령어를 실행하여 동기화를 시작합니다. 해당 이미지는 여러 차례 수정을 거쳐 만들어진 것이기 때문에 다운로드에 다소 시간이 걸리니 조금 오래 기다리셔야 합니다.

sudo docker pull rwentzel/ubuntu-mono

이미지 다운로드가 완료되었다면, 이제 이 이미지를 이용하여 컨테이너를 만들고 정말 안에 mono 개발 환경이 들어있는지 확인해볼 차례입니다. 아래 명령어로 이미지를 우선 확인합니다.

sudo docker images -a

설치한 이미지의 갯수가 매우 많아졌습니다. 그런데 주의할 것이 하나 있습니다. docker rmi 명령으로 이미지를 제거하는 것이 가능하지만, 꼭 필요한 경우가 아니라면 가급적 리포지터리나 TAG가 none으로 설정된 이미지를 임의로 삭제하는 일은 피해야 합니다. 콘솔에서는 잘 드러나지 않지만 이미지는 차이점 보관 방식으로 생성되어있고 일종의 트리 계층을 형성하기 때문입니다. 그래서 docker로 이미지를 만들 때 정말 중요하게 관리되어야 하는 이미지는 REPOSITORY나 TAG에 정확한 속성을 지정하여 쉽게 찾을 수 있도록 해주는 것이 중요합니다.

REPOSITORY             TAG                 ID                  CREATED             SIZE
rwentzel/ubuntu-mono   latest              2e8ec476cfd1        3 weeks ago         12.29 kB (virtual 2.627 GB)
<none>                 <none>              866ee2ba174c        3 weeks ago         12.29 kB (virtual 2.627 GB)
<none>                 <none>              9d58fdd1f145        3 weeks ago         28.67 kB (virtual 2.627 GB)
<none>                 <none>              48d313d93277        3 weeks ago         194.1 kB (virtual 2.627 GB)
<none>                 <none>              a6f3f7d033e8        3 weeks ago         1.151 GB (virtual 2.627 GB)
<none>                 <none>              0063533722a2        3 weeks ago         486.2 MB (virtual 1.476 GB)
<none>                 <none>              563cc9ce1df7        3 weeks ago         43.2 MB (virtual 989.9 MB)
<none>                 <none>              d963202bdca8        3 weeks ago         62.24 MB (virtual 946.7 MB)
<none>                 <none>              fa1d9d247e8a        3 weeks ago         27.46 MB (virtual 884.5 MB)
<none>                 <none>              e01eae29dae1        3 weeks ago         32.86 kB (virtual 857 MB)
<none>                 <none>              eb5606044c61        3 weeks ago         70.23 MB (virtual 857 MB)
<none>                 <none>              519b96c9a701        3 weeks ago         29.08 MB (virtual 786.8 MB)
<none>                 <none>              552c68e56d9a        3 weeks ago         46.38 MB (virtual 757.7 MB)
<none>                 <none>              17f3c8064eb6        3 weeks ago         25.59 MB (virtual 711.3 MB)
<none>                 <none>              0cb9cc3fc7b8        3 weeks ago         144.5 MB (virtual 685.7 MB)
<none>                 <none>              898780b65670        3 weeks ago         12.29 kB (virtual 541.2 MB)
<none>                 <none>              de6790a79ec8        3 weeks ago         158.2 MB (virtual 541.2 MB)
<none>                 <none>              29002fa46318        3 weeks ago         24.58 MB (virtual 383 MB)
<none>                 <none>              270acf4d2474        3 weeks ago         96.52 MB (virtual 358.5 MB)
<none>                 <none>              1d4aaea09576        3 weeks ago         81.83 MB (virtual 261.9 MB)
ubuntu                 12.04               8dbd9e392a96        6 months ago        131.5 MB (virtual 131.5 MB)
ubuntu                 latest              8dbd9e392a96        6 months ago        131.5 MB (virtual 131.5 MB)
ubuntu                 precise             8dbd9e392a96        6 months ago        131.5 MB (virtual 131.5 MB)
ubuntu                 12.10               b750fe79269d        7 months ago        24.65 kB (virtual 180.1 MB)
ubuntu                 quantal             b750fe79269d        7 months ago        24.65 kB (virtual 180.1 MB)
<none>                 <none>              27cf78414709        7 months ago        180.1 MB (virtual 180.1 MB)

위의 rwentzel/ubuntu-mono 이미지를 사용하여 내부에 들어있는 /bin/bash를 컨테이너로 실행하기 위하여 아래 명령을 실행합니다.

sudo docker run -i -t rwentzel/ubuntu-mono /bin/bash

그러면 다음과 같이 프롬프트가 변경되는 것을 볼 수 있습니다.

rkttu@dockertest:~$ sudo docker run -i -t rwentzel/ubuntu-mono /bin/bash
root@f739c613d0ae:/#

이제 간단한 C# 소스 코드를 작성하여 프로그램으로 컴파일하고 잘 작동하는지 확인해보겠습니다. vi를 사용해도 좋고, vi 사용에 익숙하지 않은 경우 아래와 같이 명령을 실행하여 pico/nano 에디터를 추가 설치할 수 도 있습니다.

apt-get install nano

Hello.cs 라는 소스 코드를 원하는 에디터로 아래와 같이 작성합니다.

using System;
using System.Linq;
using System.Collections.Generic;

public static class Program {
        [STAThread]
        public static void Main(string[] args) {
                var count = args.Count();
                List<string> options = args.Where(x => x.StartsWith("-")).ToList();
                Console.Out.WriteLine("Hello, World!");
                Console.Out.WriteLine("Total Args: {0}, Option Args: {1}", count, options.Count);
        }
}

Mono 3.2.3은 LINQ와 제네릭을 모두 잘 지원하므로 위의 코드를 아래와 같이 컴파일하였을 때 문제없이 컴파일이 완료될 것입니다.

mcs Hello.cs

그리고 JVM과 마찬가지로 Mono VM을 실행하여 컴파일한 어셈블리 파일을 실행해봅니다.

root@f739c613d0ae:/# mono Hello.exe
Hello, World!
Total Args: 0, Option Args: 0
root@f739c613d0ae:/# mono Hello.exe a b c
Hello, World!
Total Args: 3, Option Args: 0
root@f739c613d0ae:/# mono Hello.exe a b -c
Hello, World!
Total Args: 3, Option Args: 1

의도한 대로 LINQ와 제네릭을 잘 받아서 처리하고 있습니다.

mono 실행 속도 개선하기

최신 버전의 mono는 실행 속도를 개선하기 위하여 가비지 컬렉터를 새롭게 디자인하였고, 전처리 컴파일을 미리 수행하는 방법을 제공합니다. 특히, Microsoft .NET Framework와 마찬가지로 GAC에 대해 ngen을 수행하는 것과 비슷하게 AOT 컴파일을 미리 수행하도록 명령어를 한 번 실행해주면 실행 속도 개선에 큰 도움이 됩니다. 아래 명령어를 실행하여 GAC 내부의 모든 어셈블리에 대해 AOT 컴파일을 실행합니다.

for i in /usr/local/lib/mono/*/mscorlib.dll; do mono --aot $i; done
for i in /usr/local/lib/mono/gac/*/*/*.dll; do mono --aot $i; done

위의 이미지 환경 내에서의 mono 설치 경로는 /usr/local에 있으므로 경로는 mono 실행 파일의 위치를 which 명령으로 확인하여 적절하게 변경해야 합니다. 참고로 GAC에 대한 AOT 컴파일은 시간이 오래 걸릴 수 있으며, 이 버전의 가상 환경에서는 .NET Framework 1.0이나 1.1 기준으로는 개발이 불가능하므로 버전을 업그레이드 하거나 base image로부터 구 버전의 mono를 설치하도록 수동 구성해야 합니다.

그리고 개별 어셈블리에 대한 AOT 컴파일은 다음과 같이 실행할 수 있으며, 실행 결과로 .so 파일이 생성되므로 AOT 컴파일의 효과를 위하여 항상 같은 위치에 배포될 수 있도록 배포합니다.

mono --aot Hello.exe
mono Hello.exe

마무리

지금까지 Windows Azure Linux VM에서 docker를 이용한 mono 개발 환경의 구축 방법을 살펴보았습니다. 언제든 원하는 때에 즉시 Linux VM을 만들 수 있다는 것 말고도, 한 번 만든 VM을 손상시키지 않으면서 환경을 독립적으로 구성할 수 있도록 하는 환경 상의 완결성을 제공하는 docker를 이용함으로서 최상의 리눅스 개발 환경을 체험할 수 있게 된 것은 참 좋은 일입니다.

그리고 한 가지 더 덧붙이면, docker를 이용하여 컨테이너를 만들 때 -v 스위치를 사용하여 호스트 파일 시스템과 링크를 연결할 수 있으므로, 컨테이너 셸 상에서 만든 파일을 쉽게 호스트로 반출하거나 반입할 수 있습니다. -p 스위치는 가상 NAT를 통하여 포트 리디렉션을 할 수 있는 방법을 제공하며, 양쪽 스위치의 자세한 사용법은 http://blog.docker.io/2013/07/docker-0-5-0-external-volumes-advanced-networking-self-hosted-registry/ 의 내용을 확인하기 바랍니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

기술 소식2012. 10. 29. 02:06

오랜 기간을 거쳐 Mono가 드디어 3.0 버전에 진입하였습니다. 여러가지 악재가 있었음에도 포기하지 않고 지속적으로 Mono 프로젝트를 이끌어나가시는 여러 개발자분들의 노고에 응원을 드리며 즐겁게 블로그 포스팅을 올립니다. 이번 Mono 릴리즈는 다른때보다도 더 의미가 깊고 강렬합니다. 무엇보다도, Windows Azure를 포함하여 국내외 여러 클라우드 컴퓨팅 환경 상에서 Mono를 다양하게 활용할 수 있다는 것은 바람직한 일이라고 할 수 있겠습니다. 특히 서버 사이드에서의 .NET 기반 개발 환경은 더 이상 Windows에만 국한되는 것이 아닙니다.

Mono 3.0은 Mono 2.10 브랜치를 계승하는 메이저 업데이트로 다음의 주요 변경 사항들을 포함합니다. 정보의 출처는 공식 릴리즈 노트 (http://www.mono-project.com/Release_Notes_Mono_3.0) 입니다.

비동기 프로그래밍 모델을 지원하는 새 C# 컴파일러

비동기 프로그래밍 모델의 원형은 .NET 4.0의 소개와 함께 TPL (Task Parallel Library)로 이미 소개된 이력이 있습니다만 이것을 C# 4.0의 다음 세대 언어인 C# 5.0에서 언어 차원에서 자연스럽게 통합하기 위한 노력이 있었는데 이것이 Mono 3.0의 C# 컴파일러부터 온전하게 지원됩니다. 즉, async~await 키워드의 사용이 가능해져서 좀 더 자연스럽게 비동기 프로그래밍의 이점을 여러 플랫폼으로 확장할 수 있게 됨을 뜻합니다.

복잡하고 정신없던 컴파일러 별칭의 구조 조정

오픈 소스의 특성상, 그리고 여러 플랫폼을 동시에 지원해야 했기 때문에 긴 시간 동안 고수되어왔던 컴파일러 별칭의 혼재가 드디어 정리됩니다. mcs라는 단일 컴파일러 유닛으로 정리되고 -sdk 플래그를 사용하여 특정 프로필을 목표로 컴파일 방법 및 라이브러리를 정의할 수 있습니다. 그리고 이와 동시에 특정 프레임워크의 버전의 영향을 받지 않는 IKVM.Reflection 기반의 코드 생성 체계로 변경되어 더욱 유연하고 단순한 Code Emitter를 만들어 컴파일러를 단순화한 것이 큰 변화입니다.

개선된 Evaluator API

Mono의 유용한 명령줄 도구인 C# Interactive Shell의 핵심인 Evaluator 클래스의 Eval 메서드에서 이제는 단순 표현식이 아니라 각종 Type (Class, Structure, Interface, Delegate, Namespace)들을 직접 명령문 세그먼트에서 해석하고 처리할 수 있도록 기능이 확장되어 더 강력한 코드 작성이 가능해졌습니다. 그리고 Evaluator 클래스는 이제 싱글턴이 아니라 인스턴스화가 가능한 유틸리티로 기능이 개선되어 단일 코드에서 다중 컴파일러를 사용할 수 있는 막강한 확장성을 보유하게 되었습니다.

Microsoft 기술과의 호환성 향상

.NET Framework 4.5의 출시에 따라 Microsoft 기술과의 차이가 생기는 부분이 있었는데, 이 부분에 대해 적극적으로 지원을 하게 되어 아래와 같은 부분에서 확실한 호환성을 기대할 수 있습니다.

  • 새로운 비동기 메서드
  • WinRT 호환 API
  • System.Net.Http
  • System.Net.Http.Formatting
  • System.Thrading.Tasks.Dataflow
  • System.Web.Http
  • System.Web.Razor (Razor 문법 지원)
  • System.Web.WebPages (ASP.NET Web Pages 2 지원)
  • System.Web.WebPages.Deployment
  • System.Web.WebPages.Razor (ASP.NET Web Pages 2 Razor 지원)
  • System.Web.Mvc.dll (ASP.NET MVC4 지원)
  • System.Json.dll (Microsoft 구현으로 대체)
  • EntityFramework.dll

향상된 가비지 컬렉터

가비지 컬렉터의 새 구현체인 SGen이 좀 더 전면에 적극적으로 등장하고 활용 폭도 넓어졌습니다. 이전에 사용하던 Boehm GC를 대체하는 것으로 다중 프로세서 지원, Win32 지원, MIPS 지원, Mac OS X 환경에서 Mach API와 직접 연동을 통한 성능 향상 등 장족의 발전이 있었고 이번 3.0 릴리즈에서 크게 활약할 것으로 보입니다.

그 외 개선된 사항들

  • 리눅스에서 System.Net.NetworkInformation.NetworkChanged 사용 가능
  • 유니코드 지원 개선 및 성능 향상
  • ThreadLocal<T> 성능 향상, List<T> 성능 향상
  • Mac OS X에서 System.IO.DriveInfo를 올바르게 사용할 수 있음
  • Mono.Data.Sqlite에서 스레딩 모델 설정 가능 및 iOS의 암호화 API 지원

좀 더 자세한 정보는 http://www.mono-project.com/Release_Notes_Mono_3.0 에서 확인해보실 수 있습니다. 이 글을 쓰는 시점에서는 Mac OS X 패키지만 공개되었지만 2012년 11월 중으로 Windows와 Linux에서도 사용 가능한 패키지가 빌드될 것으로 보입니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Exploring2011. 4. 2. 21:40

[Internet Explorer에 대한 내용을 정정하였습니다.]

이전부터 포터블 응용프로그램을 만들기 위한 시도는 많이 있었고 최근들어서는 이러한 시도들이 가상화 기술의 한 장르로 발전하게 되었습니다. 대표적으로 Thinstall이 과거에 있었는데 이 솔루션은 VMware ThinApp으로 재탄생했지요. 그리고 Microsoft 역시 App-V 기술을 제공하고 있습니다. 그런데 오늘은 이러한 응용프로그램 가상화 솔루션에 클라우드 기술, 그리고 적절한 사용자 인터페이스까지 결합한 해외 서비스 하나를 소개해볼까 합니다. 바로 roozz입니다.


roozz는 Firefox와 Google Chrome 브라우저에서 사용할 수 있는 전용 플러그인을 다운로드받아 설치하고, Application Feed 페이지를 방문하기만 하면, Application이 Windows 기반이었든, Linux 기반이었든 관계없이, 거기에 roozz 플러그인을 설치하고 실행하는 컴퓨터 역시 Windows이든 Linux이든 관계없이 가상화된 소프트웨어가 별도의 격리된 공간 안에서 실행됩니다. 뿐만 아니라, 응용프로그램 가상화를 기반으로 하기 때문에 3D 게임까지 지원됩니다.

제가 평소에 애용하는 개발 도구 중 하나인 LINQpad 역시 roozz에서 호스팅되는 응용프로그램 중 하나입니다. 시험 삼아서, roozz에서 호스팅되는 LINQpad를 직접 실행해보았습니다. 그리고 편의를 위하여, Google Chrome의 웹 어플리케이션 생성 기능을 LINQpad roozz feed page에 대해 적용하여 실행해보았습니다. 그 결과 아래와 같이, 마치 Native application과 같은 UI가 구현되는 것을 볼 수 있습니다.


.NET Framework를 사용하는 응용프로그램임에도, LINQ expression을 잘 실행하고 있는 것이 보입니다. .NET Framework 기반의 응용프로그램을 이와 같이 가상화를 통하여 원활하게 사용할 수 있다는 것은 Windows 개발자에게도 굉장한 메리트가 아닐 수 없습니다. 그런데 여기서 한 가지 궁금한 점이 생깁니다. 지금 이렇게 보는 화면이 원격지 서버의 화면을 기반으로 하는 것은 아닐까 하는 것이지요. 시험삼아서 파일 저장과 로드 기능을 테스트해보았습니다.


터미널 서비스나 Citrix Xen과 같은 VDI나 Remote Session 서비스와는 달리, 로컬 컴퓨터에 직접 저장하는 대화 상자가 나타납니다. 그리고 한 가지 더 확실한 증거가 있는데, 작업 관리자의 메모리 사용량을 보면 알 수 있습니다.


커서를 가져다 놓은 부분에서 알 수 있듯이, LINQpad 자체는 원격지가 아닌 현재 컴퓨터의 메모리를 사용하고 있습니다. 단, 응용프로그램 가상화를 기반으로 하므로 .NET Framework를 메모리에 올려서 사용하기 때문에 다소 메모리 사용량이 많은 것이 보입니다. 하지만, 메모리 사용량과는 별개로 이러한 수준의 기능을 제공할 수 있는 서비스라고 한다면 상당히 괜찮지 않을까 생각합니다.

roozz에 이와 같이 소프트웨어를 게시할 수 있는 방법은, 소스 코드가 아닌, 컴파일된 - 혹은 - 패키징된 소프트웨어 사본을 roozz에 전송하는 것입니다. 이렇게 전송된 사본은 roozz Conversion Tool에 의하여 재배포 가능한 형태로 구성되며 이것을 사용자가 받아볼 수 있는 형태로 게시됩니다. 좀 더 자세한 내용은 http://www.roozz.com/node/3 에서 확인할 수 있습니다.

Roozz로 게시된 LINQpad를 사용해보시려면, Google Chrome이나 Firefox로 아래 웹 사이트를 방문해 보시면 됩니다. 최초에 Roozz Plugin을 설치한 후 페이지를 새로 고치면 됩니다. 아쉽게도, 현재 Internet Explorer는 지원되지 않는다고 합니다. Internet Explorer의 경우 보호 모드때문에 roozz 플러그인이 곧바로 실행되지는 않으며, 적어도 신뢰할 수 있는 사이트 목록에 http://prod.roozz.com 을 추가한 이후에야 아래와 같이 동작합니다. Internet Explorer 9에서도 잘 동작합니다. :-)

http://prod.roozz.com/apps/61/LINQPad.htm

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Azure Storage/Database2010. 12. 30. 13:29

Windows Azure BLOB Storage는 클라우드 기반의 저장소이지만 외부에서 사용하기에 무척 편리하고 유용한 HTTP 및 HTTPS 프로토콜을 기반으로 서비스가 제공됩니다. 대화형 서버 처리 엔진이 존재하지 않는다는 점만 제외하면 HTTP 기반의 서비스를 구축하기 위해서 필요한 모든 사항이 제공되며, 파일 이어받기, ETAG 처리 등 HTTP 프로토콜의 최신 사양들을 정확히 구현하고 있습니다.

최근에 저는 Windows Azure Platform의 Storage Service를 활용하면서 또 한 가지 좋은 정보를 얻을 수 있었습니다. Windows Azure Storage를 사용하면서 가장 필요한 것 중 하나는 RIA 응용프로그램과 충분히 활용하는 전략인데, 여기에는 Smooth Streaming 뿐만 아니라 일상적으로 활용 가능한 이미지, 사운드, 비디오, 텍스트 파일들도 포함될 수 있습니다. 그러나 익히 알려진대로 보안 상의 이유 때문에 RIA 플랫폼 자체적으로는 접근하려는 원격 웹 서버의 루트 경로 상에 반드시 매니페스트 파일이 존재해야 합니다. 하지만 일반적인 클라이언트나 API를 이용해서는 우리가 아는 루트 경로 상에 BLOB 객체를 올릴 수 없습니다. 어떻게 하면 좋을까요?

여기에 대한 답이 있습니다. Windows Azure Storage는 루트 경로에 대한 예약된 컨테이너 이름을 제공합니다. 바로 $root 인데, 최상위 컨테이너 중 $root라는 이름의 컨테이너를 생성하고 여기에 파일을 업로드하면 바깥에서 보기에 루트 경로에 올라온 것과 동일하게 취급됩니다. 여기에 RIA 응용프로그램이 필요로 하는, Adobe Flash의 경우 crossdomain.xml 파일, Microsoft Silverlight의 경우 clientaccesspolicy.xml 파일을 text/xml MIME 형식으로 정확히 지정하여 업로드하면 문제가 해결됩니다.

crossdomain.xml 파일과 clientaccesspolicy.xml 파일의 예제는 휴즈플로우의 CTO이신 이길복 MVP님의 블로그 아티클 (http://gilverlight.net/2888)을 참조하시면 되겠습니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Exploring2010. 11. 30. 22:24

오늘 도착한 ZDNET 코리아 메일링 리스트를 통해 아주 흥미로운 웹 캐스트가 하나 게시되었습니다. Delphi Prism은 Embacadero의 최신 Rapid Application Development Tool로서 종전의 Delphi .NET에 대한 업그레이드 제품이자, RemObjects사의 Object Pascal 제품의 최신 버전입니다. Visual Studio Shell을 통하여 이전 버전의 Delphi IDE에서 사용헀던 .NET 개발 환경때 보다 더 풍부하고 확장된 기능을 제공하기도 하고, 특히 이번 XE 버전에서는 MonoDevelop 기반의 IDE도 동시에 지원하고 있어서 기능성이 매우 돋보이기도 합니다.

이 동영상에서 소개하는 기술들에 대해 간단히 요약하면, 리눅스, 솔라리스, 맥 OS X에서 데스크 탑 및 서버 닷넷 프레임워크 대체 구현을 제공하는 Mono 프레임워크 (http://mono-project.com/Downloads), 상용 제품군으로 판매되는 MonoTouch (http://monotouch.net/) SDK, 맥 OS X 환경에서 구동 가능한 iPhone 및 iPad SDK (http://developer.apple.com/iphone/), 그리고 Embacadero Delphi Prism XE (http://www.embarcadero.com/products/delphi-prism)를 활용하여 iPhone과 iPad에서 실행 가능한 응용프로그램을 디자인하는 것입니다. 이 중에서 상용 라이선스가 필요한 것은 MonoTouch와 Delphi Prism XE가 되겠습니다.

만약 Delphi가 아닌 C#을 이용하여 응용프로그램을 개발하기 원한다면 Delphi Prism XE 대신 MonoTouch와 함께 제공되는 기본 IDE인 MonoDevelop 만으로도 충분한 개발이 가능합니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

기술 소식2009. 1. 16. 21:07

Mono 2.2가 새로 발표되었습니다. 이번 버전에서도 꾸준히 업그레이드되고 나아지는 모습을 보여주고 있습니다. 이번의 릴리즈 노트를 요약한다면 Mono에 대하여 제기되어왔던 성능 문제를 해결하기 위하여 상당히 많은 노력을 기울이고 있는 듯 보입니다.

  • 새로운 중간 언어 처리 엔진인 선형 엔진 (Linear IL)을 도입하였습니다. (http://www.mono-project.com/Linear_IL)
  • 전체 선행 컴파일 (Full AOT) 기능이 구현되었습니다. (http://www.mono-project.com/AOT)
  • 정적 링크 기능이 지원됩니다.
  • 성능 모니터 기능이 구현되었으며, mperfmon이라는 GUI 프로그램으로 시각적으로 내용을 살펴볼 수 있게 되었습니다.
  • SIMD 연산이 구현되었습니다. (http://go-mono.com/docs/monodoc.ashx?tlink=0@N%3aMono.Simd)
  • Mono.CSharp.Evaluator 클래스를 새로 추가하였고 컴파일러의 기능을 응용프로그램의 일부로 가져오기가 더욱 쉬워졌습니다.
  • C# 언어를 이용한 대화형 셸의 콘솔 버전과 GUI 버전이 제공됩니다. (http://www.mono-project.com/CsharpRepl)
  • 컴파일러가 생성하는 빈 문자열에 대한 참조를 모두 System.String.Empty 인스턴스로 통일하여 효율성을 높였습니다.
  • C# 컴파일러의 기본 경고 수준이 최고 수준인 4로 변경되었으며, 이전보다 C# 컴파일러의 오류 감지 및 오류 복구 능력이 향상되었습니다.
  • regex-to-CIL 엔진의 도입으로 정규 표현식 처리 성능이 좋아졌습니다.
  • ASP.NET 3.5 SP1의 MVC를 고려하여 라우팅 핸들러만이 우선 새로 추가되었습니다.
  • 첫 Windows Forms 2.0 버전 이후 보고된 약 200여개 가까운 수의 버그를 수정하였으며, Win32/Win64 환경에서 Application.EnableVisualStyles() 메서드를 이용하여 루나 테마나 에어로 테마를 적용할 수 있게 되었습니다.

출처: http://www.mono-project.com/Release_Notes_Mono_2.2

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Linux + .NET2008. 7. 20. 23:24

이번 강좌에서는 Mono를 이용하여 Hello World 콘솔 응용프로그램을 만들어보고 MonoDevelop와 Visual Studio의 차이점을 살펴보기로 하겠습니다. Visual Studio가 Windows용 응용프로그램을 위하여 최적화된 환경을 제공한다면 MonoDevelop는 Linux 응용프로그램, 정확하게는 Gnome Desktop을 위하여 최적화된 환경을 제공합니다.

이번 강좌에서는 지난번에 이어 VMware 기반 Mono 패키지를 이용합니다. 다음의 웹 사이트에서 Mono 패키지와 VMware Player를 무료로 다운로드받으실 수 있습니다.

새 프로젝트 만들기

VMware 패키지를 부팅한 이후에 바탕 화면에 MonoDevelop 아이콘이 있을 것입니다. 이것을 더블 클릭하면 MonoDevelop IDE가 시작됩니다.

사용자 삽입 이미지

위의 화면에서 Start New Solution을 클릭하면 아래와 같은 대화 상자가 나타납니다.

사용자 삽입 이미지

Visual Studio의 새 프로젝트 만들기 대화 상자와 거의 비슷합니다. 하지만 Visual Studio와는 달리 Boo와 같은 신흥 닷넷 기반 스크립트 언어와 MSIL에 대한 직접 지원 (ILAsm), Java 프로그래밍, NUnit 프로젝트 등을 지원합니다. 패키징 프로젝트는 Visual Studio가 제공하는 설치 프로젝트의 성격을 가지지만 오픈 소스 프로젝트를 진행하는 사례가 많음을 감안하여 소스 코드 패키징까지 지원합니다.

Packaging

C# Console 프로젝트를 선택하고 프로젝트 이름을 HelloWorld로 입력하여 프로젝트 생성을 시작하면 새 프로젝트에 대한 상세 설정 대화 상자가 나타납니다. 크게 세 가지 설정이 존재하는데 일단 Packaging 설정부터 살펴보기로 하겠습니다.

사용자 삽입 이미지


Archive of Sources

프로젝트 소스 코드를 패키징하는 방법을 정의합니다. 오픈 소스 프로젝트를 많이 진행하는 경우를 감안한 MonoDevelop만의 독특한 설정입니다.

  • MonoDevelop Project Model Sources: 패키지의 압축을 풀었을 때 바로 MonoDevelop에서 열 수 있도록 패키징합니다.
  • SharpDevelop 1.0 Project Model Sources: 패키지의 압축을 풀었을 때 바로 무료 Windows/.NET 기반 오픈 소스 IDE인 SharpDevelop에서 열 수 있도록 패키징합니다. 여기에는 만드려는 MonoDevelop 프로젝트 파일의 형식 변환이 같이 포함됩니다.
  • Visual Studio .NET 2005 Sources: 패키지의 압축을 풀었을 때 바로 Visual Studio 2005 이상의 IDE나 msbuild 유틸리티에서 열 수 있도록 패키징합니다. 여기에는 만드려는 MonoDevelop 프로젝트 파일의 형식 변환이 같이 포함됩니다.

Archive of Binaries

  • Linux Binaries: GTK# 등의 크로스 플랫폼 라이브러리나 런타임이 있을 경우 이것들의 리눅스 버전을 모아서 패키징하기 위한 설정입니다. 이후에 패키징 대상을 수기로 변경할 수 있습니다.
  • Windows Binaries: GTK# 등의 크로스 플랫폼 라이브러리나 런타임이 있을 경우 이것들의 Windows 버전을 모아서 패키징하기 위한 설정입니다. 이후에 패키징 대상을 수기로 변경할 수 있습니다.

Tarball: 관련 소스 코드와 바이너리를 타르볼 파일 (.tar 시리즈)로 압축합니다.

Unix Integration

사용자 삽입 이미지

Generate Launch Script: PE 기반 MSIL 혼용 이미지를 만들어내는 것이므로 사실 만들어진 exe 파일 자체는 여전히 mono 런타임에 의하여 별도로 취급되어야 합니다. 이 과정을 단순화하기 위하여 별도의 스크립트를 생성할 수 있는 데 이 작업을 자동화해줍니다. 프로젝트 이름과 다르게 스크립트 이름을 지정할 수 있습니다.

Generate .desktop File: GNOME 노틸러스를 위한 단축 아이콘 파일 (Windows의 Explorer Shell과 대응됩니다. Windows의 단축 아이콘이 .lnk 확장자를 가지는 것과 같습니다.)을 생성하도록 할 수 있습니다. 이 설정 또한 스크립트를 만드는 것과 같은 용도이지만 GNOME 노틸러스를 위한 부가 설정이 포함됩니다.

Translation

사용자 삽입 이미지

Mono는 다른 Linux 기반 응용프로그램들과 같은 방법으로 지역화 기능을 제공합니다. Microsoft .NET Framework가 사용하는 지역화와는 다른 방법으로 프로그램과 함께 배포되는 PO (Portable Object) 파일을 i18n 라이브러리로 가져오는 방식입니다. 이 설정을 통하여 국적 및 국가 코드 별로 PO 파일을 미리 구성할 수 있습니다.

사용자 삽입 이미지

언어와 국적 코드를 설정하는 단계에서 User Defined Locale 설정을 이용하여 단순한 언어 번역을 위한 용도 외에도 사용자 정의 메시지 테이블을 구성할 수 있습니다.

Gtk# Support

사용자 삽입 이미지
 

프로젝트에 Gtk# 설정을 추가하고 대상 라이브러리 버전을 지정할 수 있습니다. 별도로 Mono SVN Repository에서 소스 코드를 체크아웃하여 가져오거나 Night Build (개발자에 의하여 짧은 시간 내에 Commit되는 검증 수준이 약한 최신 빌드)를 설치한 경우가 아니면 버전이 보통 한 개입니다.

여러 설정이 있었지만 우리는 여기서 아무런 설정을 택하지 않고 프로젝트를 만들어보기로 합니다. 하나씩 하나씩 다음 Chapter에서 짚어보기로 합시다.

첫 프로젝트 프로그래밍

사용자 삽입 이미지

우리가 예상하던것과 다르지 않게 프로젝트가 만들어지고, 편집과 테스트가 가능한 환경이 생성되었습니다. 리눅스 환경이라고는 해도 C# 코드를 보니 별로 어색하지 않습니다. Visual Studio와 유사한 Keyboard Scheme를 제공하므로 Console.WriteLine 메서드에 출력할 메시지를 Hello World!로 바꾸고 F5키를 눌러보기로 하겠습니다.

사용자 삽입 이미지

Visual Studio와는 조금 다르게 하단에 콘솔 프로그램의 실행 결과가 표시됩니다. GNOME의 경우 Windows에서처럼 기본 콘솔 인터프리터가 통일되지 않습니다. 하나도 설치되지 않았을 수도 있고, 수십 개가 설치된 상태일 수도 있습니다. 그래서 기본 설정은 이와 같이 표준 출력과 표준 오류 출력 스트림 (stdout, stderr)을 리디렉션하여 출력 결과를 보여주는 방식을 사용합니다.

사용자 삽입 이미지

프로젝트 디렉터리 구성 살펴보기

그러면 우리가 만든 프로젝트 파일들의 구성이 어떻게 되는지 한 번 살펴보기로 하겠습니다. 프로젝트 디렉터리는 위와 같이 사용자 프로파일 디렉터리 (이 가상 머신의 경우 /home/linux입니다.)에 솔루션 이름과 동일한 디렉터리가 생성된 것을 볼 수 있습니다.

사용자 삽입 이미지

mds 파일이 보입니다. 이 파일은 Visual Studio의 sln 파일과 같은 것이고 같은 솔루션 파일로 여러 프로젝트에 대한 정보가 mds 파일에 서술됩니다. 그리고 하위 프로젝트 디렉터리들도 이 디렉터리 아래에 생성되겠지요.

사용자 삽입 이미지

우리가 만든 C# 프로젝트가 저장되어있는 디렉터리로 들어가보면 이번엔 mdp 파일이 보입니다. Visual Studio의 csproj, vbproj, vdproj와 대응되는 것이 mdp 파일입니다. AssemblyInfo.cs, Main.cs 파일도 보이는군요. bin 폴더에 우리가 컴파일한 바이너리가 들어있을 것입니다.

사용자 삽입 이미지

Debug, Release 디렉터리가 마찬가지로 존재합니다. 디버그 모드와 릴리즈 모드를 프로젝트 작업 도중 IDE 도구 모음에서 설정할 수 있으며 여기에 맞추어 빌드하고 테스트할 수 있습니다.

사용자 삽입 이미지

Debug 폴더의 경우 exe 파일 외에 exe.mdb 파일 혹은 dll.mdb 파일이 생성될 수 있습니다. 이것은 Microsoft .NET Framework 환경에서의 pdb 파일과 같은 용도로 디버거가 문맥을 탐색할 수 있도록 정보를 저장한 Program Database 파일이지만 Mono Debugger를 위하여 생성된 것입니다.

요약

오늘 단원에서는 MonoDevelop로 프로젝트를 만들었을 때의 실제 디렉터리 및 파일의 배치, 소스 코드의 특성, 새 프로젝트를 만들 때 사용할 수 있는 설정들에 대하여 간략히 알아보았습니다. 다음 단원에서는 패키징 개념에 대하여 살펴보기로 하겠습니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

포트폴리오2007. 12. 8. 13:35

xPlatform의 Win32 API 영역 중에 Microsoft Visual C++ Runtime에 관한 정보 수집이 어느 정도 완료되었고 이것을 바탕으로 Phase #1을 이번달 말에 예정대로 런칭할 계획입니다. Visual C++ Runtime (msvcrt.dll)의 상당 수의 시그니처는 libc와 일치하는 것으로 보이며 이 중 몇 가지를 시험삼아 Linux 버전으로 옮겨서 작업 중입니다.

아래의 동영상은 Asianux 2.0 SP1 기반의 PC에서 Mono 1.2.5.1_3을 이용하여 xPlatform DLL을 이용한 간단한 파일 입/출력 서비스를 보여주고 있습니다. 점진적인 테스트를 거쳐 Linux에서도 표준 파일 입/출력 서비스 및 기본 C 런타임을 사용할 수 있도록 준비할 계획입니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2006. 1. 4. 17:00

오랫만에 업로드하는 강좌이군요. 그간 별 일 없으셨나요?

갈수록 바쁜일이 많아지다보니 강좌를 올리는 것 자체가 어려워지고 있습니다. 많은 양해를 부탁드립니다.

오늘은 C#에서 편하게 쓸 수 있는 유틸리티인 Verbatim String에 대해서 조명해 보도록 하겠습니다. Verbatim String은 마치 꽁수처럼 알려져 왔던 @ 이라는 기호가 붙는 문자열을 뜻합니다. 다음의 예제를 살펴보도록 하지요.

string test = @"
Welcome to
"" My ""
Program!";

Verbatim 문자열로 사용할 수 있는 모든 것이 드러난 코드입니다. 따옴표를 이스케이프시키기 위하여 두번 따옴표를 표기했다는 점을 빼면 이스케이프 기호를 한번도 사용하지 않고도 있는 그대로를 가져다 붙인 점이 매우 독특합니다. 이 점을 이용하면 정규표현식과 같이 매우 복잡한 문자열을 보이는 그대로 가져다 쓸 수 있습니다. 즉, 다음과 같은 코드가 가능합니다.

string innerCode = @"/// <summary>
/// 대한민국의 주민등록번호 식별 체계에 의하여 제공된 주민등록번호가 올바른 주민등록번호인지의 여부를 판별합니다.
/// </summary>
/// <remarks>
/// 이 메서드는 20세기, 21세기 출생자에 한해서 주민등록번호 유효성 검사를 지원합니다.
/// 20세기보다 이전의 출생자의 주민등록번호, 21세기보다 이후의 출생자의 주민등록번호들은
/// 검사 결과가 모두 False로 출력됩니다.
/// </remarks>
/// <param name=""firstPart"">주민등록번호를 구성하는 앞의 6자리 숫자입니다. 연, 월, 일 순서로 각각 두 자리로 구성된 앞의 6자리입니다.</param>
/// <param name=""lastPart"">주민등록번호를 구성하는 뒷쪽의 7자리 숫자입니다. 고유 번호 7자리로 구성되어있습니다.</param>
/// <returns>형식에 맞는 주민등록번호이면 True, 맞지 않다면 False를 반환합니다.</returns>
public static bool CheckSocialNumber(string firstPart, string lastPart)
{
  string socialNo = firstPart.Trim() + lastPart.Trim();

  // 정규식 패턴 문자열입니다. 6자리의 정수 + [1, 2, 3, 4 중 택 1] + 6자리의 정수
  string pattern = @""\d{6}[1234]\d{6}"";

  // 입력 내역과 정규식 패턴이 일치하면 이 조건문을 통과합니다.
  if(!Regex.Match(socialNo, pattern, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace).Success)
       return false;

  // 20세기 출생자와 21세기 출생자를 구분합니다.
  string birthYear = ('2' >= socialNo[6]) ? ""19"" : ""20"";

  // 연도 두 자리를 추출하여 추가합니다.
  birthYear += socialNo.Substring(0, 2);

  // 월 단위 두 자리를 추출합니다.
  string birthMonth = socialNo.Substring(2, 2);

  // 일 단위 두 자리를 추출합니다.
  string birthDate = socialNo.Substring(4, 2);

  try
  {
       // 정수로 변환을 시도합니다. 예외가 생기면 catch 블럭으로 이동됩니다.
       int bYear = int.Parse(birthYear);
       int bMonth = int.Parse(birthMonth);
       int bDate = int.Parse(birthDate);

      // 20세기보다 이전연도, 21세기보다 이후연도,
       // 월 표기 수가 1보다 작은 값, 월 표기 수가 12보다 큰 값,
       // 일 표기 수가 1보다 작은 값, 일 표기 수가 12보다 큰 값에 해당되면
       // catch 블럭으로 이동됩니다.
       if(bYear < 1900 || bYear > 2100 || bMonth < 1 || bMonth > 12 || bDate < 1 || bDate > 31)
           throw new Exception(""잘못된 날짜 표현입니다."");
  }
  catch { return false; }

  // 고유 알고리즘입니다.
  int[] buffer = new int [13];

  for(int i=0; i<buffer.Length; i++)
       buffer[i] = Int32.Parse(socialNo[i].ToString());

  int summary = 0;
  int[] multipliers = new int [] { 2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5 };

  for(int i=0; i<12; i++)
       summary += (buffer[i] *= multipliers[i]);

  return !((11 - (summary % 11)) % 10 != buffer[12]);
}
";

Verbatim 문자열을 이용하여 코드 자체를 미리 내장하고 프로그램을 실행하는 도중에 동적으로 컴파일할 수 있습니다. 또한 이와 같이 포함된 Verbatim 문자열은 Obfuscator와 같은 도구로 암호화할 수도 있기 때문에 정보를 보호하기 위한 목적으로도 사용할 수 있습니다.

프로그램을 개발하는데에 좀 더 도움이 되시길 바라며 올해도 건강하십시오. ^^

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2005. 10. 18. 17:23

닷넷은 프레임워크라고 하는 구성 요소를 중심 축으로 하여 여러 가지 서비스들을 제공하고 있습니다. 이 프레임워크는 대개의 닷넷 개발자들이 Microsoft 제품 외에는 없다고 단정을 지을 때가 상당히 많은데, 사실은 프레임워크도 종류가 여러가지입니다. Mono는 이러한 것들 중의 한 종류라고 할 수 있습니다.

* Microsoft .NET Framework

Windows 환경에서 가장 널리 사용됩니다. Microsoft가 .NET에 관해서 보여주고 싶어하는 것을 가장 빨리 확인할 수 있는 프레임워크이기도 합니다. 현재 공개된 가장 최근의 버전은 2.0입니다. 소스 코드는 사용하실 수 없지만 무료로 다운로드하실 수 있습니다. 이 글을 새로 고친 현 시점에서 2.0을 기반으로 하는 3.0이 새롭게 출시되었습니다. .NET Framework 3.0은 C# 3.0이 아닌 C# 2.0 스펙입니다.

* Microsoft Rotor Framework

.NET Framework에 대한 얼터너티브 버전으로, Mac OS X, BSD 계열 유닉스, Windows를 대상으로 배포 중인 프레임워크입니다. .NET Framework의 버전에 맞춰서 같이 발매되는데, .NET Framework 2.0의 정식 버전이 발표되면 마찬가지로 이 프레임워크의 새 버전이 나옵니다. 이 글을 새로 고친 현 시점에서 FreeBSD와 Windows NT에서 사용 가능한 Rotor (SSCLI) 2.0이 나왔습니다. 이 프레임워크는 오픈 소스이며, 소스 코드로 제공됩니다.

* Mono Framework

원래 Ximian에서 처음 개발을 시작한 오픈 소스 프레임워크입니다. Linux, Unix, Mac OS X, Windows를 대상으로 개발되는 프레임워크입니다. Microsoft .NET 영역에서 제공되지 않는 툴킷을 다수 기본 제공하며 많은 수의 닷넷 계열 오픈 소스 프로젝트들이 이 프레임워크를 기준으로 작성됩니다. GTK#, COCOA#, 다수의 ADO .NET 어댑터 (SqLite, MySQL, ...)들을 기본 제공합니다. 최근 Mono Framework는 많이 안정화되고 성숙해졌습니다.

* DotGNU Framework

GPL 라이센스를 따르는 오픈 소스 프레임워크입니다. Mono에 비해 늦게 시작하였지만 Mono와는 달리 성능에 상당히 초점을 맞추고 개발하는 프레임워크입니다. 이 프레임워크가 지향하는 환경은 리눅스에서 네이티브 모드로 닷넷 어플리케이션들을 구동하는 것이라 합니다.

현재까지 알려진 프레임워크는 이 정도입니다. 진행하시고자 하는 프로젝트나 학습 방향에 맞는 프레임워크와 개발 도구를 선택하시는 것은 본인의 몫이기에 어느 것이 더 좋다고 이야기할 수는 없겠습니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2005. 10. 3. 01:58

대입을 확정한 이후로 한동안 다른 프로젝트를 진행하느라 카페에 자주 못들렀습니다. 조만간 오픈 소스 프로젝트 몇 종류를 공개적으로 런칭하게 될 것 같습니다. 많은 관심 부탁드리면서 간단한 기술을 또 하나 알려드리고자 합니다. 이 강좌를 읽으시기 전에 이벤트를 선언하는 방법과 대리자의 특성에 대해서 숙지하시면 유용하실 것입니다.

이벤트는 흔히 구독 (Subscription)과 해지 (Cancellation)의 개념을 사용합니다. 이는 C#의 문법을 통해서 관찰하면 훨씬 쉽게 이해할 수 있습니다. 다음의 예를 살펴보도록 하지요.

public delegate void SampleCallback(object arg1, object arg2, object arg3);

public class Ex1
{
  public event SampleCallback Sample;

  public Ex1()
  {
      this.Sample += new SampleCallback(SampleTest);
      this.FireSampleEvent();

      this.Sample -= new SampleCallback(SampleTest);
  }

  protected void FireSampleEvent()
  {
      if(this.Sample != null)
          this.Sample(123, 456f, "7890");
  }

  private void SampleTest(object arg1, object arg2, object arg3)
  {
      Console.Out.WriteLine(arg1);
      Console.Out.WriteLine(arg2);
      Console.Out.WriteLine(arg3);
  }

  [MTAThread()]
  public static void Main(string[] arguments)
  {
      Ex1 tester = new Ex1();
      Console.WriteLine("Tester launched.");
  }
}

위의 코드에서는 Sample이라는 이벤트에 SampleCallback 형식의 대리자를 구독하고 해지하는 예를 보여주었습니다. 아주 기본적인 이벤트 처리 방법을 보여주는 예제입니다. 하지만 지금 보여드릴 예제는 C# 문법을 깊숙히 살펴보지 않으신 분들께는 상당히 생소한 예제가 될 것입니다.

public delegate void SampleCallback(object arg1, object arg2, object arg3);

public class Ex1
{
  public event SampleCallback ExactSample;

  public event SampleCallback Sample
  {
      add
      {
          Console.Out.WriteLine("Event Added.");
          this.ExactSample += value;
      }
      remove
      {
          Console.Out.WriteLine("Event Removed.");
          this.ExactSample -= value;
      }
  }

  public Ex1()
  {
      this.Sample += new SampleCallback(SampleTest);
      this.FireSampleEvent();

      this.Sample -= new SampleCallback(SampleTest);
  }

  protected void FireSampleEvent()
  {
      if(this.Sample != null)
          this.Sample(123, 456f, "7890");
  }

  private void SampleTest(object arg1, object arg2, object arg3)
  {
      Console.Out.WriteLine(arg1);
      Console.Out.WriteLine(arg2);
      Console.Out.WriteLine(arg3);
  }

  [MTAThread()]
  public static void Main(string[] arguments)
  {
      Ex1 tester = new Ex1();
      Console.WriteLine("Tester launched.");
  }
}

결과적으로는 위의 예제와 동일하지만 콘솔 윈도우에는 메시지가 몇 줄 더 기록될 것입니다. 기록되는 부분의 코드는 이벤트 선언을 확장한 곳에서 쓰여지는 코드인것을 알 수 있습니다. add 섹션에서는 이벤트를 등록하는 과정에 일어나는 일들이 기록되어있으며 remove 섹션에서는 반대로 이벤트를 해지하는 과정에서 일어나는 일들일 기록되어있음을 알 수 있습니다. 그리고 재미있는 점은, value라는 키워드가 프로퍼티에서처럼 여전히 유효하다는 점입니다. 그렇다면 value는 무슨 형식일까요?

public delegate void SampleCallback(object arg1, object arg2, object arg3);

public class Ex1
{
  public event SampleCallback ExactSample;

  public event SampleCallback Sample
  {
      add
      {
          Console.Out.WriteLine("Event Added. Type: " + value.ToString());
          this.ExactSample += value;
      }
      remove
      {
          Console.Out.WriteLine("Event Removed. Type: " + value.ToString());
          this.ExactSample -= value;
      }
  }

  public Ex1()
  {
      this.Sample += new SampleCallback(SampleTest);
      this.FireSampleEvent();

      this.Sample -= new SampleCallback(SampleTest);
  }

  protected void FireSampleEvent()
  {
      if(this.Sample != null)
          this.Sample(123, 456f, "7890");
  }

  private void SampleTest(object arg1, object arg2, object arg3)
  {
      Console.Out.WriteLine(arg1);
      Console.Out.WriteLine(arg2);
      Console.Out.WriteLine(arg3);
  }

  [MTAThread()]
  public static void Main(string[] arguments)
  {
      Ex1 tester = new Ex1();
      Console.WriteLine("Tester launched.");
  }
}

눈치가 빠르신 분들은 value 키워드가 어떤 형식의 변수인지 금새 알아채셨을 것입니다. 컴파일해보면 나타나겠지만 SampleCallback 대리자임을 나타낼 것입니다. 그리고 value 변수가 대리자이기 때문에 대리자를 가지고 처리할 수 있는 일은 모두 가능합니다. 그래서 위의 예제에서는 전달된 대리자를 다른 이벤트에 += 연산자와 -= 연산자를 사용하여 이벤트 구독과 해지를 대신 처리하도록 코드를 변형하였습니다. 그리고 += 연산자와 -= 연산자는 위에서 나타난 그대로 반드시 new 키워드와 함께 쓰여야만 하는 것이 아닌 것도 알 수 있습니다.

위의 예제에서는 이벤트 구독과 해지를 단순히 다른 이벤트에게도 위임하는 방법을 보여주었습니다만, 이 정도 단계에까지 왔다면 이벤트 자체를 직접 관리할 수도 있지 않겠는가라는 생각을 하시는 분들도 계실 것입니다. 이러한 호기심을 해결해 드리기 위하여 또 하나의 예제 코드를 보여드립니다.

using System;
using System.Collections;

public delegate void MyDelegate1(int i);
public delegate void MyDelegate2(string s);
public delegate void MyDelegate3(int i, object o);
public delegate void MyDelegate4();

public class PropertyEventsSample
{
  private Hashtable eventTable = new Hashtable();

  public event MyDelegate1 Event1
  {
     add
     {
        eventTable["Event1"] = (MyDelegate1)eventTable["Event1"] + value;
     }
     remove
     {
        eventTable["Event1"] = (MyDelegate1)eventTable["Event1"] - value;
     }
  }

  public event MyDelegate1 Event2
  {
     add
     {
        eventTable["Event2"] = (MyDelegate1)eventTable["Event2"] + value;
     }
     remove
     {
        eventTable["Event2"] = (MyDelegate1)eventTable["Event2"] - value;
     }
  }

 public event MyDelegate2 Event3
  {
     add
     {
        eventTable["Event3"] = (MyDelegate2)eventTable["Event3"] + value;
     }
     remove
     {
        eventTable["Event3"] = (MyDelegate2)eventTable["Event3"] - value;
     }
  }

  public event MyDelegate3 Event4
  {
     add
     {
        eventTable["Event4"] = (MyDelegate3)eventTable["Event4"] + value;
     }
     remove
     {
        eventTable["Event4"] = (MyDelegate3)eventTable["Event4"] - value;
     }
  }

  public event MyDelegate3 Event5
  {
     add
     {
        eventTable["Event5"] = (MyDelegate3)eventTable["Event5"] + value;
     }
     remove
     {
        eventTable["Event5"] = (MyDelegate3)eventTable["Event5"] - value;
     }
  }

  public event MyDelegate4 Event6
  {
     add
     {
        eventTable["Event6"] = (MyDelegate4)eventTable["Event6"] + value;
     }
     remove
     {
        eventTable["Event6"] = (MyDelegate4)eventTable["Event6"] - value;
     }
  }
}

public class MyClass
{
  public static void Main()
  {
  }
}

위의 예제에서는 이벤트를 해시 테이블을 통하여 관리할 수 있도록 통제합니다. 또한, += 연산자와 -= 연산자의 의미를 정확하게 보여주고 있습니다. 사실, 막연하게 생각하고 있던 += 연산자와 -= 연산자의 정확한 의미는 위에 아주 잘 표현되어있습니다.

eventTable["Event6"] = (MyDelegate4)eventTable["Event6"] + value;
eventTable["Event6"] = (MyDelegate4)eventTable["Event6"] - value;

이 한 줄 안에는 상당히 의미있는 내용들이 농축되어있습니다.

대리자는 보통 하나의 메서드만을 사용하여 초기화하는 것이 일반적입니다. 그러나, 여기에 나타난 것처럼 대리자는 한 메서드에 대해서만 컨테이너를 생성하는 것이 아니라, 메서드 컬렉션을 관리하고 있습니다. 이것을 정확한 표현으로는 호출 목록이라고 하며, 호출 목록에 또 다른 메서드들을 더하고 빼는 것을 컴바인과 제거라고 표현합니다. 그리고 이렇게 형성된 호출 목록을 감싸는 대리자를 멀티캐스트 대리자라고 부릅니다.

위의 구문은 다시 정리하면 아래와 같습니다.

eventTable["Event6"] += value;
eventTable["Event6"] -= value;

그렇다면 여기서 이벤트의 역할이 궁금해집니다. 이벤트는 멀티캐스트 대리자를 하나의 속성으로 관리하고 구체화할 수 있는 또 하나의 규격으로 이해하시면 됩니다. 사실 이제껏 보아왔던 이벤트에 사용하는 += 연산자와 -= 연산자는 멀티캐스트 대리자로 처리하더라도 얼마든지 가능했던 일입니다. 하지만 멀티캐스트 대리자는 호출 목록을 관리하기 위한 하나의 수단일 뿐 본 강좌에서 살펴본것과 같이 하나의 프로퍼티로서 확장하는 것은 어렵습니다.

이벤트와 대리자 사이의 차이점은 기존에 널리 사용되던 개념만을 가지고는 이와 같은 상황에 직면했을 때 사라지게 됩니다. 하지만 본 강좌에서는 이와 같이 모호한 상황에 대해서 구체적인 결론을 내리고자 합니다.

단순히 변수에 값을 집어넣고 빼는 것만으로 보았을 때에는 변수나 프로퍼티가 유사한 것처럼 보이지만 본질적으로 변수는 변수일 뿐, 프로퍼티와 같이 커스터마이징을 할수는 없는것을 여러분들께서는 알고 계십니다.

대리자와 이벤트 사이의 관계도 이와 같이 정의할 수 있습니다. 대리자나 이벤트 모두 호출 목록을 만들어서 관리할 수 있는 것은 동일합니다. 그러나, 대리자는 호출 목록을 관리하는 것 까지이지만 이벤트는 어떻게 호출 목록을 추가하고 제거할 것인지에 관해서 다룰 수 있는 프로퍼티의 역할을 이행할 수 있습니다.

긴 강좌를 읽어주셔서 감사합니다. 많은 도움이 되셨기를 바랍니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Linux + .NET2005. 7. 17. 21:30

Mono 1.1.x 빌드에서 C# 2.0 문법을 사용하기 위해서는 "반드시" 참고하셔야 합니다. mcs 컴파일러는 C# 1.x 문법을 다루는 컴파일러입니다. 당연히 아래의 세 강좌를 mcs 컴파일러로 구동하려고 한다면 오류를 만납니다. gmcs 컴파일러를 사용하여 컴파일하셔야 합니다.

Mono 1.1.x 빌드는 Microsoft .NET Framework 2.x와 마찬가지로 ASP .NET 2.0을 지원합니다. ASP .NET 2.0에 관한 간단한 기능 시험을 해보시려면 우선 XSP 웹 서버로 테스트해보시길 권장하고 싶습니다. ASP .NET 1.x 만을 지원하는 웹 서버는 xsp 이며, ASP .NET 2.0까지 지원하는 웹 서버는 xsp2 입니다.

최신 기술에 대한 개념 이해는 Visual Studio 2005를 활용해보시는 것이 좋을 것이라 권장하고 싶습니다. Visual Studio 2005의 무료 버전인 Express Edition 버전이 MSDN에서 배포중이니 Windows 플랫폼에서 한번 사용해 보십시오. 인터넷을 통하여 다운로드하므로 프록시에 대한 설정이 필요할 수 있고 시간이 오래 걸릴 수 있습니다. 전화 접속 모뎀에서는 권장하지 않습니다.

Visual Studio 2005 Express Edition 다운로드 페이지

http://msdn.microsoft.com/express/

좋은 하루 되십시오. ^^

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2005. 7. 17. 21:21

C# 2.0은 지금 소개하는 Generic이라는 기술을 통하여 Object 형식을 다루는 방법을 새롭게 제공합니다. 더 이상 복잡한 조건문을 메서드 안에 배열할 필요가 없으며 이 개념을 통하여 새로운 디자인 패턴을 만들 수 있게 되었습니다. 이 글을 작성하는 현 시점에서는 아직까지 Generic의 정확한 번역 용어가 채택되지 않았으므로 원래의 뜻을 유지하기 위하여 Generic으로 표기하고 있으므로 양해 바랍니다.

1. Generic 클래스의 문법 구조
Generic 클래스를 선언하는 방법은 기존의 클래스와 유사합니다. 하지만 기존의 클래스 선언에는 없는 몇 가지 추가 구문이 명시되어야만 합니다. C# 구문을 기준으로 설명하도록 하지요.

Visual Studio 개발자 여러분들을 위한 안내: Visual Basic .NET, J#, Visual C++, JScript .NET 및 기타 언어를 사용중이신 프로그래머분들께서는 MSDN Library 베타를 참조하십시오. J#에서 이 Generic을 사용할 경우 기존의 Java, J++, J# 1.x 문법과 호환되지 않는다는 점을 꼭 염두에 두시기 바랍니다. Visual C++ 8.x 버전의 컴파일러에서 Managed Extensions for C++의 키워드가 새롭게 바뀌었습니다. 그에 따라, Managed Extensions for C++ 1.x 버전으로 작성된 소스 코드에 대한 업데이트 또는 유지 보수가 필요할 것으로 예상됩니다. 그리고 이번 업데이트를 기준으로 하여 C++의 Template과도 일정 부분은 연동이 가능할 것으로 예상됩니다. (일부 형식 제한 지정과 같은 문법은 표준에 어긋나므로 주의하십시오.)

[public | private | internal | ...] [sealed | abstract] class [Class Name]
      <[Argument Type I], [Argument Type II], ..., [ArgumentType n]> : [Parent Class], [Interface I], ...
{
  // Class Body
}

클래스의 이름과 상속 관계를 지정하는 영역 사이에 부등식 기호 '<', '>' 안에 사용자 정의 형식을 지정합니다. 이 사용자 정의 형식은 임의로 이름을 붙일 수 있으나 클래스 이름의 명명법을 그대로 따르므로 클래스 이름의 명명법에 어긋나는 형태의 이름은 사용할 수 없습니다.

Generic 인수에는 단순히 클래스 형식을 지정할 수도 있지만, 구조체 형식, 인터페이스 형식, 대리자 형식, 중첩 Generic 형식의 지정도 가능합니다. 다시 말해, 사용하기에 따라서는 이 Generic 인수 자체가 복잡한 인수 트리 구조를 생성할 수도 있다는 뜻입니다. 하지만 한 세대 이상 파고드는 예는 그리 많지 않으므로 어렵게 생각하실 필요는 없습니다.

이 강좌를 처음 접하시는 프로그래머 분들 중에는 비교적 근래에 C++에서 도입된 개념인 Template을 떠올리신 분들도 계시리라 생각됩니다. 예상하신 그대로, Generic은 C++의 Template를 C#에 맞게 첨삭한 클론입니다. 하지만 Template에는 없거나 다소 불편한 개념을 수정한 것이 .NET 플랫폼의 Generic의 특징이라고 할 수 있습니다.

2. Generic 클래스에서의 사용자 정의 형식 활용 방법
Generic 클래스 안에서 사용자 정의 형식을 활용하시는 방법은 단순합니다. 사용자 정의 형식은 사실 System.Object 형식을 상속한 것과 다를 바가 없습니다. 그리고 Generic 클래스 인수 안에서 명시된 적이 있는 사용자 정의 형식은 중첩된 서브 클래스에서도 유효합니다.

3. Generic 클래스의 인스턴스 만들기
다음의 예제 코드를 살펴보도록 하지요.

using System;

namespace Test2
{
  public class Generic1 <FirstType> : System.MarshalByRefObject
  {
       private FirstType m_firstType;

      public Generic1(FirstType firstType)
       {
           this.m_firstType = firstType;
       }

      public FirstType FirstTypeInstance
       {
           get
           {
               return this.m_firstType;
           }
       }

      public override int GetHashCode()
       {
           return this.m_firstType.GetHashCode();
       }

      public override string ToString()
       {
           return this.m_firstType.ToString();
       }
  }

  public sealed class MainObject
  {
       [MTAThread()]
       public static void Main(string[] arguments)
       {
           Generic1<string> s = new Generic1<string> ("Test");
           Console.WriteLine("{0}", s);
           Console.ReadLine();
       }
  }
}

Generic이 지정된 클래스는 형식 선언을 할 때 부터 인수를 지정되는 모습을 볼 수 있습니다. Generic1<string> 이라는 형식의 의미는 string (System.String) 형식만을 받아들이는 Generic1 클래스의 단편을 뜻합니다. 그리고 Generic1<string> 이라는 선언 자체는 하나의 형식 키워드가 되기 때문에 생성자를 호출할 때에도 다시 한번 Generic1<string> 이라는 이름을 사용하게 됩니다.

4. Generic 클래스의 상속
Generic 클래스의 상속에 관하여 새롭게 소개되는 용어가 하나 있습니다. 의미는 우리가 익히 알고 있으나 용어 자체는 새로울 것입니다. 기존의 클래스를 C# 2.0에서는 Concrete 클래스라고 새롭게 명명하였는데, 상속 관계를 설명할 때에는 반드시 알아두셔야 하는 개념입니다.

Generic 클래스의 특성을 유지하면서 상속을 할 수 있을 때에는 자식 클래스도 Generic 클래스로 인정됩니다. 하지만 Generic 클래스의 특성을 유지할 수 없는 상속을 하여 Generic 클래스의 특성을 소멸시킨 상속을 한 자식 클래스는 Concrete 클래스로 취급됩니다. 봉인된 Concrete 클래스가 아닐 경우 얼마든지 새로운 상속 관계를 형성할 수는 있지만 부모에 관한 모든 Generic 정보에 대한 접근 권한은 잃어버립니다.

4.1. Generic 클래스의 인수를 줄이는 상속
다음의 구문을 살펴보도록 합시다.

public class Parent1 <A, B, C> { ... }
public class Child1 <A, B> : Parent1 <A, B, int> { ... }

어떻습니까? Child1 클래스에서는 A와 B 형식만을 받아들이도록 되어있지만 Parent1 클래스의 형식 인자 목록을 충족하도록 선언하였습니다. 이것이 Generic 클래스의 인수를 줄이는 상속의 한 예입니다. 여기서는 특정 위치에 있는 하나의 인수를 제거하였지만, 원하는 위치에 원하는 수 만큼의 인수를 제거할 수 있습니다.

4.2. Generic 클래스의 인수를 늘이는 상속
다음의 구문을 살펴보도록 합시다.

public class Parent2 <A, B, C> { ... }
public class Child2 <A, B, C, D> : Parent2 <A, B, D> { /* 인수 C를 활용하는 코드 */ }

4.1 섹션과 마찬가지로 Parent2의 인수 목록을 Child2가 모두 만족하는 상속을 하였습니다. 남아있는 C 형식에 관한 활용에만 집중하면 Child2 클래스의 작업은 끝이 나는 것입니다.

4.3. Generic 클래스의 인수를 유지하는 상속
다음의 구문을 살펴보도록 합시다.

public class Parent3 <A, B, C> { ... }
public class Child3 <AChild, BChild, CChild> : Parent3 <AChild, BChild, CChild> { ... }

인수의 이름이 달라지더라도 이상이 없음을 보여주는 예제였습니다.

4.4. Generic 클래스의 관계를 끊는 Concrete 클래스
다음의 구문을 살펴보도록 합시다.

public class Parent4 <A, B, C> { ... }
public class Child4 : Parent4 <short, int, long> { ... }

Child4는 클래스 자체에 대한 상속은 제공하고 있지만 Generic에 관한 특성을 완전히 소멸시킨 것입니다. Child4 클래스가 만약 봉인된 클래스라면 Generic에 관한 측면은 물론 클래스 자체에 대한 상속마저도 봉인한 고립된 클래스가 됩니다.

4.5. Concrete 클래스로부터 새로운 Generic 관계를 생성하기
다음의 구문을 살펴보도록 합시다.

public class Parent5 <A, B, C> : System.MarshalByRefObject { ... }

System.MarshalByRefObject 클래스 자체는 봉인 클래스가 아니기 때문에 새로운 Generic 관계의 생성을 허용한 것이 되었습니다. 만약 System.MarshalByRefObject 클래스가 봉인 클래스였다면 위의 구문은 오류를 유발할 것입니다.

오늘 강좌는 Generic 클래스에 대한 것들을 살펴보았습니다. 다음회 강좌에서는 Generic 인터페이스에 대해서 살펴보도록 하겠습니다. 미리 말씀드리는 부분이지만 Generic 인터페이스의 상속도 섹션 4의 내용을 그대로 적용하므로 이 부분에 관한 충분한 실습을 통하여 감각을 익혀두시기를 권장합니다.

좋은 하루 되십시오. ^^

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2005. 7. 17. 00:40

이제까지 대리자의 선언은 명시적인 대리자 선언이 많이 사용되어왔습니다. 하지만 C# 2.0에서는 새롭게 추가된 대리자 프로그래밍 방법론을 제공합니다. 바로 익명 대리자 혹은 인라인 대리자라고 합니다. 이 기술을 이용하여 특정 메서드나 코드 블럭 안에서 간단히 새로운 메서드를 작성하는 것이 가능해졌습니다.

이 익명 대리자를 사용하는 조건은 기존의 명시적인 대리자 선언과 마찬가지로 대리자 형식의 선언이 되어있어야 한다는 전제가 깔려있습니다. 하지만 구현 방법과 시점의 차이가 명시적인 대리자와 익명 대리자를 구분하는 기준선이 되는 것입니다.

명시적인 대리자를 구현하는 클래스를 살펴보도록 하지요.

    // Common.cs

    using System;

    namespace Test

    {

      [Serializable()]

      public delegate void TestDelegate(int a, string b);

      public interface ITester

      {

           void Test();

      }

    }

    // NamedTest.cs

    using System;

    using Test;

    namespace Test.Named

    {

      public class NamedTest : ITester

      {

           public void Test()

           {

               TestDelegate d = new TestDelegate(this.NamedMethod);

               d(123, "named: one-two-three!");

           }

           private void NamedMethod(int a, string b)

           {

               Console.WriteLine(a.ToString());

               Console.WriteLine(b);

               Console.ReadLine();

           }

      }

    }

    // AnonTest.cs

    using System;

    using Test;

    namespace Test.Anon

    {

      public class AnonTest : ITester

      {

           public void Test()

           {

               TestDelegate d = delegate(int a, string b)

               {

                   Console.WriteLine(a.ToString());

                   Console.WriteLine(b);

                   Console.ReadLine();

               }; // 세미콜론은 필수!

               d(123, "anon: one-two-three!");

           }

      }

    }

    // MainObject.cs

    using System;

    using Test.Named;

    using Test.Anon;

    namespace Test

    {

      public sealed class MainObject

      {

           [MTAThread()]

           public static void Main(string[] arguments)

           {

               ITester t = new NamedTest();

               t.Test();

               t = new AnonTest();

               t.Test();

           }

      }

    }

개략적인 문법 사항만을 살펴보도록 하겠습니다. 지금 지적하는 부분 이외에는 기존의 대리자 프로그래밍 방법과 동일합니다.

익명 대리자를 지정하기 위하여 다음과 같은 구문을 사용합니다.

    delegate(매개변수 목록)

    {

      // 함수 본문;

    };

매개변수 목록은 해당 대리자의 인수 목록과 일치하도록 선언되어야만 합니다. 함수 본문은 메서드와 동일하게 작성할 수 있으며, return 키워드로 반환값을 반환할 수 있는 조건은 대리자의 반환값에 관한 시그니처에 따라서 달라집니다. 그리고 실수하지 말아야할 부분은 이 블럭이 끝나는 대괄호 끝에는 반드시 세미콜론을 기록해야 합니다.

다음 강좌에서는 클래스의 Generic에 관하여 살펴보도록 하겠습니다. 좋은 하루 되십시오.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2005. 7. 16. 11:05

수시 1학기 원서 접수가 끝이나고 서류들을 다 붙이고 나니 비로소 시간이 많이 남는군요. 오랫만에 뵙겠습니다. Mono 1.1.8 이후 빌드가 충실하게 C# 2.0 스펙을 지원하고 있더군요. 모르고 있었습니다. 역시 고등학교 3학년으로서 살면 안되는 이유가 여기에 있구나 하는 생각이 절실히 들더군요.

Visual Studio 2005를 사용해 보신 분들도 계실겁니다. Express Edition (MSDN Lab에서 다운로드 받으실 수 있는 무료 트라이얼 버전)도 좋고 MSDN Connection으로 Full Beta Package를 우편으로 받으신 분들도 계실 것입니다. 하지만 문법 사항이 많이 달라지고 뭔가 난해해졌다는 느낌부터 받으셨을지 모르겠습니다.

이제부터 시리즈로 올라가는 이 강좌들은 달라진 C# 2.0 구문에 대해서 차근차근히 살펴보도록 하고자 합니다. 오늘 강좌는 partial 키워드에 대해서 알아보도록 합시다.

partial 키워드는 특히 ASP .NET 2.0의 페이지 객체 모델을 향상시키는데에 큰 공헌을 한 매우 중요한 키워드입니다. ASP .NET 1.x 버전들에서는 클래스 라이브러리 (.dll) 파일 안의 클래스를 .aspx 페이지의 클래스가 상속을 받는 방법을 사용했습니다. 이러하다 보니 매우 복잡한 상속 관계 양상을 띄게 되었습니다. Windows Forms 처럼 페이지 상속이 불가능한 이유도 여기에 있었습니다.

그러나 partial 키워드는 우리가 이제껏 상상해왔던 모델을 구현할 수 있도록 도와줍니다. 예를 들어보도록 하지요.

    // APart1.cs 파일의 내용입니다.

    public partial class A

    {

       private int m_int = 1;

    }

    // APart2.cs 파일의 내용입니다.

    public partial class A

    {

       public int IntegerValue

       {

           get { return this.m_int; }

           set { this.m_int = value; }

       }

    }

    // MainObject.cs 파일의 내용입니다.

    public class MainObject

    {

       [MTAThread()]

       public static void Main(string[] arguments)

       {

           A test = new A();

           Console.WriteLIne(test.IntegerValue);

           test.IntegerValue += 5;

           Console.WriteLine(test.IntegerValue);

           Console.ReadLine();

       }

    }

결과는 예상한 그대로 A 라는 클래스 하나가 완성되는 형태로 컴파일됩니다. ASP .NET 2.0은 이와 같은 원리로 하나의 페이지 클래스를 작성할 수 있도록 합니다. .aspx 페이지를 소스 코드로 번역하고, 사용자가 작성한 비하인드 코드를 컴파일러에 같이 전송하여 컴파일하는 것입니다.

이 partial 키워드의 사용 범위는 단일 어셈블리 안에서만 한정되므로 주의해야 합니다. 참조 관계에 있고 같은 네임스페이스 안이라고 할지라도 전혀 별개로 취급됩니다. 또 partial 키워드는 인터페이스, 구조체에도 적용이 가능하므로 유용하게 사용하실 수 있을것입니다.

다음 강좌에서는 익명 대리자 (Anonymous Delegate)에 관한 것들을 살펴보도록 하지요. 좋은 하루 되십시오. ^^

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2005. 6. 17. 12:29

안녕하세요. 오랫만입니다. 대입 수시 1학기를 준비하느라 다른 생각이 없던 차에 잠시 시간을 내어 글을 쓰고 갑니다. ^^

알고 계시는 분도 있으실텐데, C#에서 Visual Basic .NET의 Imports 구문과 같은 용법을 지원합니다. 바로 using 구문이 여기에 해당됩니다.

using [Alias] = [Namespace Path] | [Interface Name] | [DelegateName] | [Structure Name] | [Class Name];

다음의 예제 코드를 보시면 선언된 Alias가 상속을 위한 목적은 물론 인스턴스 선언에서도 그대로 사용될 수 있음을 보실 수 있습니다.

    using Abcd = System.Console;
    using StringBuilder = System.Text.StringBuilder;
    using Parent = System.MarshalByRefObject;

    using System;

    public sealed class MainObject : Parent
    {
    [MTAThread()]
    public static void Main(string[] arguments)
    {
     Abcd.WriteLine("Test with Abcd");
     StringBuilder a = new StringBuilder("Test with Alias of StringBuilder");
     Console.WriteLine(a.ToString());
     Console.Read();
    }
    }

좋은 하루 보내십시오. 9월달 이후에 다시 만날 수 있을것 같군요. 그 때 까지 안녕히 계십시오. ^^

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2005. 2. 23. 22:10

안녕하세요. 3월이 되기 직전에 어쩌면 마지막으로 올리는 강좌일 수도 있겠군요. 저 개인적으로는 수시 1학기때 대입에 관련된 모든 것을 끝을 내야 하는 입장인지라 당분간은 잠수를 타겠지요. ㅎㅎ 하지만 잠수를 타기 전에 잊지 않고 강좌를 올리고 갑니다. ^^

ASP .NET을 요즈음 들어서 많이 연구하는 중입니다. ASP .NET은 여러가지 장점도 있고 재미있는 토픽이 많지만 한 가지 불편한 점이 있습니다. 디자인 타임과 코드 비하인드 사이를 넘나드는 일이 그리 쉽지는 않다는 점입니다. ASP .NET 2.0 에서는 partial 키워드가 지원이 되므로 복잡한 상속 체인을 이용할 필요가 없습니다. (상속 체인을 쓰지 않아도 된다는 말의 본질적인 뜻은 결국 서술하는 위치만이 다를 뿐 결국에는 하나의 단일 클래스를 완성할 수 있다는 뜻이 되기도 합니다.) 하지만 아직은 ASP .NET 1.1이 많이 쓰이는 시대. 김칫국만을 마실 수는 없겠지요?

이번 강좌는 아주 약간의 노력으로 디자인 타임과 코드 비하인드 모두에서 편리하게 세션을 사용하는 방법을 소개 합니다. 디자인 타임에서는 코드 비하인드와 달리 복잡한 구문을 이용하는 것이 적절하지 않습니다. 반면 코드 비하인드는 디자인 타임처럼 자유로운 배치를 하거나 예약 변수가 전혀 없으므로 대체로 코드가 길어지기 마련입니다.

Step1. 클래스를 정의한다

여러분이 원하시는 이름의 클래스를 정의하시면 됩니다. 다만, 여기에 네임스페이스를 정의해야 한다면 약간의 추가 사항이 포함됩니다. 디자인 타임에서는 <%# Import Namespace="네임스페이스 식" %>를 상단부에 배치해 주세요. 코드 비하인드에서는 늘 그래왔듯이 using (C#) 구문이나 Imports (VB.NET) 구문을 사용하여 네임스페이스 참조를 해주시는 것을 잊지 않도록 해야 하겠지요.

Step2. 구현하는 인터페이스로 반드시 System.Web.SessionState.IRequiresSessionState를 포함한다!

Step2를 놓쳐서 흔히 "안되겠구나" 하는 생각을 가지고 좌절을 맛보시는 분들이 많을겁니다. 놓치시면 안되겠지요? ^^

아, 그리고 인터페이스라고 해서 구현해야 하는 멤버수가 많지 않나 하여 걱정하시는 분들이 계실 거라 생각합니다. (저도 그렇게 생각하고 있습니다.) 하지만 다행히도 이 인터페이스는 단순한 표식 인터페이스로서 포함하는 멤버가 없습니다. 아주 간단한 스탬프를 하나 찍어놓는다는 기분으로 가볍게 써주세요. ^^

참고할 것은, 이 인터페이스는 세션 변수를 읽기/쓰기 할 수 있도록 표기하는 것입니다. 단순히 세션 변수를 읽기만 할 요량으로 설계할 클래스라면 이 인터페이스보다는 System.Web.SessionState.IReadOnlySessionState 인터페이스를 대신 써주시면 됩니다. 이 인터페이스도 마찬가지로 표식 인터페이스이므로 추상 멤버가 없습니다.

Step3. 어디서나 세션 변수에 접근하려면? System.Web.HttpContext.Current 속성에 주목하라!

Step2에서 좌절하고 Step3를 몰라 포기하실겁니다. 실수하지 말자구요. ^^

HttpContext.Current 속성은 정적 속성입니다. 그래서 위와 같은 표기로 접근하는 것이 맞습니다. 이 속성을 통하여 현재 시점에서 처리 중인 HTTP 요청에 대한 모든 것을 다룰 수 있습니다. 이 속성을 호출하는 대상이 누구인지, 현재 시점이 언제인지를 떠나서 웹 요청을 처리하는 클래스 라이브러리 (ASP .NET 응용프로그램)와 연결만 되면 항상 유효하므로 걱정하실 필요는 없습니다. (단 Out-Of-Process 상태인 콘솔, WinForm, GTK#, NT 서비스, 데몬 등의 프로그램 등에서는 유효하지 않을 수도 있습니다.)

그리고 디자인 타임에서 개체 생성 없이 빠르게 사용하시려면 가급적 이 클래스의 모든 멤버를 정적 멤버 (C#에서는 static, VB.NET에서는 Shared)로 선언해주는 것이 좋습니다.

Step4. 디자인 타임에서 쓰는 방법

<% = SessionHelper.UserIdentity %>님, 방문을 환영합니다!

<asp:Label id="test"><%# SessionHelper.UserIdentity %></asp:Label>

간단하지요? ^^

많은 도움이 되시기를 바랍니다. ^^

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2005. 2. 11. 02:23

아주 유용한 프로그래밍 기술을 하나 소개하도록 하겠습니다. 흔히 C/C++에서 사용되던 방법으로 몇 개의 수를 OR 연산으로 조합하여 하나의 매개 변수로 여러개의 의미를 해석하도록 매개 변수를 전달하는 방법입니다. 이것을 사용하면 오버로드를 최소화하면서 필요한 옵션 전달이 모두 가능하도록 메서드를 설계할 수 있습니다. 이것을 상수로 정의하여 전달할 수도 있지만 좀 더 세련되고 쓰기 쉬운 방법으로 나열 상수를 활용해보도록 합시다.

    [Flags()]

    [Serializable()]

    public enum Colors : int

    {

      None = 0,

      Red = 1,

      Green = Red * 2,

      Blue = Green * 2

    }

나열 상수의 값을 2의 배수 간격으로 띄웠습니다. 이것은 중/고등학교 수학 시간에 배웠던 "조합 (Combination)"이라는 수학적 원리에 기초합니다. 나올 수 있는 경우의 수를 살펴보도록 하지요.

  • 0: 아무 것도 선택 안함
  • 1: 빨간색
  • 2: 녹색
  • 3: 빨간색 + 녹색
  • 4: 파란색
  • 5: 빨간색 + 파란색
  • 6: 녹색 + 파란색
  • 7: 빨간색 + 녹색 + 파란색

유추된 경우의 수를 모두 세어보면 8가지입니다. 아무 것도 선택하지 않을 수도 있고, 적어도 1가지는 선택할 수도 있고, 2가지를 선택할 수도 있으며, 모든 것을 다 선택할 수도 있는 것입니다. 이러한 개념에 비추어 볼 때, 0은 아무 것도 조합하지 않았음을 의미합니다. 하지만 0보다 큰 1 이상의 모든 숫자들은 조합으로 나올 수 있는 내용들입니다. 이것들에 대한 의미를 정확히 파악하고 switch/case 구문으로 처리한다면 아주 간단합니다.

수학적 원리는 살펴보았고, 프로그래머의 입장에서 문법이 무엇을 의미하는지를 살펴볼 차례가 남았습니다.

나열 상수의 선언문 앞에 붙는 두 가지 특성이 있는데, FlagsAttribute와 SerializableAttribute입니다. Flags 속성은 등장할 나열 상수가 OR 연산이 가능함을 선언합니다. 이것을 붙이지 않으면 OR 연산을 위하여 반드시 (int) 라는 캐스트 연산자를 붙여야 하는 불편함이 따릅니다. 그리고 Serializable 속성은 나열 상수에는 관습 상 붙이게 됩니다. 또한, int 라는 형식 지정을 하게 되었는데 이것은 각 값들이 System.Int32의 값으로 캐스팅 될 수 있음을 약속하는 사항입니다.

그렇다면 옵션이 추가될 때 마다 특정 옵션이 정확하게 선택이 되었는지를 어떻게 확인할 수 있을까요? 사용자가 항목 선택을 위하여 OR 연산을 수행하였듯이 그 반대의 연산인 AND 연산을 수행하면 플래그의 수가 아무리 많더라도 플래그의 수 - 혹은 - 필요한 수 만큼 비교할 수 있습니다.

다음의 코드를 살펴보도록 합니다.

Colors nColors = Colors.Red | Colors.Blue;

if((nColors & Colors.Red) != Colors.None)
  Console.WriteLine("Red Selected.");

if((nColors & Colors.Blue) != Colors.None)
  Console.WriteLine("Blue Selected.");

if((nColors & Colors.Green) != Colors.None)
  Console.WriteLine("Green Selected.");


위의 코드를 실행하면 Red와 Blue가 선택되었음을 알리는 메시지가 콘솔 화면에 나타납니다. & 연산자를 이용하여 AND 연산을 수행하였을 때 특정 플래그가 선택되지 않았다면 0에 해당하는 값이 결과 값으로 나오게 되는 점을 이용한 것입니다.

많은 도움 되셨기를 바랍니다. :-)

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2005. 2. 11. 02:02

오늘 강좌는 Boxing과 Unboxing이라는 개념에 대해서 살펴볼 까 합니다. 이것은 일전의 "형 변환을 기반으로 하는 리플렉션"과 관련성이 있는 내용입니다. 그리하여 실질적인 내용을 따져보면 System.ValueType에 관한 특수한 리플렉션 기술이 Boxing과 Unboxing에 해당됩니다.

System.ValueType 형식은 메타 데이터 기반이 아닌 "낮은 수준"의 데이터 형식들의 추상 형식입니다. 여기로부터 파생되었다고 알려지는 주요 데이터 형식으로는, System.Byte, System.Int16, System.Int32, System.Int64, System.Float, System.Double 등이 있습니다. 하지만 이러한 데이터 형식들은 메타 데이터 기반이 아닙니다. 정확한 풀이로는 힙에 할당되며 크기 또한 자유자재로 변형될 수 있는 형식들입니다. 메타 데이터로 표기하는 것은 엄청난 오버로드를 수반하게 되므로 부적절합니다. 하지만 .NET Framework는 이를 매끄럽게 처리하게 되어있는데 그것이 Boxing과 Unboxing이라 불리우는 기술입니다.

Boxing과 Unboxing의 의미 풀이부터 해보도록 합니다. 두 단어 모두 Box 라는 키워드를 포함하는데, 쉽게 생각할 수 있습니다. 힙에 할당된 연속적인 데이터를 편리하게 관리하기 위하여 포장을 해놓는다 라는 의미에서 Box를 떠올리면 쉽습니다. 즉, 힙에 할당된 연속적인 데이터를 메타 데이터가 이해할 수 있는 형태로 포장해준다는 의미입니다. 그러면 실제 코딩을 살펴보도록 하지요.

int i = 123;

object o = (object)i;

위의 두 코드를 살펴보면, 사실 아무런 의미는 없습니다. 하지만 이것이 Boxing의 대표적인 예입니다. 이러한 코딩이 가능함으로서 얻을 수 있는 이점이 대단히 큽니다. Boxing을 가장 잘 활용하는 예는 ADO .NET 관련 클래스들입니다. 데이터 베이스 시스템이 질의에 대한 결과로 반환하는 데이터 형식을 한꺼번에 다루기 위한 방법으로 Boxing과 Unboxing을 복합적으로 활용합니다.

i 라는 변수에 123이라는 정수 값을 대입했습니다. 그 다음, o 라는 하나의 추상 객체를 선언하여 형식 변환을 취하였습니다. 코드 상에서는 분명히 형식 변환으로 표기되었습니다만 사실 이 둘은 형식 변환이 일어날 수 없는 관계입니다. object는 메타 데이터로 표기되는 데이터만을 다룰 수 있지만 int는 힙에 할당되는 연속적인 데이터 스트림입니다. 서로 관계가 없지만 이것이 가능했던 이유가 바로 Boxing입니다.

int x = (int)o; // Okay

long y = (int)o; // Okay

short z = (int)o; // Error!

x는 i와 같은 int 형식이고, y는 int 형식보다 큰 범위의 수를 다룰 수 있는 long 형식이며, z는 int 형식보다는 작은 범위의 수를 다루는 short 형식입니다. 세 동작 모두 object 형식을 int 형식으로 바꾸는 동시에 Unboxing을 수행하게 되었습니다. 즉, object 형식으로 포장된 데이터의 원래 내용물을 대입한 것입니다. 하지만 x, y와는 다르게 z는 컴파일 오류를 낼 것입니다.

x는 손실 변환이 일어나지 않았으며 원래의 형식 그대로를 수용하였습니다. y는 원래의 i가 요구하던 정수 범위보다 더 큰 정수 범위를 지원하게 되어 확장 변환이 일어났습니다. 이 경우 두 가지 의미로 해석이 가능한데, 말 그대로 가능성을 위하여 예약된 확장 변환일 수 있지만 반대로 불필요한 공간이 더 많이 할당된 오버헤드 변환이기도 합니다. 하지만 z는 컴파일 오류를 냅니다. 손실 변환으로 다루어질 수도 있겠지만 Unboxing의 정의에 의하면 원래 가지고 있던 데이터 형식보다 범위가 더 적어졌으므로 메모리 구조와는 일치하지 않는 것으로 해석됩니다.

int a = o as int; // Error

형 변환에 사용하는 as 키워드로 변환을 시도해 보았습니다. 하지만 오류가 나게됩니다. 왜 일까요? as 형식으로 형변환이 가능하다는 것은 메타 데이터에 한정된 내용입니다. 따라서, System.ValueType으로부터 상속받은 모든 형태의 값 형식에서는 as로 형변환을 하거나 as로 Unboxing되지 않게 되었습니다.

as를 사용하고자 하였던 의도가 예외를 Throw 하지 않고 null을 대입하려 했던 것이었다면 null을 대입하지 않는 대신 다음과 같은 방법으로 처리하는게 좋습니다.

try { int a = (int)o; }

catch { /* 예외 처리 코드 */ }

오늘 강좌가 많은 도움이 되셨기를 바랍니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2005. 2. 7. 13:41

ASP .NET을 기반으로 회원제 사이트를 구축하려고 할 때 제대로된 주민등록번호 검색 알고리즘을 제공하는 곳이 많이 없는듯 보입니다. 미약하지만 요즈음 근황에 맞게 설계된 새로운 버전의 코드를 이곳에 올립니다. 많은 도움이 되시길 바랍니다.

/// <summary>
/// 대한민국의 주민등록번호 식별 체계에 의하여 제공된 주민등록번호가 올바른 주민등록번호인지의 여부를 판별합니다.
/// </summary>
/// <remarks>
/// 이 메서드는 20세기, 21세기 출생자에 한해서 주민등록번호 유효성 검사를 지원합니다.
/// 20세기보다 이전의 출생자의 주민등록번호, 21세기보다 이후의 출생자의 주민등록번호들은
/// 검사 결과가 모두 False로 출력됩니다.
/// </remarks>
/// <param name="firstPart">주민등록번호를 구성하는 앞의 6자리 숫자입니다. 연, 월, 일 순서로 각각 두 자리로 구성된 앞의 6자리입니다.</param>
/// <param name="lastPart">주민등록번호를 구성하는 뒷쪽의 7자리 숫자입니다. 고유 번호 7자리로 구성되어있습니다.</param>
/// <returns>형식에 맞는 주민등록번호이면 True, 맞지 않다면 False를 반환합니다.</returns>
public static bool CheckSocialNumber(string firstPart, string lastPart)
{
  string socialNo = firstPart.Trim() + lastPart.Trim();

  // 정규식 패턴 문자열입니다. 6자리의 정수 + [1, 2, 3, 4 중 택 1] + 6자리의 정수
  string pattern = @"\d{6}[1234]\d{6}";

  // 입력 내역과 정규식 패턴이 일치하면 이 조건문을 통과합니다.
  if(!Regex.Match(socialNo, pattern, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace).Success)
       return false;

  // 20세기 출생자와 21세기 출생자를 구분합니다.
  string birthYear = ('2' >= socialNo[6]) ? "19" : "20";

  // 연도 두 자리를 추출하여 추가합니다.
  birthYear += socialNo.Substring(0, 2);

  // 월 단위 두 자리를 추출합니다.
  string birthMonth = socialNo.Substring(2, 2);

  // 일 단위 두 자리를 추출합니다.
  string birthDate = socialNo.Substring(4, 2);

  try
  {
       // 정수로 변환을 시도합니다. 예외가 생기면 catch 블럭으로 이동됩니다.
       int bYear = int.Parse(birthYear);
       int bMonth = int.Parse(birthMonth);
       int bDate = int.Parse(birthDate);

      // 20세기보다 이전연도, 21세기보다 이후연도,
       // 월 표기 수가 1보다 작은 값, 월 표기 수가 12보다 큰 값,
       // 일 표기 수가 1보다 작은 값, 일 표기 수가 12보다 큰 값에 해당되면
       // catch 블럭으로 이동됩니다.
       if(bYear < 1900 || bYear > 2100 || bMonth < 1 || bMonth > 12 || bDate < 1 || bDate > 31)
           throw new Exception("잘못된 날짜 표현입니다.");
  }
  catch { return false; }

  // 고유 알고리즘입니다.
  int[] buffer = new int [13];

  for(int i=0; i<buffer.Length; i++)
       buffer[i] = Int32.Parse(socialNo[i].ToString());

  int summary = 0;
  int[] multipliers = new int [] { 2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5 };

  for(int i=0; i<12; i++)
       summary += (buffer[i] *= multipliers[i]);

  return !((11 - (summary % 11)) % 10 != buffer[12]);
}
Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2005. 2. 7. 00:18

정규식을 가지고 장난을 치다가 우연히 발견한 팁이 있어서 올립니다. 다른 것은 아니고 @ 이라는 지시자 기호에 관한것입니다. 정규식을 표현하다보면 원래 표기하려는 내용이 아래와 같을 때가 종종있습니다.

"\d{6}[1234]\d{6}"

* 설명

주민등록번호의 외관상 형식을 점검하는 정규식 예제입니다. 1800년대에 사용하던 남/여 성별 구분용 7번째 숫자인 9와 0은 현재는 더 이상 의미가 없어서 정규식 상에서는 1900년대부터 시작하는 것으로 기준을 잡았습니다. 그리고 [1234]의 앞에 대시 (정규식 스타일의 표현으로는 "\-" )는 옵션입니다. ^^

위와 같은 정규식이 있다고 가정하였을 때 위의 내용을 의도하던대로 표기하기 위해서는 이스케이프 시퀀스가 꽤 복잡한 모양새를 띄게 됩니다. 위의 내용 그대로라면 \d 를 하나의 특수한 문법으로 인지하게 될 것입니다. 원하지 않던 결과일 것입니다. 따라서 이 점을 해결하기 위하여 보통은...

"\\d{6}[1234]\\d{6}"

아주 보기 안좋게 변해버렸습니다. 이것을 해결할 수 있는 좋은 방법이 없을까요? 그래서 찾게 된 것이 @ 기호입니다. @ 기호는 백슬래시를 이스케이프 시퀀스의 시작으로 취급하지 않고 백슬래시 그대로임을 나타냅니다. 따라서 @ 기호를 붙이면 \n은 @ 기호를 붙이기 이전의 \\n과 동일한 의미가 됩니다.

@ 기호를 붙인 버전으로 바꾸어봅시다.

@"\d{6}[1234]\d{6}"

훨씬 보기 편해졌습니다. 참고로, 이 기능을 활용하면 파일 경로도 좀 더 보기 쉽게 쓸 수 있습니다. 가령 Windows나 Mac OS 등에서 기본적으로 사용되는 백슬래시를 두 번 연속 붙일 필요 없이 위와 같은 방법으로 한 번만 쓰면 됩니다.

"C:\\WINDOWS\\SYSTEM32";

@"C:\WINDOWS\SYSTEM32";

도움이 되셨기를 바랍니다. ^^

P.S. 자동 링크 기능이 편한 것은 사실입니다만 웬지 백 슬래시까지 자동으로 처리를 함으로 인해서 Internet Explorer의 헛점을 찌르는 것은 아닐런지 의심스럽군요. 백슬래시 기호를 인용할 때에는 URL을 끄도록 셋팅하셔야 할 것 같습니다. (URL을 풀어도 본문에 엔터 키를 치면 자동으로 다시 붙네요. 올리기 직전에 풀어야 겠습니다.)

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2005. 2. 5. 19:56

오랫만입니다. 얼마전에 해킹 존에 올린 프로그램은 잘 사용하고 계시겠지요? 소리바다에서 제공하는 "파도"라는 플레이어를 비롯한 PLA 포맷을 지원하는 대부분의 오디오 플레이어에서 유용하게 쓰실 수 있을겁니다. ^^

오늘은 대부분의 데이터 베이스 기반 웹 사이트들이 겪는 보안 상의 취약점인 SQL-Injection을 해결하는 범용적인 솔루션을 연구해 보도록 하지요.

SQL-Injection이란, 매개 변수를 강력하게 검사하지 않고 문법의 일부로 더하는 프로그램에서 흔하게 벌어지는 공격 기법입니다. 유별난 공격법이 아니지만 막대한 양의 정보가 한꺼번에 유출되거나, Microsoft SQL Server와 같이 관리자에게 너무나도 풍족한 리소스를 주는 데이터 베이스 서버에서는 다른 파티션을 포맷하는것까지도 가능하게 합니다. (SQL Server에서 기본으로 제공하는 스토어드 프로시저 중에는 셸 명령을 실행할 수 있도록 하는 프로시저도 제공됩니다. 얼마든지 응용하기 나름인 셈이지요.) 무시무시한 이야기이지요.

SQL-Injection이 어떻게 벌어지는지 그 과정을 살펴보도록 하지요.

string commandText = "SELECT * FROM USERS WHERE USER_ID='" + value + "'";

위와 같은 코드를 IDbCommand.CommandText에 지정하고 ExecuteReader() 등으로 실행하면 원하는 결과가 나오리라 흔히 생각합니다. 하지만 큰 착각입니다. value에 무엇이 들어가느냐에 따라서 다음과 같이 크게 변조되니까요.

value = "admin"; // 이것이 흔히 기대하는 형태입니다.

// 완성된 질의문: "SELECT * FROM USERS WHERE USER_ID='admin'"

value = "admin' AND USER_NAME='관리자"; // 이와 같이 변형될 수 있습니다.

// 완성된 질의문: "SELECT * FROM USERS WHERE USER_ID='admin' AND USER_NAME='관리자'"

이런 엄청난 사태가 발생하지 않도록 하려면 몇 가지 해결책이 있습니다.

1. 동일한 기능을 수행하는 스토어드 프로시저 또는 함수를 데이터 베이스 서버에 직접 구축하고, 웹 어플리케이션은 단지 이것을 호출하는 것으로 구조를 변경합니다. 위와 같이 문자열이 전달되었을 경우 "admin' AND USER_NAME='관리자" 라는 문자 자체가 하나의 값으로 처리되고, 질의문, 스토어드 프로시저, 함수의 기능에는 영향을 주지 않으므로 제일 안전하고 빠른 방법입니다.

2. 강력한 형식 검사를 수행합니다. 이것이 오늘 살펴볼 방법이며, 위와 같이 스토어드 프로시저나 함수를 지원하지 않는 데이터 베이스에서도 유용하게 쓰일 수 있는 방법입니다. 프로그래밍 방식으로서, 비즈니스 계층 단위로서 문제를 해결할 수 있는 것이 특징입니다.

코드 살펴보기

아래의 함수는 문제가 될 수 있는 문자열을 제거하는 기능을 수행합니다. pattern이라는 매개 변수에는 제거하고 싶은 문자를 지정하며, 이스케이프 시퀀스가 필요한 문자인 백슬래시, 작은 따옴표, 큰 따옴표와 공백 문자 등을 지정합니다.

    public static string EnsureString(string pattern, string target)
    {
       bool flag = false;
       StringBuilder sb = new StringBuilder(target.Length);

       for(int i=0; i<target.Length; i++)
       {
           for(int j=0; j<pattern.Length; j++)
           {
               // 조건에 해당되는 문자임을 통지하고 검사 과정을 파기합니다.
               if(target[i].Equals(pattern[j]))
               {
                   flag = true;
                   break;
               }
           }

           // 조건에 해당하는 문자임을 확인하고 검사 문자를 생략합니다.
           if(flag)
           {
               flag = false;
               continue;
           }

           // 검사 조건에 해당하지 않는 문자이므로 추가합니다.
           sb.Append(target[i]);
       }

       return sb.ToString();
    }

위의 함수를 사용하여 SQL-Injection을 막아보도록 하겠습니다.

string commandText = "SELECT * FROM USERS WHERE USER_ID='" + EnsureString("\'\"", value) + "'";

// 공격 예시: "admin' AND USER_NAME='관리자";

// 방어 결과: "admin AND USER_NAME=관리자";

// 질의문 결과: "SELECT * FROM USERS WHERE USER_ID='admin AND USER_NAME=관리자'"

스토어드 프로시저나 함수를 이용했을 때와 같은 결과를 얻게 되었습니다. 이 함수를 사용함으로서 얻을 수 있는 또 하나의 장점은 이것이 SQL 질의문이 아니라 연결 문자열 등에도 활용될 수 있다는 점이며, 파일이나 디렉터리 경로를 통한 공격도 비슷한 형태로 막아낼 수 있다는 것이 장점입니다. 그리고 패턴은 얼마든지 추가가 가능하므로 공격 정보만 수집되면 몇 글자 추가하는 것으로 이와 유사한 형태의 공격을 방어할 수 있을 것입니다.

설날이 어느덧 돌아왔군요. 모두들 맛있는 떡국들 많이 드시고 올 해도 건강하십시오. ^^

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

포트폴리오2005. 2. 1. 14:10

고3이 되어가다보니 할 일은 많아지고 여유를 많이 잃어버렸습니다. 하지만 가끔은 인사드리고저 해킹 존에 제가 직접 작성한 유틸리티를 올려놓고 가는 일이라도 하렵니다. 양해를 구하도록 하겠습니다. ^^

using System;
using System.IO;
using System.Text;

namespace DevDream.PlaylistWriter
{
public sealed class MainObject
{
[MTAThread()]
public static void Main(string[] arguments)
{
  try
  {
   string dirPath = arguments[0].Trim();

  DirectoryInfo dirInfo = new DirectoryInfo(dirPath.Trim());
   StringBuilder sb = new StringBuilder("VERSION 10\n");

  if(!dirInfo.Exists)
    throw new ArgumentException();

  foreach(FileInfo eachFileInfo in dirInfo.GetFiles("*.mp3"))
    sb.Append("ADD " + eachFileInfo.FullName + "\n");

  string originalDirectory = Environment.CurrentDirectory;
   Environment.CurrentDirectory = dirPath;

  using(StreamWriter sw = new StreamWriter("Playlist.pla", false, Encoding.Default))
    sw.WriteLine(sb.ToString());

  Environment.CurrentDirectory = originalDirectory;

 Console.Out.WriteLine("The playlist file is created on \"" + dirPath + "\\Playlist.pla" + "\".\n");
   Environment.Exit(0);
  }
  catch(IndexOutOfRangeException)
  {
   Console.Error.WriteLine("Please specific valid directory path.");
   Environment.Exit(-3);
  }
  catch(ArgumentException)
  {
   Console.Error.WriteLine("Please specific valid directory path.");
   Environment.Exit(-2);
  }
  catch(Exception ex)
  {
   Console.Error.WriteLine(ex.Message);
   Environment.Exit(-1);
  }
}
}
}
Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

포트폴리오2005. 1. 9. 09:28

오랫만입니다. ^^; 그 동안 잘 지내셨는지요. 간단한 프로그램을 하나 올리고 갑니다. 요 근래 들어서 바쁜일이 굉장히 많군요. 이 프로그램은 C/C++ 헤더 파일의 #define 정의문을 검색하는 유틸리티입니다. xpInterop 프로젝트에서 자주 사용될 유틸리티이기도 합니다. 많은 활용 바랍니다. ^^

using System;
using System.IO;
using System.Collections;

namespace DevDream.Debug.ConstantCollector
{
public sealed class MainObject
{
public static ArrayList GetMultipleHeaderList(string directoryPath, bool writeOutConsole)
{
  DirectoryInfo di = new DirectoryInfo(@directoryPath);

  if(!di.Exists)
   throw new DirectoryNotFoundException(@directoryPath.Trim());

  FileInfo[] fiCollection = di.GetFiles("*.h*");
  ArrayList universalResults = new ArrayList();

  foreach(FileInfo fi in fiCollection)
  {
   if(writeOutConsole)
    Console.WriteLine(fi.Name + " Results: ");

  foreach(string element in GetHeaderList(fi.FullName, writeOutConsole))
    universalResults.Add(element);
  }

 if(writeOutConsole)
   Console.WriteLine("Total Count: " + universalResults.Count.ToString());

  return universalResults;
}

public static ArrayList GetHeaderList(string filePath, bool writeOutConsole)
{
  StreamReader sr = new StreamReader(@filePath.Trim(), true);
  string buffer = sr.ReadToEnd().Trim();
  sr.Close();

  ArrayList parseUnit = new ArrayList(buffer.Split('\r', '\n'));
  ArrayList realParseUnit = new ArrayList();
  string multilineBuffer = String.Empty;
  bool multiline = false;

 foreach(string element in parseUnit)
  {
   if(element.Trim().Length > 0 && element.Trim().StartsWith("#define"))
   {
    multiline = element.Trim().EndsWith("\\");

   if(multiline || multilineBuffer.Trim().Length > 0)
    {
     multilineBuffer += element.Trim().Replace("#define", String.Empty).TrimEnd('\\').Trim();

    if(!multiline)
     {
      realParseUnit.Add(multilineBuffer.Trim());
      multilineBuffer = String.Empty;
     }
    }
    else if(multilineBuffer.Trim().Length <= 0)
     realParseUnit.Add(element.Trim().Replace("#define ", String.Empty));
   }
  }

  if(writeOutConsole)
  {
   foreach(string realElement in realParseUnit)
    Console.Out.WriteLine("\t" + realElement);

 Console.Out.WriteLine();
  }

  return realParseUnit;
}

[MTAThread()]
public static void Main(string[] arguments)
{
  try
  {
   if(arguments.LongLength <= 0)
   {
    Console.WriteLine("Error: Please specify target file path or directory path.\n");
    Environment.Exit(-1);
   }

  if(arguments.LongLength > 1)
    Console.WriteLine("Warning: This program does not process multiple parameters.\n");

  if(new DirectoryInfo(@arguments[0].Trim()).Exists)
    GetMultipleHeaderList(@arguments[0].Trim(), true);
   else if(new FileInfo(@arguments[0].Trim()).Exists)
    GetHeaderList(@arguments[0].Trim(), true);
   else
   {
    Console.WriteLine("Error: Selected path is invalid path.");
    Environment.Exit(-1);
   }

  Environment.Exit(0);
  }
  catch
  {
   Console.WriteLine("Error: Unhandled Exception Throwed.");
   Environment.Exit(-1);
  }
}
}
}
Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2004. 12. 13. 12:13

또 다시 긴 시간 동안 공백이 있었군요. 그 사이에는 기말고사 시즌이라 공부에 전념하게 되어 카페를 자주 방문하지는 못했습니다. 아직 이틀이 더 남았지만 힘든 시험은 거의 다 지나가서 잠시 여유를 가지고 강좌를 올립니다. ^^

System.Object 클래스는 닷넷 세계에서 가장 최상위 클래스이며 모든 개체들은 이 클래스로 핸들링하는 것이 가능하다고 말씀드린 적이 있었습니다. System.Object의 모든 멤버를 정확히 구현하면 닷넷에서 가장 사용하기 쉬운 클래스가 됩니다. 그리고 몇 가지 멤버 함수를 더 구현하면서 확장된 기능도 추가할 수 있습니다.

1. 해시 테이블의 ID로 사용할 난수를 생성하기: GetHashCode()

해시 테이블의 정의에 의하면, 해시 테이블의 모든 원소 (Atom)들은 각자의 고유한 Identity를 가지고 있어서 어떤 개체를 무작위로 정확히 검색하는 것이 가능합니다. 이 Identity를 생성하도록 해주는 것이 System.Object.GetHashCode() 라는 함수입니다. 이것을 커스터마이징해서 더욱 더 고유한 Identity를 만들 수 있습니다.

  • Step A. 멤버 변수로 System.Guid 개체를 선언하고 Guid.NewGuid()를 써서 초기화한다.
  • Step B. GetHashCode()를 override 키워드를 써서 재정의한다.
  • Step C. Guid 개체의 GetHashCode를 비롯, 사용하는 멤버 변수에서 모두 GetHashCode()를 호출하여 각 값을 ^ 연산 처리한다.
  • Step D. 나온 값을 반환한다.

위와 같이 구현하였을 경우 Guid (Global Unique Identity)의 특성과 유동적으로 변형되는 각 멤버 함수의 값의 조합에 의하여 고유한 Identity를 가지게 됩니다.

2. 특정 개체의 동등성을 확인하기 위한 함수: Equals()

이 메서드를 직접 호출하지 않는다 하더라도 == 연산자를 씀으로서 이 메서드를 호출하게 됩니다. 진정으로 어떤 개체가 같다는 의미를 부여하기 위하여 Equals() 함수를 직접 구현하여 사용자가 의도하는 대로 비교를 하도록 처리하는 것이 좋습니다.

Equals() 함수는 두 번 써주는 것이 좋습니다. 첫 번째는 System.Object로 부터 override하는 버전, 두 번째는 새롭게 Equals 함수를 정의하는 것으로 합니다.

A. 첫 번째 함수는 다음과 같이 작성한다.

return (obj is [형식명]) ? this.Equals(obj as [형식명]) : base.Equals(obj);

B. 두 번째 함수에서는 각 멤버들을 비교하여 참/거짓 여부를 반환한다. 단, 주의할 것은 1번 섹션의 Guid는 비교 대상에서 빼야 한다는 점이다.

3. 특정 개체에 대한 상세한 정보를 출력하기: ToString()

이 메서드를 구현하지는 않더라도 개략적인 정보 하나 정도는 자동으로 추출됩니다. 현재 클래스의 전체 경로가 나오는데, 기왕이면 이것보다는 특정 개체에 대한 구체적인 정보를 표현하는 것이 더 보기 좋을 것입니다. 이것은 직접 작성하실 수 있을것입니다.

4. 리플렉션이 아닌 특별한 형변환을 가능하게 하기: implicit operator 또는 explicit operator

선언한 클래스에서 해당하는 형식의 멤버가 하나만 존재하고, 이것을 외부에 노출시키기로 결정하고 프로퍼티까지 선언하였다고 가정할 때, 좀 더 편리하게 하기 위하여 implicit operator 또는 explicit operator를 선언하면 프로퍼티를 참조하는 대신 캐스팅 연산자를 쓰거나 혹은 바로 대입하는 것이 가능합니다. (명시적인 캐스팅 연산자를 사용하는 것이 혼란을 줄일 수는 있겠습니다.)

    // [반환할 형식] abcd = [선언한 클래스 형식명] test;

    // 캐스팅 연산자 없이 개체를 특정 형식의 변수에 직접 대입하면 호출됩니다. (암묵적 형변환)

    public static implicit operator [반환할 형식] ([선언한 클래스 형식명] obj)

    {

      return obj.m_test;

    }

    // [반환할 형식] abcd = ([선언한 클래스 형식명])test;

    // 캐스팅 연산자를 써줌으로서 명시적인 형변환을 할 수 있습니다.

    public static explicit operator [반환할 형식] ([선언한 클래스 형식명] obj)

    {

      return obj.m_test2;

    }

    // [선언할 클래스 형식명] value = new [선언할 클래스 형식명]();

    // value = [값];

    // 프로퍼티의 Setter를 대신할 수 있습니다.

    public static implicit operator [선언한 클래스 형식명] ([받아들일 형식] obj)

    {

      // return new [선언한 클래스 형식명](obj);

      // // 혹은

      [선언한 클래스 형식명] result = new [선언한 클래스 형식명]();

      result.m_test3 = obj;

      return result;

    }

위와 같은 형식으로 객체를 작성하면 "잘 준비된" 형태의 객체를 작성할 수 있습니다.

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2004. 11. 26. 16:03

지난번 리플렉션 강좌에 이어서 새로운 강좌를 이어 올립니다.

* 지난번 문제에 관하여: is 연산자를 사용하는 방법으로 아주 원시적인 리플렉션이 가능하다고 말씀드린 적이 있었습니다. 하지만 이것만으로는 리플렉션의 전부를 살펴볼 수 없다고 말씀드린 적이 있었습니다. 그에 대한 해답은 앞으로 올라오는 강좌에서 적절한 내용을 활용하시는 것입니다. ^^

2. 리플렉션의 초석: System.Type 클래스

System.Type 클래스는 프레임워크 상에 존재하는 모든 종류의 개체들의 형식에 관한 정보를 대변하는 도우미 클래스입니다. 형식을 나타내는 요소로는, 그 자신이 가지고 있는 멤버 메서드들, 멤버 프로퍼티들, 멤버 필드들, 멤버 이벤트들 모두가 포함됩니다. 그리고 클래스 자신의 시그니처 (이름과 위치하는 네임스페이스)와 부모 클래스, 구현하는 인터페이스들, 특유의 속성들도 형식에 포함되는 것입니다.

위에서 언급한 모든 정보들은 메타 데이터라는 것으로 표현됩니다. 메타 데이터를 기초로 하여 각종 형변환과 리플렉션과 관련된 작업들을 처리하는 것으로 보면 맞습니다. 사실 역 어셈블리가 가능하여 코드 자체의 보안이 (런타임 상의 보안이 아니라) 매우 취약하다고 하는 것도 여기에서 발단이 된 것입니다. 네이티브 런타임에서는 RTTI (Run-Time Type Information) 라는 것으로 비슷한 기능을 제공하기도 하지만 닷넷의 리플렉션에는 훨씬 못미치는 수준의 기능입니다.

지금 설명하려는 System.Type 클래스는 위에 나열된 모든 정보를 자유자재로 탐색할 수 있도록 도와주는 클래스입니다. 하지만 System.Type 클래스 자체의 생성자를 직접 호출할 수는 없게 되어있습니다. 바로 추상 클래스이기 떄문인데, 형식을 조회하려면 생성자가 아닌 방법을 사용해야 합니다.

2.1. 리플렉션을 본격적으로 시작하기 전에 알아두어야 할 것

조사하고자 하는 클래스를 컴파일러가 컴파일을 시도할 당시에 알고 있는가, 모르고 있는가에 따라서 Type 클래스를 생성하는 방법이 크게 달라집니다. 즉, 컴파일러에 -r 옵션을 사용하여 어떤 어셈블리를 지정하였거나 코드 파일 중에 클래스가 정의되어있었다면 흔히 사용하던 언어 구문을 사용할 수 있습니다. 하지만 컴파일러에 일체의 지시를 내린적이 없었던 상태에서 어떤 어셈블리 파일을 직접 열어서 활용할 경우에는 언어 구문이 아닌 System.Reflection의 API들을 활용하지 않으면 안됩니다.

하지만 예외 사항은 있습니다. Visual Basic은 언어의 이미지와 특성 상 "편리성"에 초점을 두고 있다보니 이런 점에 있어서도 자동화를 시도합니다. 따라서, 컴파일러에 지정한 적이 없던 정보도 동적으로 분석하여 언어 구문으로 리플렉션을 사용할 수 있도록 해줍니다.

그리고 강의 중에 나오는 용어로 어셈블리라는 것이 있는데, 혼동하지 마십시오. 이것은 MASM 등에서 사용하는 어셈블리 언어를 의미하는 것이 아닙니다. 어셈블리는 프레임워크에서 다수의 네임스페이스와 다수의 개체를 묶어두는 단위로 이해하시면 되겠습니다.

2.2. typeof() 연산자

typeof() 연산자는 Type 클래스를 컴파일러가 이미 알고 있는 클래스로부터 추출할 수 있도록 도와주는 연산자입니다. 사용 방법은 무척 간단합니다. typeof() 연산자의 괄호 안에는 흔히 생각할 수 있는 형식의 이름을 지정하면 됩니다. (예: System.String, System.Object, MyNameSpace.MyClass, ...)

    Type myType = typeof(System.String);

    Console.WriteLine(myType.ToString());

2.3. GetType() 메서드

GetType() 메서드는 typeof() 연산자로는 찾을 수 없는 개체의 형식 정보를 조회할 수 있도록 도와주는 메서드입니다. System.Object (프레임워크 상의 최상위 부모 클래스)에 정의된 상태로 하위 클래스가 구현하는 형태로 되어있으므로 NULL 참조가 아닌 모든 개체가 가지고 있는 메서드입니다. 혹, 직접 구현한 적이 없었다 할지라도 프레임워크가 자동으로 현재 클래스에 대한 메타데이터를 대변하는 Type 클래스를 반환합니다.

    public class ExposedClass

    {

      public void ExposedMethod(object anyThing)

      {

         if(anyThing != null)

         {

            Type myType = anyThing.GetType();

            Console.WriteLine(myType.ToString());

         }

      }

    }

위의 2.3 섹션의 코드를 활용해 보시고 싶으시다면 두 개의 어셈블리를 만드셔야 합니다. 하나는 클래스 라이브러리 (DLL)로 위의 코드를 포함하는 파일이어야 하고, 또 하나는 실행 파일로 해당 클래스 라이브러리를 링크하고 있고 다음의 코드가 포함되어야 합니다.

    public class MyClass

    {

      public int myType = 1;

    }

    public class MainClass

    {

      [STAThread()]

      public static void Main(string[] arguments)

      {

         ExposedClass.ExposedMethod(new MyClass());

      }

    }

다음 강좌에서는 Type 클래스의 상세한 사용법을 다루어보도록 하겠습니다.

참고: ExposedMethod 자체는 static이어도 문제는 없습니다. 어떤 Object를 인수로 받을 요량으로 만들어진것이니까요. 하지만 저 ExposedMethod가 static이 되게 된다면 "this" 키워드는 사용을 못하겠지요. 이런 차이점이 있습니다.

P.S. 스탭으로 저를 선정해주신 운영자님께 감사합니다. 더 좋은 강의를 올릴 수 있도록 노력하겠습니다. ^^

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2004. 11. 22. 00:04

번외 강좌를 먼저 올립니다. 알아두면 유익한 내용이니 꼭 기억해 두시기 바랍니다.

손실 변환과 확장 변환은 제가 나름대로 이름을 붙인 현상이지만 분명히 기초 데이터 형식에서는 충분히 일어나는 일입니다. 쉬운 예로 부동소수에 대해서 이야기를 해봅시다.

System.Single 클래스는 일반적인 정밀도의 소수를 다룹니다. (C#에서는 float 라는 키워드를 가지고 있습니다.) 그리고 System.Double 클래스는 고밀도의 소수를 다룹니다. (C#에서는 double 키워드를 가지고 있습니다.) 이 둘 사이를 형변환한다고 생각해 봅시다.

System.Single에서 System.Double로 변환하는 일은 너무나도 쉽게 이루어집니다. 이유인 즉슨, Single 보다는 Double이 수용할 수 있는 정밀도가 훨씬 높기 때문입니다. 하지만 Double로 바뀌면서부터는 Single 때 다루었던 값을 다 처리하면서도 사용하지 않을 메모리를 더 소비합니다. 이것이 확장 변환입니다.

System.Double에서 System.Single로 변환하는 것은 그냥 변환할 경우 컴파일러가 정확한 캐스팅 연산자를 써달라고 이야기를 합니다. 그래서 흔히 생각없이 붙여줍니다. 그러면 잘 넘어갑니다. 여기까지는 문제가 없겠습니다. 하지만, Double이 다룰 수 있던 자릿수들이 Single로 넘어가면서 날아가 버리고 맙니다. 정밀한 수를 요구로 하는 프로그램에게 있어서는 치명타입니다. 이것이 손실 변환입니다.

또 다른 예로는 양수와 정수 사이의 변환입니다. 양수의 값을 정수로 옮기게 되면 양수 때 다룰 수 없었던 음수는 다룰 수 있게되지만 그만큼 양수로 다룰 수 있는 최대값을 잃어버립니다. 반대로 정수의 값을 양수로 옮기게 되면 음수를 다룰 수 없게 되지만 양수 범위로는 더 큰 수를 다룰 수 있습니다.

(주: 양수 계열은 CLS 표준이 아닙니다.)

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2004. 11. 21. 00:14

리플렉션 강좌의 첫 테이프를 끊게 되었습니다. 내용이 두서없거나 미숙하더라도 넓은 아량으로 이해해주시면서 지적해주시면 감사하겠습니다. ^^;

1. 리플렉션의 의미와 그 필요성

리플렉션은 사전적 의미인 "어떤 사물을 비추어 보다"와 동일한 기능상 의미를 지닙니다. 즉, 리플렉션은 코드 구문 상에서는 사용할 수 없는 제 3의 형식 개체 (클래스, 대리자, 나열 상수, 구조체, 인터페이스 등)를 동적으로 다루는 기술을 의미합니다.

리플렉션은 그닥 활용하기 쉽지도 않을 뿐더러 활용할 만한 곳을 찾기도 어렵습니다. 따라서 사람들이 잘 뒤져보지 않는 기술이기도 합니다. 하지만 리플렉션을 빼면 닷넷은 이빨빠진 호랑이, 더 심한 표현을 쓰면 뼈대 없는 건물로 전락하고야 맙니다. 그만큼 리플렉션을 활용하는 곳이 많다는 것을 강조하고 싶습니다.

2. 쉬운 리플렉션부터 살펴봅시다!

리플렉션에 대해서는 차근차근히 수순을 밟아나가는 것이 이해에 도움이 됩니다. 가장 쉬운 리플렉션부터 공략해 봅시다.

사용하는 디자인 패턴에 따라서 상속을 받는 자식 클래스를 만드는 디자인 패턴을 사용하는 경우도 가끔있습니다. 이 경우 리플렉션의 가장 대표적인 예를 시험해 볼 수 있습니다. 한가지 실험을 해봅시다.

배열을 가지고 우리는 두 가지의 리플렉션을 시험해 볼 수 있겠습니다. 프레임워크 (주: CLS 규격을 구현하는 프레임워크의 수가 많기 때문에 특정 프레임워크를 지칭할 수는 없습니다. 따라서 Microsoft .NET, Rotor, Mono, DotGNU 등을 한꺼번에 아울러 프레임워크라고 표현하겠습니다.)는 언어 문법에서 사용하는 배열의 인스턴스를 System.Array 클래스를 기초해서 동적으로 생성합니다.

System.Array 클래스는 배열에 관한 기본적인 기능을 정의한 클래스이지만 System.Array 클래스의 생성자를 우리가 직접 호출할 수는 없습니다. 그리고 CLS 규격에서 명시하였던 바와 같이 System.Object가 System.Array에 대한 상위 클래스임은 변함이 없습니다. 이와 같은 전제 조건하에서 시험을 해보겠습니다.

2.1. System.Array의 활용

배열은 가지고 있는 원소 (Atom/Element)의 형식에 따라서 각각 다른 배열로 취급되며 배열 vs. 배열 단위의 형변환은 비록 두 배열의 원소의 형식 관계가 상속 관계라고 할지라도 기본적으로는 허용되지 않습니다. 하지만 "배열" 이라는 특성은 원소의 형식과는 관계없이 공통적인 것입니다. 우리는 각기 다른 원소 형식을 가지는 배열을 System.Array 라는 클래스로 동일하게 취급할 수 있습니다. 어떤 형태의 원소 형식을 가지는 배열이 되었던간에 상관없이 말입니다.

Array test = new string [5];
test.SetValue("Test", 2);
string[] test2 = (string[])test;
Console.WriteLine(test2[2]);

기본적으로 System.Array는 추상 클래스이므로 System.Array의 생성자는 사용할 수 없습니다. 하지만 어떻게 인스턴스가 만들어지게 된 것일까요? 명시되어 있지는 않지만 컴파일러 내부적으로는 또 하나의 클래스를 System.Array로부터 동적으로 파생시켰다고 가정하고 다루게 됩니다. 다시 말하여 test 라는 인스턴스는 파생된 클래스의 멤버들 가운데서 System.Array의 멤버들과 일치하는 멤버들만을 가르키게 됩니다.

Array 클래스에 값을 대입하기 위해서 SetValue 메서드를 호출합니다. Array 클래스에서는 인덱서 연산자를 구현하지 않으므로 원시 함수인 SetValue 메서드를 직접 호출하게 되었습니다. 이 상태에서 string[] 클래스로 형변환을 다시 시도합니다. 그리고 값을 확인해 봅니다. Test 라는 문자열이 그대로 유지된 것을 확인하실 수 있습니다.

위의 예에서 중요한 원리 하나를 확인할 수 있습니다. 첫 번째는 형변환에는 방향성이 존재한다는 것입니다. 형식의 변환이 추상화될 수록 (위로 갈수록) 가급적 공통 부분으로만 표현됩니다. 반대로 형식의 변환이 구체화될 수록 (아래로 갈수록) 가급적 상세하게 표현됩니다.

2.2. System.Object의 활용

프레임워크에서 정의되는 모든 개체들의 최상위 클래스는 반드시 System.Object입니다. 심지어는 구체적인 형식을 알 수 없는 개체마저도 System.Object로 형변환 처리가 되는 것도 정당합니다. 이럴 경우 형식의 모호성이라는 문제점이 발생합니다. 즉, 전달받은 개체가 구체적으로 어떤 개체인지 알 길이 없을 수도 있으며 본래의 형식으로 복원하는 것이 불가능해질 수도 있다는 것입니다.

앞의 경우와는 달리 System.Object로의 형변환은 매우 극단적으로 추상화된 형변환이기 때문에 이러한 문제점이 발생하게 됩니다. 하지만 반드시 심각한 문제점인 것만은 아닙니다. 오히려 이것이 리플렉션이 왜 중요한지 알려주는 중요한 연습 문제가 되는 것입니다.

오늘 강좌에서는 언어 구문을 활용한 원시적인 리플렉션을 사용해 보도록 하겠습니다. 바로 is 연산자입니다. is 연산자의 문법 구조는 다음과 같습니다.

[생성된 개체명] is [확인하고자 하는 형식명];

is 연산자로 계산된 값은 System.Boolean 형식으로 반환됩니다. (즉, True 또는 False입니다.) is 연산자가 예외를 일으키지 않고 True 또는 False를 반환하려면 개체가 null이 아니어야 하고 확인하고자 하는 형식명이 컴파일러가 인식할 수 있는 형식 이름이어야 한다는 것, 그리고 생성된 개체가 해당 형식을 직접 구현했거나 해당 형식이 생성된 개체 형식보다는 추상 형식이어야 한다는 것입니다.

이것을 사용하여 예상할 수 있는 형식 몇가지를 검사해 보는 것이 가능합니다. 하지만 여기에는 몇 가지 한계가 있습니다.

  • 찾고자 하는 형식이 상속을 활용한다면 정확히 들어맞는 형식임을 보장할 수는 없게 된다.
  • 찾고자 하는 형식의 수가 많다면 굉장히 많은 수의 조건문이나 3항 연산자를 중첩 사용해야 한다.
  • 찾고자 하는 형식이 컴파일러가 인지할 수 있는 이름 목록 가운데 없으면 이 방법은 사용할 수 없다.

이런 문제점을 해결하기 위해서 몇가지 방법을 더 찾아야 하겠습니다.

오늘 강좌는 우선 여기까지 쓰도록 하겠습니다. 다음 강좌를 보시기 전까지 과제가 하나 있습니다. 과연 위에서 언급했던 문제점을 해결하기 위하여 어떤 방법을 어떻게 활용하여야 할지에 대해서 진지하게 고민해 보십시오. 다음 강좌에서 그 답을 속시원하게 알려드리도록 하겠습니다. ^^

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Windows + .NET2004. 11. 20. 20:49

생각없이 어제와 오늘 이틀간 Visual Basic 6.0 IDE를 사용하다가 보조 도구를 뒤적이게 되었는데 정말 오랫만에 API Viewer라는 녀석을 봤습니다. (사실 옛 Visual Basic은 이것을 빼고나면 거의 시체입니다. 잘 아시죠?)

API Viewer에 있는 구문을 그대로 복사/붙여넣기를 통해서 VB .NET 코드로 가져가면 얼추 들어맞습니다. 하지만 제공되는 API DB 파일이 Windows 98, NT 4.x 시절의 내용인지라 제가 찾고자 하는 API는 없더군요.

갑자기 장난기가 발동하더군요. 잽싸게 구글을 열고 API Viewer라는 검색어를 주문했습니다. 역시 이것의 필요성에 대해 굉장히 많은 수의 프로그래머들이 느끼고 있었던것이 틀림없었는지 제일 위에 한 커뮤니티 사이트에서 만든 API Viewer를 다운로드할 수 있었습니다. 중요한 것은, 이것이 VB 6.x 구문 뿐만이 아니라 다음의 언어들을 사용한 P/Invoke 호출 코드를 보여줍니다.

  • 볼랜드 델파이
  • 퓨어, 파워, 아이, 리버티, 비쥬얼 베이직 (6.0/.NET)
  • MS-MASM (매크로 어셈블러), C#, FoxPro, J++

제일 필요로 하던 C# 코드가 해결되었고 놀라운 것은 MASM용 호출 코드도 찾을 수 있었다는 점입니다. (어셈블리를 공부하고 싶었던 제 입장에서는 대단한 후원자를 찾은 셈입니다. ^^)

많은 분들께서 유용하게 사용하시리라 믿고 주소를 알려드립니다. (저작권 문제가 있을지 모르니 파일을 직접 배포하지는 않으려고 합니다.)

http://www.activevb.de/rubriken/apiviewer/index-apiviewereng.html

의견: 제 생각에는 이 프로그램을 사용하시는 것이 http://www.pinvoke.net 의 DB를 검색하는 것보다는 훨씬 낳을 것이라고 생각됩니다. 단, 이 프로그램에서는 두 가지 종류의 객체는 없습니다. (COM 인터페이스와 콜백 함수, 즉 닷넷에서는 대리자 혹은 델리게이트라고 불리우는 것)

Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요

Useful Solutions2004. 11. 19. 00:03

아마도 이 게시물의 성격 상 해킹 존으로 이동하는 것이 바람직할 것이라 생각되어 올립니다.

Win32의 API 중에 GetWindowText라는 API를 캡슐링한 콘솔 프로그램 하나를 급조했습니다. 필요에 의해서 작성한 것이지만 무척 요긴하게 쓰는 중입니다. Visual Studio의 개발 보조 도구 중 Spy++ 라는 유틸리티와 같이 사용하시면 편리합니다.

Spy++를 통하여 찾은 16진수 형태의 윈도우 핸들 값을 다음과 같이 명령줄로 실행하면 표준 출력 스트림으로 GetWindowText API의 결과물이 나옵니다.

whook [16진수 윈도우 핸들값]

이것을 리디렉션을 사용하여 파일로 쓰시면 텍스트 박스의 내용을 그대로 가져올 수 있습니다.

whook [16진수 윈도우 핸들값] > log.txt

모노와 크게 상관은 없는 것이지만 Win32용 모노에서도 빌드가 가능하니 많은 활용 바랍니다. ^^; 리플렉션 강좌를 올리기 전에 팁도 하나 올렸으면 하여 이곳에 소스 코드와 프로그램을 같이 올립니다.

using System;
using System.Text;
using System.Globalization;
using System.Runtime.InteropServices;

namespace DevDream.Services.WindowHook
{
   internal sealed class MainObject
   {
       [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
       private static extern int GetWindowText(
           IntPtr hWnd,
           [Out] StringBuilder lpString,
           int nMaxCount);

       [MTAThread()]
       private static void Main(string[] arguments)
       {
           try
           {
               IntPtr targetHandle = new IntPtr(Int64.Parse(arguments[0], NumberStyles.AllowHexSpecifier));
               StringBuilder buffer = new StringBuilder();
               GetWindowText(targetHandle, buffer, Int32.MaxValue);
               Console.Out.Write(buffer.ToString());
           }
           catch(IndexOutOfRangeException)
           {
               Console.Error.WriteLine("Handle value is not specified. whook is now closing.");
               Environment.Exit(-1);
           }
           catch(ArgumentNullException)
           {
               Console.Error.WriteLine("Handle value is not specified. whook is now closing.");
               Environment.Exit(-1);
           }
           catch(ArgumentException)
           {
               Console.Error.WriteLine("Handle value is not specified. whook is now closing.");
               Environment.Exit(-1);
           }
           catch(FormatException)
           {
               Console.Error.WriteLine("Handle value not contains ANSI characters. whook is now closing.");
               Environment.Exit(-1);
           }
           catch(OverflowException)
           {
               Console.Error.WriteLine("Handle value is too big. whook is now closing.");
               Environment.Exit(-1);
           }
           catch
           {
               Console.Error.WriteLine("Unexpected exception occurred. whook is now closing.");
               Environment.Exit(-1);
           }
       }
   }
}
Posted by Cloud Developer 남정현 (rkttu.com)

댓글을 달아 주세요