'모노개발카페'에 해당되는 글 33건

  1. 2006.01.04 [팁] Verbatim String에 관해 알아둘 몇 가지
  2. 2005.10.18 .NET 프레임워크의 종류와 근황
  3. 2005.10.03 [C# 고급] 이벤트 처리기 작성하기
  4. 2005.07.17 [팁] Mono 1.1.x 빌드에서 C# 2.0 기능 활용하기
  5. 2005.07.17 C# 2.0의 구문 살펴보기 Part 3: Generic 클래스
  6. 2005.07.17 C# 2.0의 구문 살펴보기 Part 2: 익명 대리자
  7. 2005.07.16 C# 2.0의 구문 살펴보기 Part 1: partial 키워드
  8. 2005.06.17 [팁] using 구문의 또 다른 용법
  9. 2005.02.23 [ASP .NET] 세션을 편리하고 빠르게 사용하는 노하우
  10. 2005.02.11 OR 연산으로 조립이 가능한 옵션 매개 변수 만들기
  11. 2005.02.11 Boxing과 Unboxing의 모든 것
  12. 2005.02.07 주민등록번호 검사 메서드
  13. 2005.02.07 [팁] C#에서 이스케이프 시퀀스를 좀 더 편리하게 쓰는 방법
  14. 2005.02.05 SQL-Injection을 비즈니스 계층에서 해결하기
  15. 2005.02.01 .PLA 형식의 MP3 Playlist 작성
  16. 2005.01.09 C/C++ #define 정의문 내용 검색
  17. 2004.12.13 System.Object를 제대로 구현하는 클래스 만들기
  18. 2004.11.27 [팁] PHP Setup for IIS의 운용에 관한 몇 가지
  19. 2004.11.26 [중요] 리플렉션 강좌 #2
  20. 2004.11.22 [중요] 리플렉션 강좌 #1-1 (확장 변환과 손실 변환)
  21. 2004.11.21 [중요] 리플렉션 강좌 #1
  22. 2004.11.20 [팁] pinvoke.net 보다 중요한 프로그램 하나
  23. 2004.11.19 Windows에서 자주 쓰이는 프로그램 하나
  24. 2004.11.12 인터페이스를 정의하는 이유와 그 실례 [개정판]
  25. 2004.08.27 [고급] 콜 스택, 짭짤하게 활용하기
  26. 2004.08.24 [팁] 유용한 전처리기 지시문 (C#)
  27. 2004.08.23 정의 상수를 이용한 크로스 플랫폼 코드 작성하기
  28. 2004.08.21 C#과 IDisposable 인터페이스
  29. 2004.08.18 C#에서도 쓸 수 있는 C 언어 스타일의 문자 판별식
  30. 2004.08.17 C#과 C++ 사이의 Interop 완성하기 - Step 3.
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)

댓글을 달아 주세요

Web Programming2004. 11. 27. 20:38

Mono와 상관은 없습니다만 PHP Setup for IIS를 이용해서 Windows 기반의 PHP 웹 사이트를 운영하고자 하시는 분들에게는 분명히 도움이 되리라 생각하고 글을 씁니다. PHP Setup for IIS는 http://okstart.pe.kr - 또는 - http://www.apmsetup.com/ 에서 받으실 수 있으며 Windows NT 4.x Server Family, 2000 Server Family, Server 2003 Family에서 사용할 수 있습니다. (Windows 9x 및 Windows NT 4.x, Windows 2000 Professional의 Personal Web Server와는 호환되지 않습니다.)

rkttu.com은 현재 Windows Server 2003의 IIS 6.0을 서버로 사용하며 PHP Setup for IIS를 사용하여 PHP에 관한 지원을 추가하였습니다.

하지만 실제로 몇몇 PHP 웹 어플리케이션들은 호환성 문제로 인하여 전혀 예상하지 못하거나 기대하지 않았던 불편을 초래하기도 합니다. 이 글을 통하여 조금이나마 도움이 되시기를 바라며 글을 올립니다.

1. 흔히 발생하는 퍼미션 문제

기존의 유닉스나 리눅스 계열 운영 체제에서는 세 자리의 숫자로 퍼미션에 관한 모든 설정을 제어할 수 있습니다. 하지만 Windows NT의 보안 체계는 이보다 훨씬 복잡한 구조를 나타냅니다. 따라서, 기존에 널리 사용되넌 chmod 명령어나 FTP를 통한 퍼미션 조정은 불가합니다.

Windows NT 계열의 운영 체제에서는 접근의 주체가 사용자라는 단위에 의하여 발생됩니다. 따라서, IIS도 합당한 사용자 계정을 획득한 상황에서 파일을 읽거나 쓰는 작업을 처리하게 되어있습니다. 이러한 계정은 시스템 계정으로, [IUSR_컴퓨터이름]과 같은 형태로 미리 만들어집니다.

특별히 IIS의 관리 콘솔을 이용하여 디렉터리에 접근하는 사용자의 권한을 다른 계정으로 주지 않았다면 [IUSR_컴퓨터이름] 계정에 대한 권한 할당을 해주는 것이 퍼미션 할당 작업에 해당됩니다.

퍼미션 할당 작업은 컨텐츠가 저장된 디렉터리를 탐색기로 열고, 해당 개체를 오른쪽 버튼으로 클릭하고 "속성" 또는 "등록 정보" 메뉴를 선택하면 나타나는 세부 사항 대화 상자의 "보안" 탭을 통해 조절할 수 있습니다.

2. PHP.INI에 설정된 임시 디렉터리에 관하여.

PHP.INI 파일에 파일 업로드 및 세션 처리를 위한 임시 디렉터리 관련 설정이 있습니다. 여기에 명시된 디렉터리는 반드시 Everyone (그룹)에 대해 모든 권한 할당을 해주어야 합니다.

3. IIS 6.0의 웹 서비스 확장에 대하여.

IIS 5.0과 6.0의 보안상의 차이점 중 가장 눈에 띄는 것은 ISAPI 및 CGI에 대한 보안 설정이 구체화되었다는 점입니다. 즉, IIS 6.0에서는 사용하려는 ISAPI DLL이나 CGI EXE는 반드시 웹 서비스 확장 섹션에 등록되어야 한다는 의미입니다. 여기에 등록하지 않고 웹 사이트 또는 특정 디렉터리의 응용프로그램 풀 구성에만 등록할 경우 동작하지 않게 됩니다.

4. PHP 기반 응용프로그램들은 항상 별도의 응용프로그램으로 독립시켜줄것.

PHP 기반 응용프로그램이 저장된 디렉터리 개체의 등록 정보를 IIS 관리자에서 살펴보면 디렉터리 탭이 있는데 그곳의 응용프로그램 설정 섹션을 살펴보면 기본값은 만들어져 있지 않은 것으로 되어있습니다. 좌측의 만들기 버튼을 눌러서 별도로 분리를 시키고, 반드시 실행 권한에는 "스크립트 전용"을 선택해 주셔야 합니다. 그리고 당연한 이야기이지만 "구성" 버튼을 눌러서 php 확장자에 대하여 php-cgi.exe 엔진을 연결시켜주어야 합니다.

5. 필요한 곳에서만 PHP 해석기 엔진을 사용하기

위의 4번과 같이 PHP 응용프로그램들을 따로 독립시켜두면 필요한 곳에서만 PHP 엔진을 호출하도록 구분할 수 있습니다. 이렇게 하여 성능을 최대한 발휘할 수 있도록 하는 것도 가능합니다.

6. 기본 문서에 유의할 것

IIS는 Apache와 달리 Default 라는 단어의 페이지가 미리 지정되어있습니다. 그래서 기본 문서에 index.html과 index.php 파일을 추가해 주는 것을 잊지 말아야 하겠습니다. 또한, 우선 순위를 높게 잡아주어 빨리 찾도록 해주는 것도 중요합니다.

7. 제로보드와 태터툴즈의 로그인 문제

제로보드와 태터툴즈만의 문제는 아닐 것입니다. 비슷하게 활용할 수 있는 곳이 적지 않을 것이라 보고 공통의 코드 두 줄을 알려드립니다. 이 코드 두 줄을 필요한 곳에 적절히 배치하는 것이 중요하리라 생각됩니다. 태터툴즈 구버전 (0.x) 사용자 계서는 7.2 섹션을 꼭 참고하십시오.

7.1. 제로보드의 로그인 문제 해결

제로보드의 로그인 문제를 해결하기 위해서는 lib.php 파일에서 다음의 섹션을 찾으셔야 합니다.

    /*******************************************************************************

    * 에러 리포팅 설정과 register_globals_on일때 변수 재 정의

    ******************************************************************************/

    @error_reporting(E_ALL ^ E_NOTICE);

    @extract($HTTP_GET_VARS);

    @extract($HTTP_POST_VARS);

    @extract($HTTP_SERVER_VARS);

    @extract($HTTP_ENV_VARS);

위와 같은 섹션의 바로 밑 부분에 아래의 코드를 집어넣어 주십시오.

    $_QU = ($QUERY_STRING) ? "?" : "" ;

    $REQUEST_URI = "$PHP_SELF"."$_QU"."$QUERY_STRING";

7.2. 태터툴즈의 로그인 문제 해결

태터툴즈의 클래식 버전은 이런 사항을 해결하였습니다. 따라서 이 내용을 적용할 필요는 없습니다. 하지만 아직 구 버전을 사용하고 계실 경우 아래의 지시 사항대로 따라하여 주십시오. 태터툴즈의 정식 브랜치들은 이 글이 작성된 현 시점까지도 IIS에서 실행되지 않으니 참고하여 주십시오.

태터툴즈의 로그인 문제를 해결하시기 위해서는 inc_function.php 파일을 열고 다음의 섹션을 찾아주십시오.

    reset($HTTP_SERVER_VARS);

    while (list($key, $val) = each($HTTP_SERVER_VARS)) {

    $$key = $val;

    }

바로 밑에 두 줄의 코드를 집어넣습니다.

    $_QU = ($QUERY_STRING) ? "?" : "" ;

    $REQUEST_URI = "$PHP_SELF"."$_QU"."$QUERY_STRING";

두 곳 모두 같은 코드를 집어넣었습니다. 로그인 문제가 발생하는 PHP 프로그램에는 위와 같은 방법을 사용하여 해결이 가능할 것이라 봅니다.

8. 일부 Perl 프로그램들의 파일 입/출력 문제

IIS에서는 Active Perl을 사용하여 Perl 웹 프로그램들을 구동할 수 있습니다. 하지만 문제점이 있는데, 유닉스나 리눅스와는 달리 Windows NT는 심볼릭 링크의 개념이 없습니다. 그에 따라서 파일 액세스를 하기 위하여 파일 핸들을 열어놓으면 다른 프로세스가 접근할 수 없게 됩니다. 즉, 다중 사용자 접속에 취약하게 됩니다.

이 점을 고치기 위하여 직접 Win32 API를 핸들링하여 Shared Mode로 열도록 고쳐도 되겠습니다만 많은 노력이 필요할 것입니다. 이 점에 대한 마땅한 해결책은 아직까지 없는듯 합니다.

9. MBSA (Microsoft Baseline Security Analyzer)의 검진 보고서에서 조심해야 할 것

최근에 경험한 사실 한 가지가 있습니다. MBSA 라는 유틸리티는 Microsoft사가 Technet을 통하여 무료로 배포하는 보안 점검 유틸리티입니다. 군더더기 없는 정확한 핫픽스만을 설치할 수 있어서 자주 애용하고 있는 유틸리티이기도 합니다. 최근에 저는 이 유틸리티를 사용하면서 한 가지 실수를 저질렀습니다. 그것은 다름이 아니라 IIS와 연관성이 있는(?) World Wide Web Publishing Service 라는 이름의 서비스에 대한 보고서였습니다. MBSA는 이것을 그닥 필요없는 서비스이기 때문에 꺼도 좋다고 이야기를 하고 있습니다만 이대로 시행하였다가 낭패를 봤습니다. CGI 기반으로 연결된 PHP 해석기가 동작하지 않기 시작하였습니다. 하는 수 없이 원래대로 복구를 하고 IIS를 재시작하니 그제서야 제자리를 찾았습니다. 이 점에 대해서 다른 의견이 있으시면 듣고 싶습니다. ^^

서버 관리자 여러분들께서는 많은 도움이 되셨기를 바랍니다.

남정현(junghyun0816) http://rkttu.com

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)

댓글을 달아 주세요

Windows + .NET2004. 11. 12. 16:00

여름방학때 반짝 나타났다가 잠적했었습니다. 그 사이에 카페 디자인도 많이 바뀌었군요. ㅎㅎ

그간에는 인터페이스라는 것의 활용법에 대해서 아주 깊게 심취하고 있었습니다. 제가 좋아하는 Reflection을 다루는 데에 있어서 없으면 안되는 중요한 개체이기 때문이기도 합니다.

인터페이스는 얼른 생각하면 COM이나 DCOM에서만 쓰일것 같다는 인상을 주기 쉽습니다. 실제로도 COM과 DCOM의 Interop을 위해서 인터페이스를 정의할 수도 있겠습니다. 하지만 인터페이스는 Reflection을 활용하는 데에 더 많이 쓰일 수 있다고 생각합니다.

1. 인터페이스는 어떻게 동작하는가?

인터페이스는 자신을 구현하는 하위 클래스에 대한 일종의 필터 역할을 합니다. 즉, 모든 멤버들을 다 보여주는 것이 아니라 사용자가 원하는 기능을 정의하는 인터페이스의 멤버만을 추려낸다는 뜻입니다. 그래서 다음과 같은 코드는 유효합니다.

IMyInterface sample = new MyInterfaceSuccessor();

MyInterfaceSuccessor 클래스는 IMyInterface가 아닌 System.Object의 추상 메서드들도 구현할 수 있습니다. 그러나 여기에서는 MyInterfaceSuccessor 클래스의 모든 멤버들을 사용하는 것이 아니라 IMyInterface가 정의하는 메서드들만을 호출한다는 뜻이 됩니다.

sample.About();

2. 인터페이스를 정의할 때 주의할 점들

인터페이스를 정의할 때 조심해야 할 것들이 몇 가지 있습니다.

Case A. sbyte, ushort, uint, ulong 같은 비표준 타입을 요구하거나 반환하는 메서드는 사용하지 않도록 한다: 위와 같은 타입을 생각하지 못하는 언어들을 배려해야 합니다.

Case B. 생성자와 소멸자를 정의할 수 없다: 인터페이스를 선언하면서는 기본 생성자와 소멸자를 정의할 수 없습니다. 하지만 프록시 메서드를 배치하여 임의로 생성자와 소멸자를 재정의하는 방법이 있습니다.

Case C. 정해진 인터페이스는 가급적 바꾸지 않도록 하라: 인터페이스는 대충 설계하고 마는 것이 아닙니다. 어떤 면에 있어서는 오히려 클래스를 정의하는 일보다 더 신중해야 합니다. 이미 여러차례 구현된 인터페이스에 조금이라도 변화가 생기면 그 인터페이스를 구현하는 클래스들과 상속 관계에 있는 하위 인터페이스들에게도 한꺼번에 영향을 줍니다.

Case D. 정적 프로퍼티와 메서드는 인터페이스에서 정의할 수 없다: 인터페이스에 생성자를 정의할 수 없으므로 당연히 정적 생성자는 정의할 수 없습니다. 그에 따라서, 정적 프로퍼티와 메서드도 당연히 사용할 수 없습니다.

Case E. COM, DCOM, CORBA, JAVA 등의 플랫폼과의 연동을 고려하고 있다면 인터페이스에 제네릭을 포함하면 안된다: 제네릭은 .NET Framework 스펙 2.0에만 해당되는 사항이며 C++의 템플릿과는 구분됩니다. 또한 아직까지 형식 다형성에 관하여 정의를 내린 플랫폼은 없기 때문에 당연히 제네릭은 호환되지 않습니다.

3. 인터페이스와 포함 기법의 결합

인터페이스를 하나 이상 구현하는 객체가 있을 경우, 보통 이를 형식 변환을 통하여 원하는 인터페이스를 가져오곤 합니다. 하지만 이런 방법보다 더 직관적이고 접근하기 쉽게 만드는 방법이 하나 있는데, 인터페이스 구현을 암시적 구현이 아닌 명시적 구현으로 하여 this 참조를 다른 인터페이스 형식으로 변환하여 참조를 반환하는 프로퍼티를 만드는 것입니다. 예를 들면 다음과 같은 활용법을 말합니다.

// 이 클래스가 ICloneable과 IDisposable을 동시에 구현한다고 가정하면...

public ICloneable CloneableServices { get { return (ICloneable)this; } }
public IDisposable DisposableServices { get { return (IDisposable)this; } }

얼마전에 제 도메인으로 제 블로그를 개통하였습니다. 이곳에도 강좌를 자주 올리고자 하니 많은 방문 부탁드립니다.

http://rkttu.com

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

댓글을 달아 주세요

Windows + .NET2004. 8. 27. 00:02

모든 .NET Framework들은 Common Language Runtime에서 관리하는 콜 스택을 응용프로그램에게 개방합니다. 콜 스택은 반드시 System.Exception에서만 확인할 수 있는 "디버깅 전용" 정보는 아닙니다. 콜 스택을 응용하여 기존에 구현하기 어려웠던 사항 한 가지를 다루어 볼까 합니다.

System.Diagnostic이라는 네임스페이스, 기억하고 계십니까? (처음 들으시는 분들도 적잖으리라 생각합니다.) System.Diagnostic의 클래스들 중 StackTrace와 StackFrame이라는 두 클래스를 사용하여 콜 스택을 검색할 수 있습니다. 콜 스택 검색을 사용하여 Public으로 열려있는 메서드이지만 아무데서나 호출할 수는 없게 보호하는 것이 가능합니다. 다시 말해, 비록 Public으로 열려있는 메서드이지만 호출했을 때 지정한 메서드에서 호출한게 아니면 실행되지 않거나 예외를 Throw하는 것 등이 가능합니다.

콜 스택 검색 기능을 사용하여 원하시는 여러 가지 일을 할 수 있을 것입니다. 하지만 콜 스택의 속성을 정확히 알고 검색하려면 .NET Framework의 동작 원리와 많은 시행 착오가 동반되어야 할 것입니다.

StackTrace 클래스는 콜 스택을 CLR로부터 가져와 인덱스를 기준으로 검색하는 방식을 지원합니다. StackTrace에는 중요한 메서드와 읽기 전용 프로퍼티가 각각 하나씩 있습니다. GetFrame 이라는 메서드와 FrameCount라는 읽기 전용 프로퍼티입니다. FrameCount로 기록된 프레임의 수를 조사할 수 있습니다. 반복문으로 프레임을 하나씩 가져오면 쉽습니다.

StackFrame 클래스는 호출된 "시점"에 관한 모든 정보를 포함하는 클래스입니다. 빌드하는 어셈블리의 설정에 따라서 이 클래스의 능력이 좌우되는데, 디버그 정보를 포함하도록 하는 디버그 모드로 컴파일하여 StackFrame 클래스를 사용한다면 해당 시점과 일치하는 소스 코드 파일 이름, 행 및 열 번호까지 액세스할 수 있습니다. 그러나 디버그 모드가 아니라면 이 기능은 사용할 수 없으며 단지 "시점"에 해당하는 메서드의 정보, IL 코드 및 런타임 코드의 오프셋 정도만을 알 수 있습니다. 이 중에 메서드의 정보를 반환하는 기능을 사용하면 System.Reflection 네임스페이스와 연동하여 해당 메서드를 포함하는 어셈블리까지 훑어 낼 수 있습니다. (.NET Framework의 확장성이란 이런 면에도 있는 것입니다. 저는 System.Data 또는 System.Xml 네임스페이스에 버금가는 확장성이 바로 여기서 등장한다고 봅니다.)

다음의 코드를 살펴보겠습니다.

    internal static bool AssertValidRoute(string expectedMethodName)

    {

      StackTrace tracer = new StackTrace();

      for(int i=0; i<tracer.FrameCount; i++)

       if(tracer.GetFrame(i).GetMethod().Name.Equals(expectedMethodName))

        return true;

      throw new UnauthorizedAccessException("You cannot access this method.");

    }

위에서는 아주 간단하게 코드를 작성했습니다. 위의 코드로 특정한 메서드가 호출 스택에 포함되어있는지 확인하는 것으로 조건에 해당되면 정당한 액세스라는 것을 알립니다. 여기에는 몇 가지의 헛점이 존재합니다. 연구해 보시기 바랍니다. ^^

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

댓글을 달아 주세요

Windows + .NET2004. 8. 24. 12:36

#region [설명] ~ #endregion: Visual Studio .NET, Sharp Develop과 같은 IDE를 사용할 때 코드 접이 기능을 위한 프로그래밍 방식의 영역 지정 전처리기 지시문입니다.

#warning [설명]: 특정 행에서 컴파일러 경고를 발생시킵니다.

#error [설명]: 특정 행에서 컴파일러 오류 (컴파일을 중단시키는 예외)를 발생시킵니다.

#if [선언된 정의 변수] ~ #endif: #if 절의 문장이 참이면 안에 들어있는 코드를 포함하여 컴파일을 시도합니다. 그렇지 않을 경우 안에 들어있는 코드를 제외하고 컴파일을 시도합니다.

#if [선언된 정의 변수] ~ #else ~ #endif: #if 절의 문장이 참이면 #if 절에 들어있는 코드를 포함하여 컴파일을 시도합니다. #if 절의 문장이 거짓이면 #else 절에 들어있는 코드를 컴파일합니다.

#if [선언된 정의 변수] ~ #elif [선언된 정의 변수] ~ #else [...] ~ #endif: #elif는 #else와 #if의 기능을 수행합니다. 동작 유형은 위의 전처리기 절 세트와 같습니다.

#define [변수 이름]: 새로운 변수를 선언합니다. 여기에는 값을 대입할 수 없으며 오로지 "존재한다"와 "존재하지 않는다" 만을 구분할 수 있습니다. System.Boolean과 유사하게 보이지만 다르며 런타임에 바인딩되는 인스턴스가 아니며 오로지 컴파일러 내에서만 유효합니다.

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

댓글을 달아 주세요

Windows + .NET2004. 8. 23. 16:15

현재는 Microsoft .NET Framework와 Ximian (Novell) Mono Framework 두 종류로 구분된 플랫폼이 존재합니다. 이 두 플랫폼을 모두 지원할 수 있는 방법은 Operating System의 정보와 Framework의 정보를 코드 상으로 구분하여 처리하거나 (중계 언어의 호환성이 있기에 가능합니다.) 정의 상수를 이용하는 방법이 있습니다. 저는 개인적으로 중계 언어의 호환성도 좋지만 정의 상수를 활용하는 방법을 개인적으로 선호합니다. 정의 상수라는 표현을 쓰긴 했으나 C와 C++에서 지원하던 매크로의 의미보다 "축소" 되었지만 사용 용도는 비슷하게 쓰일 수 있다고 봅니다.

C와 C++에서 이와 같이 크로스 플랫폼을 지원하기 위하여 사용하는 정의 상수는 사실 "아무런 값도 포함하지 않는" 비어있는 정의 상수입니다. (0 또는 NULL이 아닙니다!) 이 정의 상수를 MAKEFILE에서 지정하여 컴파일러에게 전달하도록 스크립트를 작성하고, 컴파일러는 소스 코드 상에서 정의된 상수에 관한 코드만을 선택적으로 컴파일합니다. 이것을 응용하여 종류가 매우 다양한 각종 컴파일러와 그 컴파일러 시리즈 각각의 버전을 구분하고 운영 체제의 종류와 그 운영 체제 시리즈 각각의 버전에 호환되는 바이너리를 작성할 수 있습니다. 대표적으로 Mono와 Rotor, 그리고 wxWindows가 그에 해당되는 좋은 예라고 할 수 있습니다. C#에서도 이와 마찬가지의 용도로 정의 상수를 사용할 수 있습니다.

C# 컴파일러에는 다음과 같은 옵션이 있습니다.

/define:arg1;arg2;arg3;~~~;arg[n]

이것은 프로그래밍 코드에 직접 서술하여 정의 상수를 정의하는 #define의 역할을 컴파일러가 대신 수행해줌을 의미합니다. 즉, 여기서 선언한 정의 상수는 "코드" 상에서 비록 선언되어있다고 하지 않을지라도 유효하게 됩니다. 만약 여기서 선언하지 않았다면 적어도 "코드" 상에는 #define으로 선언이 되어있어야 유효한 것입니다. 바로 C# 컴파일러의 이 옵션을 활용하여 크로스 플랫폼 코드를 구현할 수 있을 것입니다.

가령, Microsoft .NET Framework임을 코드에게 알려주고자 한다면 아래와 같이 프로그래머가 식별할 수 있는 정의 상수를 주면 됩니다. (프로그래머가 Microsoft .NET Framework에 해당하는 특정한 기호를 정의하고 사용하므로 어떤 기호를 써도 무방합니다. 하지만 의미는 정확히 구분되어야 합니다.)

csc test.cs /define:MSDOTNET

만약 Rotor Framework, Mono Framework, DotGNU Framework임을 각각 코드에게 알려주고자 한다면 아래와 같이 사용자 정의하면 됩니다.

    csc test.cs /define:MSROTOR

    csc test.cs /define:XIMIANMONO

    csc test.cs /define:GNU

코드 상에서는 아래와 같이 정의하면 선택적으로 컴파일이 이루어질 것입니다.

    public void Selection()

    {

    #ifdef MSDOTNET

    // Microsoft .NET Framework에서 유효한 코드 작성

    #elif MSROTOR

    // Microsoft Rotor Framework에서 유효한 코드 작성

    #elif XIMIANMONO

    // Ximian (Novell) Mono Framework에서 유효한 코드 작성

    #elif GNU

    // DotGNU Framework에서 유효한 코드 작성

    #else

    #error Please specific single valid constants in MSDOTNET, MSROTOR, XIMIANMONO, GNU.

    #endif

    }

위와 같은 방법을 사용하여 각 Framework에서 아직 덜 구현되었거나 Framework의 성격상 지원하지 않는 부분은 가려내어 효율적으로 프로그래밍할 수 있습니다. 그리고 Framework를 구별하는 용도가 아니더라도 Windows에서 사용할 코드, Linux에서 사용할 코드, Unix에서 사용할 코드, Mac OS X에서 사용할 코드로 가려내어 처리할 수도 있습니다.

만약 여러 개의 상수를 정의한다면 세미 콜론을 사용하여 끊어서 입력하시면 됩니다. 그리고 코드 상에서는 중첩해서 정의 상수를 검사해도 좋지만 Short-Circuit (여러 중첩 if문을 인라인에 서술하는 표기법) 형식으로 축약해도 됩니다. 다음의 예를 살펴보지요.

    public void SelectionSecond()

    {

    #if MSDOTNET && MSROTOR

    // Microsoft .NET Framework와 Microsoft Rotor 프레임워크가 공동으로 지원하는 기능을 코딩

    #elif !XIMIANMONO || !GNU

    // Mono 또는 DotGNU 프레임워크 상수가 정의되지 않았을 때의 기능을 코딩

    #endif

    }

이런 형태로 컴파일러 상수를 얼마든지 다룰 수 있습니다.

여기까지가 간단히 컴파일러 상수를 사용하는 방법의 소개였습니다. 이와 같은 방식으로 컴파일할 때에는 가급적 MAKEFILE과 같은 스크립트를 사용하여 컴파일 하는 것이 좋습니다. MAKEFILE이 아니라면 별도의 Compliation 프로그램을 사용하거나 셸 스크립트, 펄, PHP 등의 타 인터프리터 언어를 사용하는 것도 한 방법입니다.

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

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

댓글을 달아 주세요

Windows + .NET2004. 8. 21. 15:00

System.IDisposable 인터페이스는 일반적으로 재정의하는 클래스들 가운데서 메모리 할당 및 소거 비용이 비싼 클래스를 위하여 특별히 제공되는 인터페이스입니다. 일반적으로 System.ValueType으로부터 상속받는 System.Int16, System.Int32와 같이 가볍고 안전한 타입이거나 비교적 적은 크기의 인스턴스라면 꼭 IDisposable 인터페이스를 구현하지 않는다 할지라도 가비지 컬렉터에 의해서 자동으로 수거되도록 내버려 두어도 문제가 되지 않습니다.

그와는 달리 System.Net.Sockets.Socket과 같은 System-Wide 클래스 혹은 System.Windows.Forms.Form과 같이 많은 수의 인스턴스를 복합적으로 참조하는 클래스들은 "필요할 때에만" 사용하고 즉시 정리해주는 것이 좋습니다. 동시에 많은 처리를 해야 하는 서버 응용프로그램의 경우 가비지 컬렉터가 이 개체를 수집하도록 내버려둔다면 여러번 반복적으로 이런 개체가 생성될 것이고 생각하지 못한 예외 사항이나 성능 저하가 발생할 소지가 있기 때문입니다. (예를 들었던 두 클래스의 경우 "꽤 긴 시간동안" 사용되므로 어떤 메서드 안에서 잠깐 사용되고 말 비싼 개체의 범주에는 포함되지 않습니다.)

모든 언어에 관계없이 다음과 같은 형태로 어떤 메서드 안에서 잠깐 사용하고 말 비싼 개체를 손쉽게 이용할 수 있습니다. 여기서는 C#과 VB.NET의 코드를 예로 들어보도록 하겠습니다.

    // C# 코드

    void OpenDatabase(string providerName, string dataSource)

    {

    if(providerName.Length <= 0 || dataSource.Length <= 0)

     throw new ArgumentException("Do not provide empty string.");

    string fullOption = String.Format(new CultureInfo(String.Empty), "Provider={0};Data Source={1};", providerName, dataSource);

    OleDbConnection conn = new OleDbConnection(fullOption);

    try

    {

     conn.Open();

     // 필요한 작업은 여기서 모두 수행합니다.

    }

    finally

    {

     IDisposable disp = conn as IDisposable;

     if(disp != null) disp.Dispose();

    }

    }

    ' VB.NET 코드

    Sub OpenDatabase(ByVal ProviderName As String, ByVal DataSource As String)

    If ProviderName.Length <= 0 Or DataSource.Length <= 0 Then

     Throw New ArgumentException("Do not provide empty string.");

    End If

    Dim FullOption As String = String.Format("Provider={0};Data Source={1}", ProviderName, DataSource)

    Dim Connection As New OleDbConnection(FullOption);

    Try

     Connection.Open()

     ' 필요한 작업은 여기서 모두 수행합니다.

    Finally

     Dim disp As IDisposable

     If TypeOf Connection Is IDisposable Then

      disp = Connection

      disp.Dispose()

     End If

    End Try

    End Sub

위에서 작성된 코드에서 Try/Finally 블럭을 잘 보세요. 개체를 생성한 뒤 진입하게 된 블럭 안에서 우선은 필요한 작업을 수행하게 됩니다. 부가적으로 Catch 블럭을 이용하여 예외를 잡아낼 수도 있겠습니다만 번외의 이야기이므로 이곳에 집중합니다. Finally 블럭은 예외가 발생했든 발생하지 않았든 무조건 도착하는 영역입니다. 이곳에서 정리 작업을 수행하는데, IDisposable 인터페이스와 호환성이 있는 클래스인지 검사한 뒤 호환성이 있다면 직접 형식 변환을 수행해서 IDisposable.Dispose 메서드를 호출합니다. 이것은 다시 OleDbConnection.Dispose 메서드를 호출하게 되어있습니다.

모든 언어는 위와 같이 Try/Finally 블럭으로 IDisposable의 기능을 사용할 수 있습니다. 그러나 C#은 이와 같이 복잡한 코드를 사용하지 않아도 됩니다. 바로 using 키워드가 그 해답인데, using 키워드를 사용하는 OpenDatabase 메서드를 살펴보겠습니다.

    void OpenDatabase(string providerName, string dataSource)

    {

    if(providerName.Length <= 0 || dataSource.Length <= 0)

     throw new ArgumentException("Do not provide empty string.");

    string fullOption = String.Format(new CultureInfo(String.Empty), "Provider={0};Data Source={1};", providerName, dataSource);

    using(OleDbConnection conn = new OleDbConnection(fullOption))

    {

     conn.Open();

     // 필요한 작업은 여기서 모두 수행합니다.

    }

    }

using 구문의 시작에서 개체를 생성합니다. 그리고 필요한 작업을 수행한 뒤 블럭이 끝나는 지점에서 Finally 블럭에 써주었던 행위를 런타임이 대신 이행합니다. using 문을 사용할 때 주의하실 점은, using에서 할당한 개체는 using 블럭이 종결되는 영역 밖에서는 유효하지 않은 개체가 된다는 점입니다. 또한 using 블럭은 특정 메서드, 프로퍼티 안에서만 쓸 수 있는 코드이며 클래스의 멤버 개체에 대해 이 구문을 사용하는 것은 특별한 유형이 아닌 이상 바람직하지 않습니다.

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

댓글을 달아 주세요

Windows + .NET2004. 8. 18. 12:53

C 언어가 사용하던 표준 함수들 (isalnum, isalpha, iscntrl, isdigit, isgraph, islower, isprint, ispunct, isspace, isupper, isxdigit)과 Visual Basic이 사용하던 Asc 함수를 기억하십니까? 모두 문자를 판별할 때에 사용하던 함수들이었습니다. 이러한 함수는 주로 Lexical Analyzer (이하 LEX)와 Yet Another Compiler Compiler (이하 YACC)와 같이 문자 판별을 중요하게 여기는 부가 도구를 비롯해서 다양한 명령어 해석기에서 사용되었습니다.

C#에서도 Lex나 Yacc를 만들 수는 없을까 하는 생각에서 저는 개인적으로 C 언어 스타일의 100% Managed Library를 제작하고 있습니다. 물론, MSDN 상의 도움말 중 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/vclrfrun-timeroutinesnetframeworkequivalents.asp 과 같은 곳을 참조해서 포팅해도 좋을 것입니다. 하지만 좀 더 간결하면서도 기존 C 언어에서 호출하던 함수와 비슷한 파라미터를 가진 "유사 함수"를 만들 수 있다면 포팅이 쉬워질거라 생각합니다.

가장 첫 단계로 문자 판별식에 대한 실험을 해보고 저는 새로운 점을 알아냈습니다. C 언어에서 쓰이던 ASCII 코드가 특별히 이상한 점 없이 잘 돌아가는 듯 보입니다. ^^ 기존 C 런타임 함수의 기능을 수행하는 C#의 동일한 한줄짜리 코드들을 보여드리겠습니다.

참고: c는 변수 또는 함수의 매개 변수로 사용하시면 됩니다. 또한 모든 판별식은 조건에 맞으면 1을, 맞지 않으면 0을 반환합니다. (C 언어 스타일의 함수들이 그러했었습니다.)

// isalnum: 영어 대문자와 소문자, 숫자를 판별하는 함수입니다.
return ((0x60 < c && 0x7b > c) || (0x40 < c && 0x5b > c) || (0x2f < c && 0x3a > c)) ? 1 : 0;

// isalpha: 영어 대문자와 소문자를 판별하는 함수입니다.
return ((0x60 < c && 0x7b > c) || (0x40 < c && 0x5b > c)) ? 1 : 0;

// isascii: ASCII 영역 내의 문자인지 판별하는 함수입니다.
return (-0x01 < c && 0x80 > c) ? 1 : 0;

// isbinary: 숫자 0 또는 1에 해당하는 문자인지 판별하는 함수입니다. (제가 추가한 함수입니다.)
return (c == 0x30 || c == 0x31) ? 1 : 0;

// iscntrl: 제어 문자를 판별하는 함수입니다.
return ((-0x01 < c && 0x20 > c) || 0x7f == c) ? 1 : 0;

// isdigit: 10진수 숫자 (0 ~ 9)에 해당하는 문자인지 판별하는 함수입니다.
return (0x2f < c && 0x3a > c) ? 1 : 0;

// isgraph: 인쇄 문자 (공백 문자 포함) 인지 판별합니다.
return ((-0x01 < c && 0x20 > c) || 0x7f == c) ? 0 : 1;

// islower: 영어 소문자인지 판별합니다.
return (0x60 < c && 0x7b > c) ? 1 : 0;

// ismbcs: 2바이트 이상의 문자인지 판별합니다. (제가 추가한 함수입니다.)
return ((0x80 < c && 0xa0 > c) || (0xdf < c && 0xfd > c)) ? 0 : 1;

// isodigit: 8진수 숫자 (0 ~ 8)에 해당하는 문자인지 판별합니다. (제가 추가한 함수입니다.)
return (0x2f < c && 0x38 > c) ? 1 : 0;

// isprint: 인쇄 가능 문자 (실제로 보이며, 읽을 수 있는 문자만 해당) 인지 판별합니다.
return ((-0x01 < c && 0x20 >= c) || 0x7f == c || (0x08 < c && 0x0e > c)) ? 0 : 1;

// ispunct: 영숫자, 공백을 제외한 기호 문자인지 판별합니다.
return ((0x1f < c && 0x30 > c) || (0x39 < c && 0x41 > c) || (0x5a < c && 0x61 > c) || (0x7a < c && 0x7f > c)) ? 1 : 0;

// issbcs: 1바이트 이내의 문자인지 판별합니다. (제가 추가한 함수입니다.)
return ((0x80 < c && 0xa0 > c) || (0xdf < c && 0xfd > c)) ? 1 : 0;

// isspace: 공백 문자인지 판별합니다.
return ((0x08 < c && 0x0e > c) || c == 0x20) ? 1 : 0;

// isupper: 영어 대문자인지 판별합니다.
return (0x40 < c && 0x5b > c) ? 1 : 0;

// isxdigit: 16진수 숫자 (0 ~ 9, A ~ E)에 해당하는 문자인지 판별합니다.
return ((0x2f < c && 0x3a > c) || (0x40 < c && 0x47 > c) || (0x60 < c && 0x67 > c)) ? 1 : 0;

문자에 관한 판별 함수는 위와 같은 식으로 축약하여 표현할 수 있음을 발견하였습니다. 위와 같은 판별식을 사용하여 System.Net.Sockets.Socket으로 주고 받는 모든 데이터 스트림에 관한 분석을 신속히 할 수도 있으며 장기적으로는 앞에서 거론하였던 Lex와 Yacc의 닷넷용 버전을 제작할 수 있는 초석이 될 거라 생각합니다. 유용하게 사용하시길 바랍니다. ^^

참고 사항: 변수 c의 형식은 byte, char, short, int, long 등 제약이 없습니다. 하지만 함수로 제작하실 때에는 int와 long에 대한 두 가지 함수만 제공하시면 충분합니다. 또한 unsigned (양수) 계열 변수는 표준 규격은 아니기에 권장하지 않습니다.

참고 서적: 영진 닷컴 표준 프로그래머 시리즈 - C 언어 함수의 사용법 + 작성법 완전 제패 (Shozo Kashihara 저)

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

댓글을 달아 주세요

Windows + .NET2004. 8. 17. 12:20

이번 시간에는 지난 번 시간을 이어 마지막으로 C++에서 제작한 클래스를 바인딩하는 C# 클래스를 만들어보도록 하겠습니다.

* 지난번 글을 다시 여시지 않도록 하기 위하여 클래스를 다시 정의합니다.

    // TestClass.h에 정의하는 내용입니다.

    class TestClass

    {

    public:

        void TestFunction(int value);

        int MyFunction(void);

    private:

        int m_myValue;

    public:

        int GetMyValue(void);

        void SetMyValue(int value);

    public:

        TestClass(int value);

    }

    // TestClass.cpp에 정의하는 내용입니다.

    #include <iostream>

    void TestClass::TestFunction(int value)

    {

        stdout << value;

    }

    int TestClass::MyFunction(void)

    {

        return 123;

    }

    int TestClass::GetMyValue(void)

    {

        return this->m_myValue;

    }

    void TestClass::SetMyValue(int value)

    {

        this->m_myValue = value;

    }

    TestClass::TestClass(int value)

    {

        SetMyValue(value);

    }

6. 프록시 함수의 완성본 만들기

    #ifdef MSC_VER // VC++ 컴파일러를 위한 Export 구문 설정 (DLL 버전)

       #define EXPORT extern "C" __declspec(dllexport)

    #else // VC++ 이외의 컴파일러를 위한 Export 구문 설정 (적절한 설정이 필요할 수도 있습니다.)

       #define EXPORT

    #endif

    EXPORT TestClass* TestClass_ctor(int value)

    {

       return new TestClass(value);

    }

    EXPORT void TestClass_dtor(TestClass* pInstance)

    {

       if(pInstance != NULL)

            delete pInstance;

       pInstance = NULL;

    }

    EXPORT void TestClass_TestFunction(TestClass* pInstance, int value)

    {

       if(pInstance != NULL)

           pInstance->TestFunction(value);

    }

    EXPORT int TestClass_MyFunction(TestClass* pInstance)

    {

       if(pInstance != NULL)

           return pInstance->MyFunction();

       return 0;

    }

    EXPORT int TestClass_GetMyValue(TestClass* pInstance)

    {

       if(pInstance != NULL)

           return pInstance->GetMyValue();

       return 0;

    }

    EXPORT int TestClass_SetMyValue(TestClass* pInstance, int value)

    {

       if(pInstance != NULL)

       {

           pInstance->SetMyValue(value);

           return 1;

       }

       return 0;

    }

7. C#에서 라이브러리 참조하기

Windows의 C++ 컴파일러를 사용해서 컴파일할 경우 DLL로 컴파일시키며, Linux/Unix/Mac OS X 등의 C++ 컴파일러를 사용해서 컴파일할 경우 Dynamic Library (확장자는 대개 so 파일)로 컴파일시킵니다. 그리고 Windows에서는 %windir%\system32 디렉터리에, Linux에서는 /usr/lib과 같은 라이브러리 파일을 집결시키는 디렉터리에 만들어진 라이브러리를 배치합니다. 혹은 C# 프로그램 파일이 있는 곳에 같이 배치해도 됩니다.

    using System;

    using System.Runtime.InteropService;

    public sealed class TestClassBinding

    {

       // 모든 프록시 함수는 이 변수를 참조합니다.

       private IntPtr rawValue = null;

       [DllImport("라이브러리 파일 이름")]

       private static extern IntPtr TestClass_ctor(int value);

       [DllImport("라이브러리 파일 이름")]

       private static extern void TestClass_dtor(IntPtr instance);

       [DllImport("라이브러리 파일 이름")]

       private static extern void TestClass_TestFunction(IntPtr instance, int value);

       [DllImport("라이브러리 파일 이름")]

       private static extern int TestClass_MyFunction(IntPtr instance);

       [DllImport("라이브러리 파일 이름")]

       private static extern int TestClass_GetMyValue(IntPtr instance);

       [DllImport("라이브러리 파일 이름")]

       private static extern int TestClass_SetMyValue(IntPtr instance, int value);

       public TestClassBinding(int value)

       {

           this->rawValue = TestClass_ctor(value);

       }

       ~TestClassBinding()

       {

           TestClass_dtor(this->rawValue);

       }

       public void TestFunction(int value)

       {

           TestClass_TestFunction(this->rawValue, value);

       }

       public int MyFunction()

       {

           return TestClass_MyFunction(this->rawValue);

       }

       public int MyValue

       {

           get

           {

               return TestClass_GetMyValue(this->rawValue);

           }

           set

           {

               try

               {

                   if(TestClass_SetMyValue(this->rawValue, value) == 0)

                       throw new Exception("할당되지 않은 포인터를 사용하려 했습니다.");

               }

               catch(Exception ex)

               {

                   Console.WriteLine(ex.Message);

               }

           }

       }

       public int GetMyValue()

       {

           return TestClass_GetMyValue(this->rawValue);

       }

       public void SetMyValue(int value)

       {

           try

           {

               if(TestClass_SetMyValue(this->rawValue, value) == 0)

                   throw new Exception("할당되지 않은 포인터를 사용하려 했습니다.");

           }

           catch(Exception ex)

           {

               Console.WriteLine(ex.Message);

           }

       }

    }

이것으로 세 단계에 걸친 강좌를 모두 끝내도록 하겠습니다. 다음 시간에는 또 다른 토픽을 가지고 강좌를 올리도록 하겠습니다. 많은 도움이 되셨기를 바랍니다. ^^

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

댓글을 달아 주세요