WCF 서비스의 동시성(Concurrency) - 1

WCF 2010. 3. 10. 09:00 Posted by 알 수 없는 사용자

봄이 오고 있네요,, 
날씨가 많이 따뜻해졌고, 해도 부쩍 길어졌음을 느낍니다.
최대한 빠른 시일 내에 포스팅을 하려 했는데, 그동안 무기력증(?)에 빠져있다보니,,
하는거 없이 시간만 보내버렸네요,, ^^;;
봄이 찾아온 만큼 새로운 마음가짐으로 다시 시작해보겠습니다. 아자~!

이번 포스팅의 주제는 WCF의 Behaviors 중에서 서비스의 동시성(Concurrency)을 컨트롤 할 수 있는 Behavior 입니다.

Behavior는 서비스가 동작할 때(그러니깐 런타임 시) 동작에 영향을 끼치는 클래스들로, 서비스 클래스의 특성으로 지정하거나, 환경 설정파일을 통해 지정할 수 있습니다.

Behavior와 관련된 여러 가지 내용 중 이번 포스팅에선 동시성(Concurrency)에 대해서 얘기해보도록 하겠습니다.

동시성이라 함은, 여러 task 들이 동시에 동작하는 것을 말합니다.
동시성은 다들 아시겠지만, 그리고 아주 당연하게도 시스템의 throughput(출력률)에 큰 영향을 끼칩니다. 일정 시간동안 처리할 수 있는 작업의 양이 커지기 때문이죠.

WCF 에서는 동시성을 컨트롤할 수 있는 두 종류의 behavior 가 있습니다. 바로 “InstanceContextMode”“ConcurrencyMode” 입니다.

InstanceContextMode는 생성되는 서비스의 인스턴스를 조절할 수 있는 behavior로 다음과 같은 세 종류의 값으로 설정할 수 있습니다.

  • Single : 이 값은 서비스로 들어오는 모든 요청을 하나의 인스턴스에서 처리하도록 설정합니다.
  • PerCall : 서비스로 들어오는 요청마다 서비스의 인스턴스가 만들어지도록 하기 위한 설정입니다.
  • PerSession : 클라이언트 세션마다의 서비스 인스턴스를 생성하기 위한 설정이며, 만약 세션을 사용하지 않는 채널일 때, 이 값으로 설정이 된다면, PerCall과 같은 방식으로 동작합니다.

그리고, InstanceContextMode의 기본값은 PerSession으로 따로 어떠한 값도 설정되어 있지 않은 경우엔 세션 수에 따라 서비스 인스턴스가 생성됩니다.

WCF에서 동시성을 조절할 수 있는 또 다른 모드인 ConcurrencyMode는 하나의 서비스 인스턴스 내에서 동작하는 스레드를 통한 동시성을 컨트롤하는 behavior입니다.
다음은 ConcurrencyMode에서 설정할 수 있는 값에 대한 설명입니다.

  • Single : 하나의 서비스 인스턴스 내에 오로지 하나의 스레드만이 동작하도록 설정하는 값입니다. 따라서, 이 값으로 설정되어 있는 경우엔 스레딩 문제를 고려하지 않아도 된다는 장점이 있습니다.
  • Reentrant : 이 설정 역시 하나의 서비스 인스턴스에서 하나의 스레드만이 동작하도록 하는 설정값입니다. 하지만, 이 설정값이 Single과 다른 점은 하나의 스레드가 동작하는 도중에 다른 작업이 처리될 수 있다는 것입니다. 이 작업의 처리가 완료되면 이 전의 작업이 계속해서 동작됩니다.
  • Multiple : 하나의 서비스 인스턴스에서 하나 이상의 스레드가 동작할 수 있도록 하는 설정입니다. 이 값으로 설정되어 있는 경우엔 여러 개의 스레드에서 서비스 개체를 변경할 수 있기 때문에 항상 동기화와 상태 일관성을 처리해 주어야 합니다.

ConcurrencyMode와 InstanceContextMode의 값을 적절하게 조합하면, 서비스의 기능에 맞게 동시성과 인스턴스 관리를 할 수 있습니다. 지금 이러한 내용을 글로 써내려가봤자 설명하기도 힘들고, 받아들이기도 힘이 들겁니다. 따라서 이러한 내용은 역시 실제 코드를 작성하고, 결과를 보면서 이해하는게 가장 쉽고 빠른 방법이겠죠 ^^

네~ 이제 InstanceContextMode와 ConcurrencyMode의 값을 적절하게 조합하여 서비스에 적용하는 실습을 해보도록 하겠습니다.

우선, 가장 먼저 세션을 사용하지 않는 환경에서 InstaceContextMode와 ConcurrencyMode의 기본값을 사용한 서비스를 구현해보겠습니다. InstanceContextMode의 기본값은 PerSession 이며, ConcurrencyMode의 기본값은 Single 입니다. 이 기본값은 따로 설정해주지 않아도 적용된다는거 아시죠? ㅎ

다음은 서비스를 구현한 클래스의 코드 입니다.

class ProductService : IProductService

{

    ProductService()

    {

        Console.WriteLine("{0}: 서비스의 새로운 인스턴스 생성!!", DateTime.Now);

    }

 

    public Product GetProduct()

    {

        Console.WriteLine("{0} : GetProduct 호출, Thread Id {1}", DateTime.Now,
Thread.CurrentThread.ManagedThreadId);

        Thread.Sleep(5000);

 

        Product p = new Product();

        p.ProductId = 1234;

        p.ProductName = "ABC Chocolate";

        p.Price = 1500.0;

        p.Company = "Lotteee";

        p.CreateDate = DateTime.Parse("2010-01-22");

 

        return p;

    }

}


저번 포스팅에서 사용했던 서비스의 코드를 살짝 수정 해보았습니다.
서비스 클래스 생성자를 만들어 단순하게 인스턴스가 생성되었다는 메시지를 출력해주는 코드를 추가하였구요, GetProduct 메서드 내에서는 현재 스레드의 ID 값을 출력해주는 코드를 추가하였습니다.

다음은 이 서비스를 호출하는 클라이언트 코드입니다. 코드를 보시면 아시겠지만 클라이언트에서 서비스 메서드를 비동기로 호출하고 있습니다. 혹시 WCF 서비스를 비동기로 호출하는 클라이언트를 만들어보시지 않은 분이 계시면 제가 예전에 올렸던 포스팅을 참고해주시기 바랍니다. (http://ruaa.tistory.com/entry/async-call) 자세한 설명은 없지만 대충은 이해하실 수 있으실겁니다 ^^;;

namespace MySvcAsyncClient

{

    class Program

    {

        static int c = 0;

        static void Main(string[] args)

        {

            ProductServiceClient proxy = new ProductServiceClient();

            for (int i = 0; i < 3; i++)

            {

                Console.WriteLine("{0}: GetProduct 메서드 호출", DateTime.Now);

                proxy.BeginGetProduct(GetProductInfoCallback, proxy);

                Thread.Sleep(100);

                Interlocked.Increment(ref c);

            }

            while (c > 0)

            {

                Thread.Sleep(100);

            }

        }

 

        static void GetProductInfoCallback(IAsyncResult ar)

        {

            ProductInfo productInfo = ((ProductServiceClient)ar.AsyncState)
                                           .EndGetProduct(ar);

            Console.WriteLine("{0} : ProductName : {1}",
                                        
DateTime.Now, productInfo.Name);

            Interlocked.Decrement(ref c);

        }

    }

}


Main 메소드 내에서는 for 문을 사용하여 3번 반복하여 GetProduct 메소드를 비동기로 호출하고 있으며, 각각의 비동기 호출에 의한 작업이 끝이 나면 AsyncCallback 대리자인 GetProductInfoCallback 메소드가 호출되며, 서비스에서 받은 Product 데이터를 화면에 출력해줍니다.

이렇게 코드를 작성하고 나면, 역시 결과가 궁금해 질겁니다. 다음은 이 코드에 대한 결과 화면입니다.

[서버]


[클라이언트]


클라이언트 측 결과 화면을 보면 동시에 서비스의 메소드를 세번 호출하는 것을 확인할 수 있습니다. 그리고 6초 정도의 시간 후에 차례대로 결과값을 가져와서 출력하는 것을 볼 수 있습니다.

서비스 측 결과 화면을 확인해 보면, 각 호출마다 생성자를 통해 새로운 인스턴스를 생성하고, 인스턴스 내에 하나의 스레드를 통해 GetProduct 메소드를 호출하는 것을 확인할 수 있습니다.

여기서 잠깐 의문이 들지도 모르겠습니다. 제가 분명, InstanceContextMode의 기본값은 PerSession 이라고 했는데 왜 서버에선 클라이언트의 호출마다 새로운 인스턴스를 생성한 것일까요?

답은 아주 간단합니다. 서비스를 호스팅할 때 사용했던 binding의 종류가 BasicHttpBinding 이었던 것 기억하시나요? BasicHttpBinding의 경우엔 세션을 사용하지 않기 때문에, 이 경우엔 실제로 InstanceContextMode.PerCall 과 같은 형식으로 동작하게 되는 것입니다.

기본값으로 설정한 경우를 알아봤으니, 이번엔 두 모드의 값을 바꿔서 서비스에 적용해보겠습니다.

인스턴스는 모든 호출에 대해 하나만 생성하도록하고, 스레드의 갯수는 하나 이상으로 만들 수 있게끔 설정한 후에 결과값을 살펴보죠~

앞에서 한번 언급했지만 서비스의 Behavior를 적용하는 방법은 서비스 클래스에 특성으로 설정하는 방법과 config 파일에 설정하는 방법이 있습니다. 여기서는 클래스에 특성으로 설정하는 방법을 사용해보겠습니다.

서비스 클래스의 코드를 다음과 같이 굵은 글씨로 적용된 부분만을 추가해보죠~

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single,

            ConcurrencyMode=ConcurrencyMode.Multiple)]
class ProductService : IProductService

{

    ProductService()

    {

        Console.WriteLine("{0}: 서비스의 새로운 인스턴스 생성!!", DateTime.Now);

    }

 

    ... 생략 ...

}


이렇게만 수정한 후에 솔루션을 실행시켜 보면, 클라이언트 측 화면은 변화가 없지만 서버 측 결과 화면은 다음과 같이 변화된 것을 확인하실 수 있으실겁니다.



달라진 점이 무엇인지 보이시죠? ^^

네,, 맞습니다. 인스턴스가 하나만 생성되었다는 점이죠. 아~ 그러고보니 동작한 스레드의 ID 값들이 모두 다른 것도 보이네요. 이 말은 곧, 하나의 인스턴스에 여러 개의 스레드가 생성되었다는 것을 의미하는 것이겠죠. 앞에서 설정했던 InstanceContextMode의 값과 ConcurrencyMode의 값이 어떻게 서비스의 동시성에 적용되었는지 이해가 가실겁니다.

이 외에도 서비스의 동시성에 적용할 수 있는 두 모드의 조합이 더 있지만, 다음 포스팅에서 더 다루도록 하겠습니다. 글도 길어졌고, 아직 담아야 할 내용도 많으니깐요.
이번에는 정말 다음 포스팅때 까지 많이 걸리지 않을 것입니다. 약속드릴께요~ ^^;;

제 포스팅에 항상 댓글 남겨주시고 응원해주시는 분들께 감사 드리며, 또 너무 오랜만에 글을 남겨 죄송한 마음도 듭니다. 제가 잠깐 주춤하긴 했지만, 앞으로는 계속 꾸준한 모습 보여드리려 노력하겠습니다. ^^

감사합니다.

C#으로 프로그래밍 할 때 IntelliSense가 작동하지 않은 문제가 발생했는데 이유는 툴->옵션에서 텍스트 문자 편집기-> C#을 선택하면 아래 그림에서 동그라미로 표시한 항목이 선택되어 있지 않았기 때문입니다.






이 문제가 발생한 이유는?

 

1) VS 2010을 설치 후 처음 실행했을 때 VS 2008이 설정 되어 있는 경우 기존 VS 2008의 프로파일 설정을 가져올지 물어보는데 기본으로는 위에서 선택되지 않았던 체크 박스가 선택됩니다.


2) 몇 개의 VS 플러그인의 예를 들면 ReSharperVS에서 C#의 코드 IntelliSense를 끄고 독자적으로 구현한 것을 사용하고 있습니다. 만약 ReSharperVS 2008에 설치하고 있다면 위와 같이 VS의 코드 IntelliSense의 프로파일 설정은 꺼집니다. VS 2010의 처음 실행 시에 기존 프로파일을 가져오기로 하면 코드 IntelliSense 설정은 무효 상태로 가져옵니다. 만약 VS 2010에서 ReSharper를 따로 설치하지 않으면 기본적으로는 IntelliSense가 꺼진 상태가 됩니다.

 



수정 방법은?


이것을 VS 20101 RC에서 수정하는 것은 매우 간단합니다. 다음 둘 중 하나를 선택해서 하면 됩니다.

 

1) ->옵션의 메뉴·명령을 사용하여 텍스트 문자 편집기->C# 설정을 선택하여 위 그림의 2개의 동그라미로 둘러싼 체크 박스를 선택합니다(Auto-list membersParameter information). IntelliSense가 켜져서 올바르게 동작합니다.

또는

2) VS 2010 RC에서 동작하는 ReSharper의 버전을 설치합니다. 이후 ReSharper의 독자적인 메카니즘에 의해 IntelliSense가 동작합니다.

 

 



VS 2010의 최종 릴리스에서 프로파일의 가져오기 방식을 변경합니다


여러 사람이 이 문제를 겪어서 질문을 하였습니다. 이것은 매우 이해하기 어려워서 이것을 방지하기 위해서 VS 2010의 최종 릴리스에서는 프로파일 가져오기 방식을 변경할 예정입니다. 만약 플러그 인이 VS 2008에서 IntelliSense를 끄고 있을 경우 기본적으로는 VS 2010에 프로파일을 가져오기 할 때에 그것을 켜도록 합니다. 이것에 의해 항상 기본적으로 IntelliSense가 동작합니다.



 

원문 :

http://weblogs.asp.net/scottgu/archive/2010/02/26/no-intellisense-with-vs-2010-rc-and-how-to-fix-it.aspx

 

 


8. System.Object (2)

CLR 2010. 3. 3. 09:00 Posted by 알 수 없는 사용자
2. GetHashCode

객체 값에 대한 HashCode를 반환해주는 Method입니다. 근데 이 Method가 반환해 주는 Hashcode는 그닥 쓸모가 없다고 합니다. 각 객체마다 유일한 Hashcode를 보장해 주어야 하지만 그렇지 못하기 때문입니다.

암튼 디자이너는 어떠한 객체라도 HashTable에 담길 수 있다면 여러모로 편리할 것이라고 판단해서 모든 Object를 상속하는 객체는 이 Method를 통해 Int32형태의 hashcode를 얻을 수 있도록 설계했다고 합니다.

하지만 결국 이 Method로 유일한 hashcode를 제대로 반환해 줄 수 있도록 사용하기 위해서는 override를 통해 재작성하여 사용해야 합니다. 그래서 CLR via C# 에서는 Object의 Method로 정의되어 있을 것이 아니라 interface로 정의해 놓는게 맞는게 아닌가 라고 말합니다.

그런데 이 GetHashCode를 재정의해서 사용하기 위해서는 기억해야 할 점이 있습니다. 지난 번에 공부했던 Object의 Method인 Equals()를 재정의 한다면 이 GetHashCode()도 재정의를 해 주어야 한다는 점입니다. 만약 Equals()만 재정의 한다면 컴파일시 warning을 발생합니다.

------ Build started: Project: Test04, Configuration: Debug x86 ------
C:\Documents and Settings\XPMUser\my documents\visual studio 2010\Projects\TestSolution\Test04\Program.cs(8,11): warning CS0659: 'Test04.Animal' overrides Object.Equals(object o) but does not override Object.GetHashCode()

Animal이라는 Class에서 Equals()만 재정의 했더니 위와 같은 경고 메시지를 발생하는 것을 볼 수 있습니다. 이유는 두 객체가 같다면 (Equals()의 값이 true라면) 두 객체의 hashcode도 같을 것이라는 가정을 System.Collections.HashTable 타입과 System.Collections.Generic.Dictionary 타입이 하고 있기 때문입니다.
결국 객체의 동질성을 판단하는 Equals()의 알고리즘에 의해 두 객체가 같다고 판명이 난다면 GetHashCode()를 구하는 알고리즘도 Equals()을 판단하는 알고리즘과 연관되어 같은 값을 반환할 수 있도록 구현해 주어야 합니다.



GetHashCode()는 객체가 속한 AppDomain수준에서 유일할 수 있는 ID를 숫자로 반환하게 되어 있으며 이 값은 객체의 일생동안 바뀌지 않는 것이 보장되지만 이 객체가 가비지 수집기에 의해 수집되면 수집된 객체 hashcode를 의미하는 ID가 다른 새로운 객체에 재활용될 수 있다고 하네요. 그래서 유일한 값을 보장 못한다고 말하는데 그렇다면 AppDomain 수준에서 객체의 유일한 값을 보장해주는 HashCode를 반환해주는 Method가 CLR에 없을까요?

아니 있습니다. System.Runtime.ComplierServices 네임 스페이스에 있는 RuntimeHelpers 클래스의 정적 Method인 GetHashCode 메서드를 제공하고 있습니다. 객체가 Object의 GetHashCode 메서드를 재정의했다고 하더라도 해당 Object를 RuntimeHelpers.GetHashCode() 메소드의 인자로 제공하면 유일한 값을 보장받을 수 있는 ID를 제공해 줍니다.

아 그럼 Object의 GetHashCode()를 쓸게 아니라 저 RuntimeHelpers.GetHashCode()를 쓰면 되겠구나.

하면 되겠죠.


그런데 이 GethashCode()가 VS2010에 포함되어 있는 4.0X 버전의 mscorlib.dll 에서 구현하고 있는 것과 이전 버전에서 구현하고 있는 것이 좀 틀립니다.

우선 기존의 CLR의 mscorlib.dll에서 제공하고 있는 GetHashCode()의 내부를 살펴보도록 하겠습니다.


내부에서 Object.InternalGetHashCode()라는 Method를 호출하고 있네요. 저 함수를 들어가 보면


위와 같은 코드를 볼 수 있습니다. 함수 선언을 보면 internalcall이라는게 붙어 있는데 이것은 unmanaged코드가 불리는 것이라고 생각하면 됩니다. CLR의 관리되는 코드가 아니라 nativecode의 영역에서 구현되어 있다는 말이지요.

그래서 실제 GetHashCode는 다음과 같이 구현이 되어 있다고 합니다.

FCIMPL1(INT32, ObjectNative::GetHashCode, Object* obj) {
    
    CONTRACTL
    {
        THROWS;
        DISABLED(GC_NOTRIGGER);
        INJECT_FAULT(FCThrow(kOutOfMemoryException););
        MODE_COOPERATIVE;
        SO_TOLERANT;
    }
    CONTRACTL_END;

    VALIDATEOBJECTREF(obj);
    
    DWORD idx = 0;
    
    if (obj == 0)
        return 0;
    
    OBJECTREF objRef(obj);

    HELPER_METHOD_FRAME_BEGIN_RET_1(objRef);

        
    idx = GetHashCodeEx(OBJECTREFToObject(objRef));

    
    HELPER_METHOD_FRAME_END();

    return idx;
}
FCIMPLEND

INT32 ObjectNative::GetHashCodeEx(Object *objRef)
{
    CONTRACTL
    {
        MODE_COOPERATIVE;
        THROWS;
        GC_NOTRIGGER;
        SO_TOLERANT;
    }
    CONTRACTL_END

    VALIDATEOBJECTREF(objRef);

    DWORD iter = 0;
    while (true)
    {
        DWORD bits = objRef->GetHeader()->GetBits();

        if (bits & BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX)
        {
            if (bits & BIT_SBLK_IS_HASHCODE)
            {
                return  bits & MASK_HASHCODE;
            }
            else
            {
                SyncBlock *psb = objRef->GetSyncBlock();
                DWORD hashCode = psb->GetHashCode();
                if (hashCode != 0)
                    return  hashCode;

                hashCode = Object::ComputeHashCode();

                return psb->SetHashCode(hashCode);
            }
        }
        else
        {
            if ((bits & (SBLK_MASK_LOCK_THREADID | 
(SBLK_MASK_APPDOMAININDEX << SBLK_APPDOMAIN_SHIFT))) != 0)
            {
                objRef->GetSyncBlock();
            }
            else
            {
                if (bits & BIT_SBLK_SPIN_LOCK)
                {
                    iter++;
                    if ((iter % 1024) != 0 && g_SystemInfo.dwNumberOfProcessors > 1)
                    {
                        YieldProcessor();
                    }
                    else
                    {
                        __SwitchToThread(0);
                    }
                    continue;
                }

                DWORD hashCode = Object::ComputeHashCode();

                DWORD newBits = bits | BIT_SBLK_IS_HASH_OR_SYNCBLKINDEX | 
BIT_SBLK_IS_HASHCODE | hashCode;

                if (objRef->GetHeader()->SetBits(newBits, bits) == bits)
                    return hashCode;
            }
        }
    }
}


Hashcode 소스이지만 이 소스만 읽어서는 어떤 알고리즘으로 hashcode를 생성하는 건지 정확히 파악하기가 어렵군요. :)

암튼 이런식으로 GetHashCode()가 구현되어 있었습니다.

그런데 4.0 버전에서의 구현은 다음과 같습니다.



어라 어디서 많이 보던 함수를 호출 하고 있군요. 아까 앞에서 살펴본 AppDomain상에서 객체마다 유일한 ID를 제공하는 것을 보장해준다고 하는 System.Runtime.CompilerServices.RuntimeHelpers::GetHashCode(object)를 호출하고 있습니다!


System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode()는 역시 unmanaged code를 invoke하게 되어 있는 것 같습니다. 내부 구현 코드를 찾아볼 수 있으면 좋을텐데 찾기가 힘드네요;

결국 내부 코드 구현으로 봤을 때 .NET 4.0기반의 CLR에서는 Object.GethashCode()를 사용하는 것과 System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode()를 사용하는 건 동일합니다.



GetHashCode()를 재정의 할 때 고려해야 할 점을 생각해 볼까요.

1. 무엇보다 잘 분포된 난수를 생성할 수 있는 그러니까 같은 Application 환경에서 유일한 값을 보장할 수 있는 알고리즘을 사용해야 합니다.
2. 정의하는 알고리즘 상에서 상위 클래스의 GetHashCode() 를 호출해서 자신의 Hashcode에 값을 반영하는 경우가 있겠지만 기본적으로 제공하는 Object의 GetHashCode()는 가능하면 사용하지 말아야 합니다. 빠른 성능을 고려해서 만든 알고리즘이 아니기 때문입니다.
3. 알고리즘은 적어도 하나의 인스턴스 필드는 이용해야 합니다. 미리 정의되어 있는 정적 값만을 이용하면 안된다는 말이겠지요.
4. 사용되는 필드는 불변의 속성을 가져야 합니다. 즉 필드값은 한번 생성되면 그 객체가 소멸될 때까지는 절대 값이 바뀌어서는 안됩니다.
5. 객체가 같은 값을 지니고 있다면 HashCode도 같은 값을 반환해야 합니다.

그리고 이 GetHashCode()를 사용할 떄의 주의점도 있습니다. 절대로 이 메소드를 통해 연산한 결과를 저장하여 사용하지 말아야 한다는 점인데 예를 들어 설명하자면 어떤 사이트에서 회원 정보를 받을 때 암호를 GetHashCode()를 이용해서 만들어진 hashcode를 DB에 저장해선 안된다는 것입니다.

지금까지 알아본것 처럼 CLR이 업데이트 되면서 내부 구현이 바뀌게 된다거나, 사용자가 직접 재정의한 GetHashCode()의 알고리즘이 변경된다면 저 DB에 저장된 hashcode는 모두 쓰레기가 되어 버리게 될 테니까요.



[MS@클라우드컨퍼런스] MS 클라우드 기술과 플랫폼

Cloud 2010. 2. 24. 10:48 Posted by 알 수 없는 사용자


안녕하세요? 오랜만에 인사드립니다.
클라우드 컴퓨팅 관련해서 블로깅을 하고 있는 Ted 입니다.

이번 마이크로소프트@클라우드 컨퍼런스에서
"클라우드 애플리케이션 개발을 위해 알아야할 MS 기술과 플랫폼"
이라는 긴 제목의 세션 발표를 하게 됐습니다.

일전에 올린 PT자료와 중복되는 부분도 일부 있습니다.
앞으로 몇 차례에 걸쳐 발표 내용을 상세화한 글을 올리겠습니다.

[MFC] 태스크 대화상자(Task Dialog) - (3/3) : 활용하기

MFC 2010. 2. 22. 09:00 Posted by 알 수 없는 사용자

Intro
안녕하세요. MFC 카테고리의 꽃집총각 입니다.
우리는 지금 비주얼 스튜디오 2010에서 새롭에 추가되는 MFC 클래스인 CTaskDialog를 알아보고 있습니다.
지난 시간까지는 2회의 연재에 걸쳐서 태스크 대화상자가 무엇인지, 그리고 태스크 대화상자를 기존의 MessageBox 처럼 간단한 용도로 사용하고자 할 때엔 어떻게 해야 하는지를 함께 살펴보았습니다. 앞서 연재된 포스팅은 아래의 링크를 따라가시면 읽어보실 수 있습니다.

이번에는 태스크 대화상자의 세 번째 포스팅으로, 태스크 매니저가 제공하는 다양한 기능들을 본격적으로 활용하기 위한 방법을 알아보기로 하겠습니다.



태스크 대화상자를 띄우는 또다른 방법 - DoModal() 함수.
지난번 포스팅에서 소개했던 단순 출력 방식은 CTaskDialog::ShowDialog 함수 하나만 호출해 주면 바로 태스크 대화상자를 띄울 수 있었습니다. 8개의 인자를 통해 기본적인 기능을 어느 정도 제어할 수는 있었지만 애초부터가 심플한 사용을 위한 방법이었으므로, CTaskDialog의 여러가지 강력한 기능들을 모두 제어하기에는 무리가 있었습니다.
이번에는 CTaskDialog의 객체를 직접 생성하고, CTaskDialog의 멤버함수들을 통해 다양한 기능을 활용하는 방법을 알아보도록 하겠습니다. 이미 우리에게 익숙한 방식인 CDialog의 파생클래스를 출력하던 방식과 유사합니다. 객체를 만들어 DoModal() 함수를 호출하면 창이 뜨고, 창이 떠있는 동안 함수 내부에서 제어를 쥐고 있다가, 사용자가 창을 끄게 되면 리턴값을 내면서 함수의 호출이 종료되는 방식입니다.

// 객체 생성.
CTaskDialog dlg( L"본문", L"머릿글", L"타이틀" );

// TODO : DoModal을 호출하기 전, dlg의 멤버함수를 호출해 원하는 기능들을 설정한다.

INT_PTR iRet = dlg.DoModal();

switch( iRet )
{
case IDOK:        ... break;
case IKCANCEL:    ... break;
    ...
}

기존의 스타일을 그대로 닮아있어서 훨씬 더 친숙하게 느껴집니다 :)
다만 다른점은, DoModal을 부르기 전에 여러가지 기능을 제공하는 CTaskDialog의 멤버함수를 이용해 태스크 대화상자의 동작 및 기능을 런타임에 디자인 한다는 점입니다. 각각의 기능들은 적절한 이름의 함수로 제공되어 있으며, 매우 직관적이므로 별도의 설명 없이도 쉽게 이해하실 수 있습니다.
함수들의 레퍼런스는 아무래도 MSDN 페이지를 직접 보시는 것이 좋을 것 같습니다. ( http://msdn.microsoft.com/en-us/library/dd293651(VS.100).aspx ) 사실은 제가 한글로 설명을 덧붙인 함수 참고표를 정리해 봤는데, 굳이 부가설명이 필요 없을 뿐더러 오히려 MSDN 페이지보다 더 읽기 힘들어지기만 하네요. 대신 눈에 쏙쏙 들어오는 예제 코드를 통해 주요 함수들의 사용 방법에 대한 설명을 갈음하도록 하겠습니다.
예제는 MS VC++ MVP인 Marc Gregoire의 블로그에 공개된 예제 코드를 기반으로 하여, 주석과 인자들을 한글로 수정하고, 강의의 진행에 맞도록 일부 수정한 버전입니다. 원본 코드는 이곳 ( http://www.nuonsoft.com/blog/2009/06/10/ctaskdialog-in-mfc-in-visual-c-2010/ )에서 다운받으실 수 있습니다.

// 태스크 대화상자 객체 생성.
CTaskDialog taskDlg( L"본문", L"머릿글", L"타이틀" );

// 본문, 머릿글, 타이틀 텍스트 별도 지정
taskDlg.SetContent(_T("여기가 본문이 출력되는 위치입니다.\n")
    _T("당연히 여러 줄로 텍스트를 출력할 수 있고,")
    _T("<a href=\"http://vsts2010.net\">하이퍼 링크</a>도 설정할 수 있습니다.") );

taskDlg.SetMainInstruction(_T("여기는 머릿글을 적는 곳입니다.\n")
    _T("머릿글도 여러 줄로 적을 수 있어요."));

taskDlg.SetWindowTitle(_T("이것이 태스크 대화상자 입니다."));

// 3개의 커맨드 버튼 추가. 한 버튼은 권한 승격이 필요한 작업임을 표시.
taskDlg.AddCommandControl(1, _T("커맨드 버튼 1 "));
taskDlg.AddCommandControl(2, _T("커맨드 버튼 2 "));
taskDlg.AddCommandControl(3, _T("커맨드 버튼 3 \n")
    _T("사용자 권한 설정(UAC) 기능의 권한 승격이 필요함을 표시할 수 있습니다."), TRUE, TRUE);


// 2개의 라디오 버튼 추가.
taskDlg.AddRadioButton(4, _T("라디오 버튼 1"));
taskDlg.AddRadioButton(5, _T("라디오 버튼 2"));

// 보이기, 감추기 버튼으로 사용자가 펼치고 접을 수 있는 추가 메세지 설정.
taskDlg.SetExpansionArea(
    _T("이 메세지는 사용자가 하단의 '자세히' 버튼을 누르면 추가로 보여지는 메세지 입니다.\n")
    _T("이곳에도 하이퍼 링크가 적용됩니다. <a href=\"notepad.exe\">메모장 열기</a>."),
    _T("자세히"), _T("감추기"));

// 꼬릿말 부분 설명 & 아이콘 설정.
taskDlg.SetFooterIcon(MAKEINTRESOURCE(IDI_INFORMATION));
taskDlg.SetFooterText(_T("꼬릿말 출력 위치. \n역시 멀티라인 지원됩니다."));

// 대화상자의 메인 아이콘 설정.
taskDlg.SetMainIcon(m_hIcon);

// 태스크 대화상자의 프로그레스바 설정.
taskDlg.SetProgressBarMarquee();
taskDlg.SetProgressBarRange(0, 100);
taskDlg.SetProgressBarPosition(0);

// 대화상자 하단의 체크박스 표시 설정 및 텍스트 셋팅.
taskDlg.SetVerificationCheckboxText(_T("윈도우 시작시 자동 실행"));
taskDlg.SetVerificationCheckbox(TRUE);

// 하이퍼 링크, 타이머 옵션 추가 설정.
int options = taskDlg.GetOptions();
options |= TDF_ENABLE_HYPERLINKS;
options |= TDF_CALLBACK_TIMER;
taskDlg.SetOptions(options);


// 태스크 대화상자 출력.
INT_PTR iRes = taskDlg.DoModal();

// 사용자가 선택한 커맨드 버튼 ID와 라디오 버튼 ID 확인하기.
int iSelectedCommandControlID = taskDlg.GetSelectedCommandControlID();
int iSelectedRadioButtonID = taskDlg.GetSelectedRadioButtonID();

위의 예제 코드로 생성된 태스크 대화상자의 모습은 아래와 같습니다.

(그림 1) 예제 코드로 생성된 태스크 대화상자의 모습.


(그림 2) 예제코드로 생성한 태스크 대화상자. '자세히' 버튼을 눌러 본문이 확장된 상태.


제가 개인적으로 판단하기에는 주요함수 참고 테이블 보다는 위의 예제 코드가 훨씬 더 사용법을 익히기에 좋아 보입니다. 익스플로러 창을 두 개 띄워서 스크린샷과 소스코드를 대조해서 확인해 보세요. 어지간한 기능은 MSDN을 참고하지 않고도 바로 사용법을 이해하실 수 있을겁니다.



CTaskDialog의 프로그레스바 컨트롤.
이 부분까지 포스팅을 유심히 살펴오신 분이시라면 이상한 점을 한 가지 파악하셨을겁니다. 바로 프로그레스바 컨트롤 입니다. 지난 번 포스팅에서도, CTaskDialog::ShowDialog 함수를 이용해 프로그레스바 컨트롤을 억지로 붙여보기만 하고는 제어하는 방법을 오늘로 미루었는데, 오늘의 예제에서도 프로그레스바의 수치 등을 조절하는 함수는 있었지만 여전히 텅 빈 황량한 프로그레스바만 출력되고 있습니다.
DoModal() 함수를 호출하기 전에 SetProgressBarPosition( ... ) 함수로 프로그레스바 컨트롤의 초기값을 정해줄 수는 있지만, 태스트 대화상자가 한 번 뜨고 나면 DoModal() 함수가 제어를 쥐고 있기 때문에 컨트롤의 값을 조작하도록 손쓸 방법도 마땅치가 않습니다.

(그림 3) SetProgressBarPosition(...)으로 초기값은 정할 수 있다고 치지만, 더이상 어떻게 컨트롤 한다는 말인가?

이럴 때 CTaskDialog 클래스의 주요 기능 중의 하나인 타이머를 이용해야 합니다. 프로그레스바 컨트롤을 다루는 방법을 통해 CTaskDialog의 파생 클래스 생성 및 타이머 사용 방법을 알아보도록 하겠습니다.



CTaskDialog::OnTimer 오버로딩하기.
대화상자가 떠있는 동안은 DoModal()이 제어권을 갖고 있기 때문에, 우리가 CTaskDialog 객체의 외부에서 추가적인 조작을 가하기는 어렵습니다. 하지만 프로그레스바 컨트롤의 값을 애니메이션 하려면 대화상자가 출력되어 있는 동안에 처리가 이루어 져야 하겠지요.
이런 경우에는 CTaskDialog를 상속받는 파생 클래스를 제작하고, 가상 함수인 OnTimer를 오버로딩합니다.
OnTimer의 기본형은 다음과 같습니다.

  • virtual HRESULT OnTimer(_In_ long lTime);

예제 코드에 보면 SetOption( ... ) 함수를 이용해 기본적인 옵션에 TDF_CALLBACK_TIMER 를 추가해주어 타이머 기능을 활성화 하는 처리를 확인할 수 있습니다. 이렇게 플래그를 통해 타이머를 활성화 시켜주면 OnTimer 함수가 약 200 밀리초 (=0.2초) 주기로 호출됩니다.

int options = taskDlg.GetOptions(); options |= TDF_CALLBACK_TIMER;   // 타이머 옵션 추가 설정. taskDlg.SetOptions(options);

타이머가 필요한 대표적인 예가 바로 프로그레스바 컨트롤의 애니메이션 이겠지요. 오버로딩한 OnTimer 함수에서 간단하게 프로그레스바의 값을 진행시키는 코드를 넣어보도록 하겠습니다. 값을 조절하는 건 위에서 언급되었던 SetProgressBarPosition 함수만으로 충분합니다 :)

class CMyTaskDialog : public CTaskDialog { // ... virtual HRESULT OnTimer(_In_ long lTime); } HRESULT CMyTaskDialog::OnTimer(_In_ long lTime) { static int iProgressPos = 0; iProgressPos += 2; if (iProgressPos >= 100) iProgressPos = 0; SetProgressBarPosition(iProgressPos); return S_OK; }

위의 코드는 매번 타이머 함수가 호출될 때마다 프로그레스바 컨트롤의 값을 2씩 증가시켜주다가, 컨트롤이 만땅이 되면 꽉 차면 다시 0으로 초기화 해주고 있습니다. 0.2초에 한 번씩 호출이 되는데 2씩 채우니까... 100을 다 채우려면... 그러니까 1초에 다섯번쯤 호출 되는데... 한 번에 2씩이면 초당 10씩인가...... 뭐 아무튼 실행해보면 적당한 속도로 컨트롤이 애니메이션 되는 모습을 확인할 수 있습니다.
(웃자고 적어본 거 아시죠... 저 덧셈 잘합니다...;;)

프로그레스바 컨트롤에 대한 이야기가 나왔으니까 프로그레스바의 스타일에 대해 몇가지만 더 이야기하고 마무리 짓도록 하지요.



Marquee 타입 프로그레스바
태스크 대화상자의 프로그레스바 컨트롤은 기존의 기본적인 프로그레스바 방식보다는 Marquee 타입으로 많이 쓰일것을 예상하고 디자인된 것으로 보입니다. 프로그레스바 컨트롤을 추가하는 함수의 이름부터가 SetProgressBarMarquee(...) 이니 말입니다. 우리 예제에서는 프로그레스바의 영역을 설정하고 값을 지정해 주어 일반 프로그레스바처럼 사용하였지만, SetProgressBarRange(...) 함수와 SetProgressBarPosition(...)을 주석처리하면 프로그레스바는 기본적으로 Marquee 타입으로 설정되고, 자동으로 애니메이션 됩니다.

(그림 4) Marquee 타입 프로그레스바. 지렁이 같은게 계속 스멀스멀...

윈도우 XP의 부팅속도를 가늠하기 위한 암묵적 의사소통 수단이기도 했지요. 지렁이 몇마리...
바로 그런 식입니다. Marquee 타입 프로그레스바 컨트롤은 내부에 초록색 불빛이 좌에서 우로 이동하는 애니메이션을 반복합니다. 내부적으로 무언가 작업을 처리하고 있음을 나타내는 용도로 사용하는데 적합합니다.



CTaskDialog::SetProgressBarState(...)로 프로그레스바 상태 설정하기.
프로그레스바 관련 함수 중에 SetProgressBarState(...)가 있습니다. 이 함수를 이용해 프로그레스바의 상태를 정상 / 일시 정지 / 에러 세가지 상태 중의 하나로 설정할 수가 있습니다. 인자로 아래의 플래그를 주면 됩니다.

  • PBST_NORMAL - 정상. 디폴트값이며, 프로그레스바가 녹색으로 표시됨.
  • PBST_ERROR - 에러. 프로그레스바가 빨강으로 표시됨.
  • PBST_PAUSED - 일시 정지. 프로그레스바가 노랑으로 표시됨.

상황에 따라 적절히 사용하면 좀 더 친절한 인터페이스를 제공할 수 있겠군요. 아래는 각각의 상태에 대한 프로그레스마 컨트롤의 스크린샷 입니다. 스크린샷을 비교하는 김에 위에 붙였던 Marquee 타입의 스크린샷까지 함께 대조해 보도록 하겠습니다.

(그림 5) 위에부터 차례대로, Marquee 타입, 기본형 정상상태 / 에러상태 /일시정지 상태



Outro
이것으로 MFC 10.0의 새로운 클래스인 CTaskDialog 편을 마무리 하도록 하겠습니다. 사용법은 그렇게 어려울 것도 없는데 막상 풀어서 설명하다보니 글이 꽤나 길어졌습니다. 3차례에 걸친 강좌를 마무리하고 보니, 제가 너무 상세한 설명까지 덧붙여 괜히 글이 장황해 진 것은 아닌가 하는 걱정도 드네요. 분명 내용이 어려울 게 없는데 말입니다 ^^; 하하...
제 글이 너무 지루하거나 수준이 낮으신 분들은 매번 포스팅마다 가장 아랫부분에 따로 정리해두는 Reference 링크를 참조하시기 바랍니다. 앞으로도 강의는 가능하면 초보 개발자나 학생들도 어렵다는 느낌 없이 쉽게 접하고 내용을 익힐 수 있는 수준으로 이어나갈 예정입니다.

VisualStudio는 이미 vs2008부터 Native Programmer들을 위한 지원에 많은 노력을 들이고 있다는 점을 확인할 수 있습니다. 그런 부분을 가장 확실하게 보여주는 점이 MFC 라이브러리의 보강 입니다.
vs2010에서도 MFC는 많은 신기능들을 소개하고 있습니다. 다음 강의에도 좀 더 흥미있는 주제로 포스팅을 이어 나가도록 하겠습니다.
그럼 다음 강좌에서 또 뵙도록 하겠습니다.
감사합니다 ^^*


Reference

.NET Framework 4.0 에 포함될 MEF(Managed Extensibility Framework) 은 컴포넌트를 조합하는 방식으로 컴포넌트의 재사용성과 보다 컴포넌트를 동적으로 사용할 수 있는 프레임워크입니다.

하 지만 MEF 는 기존에 제네릭 타입(Generic Types) 을 지원하지 않습니다. 이미 C# 2.0 부터 지원하는 Generic Type 을 MEF 에서 지원하지 않는 것도 참 아이러니 합니다. 여기에 대한 내용은 아래의 링크를 참고하십시오.

[.NET/.NET Framework] - MEF 에 Generic Type 을 지원하기 위해서..?
[.NET/.NET Framework] - MEF 는 Generic Type 을 지원하지 않는다!

MEFGeneric 이란?

안타깝게도, MEF 에 Generic Type 을 지원하기 위해 적당한 대안이 아직 전세계적으로도 없다는 것입니다. Microsoft 의 MEF 개발 팀 리더도 MEF V1.0 버전에는 지원하지 못할거라고 합니다. 

http://codebetter.com/blogs/glenn.block/archive/2009/03/21/why-doesn-t-mef-support-open-generics-for-exports-because-mef-is-not-type-based.aspx
http://codebetter.com/blogs/glenn.block/archive/2009/08/20/open-generic-support-in-mef.aspx

MEFGeneric 은 전세계적으로 처음으로 정식으로 Generic Type 을 지원합니다. 세계 최초로 한국에서, 그리고 닷넷엑스퍼트에서, 그리고 나로부터 문제를 해결하니, 로또 맞은 기분이네요.

MEFGeneric 은 기존의 MEF 를 Core(코어) 소스 코드를 수정/확장하여 Generic Type 을 지원하도록 하였습니다. 필자는 이 소스 코드를 codeplex 사이트를 통해 공개하였으니, 아래의 사이트를 참고하세요. ^^

   

MEFGeneric is a framework to support CLR Generic types in MEF (Managed Extensibility Framework).

http://mefgeneric.codeplex.com

 

7. System.Object

CLR 2010. 2. 16. 09:00 Posted by 알 수 없는 사용자
오늘의 주제는 이겁니다.

CLR의 모든 타입은 System.Object로부터 파생된다.

자. 오늘의 공부 끝!


이라고 말하고 싶지만 그러면 안되겠죠;;

그럼 모든 타입의 기본인 System.Object에 대해서 좀 더 공부를 해 봐야겠습니다. System.Object는 Class입니다. Class니까 Method과 Property 들로 구성이 되어 있겠지요.

Object의 Public Method들에 대해서 알아보기로 하죠. IDE 편집창에서 Object를 하나 선언하고 Public Method들이 무엇이 있는지 살펴봅시다.

네 저렇게 4개의 Method가 존재합니다. 이 Method들은 Object의 public Method인 만큼 CLR의 모든 타입들은 저 4개의 Method를 기본적으로 갖고 있게 됩니다. 저 Method들을 알아보기 위해 우선 좀 말이 안되긴 하지만 다음과 같은 클래스들을 만들어 놓습니다.

    class Animal
    {
        Int32 Age;
        string Name;

        public Animal(string name, Int32 age)
        {
            Age = age;
            Name = name;
        }

        public Int32 GetAge()
        {
            return Age;
        }

        public string GetName()
        {
            return Name;
        }

        public virtual string Say()
        {
            return "Can't say";
        }

        public static Int32 GetLegs(Animal animal)
        {
            Int32 legs = 2;

            if (animal.GetType().ToString().Equals("Test04.Animal"))
                legs = 4;

            return legs;

        }
    }

    class Human : Animal
    {
        public Human(string name, Int32 age) : base(name, age)
        {
        }
        public override string Say()
        {
            return "Oh! Yeah!";
        }
    }


Animal 클래스가 있고 Animal에서 상속받은 Human 클래스가 있습니다. Animal의 class 선언에는 생략되어 있지만-

class Animal 은 class Animal : System.Object 와 같이 쓸 수 있습니다. 둘은 같은 의미입니다.


1. Equals

비교 하려는 객체가 같은 값을 가지고 있으면 true 아니면 false를 return 합니다. 같은 값을 가지고 있다는 말은 무슨 의미일까요? 테스트 해 봅시다. 다음과 같은 코드를 작성해 봅니다.

            Animal dog = new Animal("happy", 3);
Human korean = new Human("Park", 33);

            if (dog.Equals(korean))
                Console.WriteLine("dog == korean");
            else
                Console.WriteLine("dog != korean");

dog라는 Animal class의 객체를 선언하고 korean이라는 Human의 객체를 선언하고 이 둘을 비교합니다. 뭐가 찍힐까요? 당연히 dog != korean 이 찍힙니다.

            Animal cat = new Animal("nero", 1);

            if (dog.Equals(cat))
                Console.WriteLine("dog == cat");
            else
                Console.WriteLine("dog != cat");

자 이번엔 어떨까요? 같은 Animal 객체인 cat을 선언해 봅니다. 둘은 같다고 인정될까요? 아닙니다. 역시 dog != cat이 찍힙니다.

            Animal dog2 = new Animal("happy", 3);
            if (dog.Equals(dog2))
                Console.WriteLine("dog == dog2");
            else
                Console.WriteLine("dog != dog2");

아.. 그러면 이번엔 객체 이름은 다르지만 내부의 property값은 같은 dog2라는 객체를 선언하고 둘을 비교해 봅시다. 같다고 인정해 줄까요? 아.. 역시 다르다고 합니다. dog != dog2 라고 찍힙니다.

도대체 그럼 뭐를 어떻게 해야 같은거야? 라고 생각하면서 마지막으로 다음과 같이 테스트 해 봅시다.

            Animal dog3 = dog;

            if (dog.Equals(dog3))
                Console.WriteLine("dog == dog3");
            else
                Console.WriteLine("dog != dog3");

dog 객체를 새로 생성한 dog3에 할당합니다. 여기선 어떤 값이 찍힐까요? 아. 드디어 두 객체가 같다고 인정해 줍니다. dog == dog3 가 찍힙니다!

Object.Equals()의 같다는 의미는 좀 어렵네요. 두 객체가 포함하는 property등의 값이 같다고 equal이 아닙니다. 두 객체가 참조하는 것이(주소값 이라고 해야 할까요? C++적인 의미로 말입니다.) 이 완전히 일치해야지 equal입니다. 

음 개념적으로는 대충 이해가 되는데 좀 더 자세히 알아보고 싶어지는군요.  앞으로 자주 보게 될 ILDASM을 꺼내봐야겠습니다. ILDASM으로 지금까지 작성한 코드들이 어떻게 CLR에 들어가는지 살펴봅시다.


ILDASM을 이용하여 작성한 코드들을 MSIL코드로 볼 수 있습니다. 하나하나씩 따라가보면서 간단하게나마 분석을 해보려고 합니다. 일단 Main 앞 부분입니다. main()함수에서 사용하는 지역변수들을 초기화 하고 알아보기 쉽게 index를 붙여서 태깅해 놓고 시작하네요.

            Animal dog = new Animal("happy", 3);
            Human korean = new Human("Park", 33);

  IL_0001:  ldstr      "happy"
  IL_0006:  ldc.i4.3
  IL_0007:  newobj     instance void Test04.Animal::.ctor(string, int32)
  IL_000c:  stloc.0
  IL_000d:  ldstr      "Park"
  IL_0012:  ldc.i4.s   33
  IL_0014:  newobj     instance void Test04.Human::.ctor(string, int32)
  IL_0019:  stloc.1

1. [IL_0001] ldstr은 스트링 개체를 스택으로 PUSH합니다. "happy"가 스택에 PUSH됩니다. 
2. [IL_0006] ldc.i4.3 은 정수값 3을 역시 스택으로 PUSH합니다. 
3. [IL_0007] newobj는 받아야 할 argument들 갯수만큼 스택에서 POP 한 후 새로운 객체를 생성하고 그 객체를 스택에 PUSH하게 됩니다. 
4. [IL_000c] stloc.0 는 스택에서 값을 POP하여 지역변수 인덱스 0 에 넣습니다. 

IL_0001 부터 IL_000c까지의 내용입니다. 그대로 하면 어떻게 되나요. 위의 C#코드에서 처럼

Animal dog = new Animal("happy", 3); 

을 수행한 결과가 됩니다. 변수 dog는 지역변수 인덱스 0 으로 태깅되어 있지요. IL_000d 부터 IL_0019까지는 korean 객체를 생성하는 과정입니다. IL_0012의 ldc.i4.s 33은 정수값 33을 스택에 PUSH하라는 명령입니다.

            if (dog.Equals(korean))
                Console.WriteLine("dog == korean");
            else
                Console.WriteLine("dog != korean");

  IL_001a:  ldloc.0
  IL_001b:  ldloc.1
  IL_001c:  callvirt   instance bool [mscorlib]System.Object::Equals(object)
  IL_0021:  ldc.i4.0
  IL_0022:  ceq
  IL_0024:  stloc.s    CS$4$0000
  IL_0026:  ldloc.s    CS$4$0000
  IL_0028:  brtrue.s   IL_0037
  IL_002a:  ldstr      "dog == korean"
  IL_002f:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0034:  nop
  IL_0035:  br.s       IL_0042
  IL_0037:  ldstr      "dog != korean"
  IL_003c:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0041:  nop

자 다음은 두 객체를 비교하는 부분입니다. 

1. [IL_001a] ldloc.0 은 0번 지역변수의 값을 스택에 PUSH합니다. 
2. [IL_001b] ldloc.1 은 1번 지역변수의 값을 스택에 PUSH하겠지요.
3. [IL_001c] callvirt 는 런타임에 바인딩된 method를 호출합니다. (call뒤에 virt가 붙은 건 호출 할 method가 virtual로 선언되어 있다는 걸 의미합니다. 실제로 Object.Equals()는 'public virtual bool Equals(object obj);' 로 선언되어 있습니다.) 호출하기 전 method와 관련된 객체와 해당 method의 인수와 관련된 값을 스택에서 POP합니다. 그리고 해당 method의 결과값이 있으면 스택에 PUSH하게 됩니다. 여기서는 equals의 값을 스택에 PUSH하게 되겠네요.
4. [IL_0021] ldc.i4.0 는 정수값 0을 스택에 PUSH하게 됩니다.
5. [IL_0022] ceq 는 비교 명령입니다. 비교에 사용될 값 value0과 value1을 스택에서 POP합니다. 여기서는 4번에서 넣었던 정수값 0과 3번 equals의 결과값이 각각 value0과 value1에 들어가게 되겠지요. 그리고 두 값을 비교해서 같으면 1이 스택으로 PUSH되며 틀리면 0이 스택으로 PUSH됩니다.
6. [IL_0024] stloc.s    CS$4$0000 스택에서 값을 꺼내어 CS$4$0000 이라는 지역변수에 넣습니다. 맨 위에서 보면 알다시피 CS$4$0000은 bool값을 가진 지역변수인데 우리가 직접 선언한 변수는 아니지요. if문에서 비교를 해서 나온 bool값을 저장하기 위해 CLR이 생성한 임시 지역 변수입니다. 결국 ceq로 비교된 값이 CS$40000 이라는 변수에 들어가게 됩니다.
7. [IL_0026] ldloc.s    CS$4$0000 은 지역변수 CS$4$0000의 값을 스택에 PUSH합니다. 아 이거 왜 뺐다가 다시 넣는거야.
8. [IL_0028] brtrue.s  IL_0037 는 스택에서 값을 하나 POP한다음에 이 값이 true거나 null이 아니거나 0이 아닌경우 IL_0037로 점프하게 됩니다. 결국 bool 형식의 CS$4$0000 값이 true면 IL_0037로 점프를 하게 되겠지요. 다시 정리하면 5번에서의 ceq의 값이 같으면 IL_0037로 점프를 하게 되는 것입니다. 또 거슬러가면 ceq의 값이 같기 위해서는 3번의 결과 값이 0이 되어야 하는데 3번의 결과 값이 0이라는 얘기는 equals()의 결과값이 0 그러니까 false라는 이야기죠. 결국 dog != cat 이면 IL_0037로 점프하는겁니다.
9. [IL_002a] ~ [IL_002f] 스택에 스트링값(dog == korean)을 넣고 Console.WriteLine()을 호출합니다.
10. [IL_0034] nop 아무일도 하지 않습니다.
11. [IL_0035] br.s       IL_0042 는 어떠한 조건에 상관없이 IL_0042로 점프하게 됩니다. [IL_0042]는 if문이 모두 끝난 다음의 명령문이 있는 주소입니다.
12. [IL_0037] ~ [IL_003C] 는 dog != korean 을 찍겠지요.

와 길고 복잡합니다. 첨부된 소스를 컴파일 하고 컴파일 된 실행 파일을 ILDASM으로 보면 계속 뒤의 코드를 볼 수 있는데 쭉- false를 리턴했던 dog2까지의 비교는 위와 동일한 패턴의 코드로 진행됩니다. 무엇보다 주시해야 될 부분은 cat이나 dog2의 객체는 쭉- newobj로 새로 생성한다는 점입니다.

            Animal dog3 = dog;

if (dog.Equals(dog3))
                Console.WriteLine("dog == dog3");
            else
                Console.WriteLine("dog != dog3");

  IL_00aa:  ldloc.0
  IL_00ab:  stloc.s    dog3
  IL_00ad:  ldloc.0
  IL_00ae:  ldloc.s    dog3
  IL_00b0:  callvirt   instance bool [mscorlib]System.Object::Equals(object)

이제 true를 반환했던 dog3입니다. IL코드를 읽어보면 알겠지만 dog3의 값은 지역변수 인덱스 0인 dog의 값(주소)를 가져와서 dog3 지역변수에 그대로 넣습니다. 이건 뭘 의미하는건가요?

Animal dog3 = dog;

를 수행한다는 것은 dog3라는 새로운 객체 공간을 생성해서 dog의 값을 복사해 오는 것이 아니라 dog라는 객체가 가리키는 값(주소)를 그대로 dog3와 연결 시킨다는 의미입니다. 결국 dog와 dog3가 가리키는 값(주소)는 같은 값(주소)입니다. equals()는 그래서 같다. 라고 결과를 반환했던 것입니다. equals()가 하는 일은 정말 간단하네요. 내용이고 자시고 간에 그냥 값이 같으면 true입니다. 

간단한 내용을 정말 길게 돌아서 확인했네요.

휴-

Object는 다음에 계속 하기로 하지요. 오늘은 이만!!

참고 소스 : 

'CLR' 카테고리의 다른 글

닷넷4.0에서 네이티브코드와 매나지드코드의 동거 part 1.  (2) 2010.08.02
8. System.Object (2)  (0) 2010.03.03
6. Assembly - GAC(Global Assembly Cache)  (2) 2010.02.02
5. Assembly - Strongly named assemblies  (0) 2010.01.26
4. Assembly  (2) 2010.01.15

[MFC] 태스크 대화상자(Task Dialog) - (2/3) : 사용하기

MFC 2010. 2. 15. 09:00 Posted by 알 수 없는 사용자

Intro
안녕하세요. MFC 카테고리의 꽃집총각 입니다. 이번에는 지난번 포스팅 [MFC] 태스크 대화상자(Task Dialog) - (1/3) : 기능 소개 편에 이어서, 사용하기 편을 준비했습니다.
이번 포스팅에서 다룰 내용은

  1. 비스타 이전 OS에서 태스크 대화상자 이용에 대한 이슈
  2. 태스크 대화상자의 기본적인 구성
  3. 소스코드 한 줄로 태스크 대화상자 사용하기 - ShowDialog 함수.

이렇게 세 가지 내용을 알아보도록 하겠습니다. 3번 ShowDialog 함수의 소개가 이번 포스팅의 주된 내용입니다. 태스크 대화상자를 사용하기 위해서는 CTaskDialog의 객체를 생성해 DoModal() 함수를 실행하는 방법과, 객체를 생성하지 않고 간단히 CTaskDialog::ShowDialog(...) 함수를 이용하는 방법이 있습니다. 후자는 간편하게 함수호출 한번으로 태스크 대화상자를 이용할 수 있는 장점이 있고, 전자는 보다 디테일한 설정 및 활용이 가능하다는 장점이 있습니다. 오늘은 간단한 사용법인 ShowDialog 함수에 대해 알아보고, CTaskDialog의 객체를 선언하는 방법은 다음 포스팅에서 별도로 다루도록 하겠습니다.



CTaskDialog::IsSupported() - 비스타 이전 OS들을 위한 대비
본격적인 CTaskDialog의 기능들을 알아보기 전에, 먼저 한가지 정리해야 할 점이 있습니다. 이전 포스팅에서 짧게 언급한 바 있지만, 태스크 대화상자는 비스타 이전 OS에서는 제공되지 않습니다. 그래서 만약 개발한 응용프로그램이 윈도우 XP 등에서 실행된다면 태스크 대화상자 출력 시점에 오류를 일으킵니다.  

(그림 1) 윈도우 XP에서 태스크 대화상자 출력을 시도할 때 나오는 오류 화면.


그렇기 때문에 개발한 프로그램이 비스타 이전 OS에서도 실행되어야 한다면 이런 경우를 위한 별도의 처리를 직접 해주어야만 합니다. 이럴 땐 어쩔 수 없이 기존의 메세지 박스나, CDialog를 상속받은 클래스를 직접 제작해 주어야 겠지요.
이런 처리를 찾아볼 수 있는 좋은 예가 바로 인터넷 익스플로러 8.0의 '세션 복구 기능' 입니다. 익스플로러 8.0은 비정상 종료되었다가 실행되는 경우 이전에 열려있던 페이지를 자동 복구할 것인지를 물어보는 창을 띄우는데, 실행중인 윈도우가 비스타 이전 버전이라면 일반 대화상자를, 비스타 이후 버전이라면 태스크 대화상자를 출력합니다.

(그림 2) 인터넷 익스플로러 8.0의 세션복구 기능에서 쓰인 태스크 대화상자.


(그림 3) 비스타 이전 OS에서는 태스크 대화상자 대신 기존의 대화상자를 출력합니다.


이런 식으로 OS 차원의 태스크 대화상자 사용 가능여부를 확인하고자 할 때에는, 직접 OS의 버전을 얻어와서 기능 지원 여부를 판별할 수도 있겠지만 CTaskDialog::IsSupported() 정적 함수를 이용해서 쉽게 확인할 수 있습니다.

if( CTaskDialog::IsSupported() ) // 태스크 대화상자 사용 가능 여부를 확인
{
    // 태스크 대화상자 호출.
    CTaskDialog::ShowDialog(message, emptyString, title, 0, 0, TDCBF_OK_BUTTON);
}
else
{
    // 지원하지 않는 OS인 경우는 예전 방식으로 처리.
    AfxMessageBox(message);
}
(코드 1) CTaskDialog::IsSupported() 함수를 통해 태스크 대화상자 사용 가능 여부를 확인.

참고로 윈도우 서버 2008은 비스타 이전에 나온 OS이므로 태스크 대화상자를 사용할 수 있습니다. 물론 비스타나 윈도우 7 처럼 예쁜 모양은 아니지만요 ^^;

(그림 4) 윈도우 서버 2008에서 출력되는 태스크 대화상자의 모습. 출처 : http://mariusbancila.ro/blog/2009/03/10/task-dialog-in-mfc/

 


태스크 대화상자의 기본적인 구성
태스크 대화상자는 기본적인 구성은 MSDN에 있는 아래의 샘플 스크린샷에 아주 잘 나타나 있습니다.

(그림 5) 태스크 대화상자의 기본적인 구성. (출처 : MSDN)


(그림 5)에서 알 수 있듯이 태스크 대화상자는 다양한 컨트롤들과 기능들을 제공합니다. 오늘은 우선 예전 AfxMessageBox() 수준의 심플한 사용방법을 알아보겠습니다. 간단한 메세지를 사용자에게 노출하거나, 기본적인 버튼을 통해 사용자 입력을 받아오고자 할 때는 오늘 소개하는 ShowDialog 함수를 이용하는 것이 좋습니다.


첫 번째 예제 : 일단 한 번 띄워봅시다
자, 이제 정말로 태스크 대화상자를 사용하기 위한 방법을 알아보죠. 가장 간단하게 태스크 대화상자를 출력하는 방법은 정적 함수인 CTaskDialog::ShowDialog() 를 사용하는 방법입니다. 이 함수를 사용하면 CTaskDialog의 객체를 만들지 않고도 바로 태스크 대화상자를 띄울 수 있습니다.
일단 속 시원하게 코드부터 보겠습니다! ShowDialog 정적 함수를 이용한 코드와, 실제 출력된 태스크 대화상자의 모양입니다.

INT_PTR ret = CTaskDialog::ShowDialog( L"본문 들어가는 곳", L"제목 들어가는 곳", L"타이틀 적는 곳", IDS_STRING102, // 첫 번째 커맨드 버튼. IDS_STRING104 ); // 마지막 커맨드 버튼. if( ret == IDYES ) { // blah blah... }

(코드 2) 드디어 등장했습니다! 태스크 대화상자를 띄우는 첫 번째 코드입니다!

(그림 6) (코드 2)를 통해 출력한 태스크 대화상자의 모습.


태스크 대화상자의 설명을 위해 두 차례나 걸친 포스팅에서 수없이 떠들었던 것에 비해, 너무나도 단순합니다. 그냥 함수 호출 하나 했더니 뜨는군요. 제목, 본문, 타이틀을 설정하는 것은 인자로 직접 문자열을 넣어주고 있으므로 추가 설명이 없이도 쉽게 확인하실 수 있습니다.
스크린샷을 보면 우리의 첫 번째 태스크 대화상자에서는 세 개의 커맨드 버튼을 가지고 있습니다. 커맨드 버튼은 예, 아니요, 확인, 취소 등과 같은 기존 대화상자의 기본적인 버튼 이외에 자유로운 출력 문자열을 설정할 수 있는 의사 입력 버튼 입니다. 태스크 대화상자의 가장 중앙부에 위치하며, 사실상 태스크 대화상자에서 가장 중요한 컨트롤이라고 할 수 있습니다. ShowDialog 함수를 이용할 때 커맨트 버튼을 넣기 위해서는 응용 프로그램의 리소스 파일에 있는 스트링 테이블을 참조합니다. 스트링 테이블에 커맨드 버튼의 설명으로 사용할 문자열들을 차례로 입력하고, 해당 스트링 아이디의 처음과 마지막 값을 ShowDialog의 네 번째, 다섯 번째 인자로 넣어줍니다.

(그림 7) 커맨드 버튼을 추가하기 위해 스트링 테이블을 사용하는 예시.


(그림 6)이 우리의 첫 번째 대화상자 예시에 쓰인 스트링 테이블 입니다. 태스크 대화상자에는 스트링 테이블의 문자열이 출력되고, 사용자가 해당 커맨드 버튼을 선택한 경우 ShowDialog의 리턴값으로 스트링 ID가 반환됩니다. 예제의 세 번째 커맨드 버튼처럼 화면에 여러 줄의 텍스트를 출력하고 싶다면 문자열 안에 개행문자를 이용해 여러 줄의 텍스트를 넣어주면 됩니다.



CTaskDialog::ShowDialog(...) 함수를 좀 더 자세히 알아봅시다.
ShowDialog 함수의 인자는 모두 8개 입니다. 예제에서는 다섯개의 인자만을 사용했고 나머지 인자들은 기본값이 쓰였습니다. 함수의 전체적인 기본형은 아래와 같습니다.  

static INT_PTR CTaskDialog::ShowDialog(
   const CString& strContent,
   const CString& strMainInstruction,
   const CString& strTitle,
   int nIDCommandControlsFirst,
   int nIDCommandControlsLast,
   int nCommonButtons = TDCBF_YES_BUTTON | TDCBF_NO_BUTTON,
   int nTaskDialogOptions = TDF_ENABLE_HYPERLINKS | TDF_USE_COMMAND_LINKS,
   const CString& strFooter = _T("")
);

다섯 번째 인자까지는 설명이 되었으니 나머지 인자를 보자면,
  • nCommonButtons : 태스크 대화상자 오른쪽 하단에 출력되는 기본 버튼들을 설정합니다. 기본값으로 '예', '아니오' 두 개의 버튼이 출력됩니다.
  • nTaskDialogOptions : 태스크 대화상자의 여러가지 옵션을 조절할 수 있는 인자입니다. 옵션에 대한 플래그 값은 아래에서 보다 자세히 설명합니다.
  • strFooter : 태스크 대화상자의 가장 아래쪽에 출력되는 꼬릿말 부분입니다.
기본 버튼으로 설정할 수 있는 플래그는 총 여섯가지이고, CommCtrl.h에 정의되어 있습니다.
  • TDCBF_OK_BUTTON - 확인
  • TDCBF_CANCEL_BUTTON - 취소
  • TDCBF_YES_BUTTON - 예
  • TDCBF_NO_BUTTON - 아니오
  • TDCBF_RETRY_BUTTON - 재시도
  • TDCBF_CLOSE_BUTTON - 닫기
태스크 대화상자의 옵션을 조절할 수 있는 플래그는 총 열 여섯가지이고, 역시 CommCtrl.h 파일에 정의되어 있습니다. 이 중에서, ShowDialog() 함수와 함께 쓰일 수 있을 만한 플래그 몇 가지만을 정리해 보면 아래와 같습니다.
  • TDF_ENABLE_HYPERLINKS
    하이퍼 링크를 활성화 합니다. 텍스트에 하이퍼 링크를 넣고 싶을 때에는 문자열에서 <a href=\"http://vsts2010.net\">하이퍼 링크</a>처럼 HTML 문법으로 설정할 수 있습니다.
  • TDF_ALLOW_DIALOG_CANCELLATION
    태스크 대화상자의 오른쪽 상단에 창 닫기 버튼이 생기고, Esc키나 Alt + F4로도 창을 끌 수 있게 됩니다. 닫기버튼이나 키보드 조작으로 창을 닫은 경우는 IDCANCEL이 리턴됩니다.
  • TDF_USE_COMMAND_LINKS
    커맨드 버튼을 사용하도록 설정합니다. 이 플래그가 설정되지 않으면 커맨드 버튼으로 설정한 버튼들도 기본 버튼들처럼 출력됩니다.
  • TDF_USE_COMMAND_LINKS_NO_ICON
    커맨드 버튼에 아이콘을 출력하지 않고 텍스트만 표시하게 설정합니다.
  • TDF_SHOW_PROGRESS_BAR
    프로그레스바 컨트롤을 표시합니다.
  • TDF_SHOW_MARQUEE_PROGRESS_BAR
    프로그레스바 컨트롤을 Marquee 형식으로 출력합니다.
  • TDF_POSITION_RELATIVE_TO_WINDOW
    태스크 대화상자를 부모 윈도우의 중앙 위치에 나타나도록 합니다. 이 플래그가 설정되지 않으면 바탕화을 기준으로 중앙 위치에 출력됩니다.
  • TDF_RTL_LAYOUT
    태스크 대화상자에 출력되는 텍스트들을 오른쪽에서 왼쪽으로 출력합니다.
  • TDF_CAN_BE_MINIMIZED
    태스크 대화상자의 오른쪽 상단에 최소화 버튼이 생기고, 버튼을 누르면 창이 최소화 됩니다.
위의 아홉가지 플래그 정도가 ShowDialog 함수를 이용한 간단한 사용시에도 유용하게 쓰일만한 기능들 입니다. 이 중에 프로그레스바와 관련된 플래그 같은 경우, TDF_SHOW_PROGRESS_BAR를 넣어주면 실제로 프로그레스바가 표시되지만, 프로그레스바의 값을 조절한다거나, 오류상태를 표시하는 등의 세밀한 처리를 하는 것은 어렵습니다. 이런 경우는 ShowDialog 함수를 통한 호출보다는 직접적으로 CTaskDialog 의 객체 혹은 CTaskDialog 파생클래스의 객체를 만들어서 처리해야 합니다. TDF_SHOW_MARQUEE_PROGRESS_BAR 플래그로 Marquee 형식을 출력할 수도 있지만 이런 경우도 대게는 타이머를 설정하고 자체적으로 동작이 진행중임을 표시하기 위해 주로 사용하므로, 이또한 ShowDialog를 통한 출력에는 생뚱맞습니다.

(그림 8) 예제 대화상자에 꼬릿말과 프로그레스바를 추가한 모습.


위의 (그림 8) 스크린샷을 봅시다. 꼬릿말은 그럭저럭 쓸만 하다고 하지만, 텅 빈 프로그레스바는 보는 이의 마음까지 황량하게 만듭니다. 아무래도 좀 더 추가적인 처리가 필요해 보입니다. ShowDialog 함수를 통해서도 프로그레스바 컨트롤을 출력할 수는 있지만, 이를 세밀하게 제어하는 것은 무리입니다. 이를 포함해 여러가지 컨트롤의 처리에 대한 내용은 다음 포스팅에 설명하도록 하겠습니다.



Outro
이번 포스팅 에서는 태스크 대화상자 미지원 OS를 식별하는 방법과, 태스크 대화상자의 기본적인 구성, 그리고 메시지 박스 수준의 간단한 태스크 대화상자 출력방법을 알아보았습니다. 정적 함수 ShowDialog()를 사용하면 AfxMessageBox()를 이용하는 것보다 좀 더 다양하게 머릿말, 본문, 꼬릿말 등으로 구분에 메세지를 설정할 수 있고, 모던한 디자인의 레이아웃을 갖춘 태스크 대화상자를 출력할 수 있습니다.
다음 포스팅에서는 태스크 대화상자가 제공하는 다양한 컨트롤 들을 활용할 수 있는 방법을 알아보도록 하겠습니다. 리소스 에디터로 직접 다이얼로그를 디자인하던 불편함에서 벗어나 함수호출 몇 줄만으로 다양한 기능을 추가하는 방법들을 정리할 예정입니다. 보다 미리 관련 내용을 확인하고 싶으신 분들은 MSDN(http://msdn.microsoft.com/en-us/library/bb760441(VS.85).aspx)과 다음의 페이지( http://www.nuonsoft.com/blog/2009/06/10/ctaskdialog-in-mfc-in-visual-c-2010/ )를 참고하시기 바랍니다.
그럼 다음 포스팅에서 뵙겠습니다.
감사합니다 ^^*


Reference

SharePoint 2010 Server Object Model

SharePoint 2010 2010. 2. 12. 09:00 Posted by 알 수 없는 사용자

SharePoint의 여러 어셈블리를 직접 호출하는 경우로 웹 파트와 관리자 유틸리티, 이벤트 등에서 사용이 가능합니다. 참조할 수 있는 부분은 SDK MSDN을 참고해 볼 수 있습니다.

 

이번 시간에서는 이전 버전에서도 살펴본 SPSite, SPWeb, SPList, SPListItem 등에 대한 내용을 서버 머신에서 돌아가는 WPF 응용 프로그램에서 액세스 해봅니다.

 

WPF 응용 프로그램을 생성합니다. 프로젝트 이름은 ServerOMWPF 라고 생성합니다.


지금은 대상 프레임워크가 4.0으로 되어 있습니다. 리스트 박스와 버튼을 추가해서 사이트의 목록이름을 리스트박스에 표시되게 해봅니다. 디자인 화면은 아래와 같습니다.



프로젝트에 Microsoft.SharePoint.dll

C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI 밑에서 참조 추가합니다.


Get Item 버튼을 클릭해서 아래와 같이 코드를 작성합니다.
아래 코드는 SharePoint 사이트의 목록들의 이름을 리스트 박스에 추가하는 내용입니다.

프로젝트를 빌드하면 에러 나는 것을 알 수 있습니다. 특별한 코드가 아닌데도..,

서버 머신위에서 구동하는 Server Object Model의 응용 프로그램은 .NET Framework 3.5에서 구동되면서 X64, Any CPU로 설정되어야만 합니다. 그래서 에러를 만나게 됩니다.

Web Part, Visual Web Part 등은 별도 수정하지 않아도 됩니다.



프로젝트 속성에서 Application 메뉴에서 Target Framework3.5로 지정합니다.



Build 메뉴에서 Plaform Target“Any CPU”로 설정합니다.



프로젝트를 빌드하고 실행해봅니다. 그러면  Server Object Model을 이용한 간단한 WPF 프로그램의 결과를 아래와 같이 확인이 가능합니다.

 

 

 

개체 모델은 SDK MSDN을 참조할 수 있으며 다음 블로그에서 계속 알아보겠으며 위에서 간단하게 Server Object Model을 액세스하는 예와 실행하는 방법을 알아보았습니다.

 

SharePoint 2010 데이터 기술

SharePoint 2010 2010. 2. 11. 09:05 Posted by 알 수 없는 사용자

이전 버전에서는 SharePoint 관련 개체와 데이터를 액세스하는 것은 서버 측면에서는 SharePoint Server Object Model CAML 을 사용하고 원격 클라이언트에서는 SharePoint Web Service를 이용했습니다. SharePoint Web Service CAML 의 사용이 쉽지 않았고 개발하기가 불편했습니다.

 

SharePoint 2010에서는 아래 그림처럼 클라이언트 측면에서 Client Object Model 을 제공하고 있으며 REST 기반의 액세스가 가능합니다. 서버 측면에서는 CAML 대신 LINQ를 사용할 수 있게 되었습니다.



이전 버전과 비교해서 생산성이나 접근할 수 있는 부분이 다양해서 아주 유용하게 사용할 수 있습니다.

 

Server Object Model을 알아보면 SharePoint 머신 위에서 구동되는 웹 파트, 이벤트, 관리자 프로그램, 배치 프로그램 등이며 SharePoint 어셈블리를 직접 호출할 수 있다는 것이 Server OM입니다.

 

LINQ는 목록의 수 많은 항목 중에서 조건에 맞는 항목을 가져오고 원하는 정렬을 시키려고 하면 CAML이라는 XML을 직접 구성하고 SPQuery 라는 클래스를 통해 넘겨주어야 했습니다. 그래서 별도로 SharePoint MVP가 만든 CAML Builder 라는 도구가 유용하게 사용되었습니다. 2010 환경에서도 CAML은 사용이 가능합니다만 LINQ는 알고 있는 것처럼 쿼리 식으로 개체를 데이터처럼 액세스 할 수 있습니다. 웹 파트 등에 사용하면 아주 유용하다는 생각이 팍 드실 겁니다.

 

Client OM은 서버 머신 위가 아닌 원격 클라이언트에서 동작되는데 SharePoint 개체나 데이터를 원격 클라이언트에서 접근할 수 있다는 것입니다.

생성할 수 있는 유형은 .NET, Silverlight, Javascript 를 통해 접근할 수 있습니다. SharePoint 2010에서는 Silverlight 환경이 기본적으로 구비되어 있으며 Silverlight 웹 파트도 기본적으로 생성되어 있습니다. Client OM을 사용할 수 있는 내용으로 Silverlight 웹 파트를 생성해서 접근할 수 있습니다.

 

REST 기반 API 는 기존 웹 서비스보다 더 간단하게 사용이 가능하며 개체로 접근을 손쉽게 할 수 있습니다. ADO.NET Data Services 를 이용해서도 SharePoint 개체와 데이터를 액세스 할 수 있습니다.

 

 

SharePoint 2010 데이터 기술을 알아보았고 다음 블로그부터 하나씩 구체적으로 알아보도록 하겠씁니다.

[JumpToDX11-11] DirectCompute 를 위한 한걸음!

DirectX 11 2010. 2. 11. 09:00 Posted by 알 수 없는 사용자


앞선 시간을 통해서 GPGPU 를 위해서 마이크로소프트가 제공하는 플랫폼이
DirectCompute 라는 것이라고 말씀드렸습니다.
앞으로 DirectX11 을 지원하는 모든 그래픽카드들은 이 DirectCompute 를 지원할 것입니다.
그 이외에도 일부 DirectX10 을 지원하는 그래픽카드들도 지원을 하고 있습니다.


GPGPU 를 위해서 가장 기본적이고 핵심이 되는 기능은 무엇일까요?
저는 GPU 에서 처리된 메모리를 CPU 쪽의 메모리로 보내는 것이라고 생각합니다.
( 이는 개인 의견입니다.^^ )
즉, 그래픽카드에 있는 메모리를 메인메모리로 보내는 작업입니다.
DirectX9 세대까지는 이 작업이 불가능 했습니다.
예를 들면, 그래픽스 파이프라인 중간에 처리된 결과를 다시 가공할 수 있는 방법은
VertexShader 나 PixelShader 같은 쉐이더 스테이지 정도 뿐이였습니다.

하지만 DirectX10 부터는 이들에 대한 중간 결과를 메인메모리로 보내는 기능이 추가되어지면서,
GPGPU 의 시작을 알렸다고 생각합니다.
이 단순한 Copy 작업이 앞으로도 얼마나 유용하게 사용될 수 있을지는 기대가 상당합니다.



< DirectCompute 를 위한 ComputeShader >

DirectCompute 를 위해서 개발자가 할 일은 ComputeShader 를 작성하는 일입니다.
ComputeShader 는 HLSL 이라는 기존 DirectX 의 쉐이더 문법 구조로 작성을 합니다.




HLSL 코드는 DirectX 쉐이더 컴파일러인 FXC 나 API 를 통해서 컴파일 됩니다.
HLSL 은 결국 최적화된 IL 코드를 생성하게 되고,
이 IL 코드를 기반으로 런타임에 각각의 하드웨어에 최적화된 명령어들로 변환
되어져서 실행됩니다.


< GPGPU 에게 실행이란? >

GPGPU 를 활용해서 실행한다는 것은 하드웨어 내부적으로 어떻게 동작하도록 할까요?
앞선 시간에 GPU 는 병렬 처리에 최적화된 많은 SIMD 형태로 구성되어져 있다고 언급했었습니다.
결국 이들은 스레드들의 그룹으로써 실행합니다.
스레드들을 얼마나 많이 생성할 것인지를 개발자가 정해주면, 그에 맞게 연산을 수행합니다.

API 에서는 이들을 큰 그룹으로 나누어 줍니다.
큰 그룹으로 나누어 주는 API 는 ID3D11DeviceContext::Dispatch() 입니다.

ipImmediateContextPtr->Dispatch( 3, 2, 1 );

이렇게 큰 블럭 단위로 나누고 난 후에
ComputeShader HLSL 에서는 이들을 세부적인 스레들로 분할하는 문법을 지정합니다.

[numthreads(4, 4, 1)]
void MainCS( ... )
{
        ....
}




결과적으로 위의 그림처럼 스레드들이 생성되어서 병렬적으로 실행이 됩니다.
위에 나열된 숫자들은 스레드 ID 로써의 역활을 합니다.
즉, 어떤 스레드의 ID 가 MainCS 함수에 파라메터로 넘오오면,
그 ID 를 통해서 해당 버퍼에 값을 작성하게 됩니다.

아래에 간단한 예가 있습니다. 

[numthreads( 256,1,1) ]

void VectorAdd( uint3 id: SV_DispatchThreadID )
{

  gBufOut[id] = gBuf1[id] + gBuf2[id];

}


아무리 스레드들이 복잡하게 동작하더라도, 위와 같이 ID 를 통해서 제어한다면
그 어떤 작업도 문제없이 할 수 있습니다.

일단 먼저 어떻게 DirectCompute 가 실행되어지는지에 대해서 살펴보았습니다.
실행까지 가기 위해서는 일련의 절차를 거쳐야 합니다.
이들에 대해서는 앞으로 차근차근 살펴보겠습니다.



참고 자료
http://microsoftpdc.com/Sessions/P09-16
본 내용은 위의 PDC 를 참고해서 만들었습니다.

SharePoint 2010 Event Receiver

SharePoint 2010 2010. 2. 10. 09:00 Posted by 알 수 없는 사용자

항목을 추가하고 삭제하는 경우 이벤트 코드를 작성해야 할 필요가 있습니다. 이전 버전에서도 마찬가지로 클래스를 상속하여 Feature Object Model을 통해 작업을 했지만 수작업이 필요했습니다. Visual Studio 2010을 통해 새로워진 Event Receiver에 대한 부분을 이번 시간에 알아 보도록 하겠습니다.

 

HJ 라는 공지 사항이 있습니다. 간단히 삭제가 안되게 Event 코드를 작성해봅니다.

Visual Studio 2010 SharePoint 프로젝트중 Empty 프로젝트를 생성합니다. 이름은 EventDemo라고 합니다.

EventDemo 프로젝트를 오른쪽 클릭하여 새 항목을 하나 추가합니다. 새 항목은 아래와 같이 Event Receiver를 선택합니다.


Name
은 기본값을 그대로 하고 Add를 선택합니다. 그럼 해당 사이트에 연결하여 어떤 이벤트로 할 것인지와 어떤 목록인지, 어떤 이벤트 인지를 그래픽적으로 선택하게 됩니다.



이벤트 종류는 단순 List Item Event 뿐만 아니라 여러 가지가 나와 있습니다.


List Item Events
Announcements를 선택하고 “A item is being deleted”를 선택합니다.


마침을 선택하면 Feature가 생성되는 것을 확인 할 수 있습니다.

Feature의 활성, 비활성 이벤트를 어떻게 만드냐 하면 Feature1을 오른쪽 클릭하면 아래와 같이 Add Event Receiver를 볼 수 있습니다. 여기서 생성은 하지 않습니다.



EventReceiver1 폴더 아래의 Elements.xml을 살펴봅니다. 다 제대로 구성된 것을 확인할 수 있습니다. (이제부터는 특정 공지사항에만 이벤트를 적용시킬 수 있습니다. ^^)



마침을 선택했을 때의 코드는 아래와 같이 생성되어 있습니다. Deleting만 선택했기 때문에 이벤트 메서드는 하나만 생성되어 있습니다.



간단히 코딩을 해서 HJ 라는 공지사항에 글을 삭제하지 못하도록 해봅니다.



위의 코딩은 이전 버전의 스타일이며 2010에서는 추가로 뒤에서 다룰 Dialog Framework 으로 에러 핸들링을 할 수 있습니다.

 

EventDemo 프로젝트를 오른쪽 클릭하여 Deploy를 선택하고 완료 후 HJ 공지사항의 항목을 새로 추가합니다. 그러면 아래와 같이 결과 페이지를 확인할 수 있습니다.



Visual Studio 2010에서 만든 Event Receiver가 잘 동작되는 것을 확인했으며 Visual Studio 2010을이용해서 보다 더 생산성 있게 작업이 가능하다는 것을 알 수 있습니다.

 

'SharePoint 2010' 카테고리의 다른 글

SharePoint 2010 Server Object Model  (0) 2010.02.12
SharePoint 2010 데이터 기술  (0) 2010.02.11
SharePoint 2010 Feature  (0) 2010.02.09
SharePoint 2010 Visual Web Part  (0) 2010.01.21
SharePoint 2010 Web Part 생성  (0) 2010.01.19

Visual Studio 2010 RC 공개

Visual Studio 2010 2010. 2. 9. 11:41 Posted by POWERUMC

금일 2010년 2월 9일이 MSDN Subscription 을 통해 공개가 되었습니다. (미국 시간 2월 8일)

Visual Studio 2010 RC(Release Candidate) 공개
http://msdn.microsoft.com/en-us/vstudio/dd582936.aspx

 

이전 Visual Studio 2010 Beta 2 에서 발생하는 가상 메모리와 성능 관련된 문제에 대해서 이번 RC(Release Candidate) 버전에서는 상당히 개선이 되었다는 인터넷 블로거들의 반응이 보입니다.

이미 Visual Studio 2010 RC 버전을 설치한 외국의 블로거의 말에 의하면, Microsoft 는 이런 문제를 해결하는 것에 대해 용기있고 현명함에 칭찬을 아끼지 않고 있네요. 필자 또한 이번 RC 버전에 대해 Microsoft 대한 찬사를 아끼지 않습니다.

일반적으로 RC(Release Candidate) 버전은 더 이상의 기능이나 사용자의 피드백의 반영이 없고, RC 에 안정성을 확보하여 RTM(Release to Manufacture) 버전으로 정식 제품이 공개가 됩니다. 이전의 Beta 버전을 설치하기 꺼려하셨던 분들도 크리티컬한 이슈가 해결된 RC 버전을 설치하셔서 미리 공부하시면 될 것 같습니다.

앞으로 다가오는 4월달 정식 제품이 더욱 기대가 되는 하루입니다. ^^

SharePoint 2010 Feature

SharePoint 2010 2010. 2. 9. 09:05 Posted by 알 수 없는 사용자

앞의 블로그에서 Web Part에 대한 내용을 살펴보았는데 솔루션 탐색기에 보면 Feature 폴더가 생겨 있는 것을 알 수 있습니다. 그리고 Web Part 폴더 항목을 보면 Elements.xml 파일이 있는 것도 알 수 있습니다.

실제로 2007 버전부터 Feature가 사용되었으며 개발자들이 생성할 경우는 수작업을 해주어야만 했습니다. 2010에서는 수작업이 많이 줄어든 모습을 알아보도록 하겠습니다.

 

이번 시간에는 xml 파일에 속성을 추가해보고 Feature Designer를 살펴보도록 하겠습니다.

Visual Studio 2010 SharePoint 프로젝트의 Visual Web Part를 이용해서 Feature Designer를 알아봅니다.

프로젝트 이름을 FeatureDemo Empty 프로젝트를 생성하면 아래와 같이 솔루션 탐색기를 살펴볼 수 있습니다. Empty 프로젝트라 Features 폴더 밑에는 아직 아무것도 없습니다.


Visual Web Part 항목을 하나 추가합니다. 그러면 솔루션 탐색기는 아래 그림과 같이 변경됩니다.



Feature1
을 오른쪽 클릭해서 이름을 VisualWPFeature로 변경합니다. VisualWPFeature을 오른쪽 클릭하면 View Designer를 메뉴를 클릭 해 볼 수 있습니다.



Title
에 해당하는 제목을 FeatureDemo 라고 변경합니다. 설명에는 간단한 설명을 나열합니다. Scope는 사이트 컬렉션 또는 웹 등에서 보여주는 범위를 지정할 수 있습니다. 여기서는 Site로 지정합니다. 왼쪽에는 Package Explorer 가 나와 있습니다.

 

 

자 그럼 다른 쪽의 Feature 관련 내용을 알아봅니다. 솔루션 탐색기의 FeatureDemo 프로젝트를 오른쪽 클릭하고 속성을 선택하면 왼쪽에서 SharePoint 메뉴를 확인 가능합니다.




아래쪽 Edit Configuration에서 보면 Default, No Activation을 볼 수 있으며 Default를 선택하고 View 버튼을 클릭해봅니다.



배포 단계를 알 수 있으며 Activate Features 내용을 확인할 수 있습니다. No Activation은 배포후비활성화로 활성화 옵션이 없다는 것을 알 수 있습니다. 여기서는 Active Deployment Configuration에서 No Activation을 선택합니다.

 

FeatureDemo 프로젝트를 오른쪽 클릭하고 Deploy를 선택합니다. 배포가 완료되고 나면

Output 창에서 아래와 같은 배포 단계 결과를 확인할 수 있습니다. 현재 Feature는 비활성화 되어있기 때문에 단계에서 누락되어 있다는 것을 확인 가능합니다.



해당 사이트로 이동해서 사이트 설정의 사이트 컬렉션 기능으로 이동해보면 비활성화되어 있는 것을 아래와 같이 확인 가능합니다.



간단히 Feature Event Handler, Web Part 등의 작업시 이전 버전과는 수작업이 많이 필요 없다는 것을 살펴보았습니다. 앞으로 계속 보시게 될 겁니다.


WCF의 기본 <Contract> - Data Contract

WCF 2010. 2. 9. 09:00 Posted by 알 수 없는 사용자
2010년도 어느새 한달이 지나가고 두번째 달이 되었습니다. 한달 한달이 왜 이렇게도 빨리 지나가는지... 할 일은 많은데 시간만 무심하게 지나가는 것 같습니다... 드래곤 볼의 "시간의 방" 같은 곳이 있었으면 하는 허무한(?) 생각도 들고.. ㅎ

이번 포스팅은 저번 주제에 이어서 Contract 중 Data Contract에 대해 이야기 해볼까 합니다.

간단히 얘기하면, Data Contract는 WCF 서비스에서 사용하는 개체에 대한 정보를 클라이언트에서 인지할 수 있게끔 XSD 형태로 매핑시켜주는 역할을 합니다.
저번 포스팅에서 충분히 설명했듯이 클라이언트에서는 서비스에 대한 정보를 WSDL을 통해 얻게 됩니다. 이때, 서비스에서 사용하는 개체에 대한 정보 역시 이 WSDL을 통해 전달되는데, 이렇게 XSD 형태로 매핑시켜주는 역할을 하는 것이 Data Contract 입니다.

그럼, 일단 코드를 한번 보고 설명을 이어 나가겠습니다. 역시, 말보다는 코드를 봐야 "아~ 이게 Data Contrat 구나~" 하실겁니다,, ㅋ

저번 포스팅에서 만들었던 서비스에 다음과 같이 Product라는 이름의 새로운 클래스를 추가하였습니다.

using System.ServiceModel;

using System.Runtime.Serialization;

 

namespace MyService

{

    [DataContract]

    public class Product

    {

        [DataMember]

        public int ProductId;

 

        [DataMember]

        public string ProductName;

 

        [DataMember]

        public string Company;

 

        [DataMember]

        public double Price;

 

        [DataMember]

        public DateTime CreateDate;

    }

}

ServiceContract 속성을 줬듯이 DataContract 속성(attribute)은 서비스에서 사용할 새로운 클래스에 지정해 주면 됩니다. 그리고 이 클래스의 필드들에겐 DataMemer 속성을 줬습니다.

이제 이 클래스를 서비스에서 사용하도록 기존 서비스의 코드를 다음과 같이 살짝 바꾸었습니다.

[ServiceContract(Namespace = "http://RuAAService.co.kr/")]

interface IProductService

{

    [OperationContract]

    Product GetProduct();

}

class ProductService : IProductService

{

    public Product GetProduct()

    {

        Product p = new Product();

        p.ProductId = 1234;

        p.ProductName = "ABC Chocolate";

        p.Price = 1500.0;

        p.Company = "Lotteee";

        p.CreateDate = DateTime.Parse("2010-01-22");

 

        return p;

    }

}


사용하는 방법은 특별히 따로 존재하지 않습니다. 다른 클래스들 처럼 그냥 사용하면 되는 것입니다. 여기서 조금 주목해야할 점은 GetProduct 메소드의 반환 타입이 우리가 새로 생성한 Product 클래스라는 것입니다.

Product  클래스는 서비스에서 생성한 클래스이기 때문에 클라이언트에선 알 수 없는 타입이겠죠. 서비스를 이용하는 프로그램이 아닐때는 그냥 참조 추가를 이용해 dll 파일을 참조하여 다른 클래스를 사용할 수 있지만, 서비스를 사용할 땐 이 방법을 쓸 수는 없습니다.

이러한 문제(?)를 해결하는 방법이 바로 wsdl에 이 클래스에 대한 정보를 추가하여 클라이언트에서 인지할 수 있게 하는 것인데, 이걸 Data Contract가 해주게 되는 것입니다.

그럼, 이 클래스에 대한 정보를 닷넷 컴파일러가 어떤 형태의 xsd로 매핑시켜주는지 눈으로 확인해 보겠습니다.

서비스를 실행시킨 상태에서 브라우저를 이용하여 http://localhost:8000/ProductService?wsdl=wsdl0 위치로 이동해 봅니다. 그럼, <wsdl:types> 라는 엘리먼트를 볼 수 있고, 그 밑에 서비스에서 사용하는 여러 가지 스키마에 대한 정보를 명시한 엘리먼트를 확인할 수 있습니다.


이 스키마들 중에 http://localhost:8000/ProductService?xsd=xsd2 위치로 이동해보겠습니다. 그럼 다음과 같은 내용을 확인할 수 있을 것입니다.


complexType 엘리먼트는 우리가 생성한 클래스의 이름을 나타내고 있고, 그 밑으로는 클래스의 멤버들에 대한 정의가 엘리먼트로 포함되어 있는 것을 확인할 수 있습니다.

이제 어떻게 클라이언트에서 이러한 클래스에 대해 인지할 수 있는지,, 아시겠죠? ^^

아~ 참고로 클래스의 필드들을 DataMember 속성을 이용해 노출을 했었는데, 만약 필드에 DataMember 속성을 적용시키지 않으면 아예 노출이 안된다는 것을 유념해주시기 바랍니다.
그리고, 필드들의 한정자가 public 이든, private 이든 이 역시 상관없다는 것도 알아두시면 좋을 것 같습니다. 오로지 DataMember 속성이 적용되었는지만 신경을 써주시면 됩니다.

DataContract 와 DataMember는 몇 개의 프로퍼티(property)를 가지고 있습니다.

DataContract는 Name과 Namespace 프로퍼티를 가지고 있는데, 이는 ServiceContract가 가지고 있는 프로퍼티와 같은 역할을 하는 녀석들이기 때문에 따로 긴 설명을 필요없을 듯 합니다.

그리고, DataMember 는 Name, Order, IsRequired 라는 프로퍼티들을 가지고 있습니다.
Name은 클라이언트에 노출되는 멤버의 이름을 명시적으로 설정하는 역할을 하구요, Order는 클라이언트에 멤버들이 노출될 때 그 순서를 정하는 역할을 수행합니다. (이 순서를 명시적으로 설정하지 않으면 기본적으로 멤버들은 알파벳 순으로 xsd에 나타나게 됩니다.) 사실 .NET 어플리케이션 끼리는 이 순서가 그리 중요하지 않습니다. 하지만 다른 플랫폼의 어플리케이션 간의 상호 운용성을 고려할 땐 중요할 수가 있습니다. 클라이언트로 부터 메시지를 받았을 때, 이 순서가 다르면 서버에선 인식할 수가 없게 되어버리거든요.
IsRequired는 멤버가 적어도 한번은 포함이 되어야 하는지에 대한 정의를 내려주는 역할을 합니다. 이 프로퍼티의 값이 true인 경우엔 설정된 멤버는 메시지에 적어도 한번은 꼭 포함되어야 함을 의미하는 것이죠~

그럼, 이러한 속성들을 적용했을 때, 어떻게 xsd로 표현되는지 한번 보도록 하겠습니다.
Product 클래스를 다음과 같이 살짝 수정해보았습니다.


[DataContract (Namespace="http://RuAAService.co.kr/Product", Name="ProductInfo")]

public class Product

{

    [DataMember(Name = "ID", Order = 1, IsRequired = true)]

    public int ProductId;

 

    [DataMember(Name = "Name", Order = 2, IsRequired = true)]

    public string ProductName;

 

    [DataMember(Order = 3)]

    public string Company;


   
[
DataMember(Name = "Value", Order = 4, IsRequired = true)]

    public double Price;

 

    [DataMember(Order = 5, IsRequired = true)]

    public DateTime CreateDate;

}

이제 xsd를 어떻게 확인하시는지 아시죠? xsd를 확인해 보면 다음과 같이 나타남을 확인할 수 있으실 겁니다.


complextType 엘리먼트에 name 속성의 값이 바뀌고, 네임스페이스의 값도 바뀌고,, 여러가지가 바뀐 것을 확인할 수가 있습니다. 프로퍼티 설정과 xsd를 비교해가면서 어떻게 적용되는지 유심히 살펴보시기 바랍니다. ^^

Company 멤버의 경우 IsRequired 속성을 따로 명시해주지 않았는데, 이 경우엔 minOccrus=0 으로 표현되는 것을 볼 수가 있습니다. 이게 기본값이며, 포함되지 않아도 됨을 나타내주는 것이죠~

이렇게 해서 Data Contract에 관한 기본적인 내용은 끝이 난 것 같습니다.

마지막으로, 수정한 이 서비스를 이용하도록 클라이언트 코드 역시 수정을 조금 해보구요~ 결과 화면을 보는 것으로 이번 포스팅을 마치도록 하겠습니다.

수정한 클라이언트의 코드는 다음과 같습니다.

static void Main(string[] args)

{

    // 프록시 클래스 인스턴스 생성

    ProductServiceClient myService = new ProductServiceClient();

    // 서비스 Operation 호출

    ProductInfo product = myService.GetProduct();

 

    Console.WriteLine("Product ID : {0}", product.ID);

    Console.WriteLine("Product Name: {0}", product.Name);

    Console.WriteLine("Made by :{0}", product.Company);

    Console.WriteLine("Price : {0:c}", product.Value);

    Console.WriteLine("Created Date : {0}", product.CreateDate.ToShortDateString());

}

이 코드에 대한 추가적인 설명은 필요없겠죠? 이제 다들 "이까이꺼~" 하실테니깐,, ^^

수정을 모두 완료한 후에 서비스와 클라이언트를 동시에 실행시키면 다음과 같은 결과 화면을 확인하실 수 있으실 겁니다. 


네~ 무사히 서비스에서 Product 클래스에 대한 데이터를 받아왔고, 이 데이터를 화면에 잘 뿌려주는군요...

아직까지는 WCF의 기본이 되는 내용에 대해서만 포스팅을 하고 있기에 그렇게 어렵지 않으실 거라 생각합니다.
저도 WCF에 대해 마스터하지 못하였지만, WCF를 이제 시작하는 분들께 조금이라도 도움이 되었으면 하는 마음으로 포스팅을 하고 있는 것이라, 아주 기본이 되는 내용들을 주제로 진행하고 있습니다.

이렇게 하나씩 채워나가다 보면, 언젠가 좀 더 복잡한 주제로 포스팅을 할 수 있는 시간도 올꺼라 생각합니다.
너무 조급해하지 마시고 따뜻한 시선으로 지켜봐주셨으면 합니다.
(뭐,, 질책이나 조언도 감사한 마음으로 받겠습니다 ^^)

그럼, 다음 포스팅때 뵙겠습니다 ^^

[MFC] 태스크 대화상자(Task Dialog) - (1/3) : 기능 소개

MFC 2010. 2. 8. 09:00 Posted by 알 수 없는 사용자

Intro
안녕하세요. MFC 카테고리의 꽃집총각입니다. 이번에는 앞서 말씀 드렸던 대로, MFC 10.0에서 새로 추가되는 클래스 중에 하나인 CTaskDialog에 대해서 알아보도록 하겠습니다. 이번 포스팅에서는 CTaskDialog클래스와 이를 이용해 구현할 수 있는 기능인 태스크 대화상자(Task Dialog; 이하 한글 표기만을 사용합니다)에 대한 전반적인 개념을 정리하고, 다음시간 부터는 실제 사용방법 및 주의 사항 등을 정리해 보도록 하겠습니다.


태스크 대화상자 소개
태스크 대화상자(Task Dialog)는 윈도우 비스타 버전에서 새롭게 선보인 컨셉의 대화상자 입니다. 태스크 대화상자를 이용해 개발자들은 기존의 메세지 박스를 이용해 구현하던 기능을 보다 손쉽고 강력하게 처리할 수 있습니다. 이전부터 널리 사용되고 있는 AfxMessageBox(...)를 이용해서도 간한단 정보의 노출이나 사용자 의사선택을 처리할 수 있었지만, 추가적인 기능을 확장해 넣기에는 번거로운 점이 많았습니다. 어떻게든 예, 아니오로 대답할 수 있는 연속적인 질문을 생각해내 메세지 박스를 연거푸 출력하거나, 리소스 에디터를 열고 새로운 대화상자를 직접 만들어 주어야 했습니다. 하지만 태스크 대화상자를 이용하면 개발자가 대화상자를 커스터마이징 하기 위해 필요한 거의 대부분의 기능을 옵션으로 제공하고 있어, 손쉽고 풍부한 기능확장을 아주 심플하게 해낼 수 있습니다.

원래 길게 말해봐야 한 번 보는게 더 와닿는 법입니다. 아래의 스크린샷이 바로 태스크 대화상자의 예시 입니다.

(그림 1) Internet Explorer 8.0의 세션 복구 기능에서 등장하는 태스크 대화상자.


(그림 2) 지난번 강좌에서 설명한 리스타트 매니저에서 사용되는 태스크 대화상자.


(그림 3) 윈도우 7의 업데이트 옵션 설정할때 등장하는 태스크 대화상자.


아마도 윈도우 비스타나 윈도우 7을 사용해보신 분이라면 이곳 저곳에서 많이 보셨던 형식의 대화상자 일겁니다. 위에서 예를 든 세 가지 스크린샷도 모두 프로그래머가 별도로 만든 것이 아니라, 운영체제나 인터넷 익스플로러 등에서 쓰인 녀석들 입니다. Visual Studio 2010의 MFC기반 응용 프로그램에서는 이러한 태스크 대화상자를 몇 줄 안되는 코드만으로 쉽게 제어할 수 있는 강력한 클래스를 제공합니다. 그 클래스가 바로 CTaskDialog 입니다 :)


CTaskDialog로 할 수 있는 일들.
거의 예/아니오 내지는 확인/취소 정도의 선택밖에 제공할 수 없었던 기존의 메세지 박스와는 달리 태스크 대화상자는 아래와 같은 풍부한 커스터마이징 옵션을 제공합니다.

  • 사용자 지정 아이콘
  • 메인 헤더 텍스트 (멀티라인 지원)
  • 본문 텍스트 (당연히 멀티라인 지원)
  • 프로그레스바 컨트롤 표시 가능.
  • 라디오 버튼 표시 가능.
  • 커맨드 버튼 - 대화상자의 가장 중앙에 나타나는, 사용자의 의사 선택을 받는 버튼 - 표시 가능.
  • 추가적인 본문 텍스트를 보이거나 숨길 수 있게 해주는 확장/축소 버튼 표시 가능.
  • 체크박스 표시 가능.
  • 꼬릿말 텍스트 (멀티라인 지원)
  • 대부분의 컨트롤 및 텍스트에 하이퍼 링크 지원.
  • 타이머 기능 제공.

위처럼 다양한 옵션을 제공하기 때문에 어지간한 대화상자 UI의 구성은 번거롭게 다이얼로그 리소스에 버튼을 배열하고 CDialog 파생클래스를 만들어 일일이 코딩을 해야 하는 수고를 들이지 않아도 CTaskDialog 를 이용해 아주 심플하게 처리할 수 있게 되었습니다. 개발 편의성 뿐만 아니라 실용성 측면에서도 실제 비스타 이후 버전의 윈도우 자체에서도 널리 쓰이고 있는 것을 보면 그 실용성은 입증 되었다 볼 수 있겠네요.
각각의 기능들에 대한 자세한 사용 방법은 다음 강좌에 보다 본격적으로 다루도록 하겠습니다.



태스크 대화상자와 운영체제, API들의 관계
이번 강좌에서는 이 부분을 좀 더 일찍 정리하고 싶었습니다. 실제 기능의 사용에는 크게 상관없지만 그래도 기본 개념의 정리가 중요한 법인데, 지난 번 강좌에서는 너무 늦게 말은 한 것 같아서요. 노파심에 다시 한 번 운영체제 및 API들과 기능간의 관계를 정리해 보겠습니다.

  1. 꼭 MFC에서만 할 수 있는 건 아닙니다. MFC는 보다 편리하게 쓸 수 있도록 도와줍니다.
    태스크 대화상자는 기존의 메세지 박스보다 확장성 측면에서 훨씬 유용하게 쓰일 수 있는 새로운 컨셉의 대화상자 입니다. 이 태스크 대화상자는 윈도우 비스타에서 처음 소개되었으며, 태스크 대화상자를 제어할 수 있는 Windows API들도 함께 제공 되었습니다. MFC에서는 이러한 API들을 쉽고 편하게 사용할 수 있도록 랩핑한 클래스를 제공하는 겁니다. 그래서 굳이 MFC를 사용하지 않아도 태스트 대화상자를 이용할 수는 있지만, MFC의 CTaskDialog를 이용해 보다 편하게 작업을 할 수 있습니다.
  2. 윈도우 비스타 이전 버전에서는 사용할 수 없습니다.
    하지만 애석한 점 한가지는, 비스타 이전 버전의 윈도우에 대한 대비책은 딱히 제공되지 않는다는 점입니다. 지난번 포스팅에서 소개했었던 리스타트 매니저도 마찬가지로 비스타 이후 버전에서만 동작했었습니다. 하지만 리스타트 매니저는 응용 프로그램을 윈도우 XP에서 실행해도 기능을 하지 않을 뿐 오류를 내지는 않았지만, 태스크 대화상자를 사용하는 응용 프로그램을 윈도우 XP에서 실행한다면 태스크 대화상자 출력시점에 에러를 내게 됩니다. 화면상에 출력되어야 하는 기능이니 리스타트 매니저 처럼 오동작 없이 넘어가기가 어렵기 때문입니다.


Outro

늘 마찬가지지만 제가 설명해 드리는 MFC의 새로운 기능들은 이해하기 쉽고 빠르게 개발에 적용할 수 있는 기능들 입니다. MFC의 기본적인 방향성 자체가 보편적인 기능을 손쉽게 다룰 수 있게 제공되는 라이브러리 이기도 하고요. 그래서 초보 개발자나, 학생 분들도 어렵지 않게 내용을 따라오실 수 있다고 생각합니다. 그래도 설명 중 잘 이해가 되지 않는 부분이나 잘못된 점이 있다면 피드백을 부탁 드리겠습니다.

그럼 이번 포스팅에서는 간략하게 기능소개 정도로 글을 마치고, 다음 글에서 본격적으로 태스크 대화상자를 사용하는 방법을 다뤄 보도록 하겠습니다. 다음 글을 기다리기가 지루하신 분들은 MSDN( http://msdn.microsoft.com/en-us/library/bb760441(VS.85).aspx )이나 다음의 글( http://www.nuonsoft.com/blog/2009/06/10/ctaskdialog-in-mfc-in-visual-c-2010/ )을 참고하세요.
그럼 다음에 뵙겠습니다.
감사합니다 ^^*


Reference

디버깅 모드에서 역어셈블리 코드 보기

Visual C++ 10 2010. 2. 4. 09:00 Posted by 알 수 없는 사용자

VC++ 10 C++0x나 병렬 프로그래밍 라이브러리 이외에도 툴적인 측면에서도 여러 좋은 기능들이 추가 되었습니다. 알고 있으면 작업할 때 편리한데 시간이 부족하여 제가 아직 자세하게 찾아보지 못해서 소개하지 못한 것이 많이 아쉽습니다. 그래서 짥은 것이라도 틈틈이 시간나면 소개하려고 합니다.

 

 

VC++ 10에서는 디버깅 모드에서도 역어셈블리 코드를 볼 수 있습니다.

 

메뉴에서 “Debug” -> “Windows” -> “Disassembly”를 선택합니다.



아래와 같이 역어셈블리 코드 창이 나타납니다.



그러나 위 화면을 보면 코드 바이트는 표시되지 않고 있습니다.

코드 바이트를 보고 싶다면 위 화면 왼쪽 상단의 “Viewing Option”을 클릭합니다.



위와 같이 옵션을 선택할 수 있습니다. 이 중 “Show code bytes”를 선택합니다.

그러면 아래와 같이 코드 바이트가 표시됩니다.



 

 

 

참고

http://d.hatena.ne.jp/kkamegawa/20100130/p1

 

Visual Studio 2010 RC 공개 임박!

Visual Studio 2010 2010. 2. 3. 09:00 Posted by 알 수 없는 사용자

현재 Visual Studio 2010 Beta 2까지 공개가 되었는데 RC버전의 발표가 임박한 것 같습니다. 
원래 1월 공개 예정이었으나 알려진대로 약간 딜레이 되어서 2월 중에 발표된다는 소식이 있었는데 곧 나올 것 같습니다.

우리나라 시간으로 2월 2일 Windows Azure Tools 1.1 버전이 공개되었는데 지원하는 Visual Studio 버전이 Visual Studio 2008 SP1과 Visual Studio 2010 RC버전입니다.


이것을 보았을 때 RC버전이 곧 공개된다는 뜻이고 Windows Azure Tools 팀 블로그에서도 Windows Azure Tools의 새로운 버전 발표와 함께 Visual Studio 2010 RC 버전이 곧 나온다는 소식을 전하고 있습니다.
최신 소식에 의하면 RC버전에서는 퍼포먼스가 대폭 향상되어 아주 만족스럽다는 내용을 볼 수 있었습니다.

보통 이정도 발표되면 늦어도 2주 내에는 나올 것 같고 빠르면 며칠 내로 발표될 가능성도 있습니다.
아직 정확한 일정은 나온게 없으니 참고만 하시고 자세한 정보가 나오면 알려드리도록 하겠습니다.

6. Assembly - GAC(Global Assembly Cache)

CLR 2010. 2. 2. 09:00 Posted by 알 수 없는 사용자

오늘은 GAC (Global Assembly Cache)에 대해서 공부해 보도록 하겠습니다. GAC는 보통 윈도우 설치 폴더 아래에 위치합니다. 만약 윈도우 설치 폴더가 C:\Windows 라면

C:\Windows\Assembly

에 위치해 있습니다. 윈도우 탐색기를 이용하여 한번 확인해 봅시다.

윈도우 7에서는 이렇게 Assembly 별로 세부 정보들을 정리하여 보여주고 있네요. 지난 주에 배웠던 Strongly named assemblies의 정보들이 보입니다. 이름과 버전, culture(locale) 그리고 공개 키 토큰 정보 입니다. 실제로는 기능별/제품별로 굉장히 많은 하위 디렉토리 구조로 구성되어 있습니다. 이런 식으로요.



자 그러면 이 GAC라는 곳에 우리가 만든 Strongly named assemblies를 복사하면 우리의 Assembly를 전역적으로 사용할 수 있는 것일까요? 당연히- 아닙니다.

GAC에 넣어주는 도구가 있습니다. 바로 GACUtil.exe 라는 툴입니다. 실행시켜보면 다음과 같은 다양한 옵션에 대한 안내를 해 줍니다.


다양한 옵션 설명 중 /i 가 바로 assembly를 GAC에 인스톨하는 옵션임을 알 수 있습니다. 그리고 /u가 지정된 assembly를 언인스톨하는 옵션이군요. 이 툴을 이용하여 우리가 만든 Strongly named assembly를 GAC에 설치할 수 있습니다.

GAC라는 영역이 한번 등록시켜놓으면 편리하게 어느 어플리케이션이나 편리하게 사용할 수 있게 됨은 사실이지만 GAC에 Assembly가 설치되게 되면 단순 파일 복사, 삭제 등의 작업만으로 해당 Assembly를 관리할 수 없게 됩니다. 그래서 손쉬운 배포를 위해서는 가능하면 GAC에 등록하는 형태의 배포가 아닌 전용배포의 방식이 관리하기에 편리한 면도 있습니다.

이 GAC가 존재함으로써 시스템 전역적으로 assembly를 사용할 수 있다는 것 외에 어떤 장점이 있을까요. 우선 지난 주에 언급했던 내용 중에 DLL Hell과 관련된 이야기를 기억하시는지요? 다른 회사에서 만든 같은 이름의 DLL 모듈로 인한 충돌 문제나 같은 회사에서 만든 같은 이름의 DLL모듈이라고 해도 버전이 틀려서 발생하는 문제등을 GAC는 훌륭히 처리해 줍니다. 같은 이름을 가진 Assembly라고 하더라고 GAC의 정해진 알고리즘에 의해 제각기 분리된 영역에 설치되게 되고 각 어플리케이션이 요구하는 Assembly를 정확히 구별하여 사용할 수 있도록 구성해 주는 것입니다.


그렇다면 GAC의 내부의 구조에 대해 간략하게 살펴보도록 할까요?

우선 Assembly 디렉토리 내를 살펴보니 다음과 같이 구성되어 있네요. 여기서

GAC 는 CLR 1.0, 1.1 버전에서 생성된 Assembly들이 포함되어 있습니다. 이 곳에 있는 Assembly들은 오직 x86 그러니까 32비트 OS환경 또는 64비트 OS환경에서 WoW64기술이 적용되어 실행됩니다. 이곳에는 Native x86코드가 포함되어 있는 경우도 있습니다.

GAC_32는 CLR 2.0 버전에서 생성된 Assembly들로써 32비트 환경의 OS 또는 64비트 OS환경에서 WoW64가 적용되어 실행되는 Assembly들이 저장되어 있습니다. 여기도 역시 Native x86코드가 포함되어 있는 assembly도 있습니다.

GAC_MSIL은 CLR 2.0 버전에서 생성된 Assembly들이 저장되어 있는데 순수하게 IL코드들로만 구성되어 있는 Assembly들이 저장되어 있습니다. 이 디렉토리 하부의 Assembly들은 32비트나 64비트의 OS 환경을 가리지 않습니다.

위의 이미지에는 나와있지 않지만 GAC_64라는 디렉토리도 있습니다. GAC_32와 비슷한 이름으로 보아 유추가 가능하겠지요? CLR 2.0 버전에서 생성된 Assembly들이며 64비트 OS 환경에서 실행되는 Assembly들이 저장되어 있는 공간입니다.

그리고 NativeImages.. 로 시작되는 디렉토리도 보이네요. 이 곳에는 NGen.exe라는 CLR의 Native Code 컴파일러를 이용하여 컴파일된 Assembly들이 저장되는 곳입니다.

NGen.exe


자 이젠 좀 더 아래 디렉토리로 들어가 볼까요? GAC_MSIL 디렉토리를 들어가 봅시다.


아 딱 보니 대충 짐작이 가네요. Namespace별로 디렉토리들이 구성되어 있는 것을 알 수 있습니다. 또 들어가 봅니다.


오 이것도 대충 짐작이 갑니다. 버전과 공개키 토큰이군요. 원래는 다음과 같은 형식으로 구성되어 있습니다.

(버전)_(Culture)_(공개 키토큰)

그런데 저 디렉토리 안에 있는 Assembly는 Culture 정보가 없기 떄문에 언더바(_) 두개로 Culture 정보가 생략되어 있네요. 그럼 저런 형식의 디렉토리 안을 들어가 보면 assembly 파일이 있겠지요?

네. 존재하네요. GAC는 이런식의 구조로 Assembly를 관리하고 있습니다. :)


'CLR' 카테고리의 다른 글

8. System.Object (2)  (0) 2010.03.03
7. System.Object  (0) 2010.02.16
5. Assembly - Strongly named assemblies  (0) 2010.01.26
4. Assembly  (2) 2010.01.15
3. MSCorLib & Metadata  (4) 2010.01.06

[MFC] 리스타트 매니저(Restart Manager) - (3/3) : 활용하기

MFC 2010. 2. 1. 10:00 Posted by 알 수 없는 사용자

Intro
안녕하세요. MFC 카테고리의 꽃집총각 입니다.
지난번 [MFC] 리스타트 매니저(Restart Manager) - (2/3) : 사용하기 편에 이어서 오늘은 리스타트 매니저 시리즈의 마지막인 ‘활용하기’ 편입니다. 이번 시간에는 실제로 리스타트 매니저의 동작을 확인해 볼 수 있는 샘플을 만드는 과정을 정리하고, 실제 동작에 관련된 이슈들을 몇 가지 정리해 보겠습니다. 


예제 프로그램작성 – 1. MFC Application Wizard 설정 하기.

자, 우선은 VIsual Studio 2010을 열어서 Ctrl + Shift + N 을 힘차게 누질러서 새 프로젝트 생성 창을 띄우고, MFC Application을 선택해 MFC 어플리케이션 위자드(Application Wizard;응용 프로그램 마법사)를 띄웁니다.

(그림 1) MFC Application을 선택해 새 프로젝트를 생성합니다.

어플리케이션 위자드가 뜨면 왼쪽 메뉴에서 Application Type을 선택하고, 기본 비주얼 스튜디오(Visual Studio) 형식으로 되어 있는 프로젝트 스타일 항목을 오피스(Office) 형식으로 변경해 줍니다. 비주얼 스튜디오 스타일의 여러 가지 도킹 pane들은 이번 예제에는 크게 쓸모가 없으니까, 괜히 걸리적 거리기만 하거든요 ^^;


(그림 2) MFC Application Wizard > Application Type 항목 설정.

Document Template Properties 항목을 선택하고, 파일 확장자(File extension) 란에 임의의 확장자를 입력해줍니다. 파일 확장자를 명시하게 되면 별도의 코딩을 추가하지 않아도 생성된 프로젝트에 자동 생성되는 소스코드 부분에 Document의 저장/로딩 처리가 추가됩니다. 파일 확장자를 입력하면 오른쪽에 있는 필터 이름(Filter name)칸도 자동으로 채워집니다. 저는 custom-document-format의 뜻으로 cdf라고 적어봤어요.

( 그림 3) MFC Application Wizard > Document Template Properties 항목 설정.

그리고 가장 중요한 부분! Advanced Features 항목에 가서 리스타트 매니저 지원 사항들을 확인합니다. 리스타트 매니저와 관련된 세 개의 체크사항들이 기본적으로 체크되어 있기 때문에 수정을 할 필요는 없지만 그래도 올바로 체크되어 있는지 확인해야 겠지요. 


(그림 4) MFC Application Wizard > Advanced Features 항목 설정.

자, 이제 마지막으로, Generate Class 페이지로 가서 뷰 클래스의 부모 클래스를 CView –> CEditView로 변경해 줍니다. CEditView를 상속받은 클래스는 기본적으로 뷰 영역이 메모장 프로그램처럼 캐럿이 깜박이는 에디트 컨트롤 형식으로 되어있어서, 별다른 처리 없이도 문서의 저장을 확인할 수 있는 문자열 형식의 데이터를 입력할 수 있게 됩니다.


(그림 5) MFC Application Wizard > Generated Class 항목 설정.

이제 어플리케이션 마법사의 모든 설정이 끝났습니다. Finish를 눌러 프로젝트를 생성합니다.


예제 프로그램작성 – 2. 크래시 발생 버튼 추가하기.

리스타트 매니저가 올바르게 동작하는지 알아보기 위해서는 프로그램이 비정상 종료 되어야 합니다. 샘플 프로그램의 리본 UI에 간단하게 패널 하나와 버튼 하나를 추가하고, 핸들링 함수를 추가해서 아래와 같이 잘못된 포인터 연산을 하는 코드를 넣어줍니다. 어플리케이션 위자드에서 오피스 형식의 Application Type을 선택하셨다면 아마 기본적으로 리본 UI가 붙어있을겁니다. 그렇지 않다면 툴바든, 마우스 입력이든 상관 없이 아무 이벤트나 받아서 고의적으로 예외를 발생시키는 코드를 넣어주면 됩니다.

  
(그림 6) 리본에 누르면 터지는 버튼을 넣어줍니다.

 

 예제 프로그램 작성 – 3. 문서 자동 저장 간격 조절하기.
지난번에 리스타트 매니저 사용 팁을 정리하면서, 문서 데이터 자동 저장 기능의 기본 저장 간격 시간은 5분이라고 말씀 드린 적이 있습니다. 우리는 매우 바쁜 사람이니까, 이 시간을 당겨보죠. 이왕이면 프로그램을 띄우고 나서 바로 확인할 수 있도록 아주 짧게 잡아보겠습니다. 한 10초 정도면 나쁘지 않겠죠?

(그림 7) CWinApp 파생클래스의 생성자에서 m_nAutosaveInterval의 값을 10000 미리세크(=10초)로 설정해줍니다.

시간 설정에 대한 내용은 지난회 포스팅이었던 [MFC] 리스타트 매니저(Restart Manager) - (2/3) : 사용하기 편을 참고하시면 됩니다.

 

예제 프로그램 실행!
이제 빌드를 하고 프로그램을 실행시켜서 에디트 뷰에 블라블라 테스트용 잡담을 늘어놓은 후, 10초가 지나 임시 문서가 만들어졌을 만한 충분한 시간을 제공한 뒤에 ‘누르면 폭발!’ 버튼을 야심차게 눌러주면 프로그램이 크래시가 나고 리스타트 매니저가 동작하면서 프로그램을 다시 띄워 주겠지요? 하지만 실제로 실행해보시면 아마 리스타트 매니저는 커녕 그냥 프로그램만 죽어버리고, 프로그램을 닫든지 디버깅을 하든지 니 맘대로 하라는 쓸쓸한 대화상자만 출력되고 말 겁니다.


(그림 8) 크래시가 났지만, 재시작도 문서 복구도 아무 것도 일어나지 않았습니다.
우리는 대 사기극에 휘말린 걸까요?


(그림 9) ‘그래, 내컴은 닷넷 디버거가 깔려 있어서 그럴 거야! 일반 사용자들은 이렇지 않겠지!’
라는 일말의 희망도 부질없습니다. 닷넷 미설치 PC에서는 위와 같은 창이 출력됩니다.

이 시점에서 몇 가지 더 알아두어야 할 것이 있습니다. 우리의 샘플 프로그램에서 리스타트 매니저가 동작하지 않는 이유와 함께, 실제로 리스타트 매니저를 사용하려고 할 때 알아두면 좋은 몇가지 정보들을 함께 정리해 보도록 하겠습니다.

  

(중요*) 리스타트 매니저 사용시 고려사항.

  1. 우리가 살펴보고 있는 MFC 10.0의 리스타트 매니저 기능이 실은 첫 번째 포스팅에서 설명 드렸던 것처럼 윈도우 비스타 시스템에서 선보인, OS 차원에서 제공되는 기능입니다. MFC 10.0에서는 이 기능을 좀 더 쉽게 사용할 수 있게끔 추가처리를 지원하는 것입니다. 예를 들자면 win32 GDI의 DC(Device Context)와 MFC의 CDC 클래스 관계 정도가 되겠네요. MFC의 리스타트 매니저도 내부 구현으로 들어가면 비스타 이후 OS에서 지원하는 윈도우 API인 RegisterApplicationRestart, RegisterApplicationRecoveryCallback 등의 함수를 사용해 재시작 및 문서 복구 기능을 제공하고 있습니다. MFC를 사용하지 않은 일반 Win32 윈도우 프로그램이나, 콘솔 프로그램에서 까지도 재시작 및 복구 기능을 사용할 수 있습니다. 하지만 MFC를 이용하면 보다 쉽게 사용할 수 있게 되는 것이지요.
  2. 프로그램 재시작 기능의 핵심이 되는 RegisterApplicationRestart 함수는 사용시 한가지 주의해야 할 사항이 있습니다. 만약 프로그램의 초기화 코드에 오류가 있어서 인스턴스가 실행되자마자 크래시를 내는 상황인데 리스타트 매니저가 동작한다면 어떻게 될까요? 아마 프로그램은 뻗고, 실행되고, 다시 뻗고, 다시 실행되는 무한 재실행을 반복하게 될겁니다. 이런 경우를 피하기 위해서 비스타의 응용 프로그램 재시작 기능은 초기 재시작 후 60초 동안은 크래시로 인한 비정상 종료가 있었다고 해도 동작하지 않습니다. 우리가 실행시킨 샘플 프로그램은 구동한지 60초가 지나지 않았기 때문에, 재실행 기능이 실행되지 않았던 겁니다. 보다 자세한 내용은 MSDN(http://msdn.microsoft.com/en-us/library/aa373347(VS.85).aspx)에서 확인하실 수 있습니다.
  3. 자동 저장되는 문서 파일의 경로는 CDataRecoveryHandler::GetAutosavePath 함수로 알아낼 수 있습니다. 프로그램을 실행하고 해당 경로에 가보면 실제로 자동 저장되는 버전의 문서파일을 확인하실 수 있습니다. 우리의 샘플 프로그램은 10초마다 착실하게 문서를 잘 저장하고 있네요 :) 임시 파일 저장 경로의 변경은 CDataRecoveryHandler::SetAutosavePath 함수로 가능합니다.

 

 이제 예제를 통해 리스타트 매니저 동작을 직접 확인해 봅시다.
이제는 정말로 리스타트 매니저의 놀라운 동작을 우리 눈으로 직접 확인할 시간입니다. 준비는 이미 다 끝났습니다. 단지 리스타트 매니저가 실행될 수 있도록 예제 프로그램 구동 후 60초의 시간을 기다려 주기만 하면 됩니다.


(그림 10) 리스타트 매니저의 동작을 한 눈에 파악하기 위해
 모두 저장된 파일, 반만 저장된 파일, 저장 안 된 파일을 준비.

60초의 시간이 흐르는 동안 저는 위의 (그림 10)과 같이 세 개의 문서 파일을 준비했습니다. 저장한 문서창 하나, 저장한 후 추가로 내용을 추가한 문서창 하나, 그리고 저장하지 않고 내용만 적은 문서창 하나. 곧 리스타트 매니저가 이들을 각각 제대로 복구해 주는지 실제로 확인해 보겠습니다.
그리고 실제로 우리가 설정한 10초의 간격으로 문서 파일이 저장되는지 확인해 보겠습니다. 저는 윈도우 7을 사용하고 있는데, 임시 문서 기본 저장 경로가 C:\Users\(윈도우계정명)\AppData\Local 으로 잡혀있네요. 윈도우 탐색기로 해당 경로를 열어보면 실제 10초 단위로 갱신되고 있는 임시 저장 파일들이 보입니다.


(그림 11) 착실히 저장되고 있는 자동 임시 저장 파일들.

이제 미리 만들어 두었던 리본 UI의 크래시 버튼을 눌러 프로그램을 비정상 종료 시킵니다. 프로그램이 비정상 종료된 후, 자동으로 재시작 되면서 문서를 임시 저장된 버전으로 복구할 것인지를 묻는 대화상자가 출력됩니다.


(그림 12) 크래시 발생후 리스타트 매니저가 자동으로 응용 프로그램을 재시작.


(그림 13) 응용 프로그램 재시작 후, 임시 저장된 버전의 문서를 복구할 것인지 묻는 대화상자 출력

그림 13의 문서 복구 여부 확인창이 떴을 때 ‘자동 저장 문서 복구’ 항목을 선택하면, 크래시가 나기 전에 저장해 두었던 문서의 텍스트들이 그대로 모두 복구되는 것을 확인할 수 있습니다. 임시 버전으로 복구된 파일들은 파일명이 출력되는 탭 부분에 [복구됨] 이라는 표기가 붙어서 출력됩니다.


(그림 14) 리스타트 매니저의 '자동 복구 기능'으로 복구된 문서파일의 모습.

 

Outro
이번 포스팅 에서는 리스타트 매니저를 직접 동작시키고 확인해 볼 수 있는 예제 작성 방법과 몇 가지 주의 사항을 짚어 보았습니다. 첨부된 이미지들이 많아서 괜히 길어 보이긴 하지만, 어려운 내용은 없었으리라 생각합니다.
이것으로 3회에 걸친 리스타트 매니저 강좌를 마치고, 다음에는 MFC 10.0에서 새롭게 선보이는 CTaskDialog 클래스에 대한 강좌를 가지고 다시 찾아 뵐 예정입니다.
그럼 다음 강좌에서 뵙도록 할게요.
감사합니다 ^^*

 

Reference