WCF(Windows Communication Foundation) 아키텍처 개요

2006. 12. 3. 09:08 IT 및 개발/.NET FX & Visual C#
WCF(Windows Communication Foundation) 아키텍처와 주요 개념에 대한 고급 정보를 얻을 수 있습니다. 코드 예에서는 WCF 계약, 끝점 및 동작에 대해 설명합니다.

소개

이 문서에서는 WCF(Windows Communication Foundation) 아키텍처를 심도 깊게 살펴보고 WCF의 주요 개념과 이러한 주요 개념이 어떻게 함께 상호 작용하는지를 확인할 것입니다. 개념을 자세히 설명하기 위해 몇 가지 코드 예가 수록되어 있지만 코드가 이 문서의 중심은 아닙니다.

이 문서의 나머지 부분은 다음의 두 주요 섹션으로 구성됩니다.

  • WCF의 기본: WCF의 주요 개념, 용어 및 아키텍처 구성 요소에 대해 설명합니다.
  • 코드 예: WCF의 기본 부분에서 설명된 개념을 설명하고 이를 구체화하는 몇 가지 짧은 코드 예를 제공합니다.

이 기사에서는 Windows 워크플로 파운데이션을 간단히 소개하고 단계별 예를 통해 작동 방식을 설명합니다.

WCF의 기본

WCF 서비스는 끝점 컬렉션을 제공하는 프로그램이며 각 끝점은 세계와 통신하는 포털 역할을 합니다.

클라이언트는 하나 이상의 끝점과 메시지를 교환하는 프로그램이며 이중 메시지 교환 패턴으로 서비스로부터 메시지를 수신하도록 끝점을 제공하기도 합니다.

다음 섹션에서는 이러한 기본적인 내용을 좀 더 자세히 설명합니다.

끝점

서비스 끝점은 주소, 바인딩 및 계약으로 구성됩니다.

끝점의 주소는 끝점이 있는 위치의 네트워크 주소입니다. EndpointAddress 클래스는 WCF 끝점 주소를 나타냅니다.

끝점의 바인딩은 끝점에서 전송 프로토콜(예: TCP, HTTP), 인코딩(예: 텍스트, 이진) 및 보안 요구 사항(예: SSL, SOAP 메시지 보안)과 같은 항목을 포함하여 세계와 통신하는 방식을 지정합니다. Binding 클래스는 WCF 바인딩을 나타냅니다.

끝점의 계약은 끝점에서 통신하는 대상을 지정하며 기본적으로 단방향, 이중 및 요청/응답과 같은 기본 MEP(Message Exchange Pattern)가 있는 작업으로 구성되는 메시지 컬렉션입니다. ContractDescription 클래스는 WCF 계약을 나타냅니다.

ServiceEndpoint 클래스는 끝점을 나타내며 각각 끝점의 주소, 바인딩 및 계약에 해당하는 EndpointAddress, Binding 및 ContractDescription을 포함합니다(그림 1 참조).


그림 1. 각 서비스의 끝점에는 EndpointAddress, Binding, 그리고 ContractDescription이라는 계약이 포함되어 있습니다.

EndpointAddress

EndpointAddress는 기본적으로 그림 2에 표시된 것처럼 URI, ID 및 헤더(선택 사항)의 컬렉션입니다.

끝점의 보안 ID는 일반적으로 URI이지만 고급 시나리오에서는 Identity 주소 속성을 사용하여 URI에 관계없이 ID를 명시적으로 설정할 수 있습니다.

선택적 헤더는 끝점의 URI 이외의 추가 주소 정보를 제공하는 데 사용됩니다. 예를 들어 같은 주소 URI를 공유하는 여러 끝점을 구별하는 데 주소 헤더를 유용하게 사용할 수 있습니다.


그림 2. EndpointAddress에는 URI가 포함되고 AddressProperties에는 Identity와 AddressHeader 컬렉션이 포함됩니다.

바인딩

바인딩은 이름, 네임스페이스 및 구성 가능한 바인딩 요소의 컬렉션으로 구성됩니다(그림 3). 바인딩의 이름과 네임스페이스를 통해 서비스의 메타데이터에서 바인딩을 고유하게 식별할 수 있습니다. 각 바인딩 요소는 끝점이 세계와 통신하는 방법적 측면을 정의합니다.


그림 3. Binding 클래스와 해당 멤버

예를 들어 그림 4에서는 세 개의 바인딩 요소를 포함하는 바인딩 요소 컬렉션을 보여 줍니다. 각각의 바인딩 요소는 끝점과의 통신 방식에 대한 일부 측면을 정의합니다. TcpTransportBindingElement는 끝점에서 TCP를 전송 프로토콜로 사용하여 세계와 통신함을 나타냅니다. ReliableSessionBindingElement는 끝점에서 신뢰할 수 있는 메시징을 통해 메시지 배달 보증을 제공함을 나타냅니다. SecurityBindingElement는 끝점에서 SOAP 메시지 보안을 사용함을 나타냅니다. 각 바인딩 요소에는 대부분 끝점과의 통신 방식에 대한 고유 정보를 보다 자세히 정의하는 속성이 있습니다. 예를 들어 ReliableSessionBindingElement에는 필요한 메시지 배달 보증(예: 없음, 한 번 이상, 최대 한 번 또는 한 번만)을 지정하는 Assurances 속성이 있습니다.


그림 4. 세 가지 바인딩 요소가 있는 바인딩의 예

바인딩에서 바인딩 요소의 순서와 유형은 중요합니다. 바인딩 요소 컬렉션을 사용하여 통신 스택을 구축할 때, 통신 스택은 바인딩 요소 컬렉션에 있는 바인딩 요소의 순서에 따라 정렬됩니다. 컬렉션에 추가된 마지막 바인딩 요소는 통신 스택의 최하위 구성 요소에 해당하며 첫 번째 바인딩 요소는 최상위 구성 요소에 해당합니다. 받는 메시지는 아래에서 위로 이동하며 보내는 메시지는 위에서 아래로 이동합니다. 그러므로 컬렉션 내 바인딩 요소의 순서는 통신 스택 구성 요소에서 메시지를 처리하는 순서에 직접적인 영향을 줍니다. WCF에서는 대부분의 시나리오에서 사용자 지정 바인딩을 정의하지 않고 사용할 수 있는 미리 정의된 바인딩 집합을 제공합니다.

계약

WCF 계약은 끝점에서 외부 세계와 통신하는 대상을 지정하는 작업의 컬렉션입니다. 각 작업은 단방향 또는 요청/응답 메시지 교환과 같은 간단한 메시지의 교환입니다.

ContractDescription 클래스는 WCF 계약과 해당 작업을 정의하는 데 사용됩니다. ContractDescription 내에서 각 계약 작업에는 작업이 단방향 작업인지 또는 요청/응답 작업인지와 같은 작업의 측면을 정의하는 OperationDescription이 포함됩니다. 각 OperationDescription은 또한 MessageDescriptions의 컬렉션을 사용하여 작업을 구성하는 메시지를 정의합니다.

일반적으로 ContractDescription은 WCF 프로그래밍 모델을 사용하여 계약을 정의하는 인터페이스나 클래스에서 생성합니다. 이 형식에는 ServiceContractAttribute를 연결하고 끝점 작업에 해당하는 메서드에는 OperationContractAttribute를 연결해야 합니다. 특성을 연결한 CLR 형식으로 시작하지 않고 직접 ContractDescription을 작성할 수도 있습니다.

이중 계약은 클라이언트가 호출하도록 서비스에서 제공하는 집합과 서비스가 호출하도록 클라이언트에서 제공하는 집합 등, 논리적인 작업 집합 두 가지를 정의합니다. 이중 계약을 정의하기 위한 프로그래밍 모델에서는 각 집합을 별도의 형식으로 분할하고(각 형식은 클래스 또는 인터페이스여야 함) 서비스 작업을 나타내는 계약에 ServiceContractAttribute를 연결하여 클라이언트(또는 콜백) 작업을 정의하는 계약을 참조합니다. 또한 ContractDescription에는 각 형식에 대한 참조가 포함되어 있으므로 이를 하나의 이중 계약으로 그룹화할 수 있습니다.

바인딩과 마찬가지로 각 계약에는 서비스의 메타데이터에서 계약을 고유하게 식별하는 이름과 네임스페이스가 있습니다.

각 계약에는 계약의 동작을 수정하거나 확장하는 모듈인 ContractBehaviors 컬렉션도 포함됩니다. 다음 섹션에서는 이러한 동작에 대해 보다 자세히 설명합니다.


그림 5. WCF 계약을 정의하는 ContractDescription 클래스

동작

동작은 서비스 또는 클라이언트 기능을 수정하거나 확장하는 형식입니다. 예를 들어 ServiceMetadataBehavior에서 구현한 메타데이터 동작은 서비스에서 메타데이터를 게시할지 여부를 제어합니다. 마찬가지로 보안 동작은 가장 및 권한 부여를 제어하고 트랜잭션 동작은 트랜잭션 참여 및 자동 완료를 제어합니다.

동작은 채널 구축 프로세스에도 참여할 수 있으며 사용자가 지정한 설정 및/또는 서비스나 채널의 기타 측면에 따라 해당 채널을 수정할 수 있습니다.

서비스 동작은 IServiceBehavior를 구현하고 서비스에 적용되는 형식입니다. 마찬가지로 채널 동작도 IChannelBehavior를 구현하고 클라이언트 채널에 적용되는 형식입니다.

서비스 및 채널 설명

ServiceDescription 클래스는 서비스에서 제공하는 끝점, 서비스에 적용된 동작, 그리고 서비스를 구현하는 형식(클래스)을 비롯한 WCF 서비스를 정의하는 메모리 내 구조입니다(그림 6 참조). ServiceDescription은 메타데이터, 코드/구성 및 채널을 생성하는 데 사용됩니다.

이 ServiceDescription 개체를 직접 작성할 수 있습니다. 특정 WCF 특성을 연결한 형식에서 개체를 만들 수도 있으며 이 방법이 좀 더 일반적인 시나리오입니다. 이 형식을 위한 코드는 직접 작성하거나 svcutil.exe라는 WCF 도구를 사용하여 WSDL 문서로부터 생성할 수 있습니다.

ServiceDescription 개체를 명시적으로 만들고 채울 수 있지만 서비스 실행 과정의 일부로 백그라운드에서 만들어지는 경우가 많습니다.


그림 6. ServiceDescription 개체 모델

마찬가지로 클라이언트 쪽에서는 ChannelDescription이 특정 끝점에 대한 WCF 클라이언트의 채널을 정의합니다(그림 7). ChannelDescription 클래스에는 채널에 적용되는 동작인 IchannelBehaviors 컬렉션과 채널이 통신하는 끝점을 정의하는 ServiceEndpoint가 포함됩니다.

ServiceDescription과 달리 ChannelDescription에는 채널이 통신하는 대상 끝점을 나타내는 ServiceEndpoint가 한 개만 포함됩니다.


그림 7. ChannelDescription 개체 모델

WCF 런타임

WCF 런타임은 메시지 송수신을 담당하는 개체 집합입니다. 예를 들어 메시지 서식 지정, 보안 적용, 다양한 전송 프로토콜을 사용한 메시지 송수신 작업뿐 아니라 수신된 메시지를 해당 작업으로 전송하는 작업이 모두 WCF 런타임 내에서 이루어집니다. 다음 섹션에서는 WCF 런타임의 주요 개념에 대해 설명하겠습니다.

메시지

WCF 메시지는 클라이언트와 끝점 간의 데이터 교환 단위입니다. 메시지는 기본적으로 SOAP 메시지 InfoSet의 메모리 내 표현입니다. 메시지는 텍스트 XML에 제한되지 않으며 사용된 인코딩 메커니즘에 따라 WCF 이진 형식, 텍스트 XML 또는 다른 사용자 지정 형식을 사용하여 메시지를 serialize할 수 있습니다.

채널

채널은 끝점으로 메시지를 전송하고 끝점에서 메시지를 수신하는 핵심 추상입니다. 크게 보면 채널의 범주는 두 가지로 나뉩니다. 이 중 하나인 전송 채널은 TCP, UDP 또는 MSMQ와 같은 전송 프로토콜을 사용하여 불투명 8진수 스트림의 송수신을 처리하는 반면, 프로토콜 채널은 메시지를 처리하고 필요한 경우 수정하는 방식으로 SOAP 기반 프로토콜을 구현합니다. 예를 들어 보안 채널은 SOAP 메시지 헤더를 추가 및 처리할 수 있으며 메시지 본문을 암호화하여 수정할 수 있습니다. 채널을 다른 채널 위에 배치하고 이 채널을 다시 세 번째 채널 위에 배치하는 방식으로 채널을 구성하는 것이 가능합니다.

EndpointListener

EndpointListener는 런타임에서 ServiceEndpoint와 동일한 역할을 합니다. ServiceEndpoint의 EndpointAddress, 계약 및 바인딩(각각 통신 위치, 대상 및 방식을 나타냄)은 각각 EndpointListener의 수신 주소, 메시지 필터링 및 전송, 그리고 채널 스택에 해당합니다. EndpointListener에는 메시지 송수신을 담당하는 채널 스택이 포함됩니다.

ServiceHost 및 ChannelFactory

WCF 서비스 런타임은 대개 ServiceHost.Open을 호출함으로써 백그라운드에서 생성됩니다. ServiceHost(그림 6)는 서비스 형식에 따라 ServiceDescription 생성을 주도하며 ServiceDescription의 ServiceEndpoint 컬렉션을 구성 또는 코드(또는 둘 다)에 정의된 끝점으로 채웁니다. 그런 다음 ServiceHost는 ServiceDescription을 사용하여 ServiceDescription 내의 각 ServiceEndpoint에 대한 EndpointListener 개체 형태로 채널 스택을 만듭니다.


그림 8. ServiceHost 개체 모델

마찬가지로 클라이언트 쪽에서는 클라이언트 쪽 ServiceHost에 해당하는 ChannelFactory에 의해 클라이언트 런타임이 생성됩니다.

ChannelFactory는 계약 형식, 바인딩 및 EndpointAddress에 따라 ChannelDescription 생성을 주도합니다. 그런 다음 이 ChannelDescription을 사용하여 클라이언트의 채널 스택을 만듭니다.

서비스 런타임과는 달리 클라이언트 런타임은 EndpointListener를 포함하지 않습니다. 클라이언트에서 항상 서비스에 대한 연결을 시작하므로 클라이언트는 받는 연결을 위해 "수신 대기"할 필요가 없기 때문입니다.

코드 예

다음 섹션에서는 서비스와 클라이언트를 어떻게 작성하는지 보여 주는 코드 예를 제시하겠습니다. 이러한 예는 위에서 소개한 개념을 구체적으로 설명하기 위한 것이며 WCF 프로그래밍을 가르치기 위한 것은 아닙니다.

계약 정의 및 구현

위에서 설명했듯이 계약을 정의하는 가장 쉬운 방법은 인터페이스나 클래스를 만들고 ServiceContractAttribute를 연결하는 것입니다. 이렇게 하면 계약으로부터 ContractDescription을 손쉽게 만들 수 있습니다.

인터페이스나 클래스를 사용하여 계약을 정의할 때 계약의 멤버인 각 인터페이스 또는 클래스 메서드에는 OperationContractAttribute를 연결해야 합니다. 예를 들면 다음과 같습니다.

using System.ServiceModel;

//인터페이스를 사용하여 정의된 WCF 계약
[ServiceContract]
public interface IMath
{
  [OperationContract]
  int Add(int x, int y);
}
(참고: 프로그래머 코멘트는 샘플 프로그램 파일에는 영문으로 제공되며 기사에는 설명을 위해 번역문으로 제공됩니다.)

이 경우에 계약을 구현하려면 IMath를 구현하는 클래스를 만들면 됩니다. 이 클래스는 WCF 서비스 클래스가 됩니다. 예를 들면 다음과 같습니다.
//인터페이스를 구현하는 서비스 클래스
public class MathService : IMath
{
  public int Add(int x, int y)
  { return x + y; }
}

끝점 정의 및 서비스 시작

코드나 구성에 끝점을 정의할 수 있습니다. 아래 예에서 DefineEndpointImperatively 메서드는 코드에서 끝점을 정의하고 서비스를 시작하는 가장 쉬운 방법을 보여 줍니다.

DefineEndpointInConfig 메서드는 구성(구성 예는 아래 코드 다음에 제시됨)에 정의된 동일한 끝점을 보여 줍니다.

public class WCFServiceApp
{
  public void DefineEndpointImperatively()
  {
       //MathService를 위한 서비스 호스트를 만듭니다.
       ServiceHost sh = new ServiceHost(typeof(MathService));

       //AddEndpoint 도우미 메서드를 사용하여
       //ServiceEndpoint를 만들고
       //이를 ServiceDescription에 추가합니다.
       sh.AddServiceEndpoint(
         typeof(IMath), //계약 형식
         new WSHttpBinding(), //기본 제공 바인딩 중 하나
         "http://localhost/MathService/Ep1"); //끝점의 주소

       //서비스 런타임을 만들고 엽니다.
       sh.Open();

  }

  public void DefineEndpointInConfig()
  {
       //MathService를 위한 서비스 호스트를 만듭니다.
       ServiceHost sh = new ServiceHost (typeof(MathService));

       //서비스 런타임을 만들고 엽니다.
       sh.Open();

  }
}
<!-- 위의 코드에서 사용하는 구성 파일 -->
<configuration
  xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
  <system.serviceModel>
     <services>
                <!-- 서비스 형식에서 참조하는 서비스 요소 -->
        <service type="MathService">
           <!-- ABC의 끝점을 정의하는 끝점 요소 -->
             <endpoint
           address="http://localhost/MathService/Ep1"
           binding="wsHttpBinding"
             contract="IMath"/>
        </service>
     </services>
  </system.serviceModel>
</configuration>

끝점으로 메시지 전송

아래 코드는 IMath 끝점으로 메시지를 전송하는 두 가지 방법을 보여 줍니다. SendMessageToEndpoint는 채널 생성을 숨깁니다. SendMessageToEndpointUsingChannel 예에서는 이 과정이 명시적으로 수행되지만 여기에서는 백그라운드에서 수행됩니다.

SendMessageToEndpoint의 첫 번째 예에서는 svcutil.exe라는 도구와 서비스의 메타데이터를 사용하여 계약(이 예에서는 IMath), 계약을 구현하는 프록시 클래스(이 예에서는 MathProxy) 및 관련 구성(여기에는 나와 있지 않음)을 생성합니다. 다시 설명하지만 IMath에 의해 정의된 계약은 대상(즉, 수행할 작업)을 지정하며 생성된 구성에는 바인딩(방식)과 주소(위치)가 포함됩니다.

이 프록시 클래스를 사용하려면 인스턴스화한 다음 Add 메서드를 호출하면 됩니다. 프록시 클래스는 백그라운드에서 채널을 만들고 이 채널을 사용하여 끝점과 통신합니다.

아래에 있는 SendMessageToEndpointsUsingChannel의 두 번째 예는 ChannelFactory를 직접 사용하여 끝점과 통신하는 경우를 보여 줍니다. 이 예에서는 프록시 클래스와 구성을 사용하지 않고 직접 ChannelFactory<IMath>.CreateChannel을 사용하여 채널을 만들었습니다. 또한 끝점의 주소와 바인딩을 정의하는 데 구성을 사용하지 않고 ChannelFactory<IMath> 생성자가 이 두 가지 정보를 매개 변수로 받았습니다. 끝점 정의에 필요한 세 번째 정보인 계약은 T 형식으로 전달됩니다.

using System.ServiceModel;
//이 계약은 svcutil.exe에 의해
//서비스의 메타데이터로부터 생성됩니다.
public interface IMath
{
  [OperationContract]
  public int Add(int x, int y)
  { return x + y; }
}


//이 클래스는 svcutil.exe에 의해
//서비스의 메타데이터로부터 생성됩니다.
//생성된 구성은 여기에서 제시되지 않습니다.
public class MathProxy : IMath
{
  ...
}

public class WCFClientApp
{
  public void SendMessageToEndpoint()
  {
       //여기에서는 svcutil.exe에 의해 서비스의 메타데이터로부터 생성된
       //프록시 클래스를 사용합니다.
       MathProxy proxy = new MathProxy();

       int result = proxy.Add(35, 7);
  }
  public void SendMessageToEndpointUsingChannel()
  {
       //여기에서는 채널을 만들기 위해 ChannelFactory를 사용합니다.
       //주소, 바인딩 및 계약 형식(IMath)을
       //지정해야 합니다.
       ChannelFactory<IMath> factory=new ChannelFactory<IMath>(
           new WSHttpBinding(),
           new EndpointAddress("http://localhost/MathService/Ep1"));
       IMath channel=factory.CreateChannel();
       int result=channel.Add(35,7);
       factory.Close();

  }
}

사용자 지정 동작 정의

사용자 지정 동작을 정의하려면 IServiceBehavior를 구현해야 합니다(클라이언트 쪽 동작의 경우에는 IChannelBehavior). 아래 코드에서는 IServiceBehavior를 구현하는 동작의 예를 보여 줍니다. 이 코드는 IServiceBehavior.ApplyBehavior에서 ServiceDescription을 검사하며 ServiceDescription 내의 각 동작의 이름은 물론 각 ServiceEndpoint의 주소, 바인딩 및 계약을 작성합니다.

이 특정 동작은 System.Attribute에서 상속한 특성이기도 하므로 아래와 같이 선언적으로 적용할 수 있습니다. 그러나 동작이 특성이어야 하는 것은 아닙니다.

[AttributeUsageAttribute(
        AttributeTargets.Class,
        AllowMultiple=false,
        Inherited=false)]
public class InspectorBehavior : System.Attribute,
                                System.ServiceModel.IServiceBehavior
{
  public void ApplyBehavior(
      ServiceDescription description,
      Collection<DispatchBehavior> behaviors)
  {
      Console.WriteLine("-------- Endpoints ---------");
      foreach (ServiceEndpoint endpoint in description.Endpoints)
      {
          Console.WriteLine("--> Endpoint");
          Console.WriteLine("Endpoint Address: {0}",
                            endpoint.Address);
          Console.WriteLine("Endpoint Binding: {0}",
                            endpoint.Binding.GetType().Name);
          Console.WriteLine("Endpoint Contract: {0}",
                             endpoint.Contract.ContractType.Name);
          Console.WriteLine();
      }
      Console.WriteLine("-------- Service Behaviors --------");
      foreach (IServiceBehavior behavior in description.Behaviors)
      {
          Console.WriteLine("--> Behavior");
          Console.WriteLine("Behavior: {0}", behavior.GetType().Name);
          Console.WriteLine();
      }
  }
}

사용자 지정 동작 적용

모든 동작은 ServiceDescription(클라이언트 쪽에서는 ChannelDescription)에 동작의 인스턴스를 추가하여 강제로 적용할 수 있습니다. 예를 들어 InspectorBehavior를 강제로 적용하려면 다음과 같이 코드를 작성합니다.

ServiceHost sh = new ServiceHost(typeof(MathService));
sh.AddServiceEndpoint(
      typeof(IMath),
       new WSHttpBinding(),
      "http://localhost/MathService/Ep1");
//강제로 동작을 추가합니다.
InspectorBehavior behavior = new InspectorBehavior();
sh.Description.Behaviors.Add(behavior);
sh.Open();

또한 System.Attribute에서 상속한 동작을 서비스에 선언적으로 적용할 수 있습니다. 예를 들어 InspectorBehavior는 System.Attribute에서 상속되었으므로 다음과 같이 선언적으로 적용할 수 있습니다.

[InspectorBehavior]
public class MathService : IMath
{
  public int Add(int x, int y)
  { return x + y; }
}

요약

WCF 서비스에서는 각 끝점이 세계와 통신하는 포털이 되는 끝점 컬렉션을 제공합니다. 각 끝점에는 주소, 바인딩 및 계약(ABC)이 포함됩니다. 주소는 끝점이 있는 위치이며 바인딩은 끝점의 통신 방식이고 계약은 끝점의 통신 대상입니다.

서비스에서는 ServiceDescription에 서비스에서 제공하는 끝점을 각각 정의하는 ServiceEndpoint 컬렉션이 포함됩니다. ServiceHost에서는 이 정의로부터 ServiceDescription 내의 각 ServiceEndpoint에 대한 EndpointListener를 포함하는 런타임을 만듭니다. 끝점의 주소, 바인딩 및 계약(통신 위치, 대상 및 방식을 나타냄)은 각각 EndpointListener의 수신 주소, 메시지 필터링 및 전송, 그리고 채널 스택에 해당합니다.

마찬가지로 클라이언트에서 ChannelDescription에는 클라이언트가 통신하는 ServiceEndpoint가 하나 포함됩니다. ChannelFactory에서는 이 ChannelDescription으로부터 서비스의 끝점과 통신할 수 있는 채널 스택을 만듭니다.

출처 : 한국 마이크로소프트 MSDN (2006년 3월)