현재 윈도우가 x86인지 x64인지 확인하는 방법

링크 : http://msdn.microsoft.com/en-us/library/windows/desktop/ms684139(v=vs.85).aspx

 

#include <windows.h>
#include <tchar.h>

typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL);

LPFN_ISWOW64PROCESS fnIsWow64Process;

BOOL IsWow64()
{
    BOOL bIsWow64 = FALSE;

    //IsWow64Process is not available on all supported versions of Windows.
    //Use GetModuleHandle to get a handle to the DLL that contains the function
    //and GetProcAddress to get a pointer to the function if available.

    fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress(
        GetModuleHandle(TEXT("kernel32")),"IsWow64Process");

    if(NULL != fnIsWow64Process)
    {
        if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64))
        {
            //handle error
        }
    }
    return bIsWow64;
}

int main( void )
{
    if(IsWow64())
        _tprintf(TEXT("The process is running under WOW64.\n"));
    else
        _tprintf(TEXT("The process is not running under WOW64.\n"));

    return 0;
}

posted by 뚱2

링크 : http://support.microsoft.com/kb/190351/ko 

한마디로 자식 프로세스를 생성해서 Input, Output으로 주고 받는 방법이다.

 

참고 : http://www.tipssoft.com/bulletin/board.php?bo_table=update&wr_id=941

팁스소프트의 좋은 예제

posted by 뚱2

[VC++] DevCon 사용법

C/C++/VC++ / MFC 2013. 5. 31. 15:01

링크 : http://ct_starter.blog.me/130163495738 

windows 7 x64 용 : http://www.diskool.com/pcman_tip/1225619

 

devcon 옵션 : http://msdn.microsoft.com/en-us/library/windows/hardware/ff544746(v=vs.85).aspx#ddk_example_3_find_hardware_ids_by_using_a_class_tools

 

 

 

* 네트웍 장비 검사

#네트웍 장비 검사

devcon listclass net

 

 

posted by 뚱2

링크  1 : http://bobmoore.mvps.org/Win32/w32tip26.htm

링크  2 : http://november11.tistory.com/55

 

링크 1은 프레임워크를 조금 수정해서 모달리스로 만드는 방법이고

링크 2는 WindowPosChanging을 이용한 방법이다.

둘다 테스트 해본결과 링크 2번이 구조도 덜 바꾸고 편했다.

posted by 뚱2

링크 : http://www.codeproject.com/Articles/14500/Detecting-Hardware-Insertion-and-or-Removal

링크 : http://msdn.microsoft.com/en-us/library/windows/desktop/aa363432(v=vs.85).aspx

'C/C++ > VC++ / MFC' 카테고리의 다른 글

[VC++] DevCon 사용법  (0) 2013.05.31
[MFC] Dialog 베이스로 시작시 숨기기  (0) 2013.05.31
[ATL] ATL Com Programming  (0) 2013.05.24
[COM] Com Event Handling  (0) 2013.05.24
[VC++] IOCP 프로그래밍  (1) 2013.05.21
posted by 뚱2

링크 : http://valley.egloos.com/viewer/?url=http://guenang.egloos.com/2341347 

posted by 뚱2

출처 : http://www.codeproject.com/Articles/309781/Advanced-Debugging-in-Visual-Studio

 

Download AdvancedDebugging.zip - 28.2 KB (Visual Studio 2010 Solution)

Introduction

Many of us developers do not look beyond the basic F9, F10, F11, F5 and Watch windows while debugging in Visual Studio. Due to this we end up wasting hours debugging an issue or simulating a condition which ideally could have been achieved in a matter of minutes if we utilized the rich debugging features available out of the box in Visual Studio.

Advanced debugging tips are scattered all over the web but I thought that a consolidated list would be very useful for developers to embrace and start using the techniques.

Environment

The tips in this article should work in Visual Studio 2008/ 2010. Many of these might still be valid for the next version of Visual Studio.

Tip List

To make going through this article easier, I am breaking it into six different tips which I will present with the help of sample code and screenshots.

1. Magic of "Make Object Id"

2. Attach to process - using macro

3. Immediate Window
- Calling functions directly
- Setting and Displaying variables

4. Debugging a Windows Service

5. Having fun with breakpoints
- Trace Points
- Condition
- Hit Count
- Filter
- Changing breakpoint location

6. Locals/Auto/ Call Stack

Bonus Tip!
Enable Sound when Breakpoint is hit

1. Magic of “Make Object Id”

Sometimes we want to track an object even after it went out of the scope. We may need this ability to debug an issue which requires us to track the object until it is garbage collected. This ability is provided with the Object Id feature in Visual Studio debugger. Follow the below steps to try it yourself.

  1. Set a Breakpoint on a line of code which uses a variable your want to track as shown below.

image001.png

  1. Run your application in debug mode and let it stop at the Breakpoint.
  2. Right Click on str and click Add Watch.
  3. In your Watch 1 window, right-click the object variable str and choose "Make Object Id" from the context menu.

    image003.png

  4. You will now see 1# appended in the Value column. This is the unique ID given by the debugger to your variable for the current debug session.

    image005.png

  1. We can track the object value using this ID even after str goes out of scope as shown below. Simply put the object id 1# in the watch window to watch its value.

image007.png

  1. If we continue the iteration of for loop str changes its value but 1# remains the same. This tells us that although the previous str object has gone out of scope we can still keep track of its value using the Object Id that we assigned to it.

image009.png

  1. Lastly if you move out of the function then all instances of str would go out of scope and you would no longer be able to track str using the Watch window. It gets grayed out. However the Object Id 1# is still active and you can continue to track its value as you move through other functions.

image011.png

Note: As the name suggests, this works only with reference and not value types. Makes sense as value types will get stored on the stack and will get popped out as soon as the scope ends. So they would ideally not depend the Garbage Collector to get cleaned up.

2. Attach to process – using macro

There are many tasks that we do in Visual Studio that are repetitive and which can be automated using macros. One such example is attaching to process for debugging. Having the ability to debug an existing running process (Ex: Process of a .net console application exe) is a common requirement. The usual way would be using the Attach To Process window from Debug -> Attach To Process in Visual Studio. But this can become cumbersome and irritating if we have to do it again and again to test iterative changes. This is where macros come to our rescue.

1. Create a simple console application with a Main method and a method TestAttachToProcessMacro shown below. Make a call to this method from the Main function.

image013.png

2. Build the console application. This will generate the exe file in the debug folder. Double click and start the application using this exe.

3. The first break point shown in the code above will not be hit (as we are not yet debugging) and you will see the below output in console window.

image015.png

4. We want to debug from the second breakpoint by attaching to this process so, Now we start recording our macro in 5 simple steps –

i. Click Record TemporaryMacro from the Tool -> Macros menu as shown below:

image017.png

ii. Recording is started. Now perform the necessary actions to attach to the process as below:

Click Debug -> Attach to Process

image019.png

In the popup below find your process and click Attach.

image021.png

iii. Stop recording the macro using Tools -> Macros as below:
image023.png

iv. Save the macro using Tools -> Macros as below:

image025.png

v. Upon saving, the macro will appear in the Macro Explorer. I have named it AttachToMyProgram.

image027.png

5. Lastly we can also place a shortcut to this macro on the Debug toolbar to make things even simpler.


i. Go to Tools -> Customize -> Commands and under Toolbar dropdown select Debug as below:

image029.png

ii. Hit the Add Command button and on the below popup select macros under Categories and AttachToMyProgram under commands:

image031.png

iii. Now from under the Modify Selection rename the command as shown below:

image033.png

iv. Now the AttachToMyProgram shortcut show appear in the Debug toolbar as shown below:

image035.png

6. Now close the console application and start again. We will again see the “I am started” message. Now simply hit the AttachToMyProcess shortcut on the Debug bar and press any key in the console application window. There you are! You are in the debug session and the second breakpoint is hit. Now you can easily attach to your process with a click of a button.

image037.png

3. Immediate window

So many times we write a function and wish to debug just that function directly, again and again until it gives the output we need. Many of us have been running the entire application in effort to reach that function every time we debug. Well, that’s unnecessary. This is where the Immediate window comes is handy. You can open it using the keyboard shortcut Ctrl + Alt + I.

And this is how it works:

Calling functions directly

Let us try to call the below function directly from the Immediate window:

image039.png

We can call this function from Immediate window directly as below:

image041.png

Upon hitting enter in Immediate window, the breakpoint in the TestImmediateWindow1() function is hit without you having to debug the entire application.

image043.png

On proceeding you get the output in the Immediate window too as below:

image045.png

You can play around with the _test variable by changing its values and testing the reverse output:

image047.png

Setting & Displaying variables

We may want to pass a variable to the function we call from the Immediate window. Lets take an example of a function below:

image051.png

Using commands in the Immediate window as shown below we can declare, set and pass a variable to our function.

image049.png

Below is yet another example to call a function passing a complex object type like object of class Employee.

image055.jpg

Immediate window commands to test the function:

image057.jpg

There is much more you can do with the Immediate window but I leave it up to you to explore more if interested.

4. Debugging a Windows Service

Debugging the windows service can become a daunting task if you are not aware about this tip. You would build and deploy the service and start it. Then from Visual Studio you would use Attach to Process to start debugging. Even then if you need to debug what happens in the OnStart method, then you would have to do a Thread.Sleep() or something so that the OnStart method waits for you while you attach to the process. We can avoid all the pain by this simple tip.

Step 1: Set the Output type of the Windows Service to Console Application:

image002.png

Step 2 : Next get rid of the Program.cs file and instead paste the below code in the Service file which inherits from ServiceBase. That’s it. Now you can run the windows service in debug and it will run as a console application. Or you can deploy as usual and it will function as a windows service.

partial class MyService : ServiceBase
    {
        public static void Main(string[] args)
        {
            /*EDIT: 18th January 2012
             * As per suggestion from Blaise in his commments I have added the Debugger.Launch condition so that you 
             * can attach a debugger to the published service when it is about to start.
             * Note: Remember to either remove this code before you release to production or 
             * do not release to production only in the 'Release' configuration.
             * Ref: http://weblogs.asp.net/paulballard/archive/2005/07/12/419175.aspx
             */

            #if DEBUG
                    System.Diagnostics.Debugger.Launch();
            #endif

            /*EDIT: 18 January 2012
            Below is Psuedo code for an alternative way suggested by RudolfHenning in his comment. However, I find 
            Debugger.Launch() a better option.
                        
            #if DEBUG
                //The following code is simply to ease attaching the debugger to the service to debug the startup routine
                DateTime startTime = DateTime.Now;
                // Waiting until debugger is attached
                while ((!Debugger.IsAttached) && ((TimeSpan)DateTime.Now.Subtract(startTime)).TotalSeconds < 20)  
                {
                    RequestAdditionalTime(1000);  // Prevents the service from timeout
                    Thread.Sleep(1000);           // Gives you time to attach the debugger
                }
                // increase as needed to prevent timeouts
                RequestAdditionalTime(5000);     // for Debugging the OnStart method <- set breakpoint here,
            #endif

            */

            var service = new MyService();

            /* The flag Environment.UserInteractive is the key here. If its true means the app is running 
             * in debug mode. So manually call the functions OnStart() and OnStop() else use the ServiceBase 
             * class to handle it.*/
            if (Environment.UserInteractive)
            {
                service.OnStart(args);
                Console.WriteLine("Press any key to stop the service..");
                Console.Read();
                service.OnStop();
            }
            else
            {
                ServiceBase.Run(service);
            }
        }

        public MyService()
        {
            InitializeComponent();
        }
        protected override void OnStart(string[] args)
        {
        }
        protected override void OnStop()
        {
        }
    } 

5. Having fun with breakpoints

You can use below variations of breakpoints in isolation or combine them together and enjoy the cocktail!

Trace Points (When Hit..)

Sometimes we want to observe the value of one or more variables each time a particular line of code is executed. Doing this by setting a normal breakpoint can be very time consuming. So we usually use Console.WriteLine to print the value. Instead if it’s a temporary check using TracePoints is better. It serves the same purpose as a Console.WriteLine would. The advantage is that you don’t have to disturb the code by adding the your Console.WriteLine and risk forgetting to remove it when done. Better still, this way you can utilize other features of breakpoint by superimposing different conditions of breakpoint on a TracePoint.

Lets see a trace point in action.

Set a break point at call to ReverseString function as shown below.

image010.png

Then right click and click "When Hit.." then check Print a message. In test box copy "Value of reverseMe = {reverseMe}". Keep "Continue Execution" checked and click OK.

image077.png

image004.jpg

The breakpoint will get converted into a TracePoint (diamond shaped) as shown below.

image079.jpg

Now whenever the breakpoint is hit, it does not break in the code but continues execution and you will see the value of reverseMe variable at each hit as below in the output window:

image080.png

Condition

Condition breakpoints can be used to avoid having to write extra if/ else conditions in our code if we want a breakpoint to be hit only for a particular value.

Right click the tracepoint we set above and click Condition from under Breakpoints. Then type "i==45" in condition text box & click OK. (IMP: NEVER use single "=" in condition. Always use "==".)

Now the breakpoint will be activated only when i = 45; so the tracepoint should print only “Live45”.

image073.jpg

image074.jpg

Hit Count

Hit count can be used to find out how many times a breakpoint is hit. Also you can choose when you want break at the breakpoint.Change the Condition breakpoint to i > 45. Then Right Click -> Breakpoint -> Hit Count. Select "break when hit count is a multiple of " and type 5 as the value. Click OK.

image075.png

Now the breakpoint will be hit after every 5 iterations. Notice below output is effect of both the Condition and the Hit Count breakpoint.

image078.png

The hit count shown below says that the breakpoint was hit 54 times between from i = 46 to i = 99, but it broke the execution only after every 5 iterations.

image081.png

Filter

Useful for multi threaded applications. If multiple threads are calling the same function, you can use filter to specify on which thread should the breakpoint be hit.

Right Click -> Breakpoint -> Filter

image012.jpg

Changing Breakpoint Location

If you wish to move the breakpoint to a different line then use this option.

image082.jpg

6. Locals/ Autos/ Call Stack

The following three windows can come in handy while debugging. You can access them after you start debugging. Go to Debug -> Windows in the Visual Studio menu bar.

AUTOS: The Autos window displays variables used in the current statement and the previous statement. Helps you concentrate only on the variables being used in and around the current line.

(For Visual Basic.Net, it displays variables in the current statement and three statements on either side of the current statement.)

LOCALS: The Locals window displays variables local to the current context. You can observe values of local variables in a function here. Note that class level variable will not be visible under locals.

CALL STACK: The Call Stack displays the entire tree of function calls leading to the current function call. Can help you trace back the culprit!

Bonus Tip!

Enable Sound when Breakpoint is hit

1. Go to Control Panel -> Sounds and Audio Devices (Windows XP). Its Control Panel -> Sound in Windows 7.

2. Find and Select “Breakpoint Hit” under Program events in Sounds tab. (see pic below)

3. Choose the sound of your choice and click OK.

4. Now when a breakpoint is hit, you will hear the sound!

image006.png

History

  • 18th January 2012: Incorporated Blaise's suggestion to add Debugger.Launch() option in Tip no. 4.
  • 23rd January 2012: Added a note at the end of tip 1 - Make Object Id; as per suggestion by Shivprasad Koirala.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

posted by 뚱2

[ATL] ATL Com Programming

C/C++/VC++ / MFC 2013. 5. 24. 17:31

너무 정리가 잘되어 있는곳 : http://codecrue.egloos.com/category/ATL%2FActiveX

 

posted by 뚱2

[COM] Com Event Handling

C/C++/VC++ / MFC 2013. 5. 24. 15:04

http://www.codeproject.com/Articles/9014/Understanding-COM-Event-Handling

http://www.codeproject.com/Articles/3541/COM-Connection-Points

XMLHttpRequest onStatusChange Event Handling : http://www.ookii.org/Blog/using_ixmlhttprequestonreadystatechange_from_c

 

XMLHttpEventSample.zip

 

'C/C++ > VC++ / MFC' 카테고리의 다른 글

[VC++] Detecting Hardware Insertion and/or Removal  (0) 2013.05.30
[ATL] ATL Com Programming  (0) 2013.05.24
[VC++] IOCP 프로그래밍  (1) 2013.05.21
[VC++] Visual Studio Predefine Macro  (0) 2013.05.01
[VC++] Tray Icon Animation  (0) 2013.04.26
posted by 뚱2

[C#] Linq for xml tutorial

.Net/C# 2013. 5. 23. 16:29

링크 : http://www.dotnetcurry.com/ShowArticle.aspx?ID=564 

'.Net > C#' 카테고리의 다른 글

[C#] ref, out의 차이  (0) 2013.06.12
[C#] XML Serialize Tutorial  (0) 2013.05.08
[C#] Mutex를 통한 다중 인스턴스 실행방지  (0) 2013.02.08
[C#] Visual Studio TODO 만들기  (0) 2013.01.30
[C#] Form close와 Dispose  (0) 2013.01.28
posted by 뚱2

[VC++] IOCP 프로그래밍

C/C++/VC++ / MFC 2013. 5. 21. 17:50

참고소스 : http://blog.daum.net/aswip/2580198


출처 : http://blog.naver.com/sonmg?Redirect=Log&logNo=20000462755


IOCP- 윈속 프로그래밍

 2002년 08월 21일 | 03시 05분

출처 :  http://www.winapiprogramming.com/

이 글은 제가 얼마 전에 프로그램 세계에 연재했던 글입니다. 다음 3회 연재 글 중에서 마지막 회에 해당합니다. 1,2회에 해당하는 글은 이 책에서 찾아보실 수 있습니다.

  • 2002/2 - 1. 윈속이란 ? - 간단한 에코우 서버/클라이언트 프로그램 만들기
  • 2002/4 - 2. 멀티스레드 윈속 서버 프로그램으로 업그레이드 하기
  • 2002/5 - 3. IOCP 윈속 서버 프로그램

    이번 회에는 지난 회에서 멀티스레드 윈속 서버 프로그램을 IOCP(Input Output Completion Port)를 이용하는 것으로 변경해보도록 하자. 전에 서버 프로그래밍에 관한 필자의 연재기사에서 수차례 IOCP를 언급한 바 있었는데 이제서야 설명을 하게 되었다.

    지난 회에 만들어본 멀티스레드 윈속 서버 프로그램의 문제점은 사용자의 수가 많아지면 스레드의 동적 생성과 스레드간의 잦은 컨텍스트 스위칭으로 인한 오버헤드가 크다는 점이었다. 이러한 점을 극복하기 위해 도입된 것이 바로 IOCP이다. 방금 설명한 것처럼 이는 멀티스레드 프로그래밍에서 유용하게 사용할 수 있으며 그 중에서도 소켓이나 파일, 메일슬롯, 파이프와 같은 입출력 관련 프로그램에서 유용하게 사용할 수 있다.

    필자는 IOCP를 파일 I/O가 많은 응용프로그램과 네트웍 I/O가 많은 윈속 프로그램에서 사용해봤는데 그냥 단순한 멀티스레드 프로그램을 작성하는 것보다 괜찮은 성능을 가짐을 알 수 있었다. 부하가 그리 크지 않다면 IOCP를 사용하나 사용하지 않으나 성능상에 큰 차이가 없다. 하지만 부하가 클 경우에는 (예를 들어 윈속 서버 프로그램이라면 현재 접속 사용자수가 많을 경우에는) 상당한 차이를 가져온다는 점을 잘 새겨두기 바란다. 하지만 파일 I/O가 아주 빈번한 응용프로그램에서는 IOCP를 사용한 멀티스레드 프로그램이나 그냥 멀티스레드 프로그램이나 성능에 있어 별 차이가 없다. 그 이유는 스레드로 인한 오버헤드보다 파일 I/O 자체로 인한 오버헤드가 더 크기 때문이었다.

    단, IOCP가 무슨 마법처럼 시스템이 가진 하드웨어 제약조건 이상으로 많은 수의 사용자를 처리할 수 있도록 해주거나 하는 것은 아니란 점을 명심하기 바란다. 부하가 많은 시점에 그냥 멀티스레드 프로그래밍을 하는 것보다 더 좋은 성능을 보일 뿐이다. 획기적으로 좋은 성능을 보이거나 하는 마술과 같은 것은 아니란 것이다. 또한 IOCP는 NT 4.0, 2000, XP에서만 사용가능하다는 점도 알아두기 바란다.

    먼저 IOCP라는 것이 그리 이해하기 쉬운 편은 아니고 이해해서 사용하기는 더욱 어렵다는 점을 밝히고 싶다. 겁먹으라고 하는 소리는 아니고 잘 이해가 안되어도 필자 탓을 하거나 자신의 머리탓(?)을 하지말고 한번 더 읽어보라는 의미에서 하는 말이다. 참고문헌 2>와 3>에 필자가 처음 IOCP를 공부할 때 봤던 책과 인터넷 기사를 적어두었다. 참고하기 바란다. 또, 마이크로소프트 플랫폼 SDK의 예제 프로그램 중에 보면 윈속에서 IOCP를 어떻게 사용할 수 있는지 보여주는 간단한 예가 있다. 참고문헌 4에 적었다. 사실 이번 연재에서 작성한 서버 예제 프로그램도 이 것을 바탕으로 작성되었다. 클라이언트 예제 프로그램은 사실 지난 회와 동일하다. 그렇기 때문에 클라이언트 프로그램에 대해서는 다루지 않겠다.

    1. IOCP의 필요성 ?

    IOCP가 왜 필요한지 알아보려면 기존 멀티스레드 프로그래밍의 제한점을 먼저 이해해야 한다.

    많은 수의 스레드 생성으로 인한 오버헤드 : 확장성의 제한

    동시에 여러 사용자를 처리할 수 없는 프로그램을 서버 프로그램이라고 부를 수 없을 것이다. 서버 프로그램이 되려면 동시에 여러 사용자로부터의 요구를 처리할 수 있어야 하고 그렇게 하기 위해서 스레드를 사용할 수 밖에 없다. 결론적으로 진정한 다중 사용자용 서버 프로그램을 짜본 사람이라면 동시 사용자 처리를 위해 누구나 스레드를 사용하고 있을 것이다. 대부분의 경우 지난 회에 살펴본 예제 프로그램처럼 현재 접속 중인 사용자의 수만큼 스레드를 만드는 방식을 취하게 된다. 즉 사용자마다 그 요구를 처리하기 위한 전담 스레드를 생성하는 것이다.

    하지만 이 방식의 문제점 중의 하나는 바로 현재 접속 중인 사용자의 수가 늘어날 경우에 발생한다. 스레드의 생성은 당연히 자원의 사용을 가져온다. 어느 수 이상으로 스레드가 생성되면 프로그램의 성능이 오히려 전체적으로 저하된다. 이유는 너무 많은 스레드가 생성되면 아무래도 그로 인해 자원이 많이 필요하게 되고 또 그 많은 스레드들간의 컨텍스트 스위칭으로 인해 실제 CPU가 어떤 일을 하는 시간보다 컨텍스트 스위칭하는데 상당한 시간을 보내게 되기 때문이다. 예를 들어 CPU의 수보다 스레드의 수가 많다면 사실 스레드간의 컨텍스트 스위칭으로 인한 오버헤드가 있다고 볼 수 있다. (사실 CPU의 수만큼 스레드의 수가 존재하는 것이 이상적이지만 이는 사실상 불가능한 일이다. 이는 만들고자 하는 응용프로그램의 특성에 따라 굉장히 달라질 수 있다.)

    IOCP는 이러한 단점을 극복하기 위해 하나의 스레드가 하나 이상의 사용자로부터의 요구를 처리할 수 있도록 해준다. 그렇다고 하나의 스레드만을 생성하는 것은 아니다. 여러 개의 스레드를 생성하지만 한 스레드가 한 사용자만을 전담하는 방식은 아니라는 것이다. 즉, 실행되는 스레드의 수를 적게 해서 이로 인한 컨텍스트 스위칭의 수를 줄이는 것이다. 이것이 가능하려면 이제 뒤에서 살펴볼 것처럼 프로그램내에서 I/O시에 비동기 방식을 사용해야 한다.

    비동기 I/O는 서버 프로그래밍의 필수

    서버 프로그램에서 성능 향상을 위해서 사용할 수 있는 다른 하나의 테크닉은 비동기(Asynchronous) I/O를 사용하는 것이다. 이를 이용하면 동시에 여러 개의 I/O 작업을 수행할 수 있는데 이는 어디까지나 작업의 시작만 비동기로 가능하다는 것이지 작업이 끝나는 부분은 즉, I/O 결과를 받는 부분은 동기화가 되어야 한다는 것이다. 만일 비동기 I/O의 결과를 그냥 무시해도 좋은 프로그램이라면 또다른 이야기가 되겠지만 아마 대부분의 프로그램에서는 비동기 I/O를 수행하고 그 결과를 살펴봐야 할 것이다.

    비동기 I/O에는 여러가지 방식이 존재한다. 간략히 참고 1에 윈도우에서 지원되는 비동기 I/O 방식을 나열해 보았다. 당연한 이야기이지만 이러한 비동기 I/O 방식은 특히 시간이 오래 걸리는 작업을 할 때 적합한 방식이다. 이러한 비동기 I/O 방식은 IOCP와 결합되었을 때 최적의 성능과 확장성을 자랑한다. 다시 정리해서 말하자면 비동기 I/O의 성능은 I/O가 끝났을 때 그 결과를 어떻게 확인하느냐에 달려 있는데 IOCP는 이러한 비동기 I/O를 가장 효율적으로 사용할 수 있게 해준다.


    참고 1. 윈도우의 비동기 I/O

    윈도우에서는 다양한 방식의 비동기 I/O를 제공한다 (사실 너무 다양한 방법을 제공한다.) 여기서는 간략히 언급하기로 하겠다. 다음에 기회가 닿으면 파일 I/O 관련 연재 기사를 다뤄볼 생각인데 그 때 자세히 언급하기로 하겠다.

    1> 오버랩드 I/O를 사용하기.

    예로 파일 I/O를 들어보자. 파일을 오픈할 때 CreateFile API를 사용하는데 이 때FILE_FLAG_OVERLAPPED를 인자로 주면 오버랩드 I/O를 수행할 수 있다. ReadFile과 WriteFile을 사용하여 I/O를 수행하게 되는데 이 함수들은 실행이 끝날 때까지 기다리지 않고 바로 리턴한다(비동기 I/O니까 당연한 이야기이지만). 이 때 마지막 인자로 OVERLAPPED 구조체를 사용하는데 여기에 이벤트(지난 회에 설명한 바 있다)를 지정하도록 되어있다. 작업이 끝나면 이 이벤트로 시그널이 가게 된다. 이벤트를 사용하는 대신에 함수의 실행이 끝났는지를 검사하기 위해 GetOverlappedResult 함수를 호출할 수도 있다. 참고로 ReadFile이나 WriteFile과 같은 함수는 꼭 파일 I/O에 사용되는 것이 아니란 점도 알아두기 바란다. 소켓에서 데이터를 읽고 쓰는데도 사용할 수 있다.

    2> 콜백 함수 사용하기

    기본적으로는 1<의 방식과 갖다. 다만 이벤트를 사용하는 대신에 콜백 함수를 지정해서 작업이 끝나면 그 함수를 호출하도록 하는 것이다. 이때는 ReadFile, WriteFile과 같은 함수 대신에 ReadFileEx와 WriteFileEx와 같은 함수를 사용해야 한다. 이 함수들은 인자 중의 하나로 콜백 함수의 주소를 받아들이도록 되어있다.

    3> IOCP 사용하기

    사실 IOCP를 비동기 I/O 작업 방식이라고도 할 수 있는데 이에 대해서는 이 기사의 뒷부분에서 자세히 살펴볼 것이다.


    지금까지 살펴본 것과 같은 기존의 멀티스레드 서버 프로그래밍의 문제점을 해결하기 위해 만들어진 것이 바로 IOCP이다. 기본적으로 IOCP는 비동기 I/O 작업을 지원하면서 적은 수의 스레드로 최대한의 요청을 처리하기 위한 방법이란 점이라고 이해하면 된다. 너무 많은 스레드가 동시에 동작함으로 인한 문제를 해결하면서 비동기 I/O 작업시 결과를 체크해야 하는 문제를 해결함으로써 서버 프로그램의 성능을 극대화하는 것이다.

    2. IOCP란 ?

    IOCP란 특정 입출력 객체(예를 들면 파일, 소켓, 메일 슬롯 등등)와 관련된 일종의 I/O 작업 결과 큐라고 생각할 수 있다. 좀더 자세히 설명하자면 먼저 IOCP 객체가 별도로 생성되어야 한다. 그 다음에 이 객체와 입출력 객체 중의 하나가 연결되어야 한다. 다음으로 이 입출력 객체에 비동기 I/O 작업이 수행되면 운영체제가 이 큐에 그 비동기I/O의 결과를 집어넣게 된다.

    또한 이 큐는 하나 이상의 스레드와 연관지어지게 된다 (스레드의 수는 비동기 I/O의 특성에 따라 달라지게 된다. 만일 I/O가 오래 걸리는 것이라면 스레드의 수는 적어도 관계없다. 하지만 I/O가 시간이 아주 조금밖에 안 걸리는 것이라면 스레드의 수는 많아야 한다). 운영체제는 큐에 결과가 있고 관련 스레드들 중에서 놀고 있는 놈이 있으면 그 스레드가 결과를 받아서 다음 작업을 수행할 수 있게 해준다. 즉, IOCP와 관련되어 동작할 수 있는 스레드를 미리 여러 개 만들어 놓고 이 중에서 필요에 따라 놀고 있는 것을 가져다 큐에서 비동기 I/O 결과를 읽어가도록 하는 것이다. 참고로 한 IOCP 객체는 동시에 여러 입출력 객체와 연관지어질 수 있다.

    자 이러한 과정을 코드를 통해 좀더 자세히 살펴보자. 그림 1을 참고하기 바란다. 본 기사의 서버 예제 프로그램의 코드를 바탕으로 설명하겠다.

    < 그림 1. IOCP의 동작 >

    1> IOCP의 생성

    먼저 첫번째 절차는 IOCP를 생성하는 것이다. 이는 CreateIoCompletionPort 함수를 통해 가능하다. 이 같은 함수를 이용해 입출력 객체와 IOCP를 연관짓는데 사용할 수 있다. 다음은 IOCP를 일단 생성하는 예(CreateIoCompletionPort 함수의 첫번째 인자로 INVALID_HANDLE_VALUE를 지정해야 한다. 이 함수에 대한 보다 상세한 설명은 참고 2를 보기 바란다)이다. 생성의 결과는 HANDLE로 리턴된다.

      g_hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
      if (NULL == g_hIOCP) 
      {
        printf("CreateIoCompletionPort failed: %d\n", GetLastError());
        CleanUp();
      }
    

    2> IOCP 큐에서 결과를 읽어들일 스레드 생성

    앞서 이야기한 것처럼 IOCP와 연관된 입출력 객체에 비동기 I/O를 수행하면 그 결과가 IOCP 큐에 쌓인다고 하였다. 이 큐에서 결과를 읽어들이는 일을 수행하는 스레드를 만들어야 한다. 다른 스레드 생성과 특별히 다를 것은 없다. 단 생성할 스레드의 수는 CPU수 X 2로 되어있다. 이는 마이크로소프트에서 권장하는 방식이다. 응용프로그램에 따라 이것이 적당할 수도 있고 훨씬 더 많은 스레드가 필요할 수도 있다. 이를 위해서 GetSystemInfo라는 함수를 이용해서 현재 시스템의 CPU수를 알아내는 코드가 들어있다.

    #define MAX_WORKER_THREAD	16
    
    DWORD g_dwThreadCount;
    unsigned int g_hThreads[MAX_NUMBER_OF_THREADS];
    
    …
    SYSTEM_INFO         systemInfo;
     DWORD dwThreadId;
    
    GetSystemInfo(&systemInfo);
    g_dwThreadCount = systemInfo.dwNumberOfProcessors * 2;
      …
    for (DWORD dwThread = 0;dwThread < g_dwThreadCount; dwThread++)
    {
      g_hThreads[dwThread] = _beginthreadex(NULL, 0, EchoThread, 
                                 g_hIOCP, 0, &dwThreadId);
      If (g_hThreads[dwThread] == NULL)
      {
        printf(“%d번째 스레드 생성에 실패했습니다.\n”, dwThread);
      }
    }
    

    위에서 볼 수 있는 것처럼 스레드의 생성에는 _beginthreadex 함수를 사용하였다. 스레드 함수는 EchoThread이며 스레드 함수의 인자로는 IOCP 핸들을 넘긴다. EchoThread 함수의 자세한 내용은 5>에서 살펴볼 것이다.

    3> IOCP와 입출력 객체의 연결

    다음은 이 IOCP와 입출력 객체를 연결하는 부분이다. 입출력 객체는 반드시 비동기 I/O 모드로 오픈되어야 한다. 연결된 객체에 대한 비동기 오버랩드 I/O 결과가 이 IOCP 큐에 들어간다. 예를 들어 소켓과 IOCP를 연결하는 간단한 예를 보면 다음과 같다.

    SOCKET sh;
    
    sh = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_IP, NULL, 
                 0, WSA_FLAG_OVERLAPPED);…
    if (sh != INVALID_SOCKET)
    {
    	CreateIoCompletionPort((HANDLE)sh, g_hIOCP, (ULONG_PTR)0, 0);
    

    먼저 소켓을 생성할 때 socket 함수를 사용한 것이 아니라 WSASocket 함수를 사용하였고 마지막 인자로 WSA_FLAG_OVERLAPPED가 지정되었다. 그 다음에 앞에서와 같은 CreateIoCompletionPort 함수를 사용하고 그 첫번째 인자로 소켓의 값을 지정하면 된다. 즉, CreateIoCompletionPort 함수는 IOCP의 생성에도 사용되고 생성된 IOCP와 입출력 객체를 연결하는데도 사용된다. 참고 2에서 CreateIoCompletionPort 함수의 세 번째 인자 설명을 보면 알 수 있겠지만 세 번째 인자가 가장 중요한 역할을 한다.

    앞서 잠깐 언급했던 것처럼 IOCP는 여러 개의 입출력객체와 동시에 연관지어질 수 있다. 예를 들어 여러 개의 소켓이 하나의 IOCP와 연관될 수 있다. 즉 그러한 소켓들에 대해 이루어지는 비동기 작업의 결과는 모두 그 하나의 IOCP 큐로 들어간다는 것이다. 그러기 때문에 IOCP 큐에서 비동기 작업 결과를 읽어들일 때 이 결과가 도대체 어느 입출력 객체로부터 온 것인지를 구분할 수 있는 방법이 있어야 한다. 이 때 CreateIoCompletionPort 함수의 세 번째 인자로 지정했던 값이 구분하는 역할을 담당한다. 뒤에서 살펴보겠지만 IOCP 큐에서 결과를 읽어들일 때 사용하는 함수는GetQueuedCompletionStatus라는 것이다. 이 함수의 세 번째 인자로 앞서 CreateIoCompletionPort 함수에서 지정했던 값이 넘어오게 되어 있다. 예제 프로그램에서는 입출력 객체마다 다음과 같은 구조체를 생성하고 이를 IOCP 객체와 연관지을 때 세번째 인자로 지정할 것이다.

    // IOCP와 연관되는 소켓마다 할당되는 구조체
    typedef struct _PER_SOCKET_CONTEXT 
    {
        SOCKET                 Socket;
        PPER_IO_CONTEXT       pIOContext;  
    } PER_SOCKET_CONTEXT, *PPER_SOCKET_CONTEXT;
    

    위에서 Socket은 클라이언트가 하나 연결될 때마다 부여되는 소켓이다. pIOContext는 이 소켓과의 입출력 작업에 사용되는 메모리 버퍼와 각종 구조체를 모아둔 구조체로 이 소켓내에서 벌어지는 입출력 작업의 상태를 나타낸다고 생각하면 된다. 다음과 같이 정의되어 있다.

    #define MAX_BUFF_SIZE       8192
    // 소켓에 대한 입출력 작업에 사용되는 구조체
    typedef struct _PER_IO_CONTEXT 
    {
        WSAOVERLAPPED        Overlapped;
        char                     Buffer[MAX_BUFF_SIZE];
        WSABUF                 wsabuf;
        int                       nTotalBytes;
        int                       nSentBytes;
        IO_OPERATION           IOOperation;
    } PER_IO_CONTEXT, *PPER_IO_CONTEXT;
    

    먼저 첫번째 필드인 Overlapped는 사실 ReadFile, WriteFile과 같은 함수를 이용해서 수행하는 비동기 I/O에서 사용하는 OVERLAPPED 구조체와 동일한 것이다. typedef로 이름만 바꾸었을 뿐이다. 뒤에서 WSARecv와 WSASend를 이용해서 비동기 I/O를 해볼 텐데 그 때 이 필드가 사용된다. 그 함수들을 호출할 때 로컬 변수로 사용하면 안 될까 생각할 수도 있는데 이 변수는 작업이 끝날 때까지 접근이 가능해야 하기 때문에 이렇게 글로발하게 별도로 잡아두는 것이다. (로컬 변수로 잡고 그걸 인자로 비동기 함수를 호출하면 그 변수가 선언한 블럭을 벗어날 경우 그 로컬 변수는 더 이상 유효하지 않다. 이런 문제를 해결하기 위함이다)

    사실 이 구조체는 의도적으로 WSAOVERLAPPED 타입의 필드로부터 시작한다. 비동기 I/O 작업에 사용되는 WSASend, WSARecv함수의 경우 인자 중에 WSAOVERLAPPED 타입의 변수를 받아들이는 인자가 있다. 또한 비동기 I/O가 끝나고 그 결과를 IOCP 큐에서 읽어들일 때 앞서 사용했던WSAOVERLAPPED 타입의 변수를 그대로 받아볼 수 있다.

    사실 Overlapped가 이 구조체의 첫 번째 필드이기 때문에 이 필드의 주소나 이 구조체의 주소나 동일하다. WSASend와 WSARecv를 이용해 비동기 I/O를 개시할때 이 구조체의 Overlapped 필드의 주소를 넘기면 사실 이것이PER_IO_CONTEXT 타입 변수의 주소를 넘긴 것이나 다름없다. 그렇게 해서 비동기 I/O의 결과를 큐에서 꺼낼 때 현재 작업의 상태를 알 수 있는 것이다. 앞서 이야기한 것처럼 PER_IO_CONTEXT 구조체는 현재 비동기 I/O 작업의 상태를 나타낸다.

    두 번째 필드인 Buffer는 읽기/쓰기 작업을 할때 사용할 메모리 영역이다. 세 번째 필드인 wsabuf는 읽기/쓰기 작업시 데이터의 시작 포인터와 데이터 크기를 지정하는데 사용되는 구조체이다. WSASend와 WSARecv 함수의 인자로 필요하다. 네 번째 인자인 nTotalBytes는 쓰기 작업시 전송해야할 데이터의 양을 나타낸다. 다섯 번째 인자인 nSendBytes는 지금까지 전송된 데이터의 양을 나타낸다. 마지막 인자인 IOOperation은 다음과 같이 정의된 열거자로서 현재 소켓에 대해 진행 중인 작업의 종류를 나타낸다.

    typedef enum _IO_OPERATION 
    {
        ClientIoRead, // 읽기 작업 진행 중
        ClientIoWrite  // 쓰기 작업 진행 중
    } IO_OPERATION, *PIO_OPERATION;
    

    이제 이를 바탕으로 예제 프로그램의 코드를 살펴보자. 다음에서 볼 수 있는 것처럼 클라이언트로부터의 요청이 들어오기를 대기하다가 요청이 들어오면 그로 인해 생성되는 소켓을 인자로 위의 작업을 수행하는UpdateCompletionPort라는 함수를 별도로 만들었다.

      SOCKET                 sdAccept = INVALID_SOCKET;
      PPER_SOCKET_CONTEXT lpPerSocketContext = NULL;
    
      while (g_bEndServer == FALSE) 
      {
        // 클라이언트가 들어오기를 대기한다.
        sdAccept = WSAAccept(g_sdListen, NULL, NULL, NULL, 0);
        if (SOCKET_ERROR == sdAccept) 
        {
          printf("WSAAccept: %d\n", WSAGetLastError());
          CleanUp();
        }
        printf("클라이언트가 하나 들어왔습니다\n.");
        // 만들어진 sdAccept 소켓에 앞서본 PER_SOCKET_CONTEXT 구조체를 할당한다.
        // 그리고나서 이를 IOCP 객체와 연결한다. 두 번째 인자로는 이제 일어날 작업의     
        // 종류를 명시한다. 에코우 서버이므로 첫 번째 할 작업은 클라이언트로부터 
        // 데이터를 읽는 것이기 때문에 ClientIoRead를 명시한다.
        lpPerSocketContext = UpdateCompletionPort(sdAccept, ClientIoRead, TRUE);
        if (NULL == lpPerSocketContext) 
        {
          CleanUp();
        }
        // …
    

    UpdateCompletionPort 함수의 내용은 다음과 같다. 첫 번째 인자로 지정된 소켓을 바탕으로 앞서본 PER_SOCKET_CONTEXT 구조체를 할당한다. 이것과 소켓을IOCP 객체와 연결한다. 두 번째 인자로는 이제 이 소켓에 일어날 작업의 종류를 명시한다. 에코우 서버이므로 첫 번째 할 작업은 클라이언트로부터 데이터를 읽는 것이기 때문에 ClientIoRead를 명시한다.

    // 첫번째 인자로 명시된 소켓을 IOCP에 연결짓는다.
    PPER_SOCKET_CONTEXT UpdateCompletionPort(SOCKET sd, IO_OPERATION ClientIo)
    {
      PPER_SOCKET_CONTEXT lpPerSocketContext;
    
      // PER_SOCKET_CONTEXT를 할당하는데 CtxtAllocate 함수를 사용한다.
      lpPerSocketContext = CtxtAllocate(sd, ClientIo);
      if (lpPerSocketContext == NULL) 
        return NULL;
    
      // 할당된 구조체와 소켓을 g_hIOCP에 연결한다.
      g_hIOCP = CreateIoCompletionPort((HANDLE)sd, g_hIOCP, 
             (DWORD)lpPerSocketContext, 0);
      if (NULL == g_hIOCP) 
      {
        printf("CreateIoCompletionPort: %d\n", GetLastError());
        if (lpPerSocketContext->pIOContext)
          free(lpPerSocketContext->pIOContext);
        free(lpPerSocketContext);
        return(NULL);
      }
    
      // 이 구조체를 링크드 리스트에 보관한다. 
      CtxtListAddTo(lpPerSocketContext);
      return(lpPerSocketContext);
    }
    

    위의 코드를 보면 PER_SOCKET_CONTEXT 타입의 구조체를 할당하기 위해서 CtxtAllocate라는 함수를 사용하고 있다. 이 함수에 대해서는 뒤에서 다시 설명할 텐데 구조체를 할당하고 초기화하는 일을 담당한다. 그 다음에 CreateIoCompletionPort 함수를 이용해서 이 구조체와 소켓을 IOCP에 연결한다. 마지막으로 이렇게 생성된 구조체를 전체적으로 관리하기 위해서 CtxtListAddTo 함수를 호출한다. 이 함수 역시 뒤에서 다시 설명하겠다.


    참고 2. CreateIoCompletionPort

    이 함수의 원형은 다음과 같다.

    HANDLE CreateIoCompletionPort(HANDLE FileHandle, 
       HANDLE ExistingCompletionPort, 
       ULONG_PTR CompletionKey, 
       DWORD NumberOfConcurrentThreads);
    

    첫 번째 인자인 FileHandle은 IOCP의 대상이 되는 입출력 객체의 핸들이어야 한다. 이 객체는 반드시 오버랩드 I/O 모드로 오픈된 것이어야 한다. 만일 이 인자의 값이 INVALID_FILE_HANDLE로 주어지고 두 번째 인자의 값이 NULL이 되면 이 함수의 리턴값은 새롭게 생성된 IOCP의 핸들이 된다. 이 때 세번째 인자의 값은 무시된다.

    두 번째 인자인 ExistingCompletionPort는 IOCP에 대한 핸들을 지정하기 위해 사용된다. 이 경우 첫번째 인자의 값은 입출력 객체의 핸들이 되어야 하며 이 둘은 연결되게 된다. 그런 경우 이 함수는 두번째 인자로 지정된 IOCP 핸들을 그대로 다시 리턴한다.

    세 번째 인자인 CompletionKey는 IOCP와 연결된 입출력 객체에 특정한 포인터라고 할 수 있다. 한 IOCP에는 여러 개의 입출력 객체가 동시에 연관될 수 있기 때문에 이 값을 통해 어느 객체로부터의 I/O 결과인지를 구분할 수 있다. 따라서 여러 개의 입출력 객체를 사용할 경우 이 인자는 아주 중요한 역할을 하게 된다.

    마지막 인자인NumberOfConcurrentThreads는 이 IOCP에 연관지어지는 스레드의 최대 수를 지정하는데 사용된다. 0을 주면 시스템의 자원이 허용하는 한 스레드가 계속 만들어지게 된다.


    4> 비동기 I/O의 수행

    앞 절차에서 소켓이 제대로 IOCP에 연결이 되고 나면 이제 그 소켓에 대해 비동기 I/O 작업을 수행해야 한다. 소켓의 경우, WSASend와 WSARead를 호출하면 그 결과는 g_hIOCP라는 것이 가리키는 큐안에 쌓이게 된다. 다음과 같은 함수들이 비동기 I/O 결과를 IOCP큐에 넣는다.

  • ReadFile, WriteFile
  • WSASend, WSARecv
  • ConnectNamedPipe
  • DeviceIoControl
  • LockFileEx
  • ReadDirectoryChanges
  • TransactNamedPipe
  • WaitCommEvent

    예제 프로그램에서는 UpdateCompletionPort 함수의 호출이 성공적으로 끝난 후에 클라이언트에서 보내는 데이터를 받기 위해서 WSARead 함수를 한번 호출한다. 참고로 다시 한번 이야기하자면 이 서버 프로그램은 에코우 서버이기 때문에 클라이언트가 보낸 데이터를 그대로 다시 클라이언트로 전송한다.

        lpPerSocketContext = UpdateCompletionPort(sdAccept, ClientIoRead);
        if (NULL == lpPerSocketContext) 
        {
          CleanUp();
          return 1;
        }
    
        // 소켓에 비동기 읽기를 수행한다. 
        nRet = WSARecv(sdAccept, &(lpPerSocketContext->pIOContext->wsabuf), 1, 
                    &dwRecvNumBytes, &dwFlags,
                    &(lpPerSocketContext->pIOContext->Overlapped), NULL);
        if (nRet == SOCKET_ERROR && (ERROR_IO_PENDING != WSAGetLastError())) 
        {
          printf("WSARecv Failed: %d\n", WSAGetLastError());
          CloseClient(lpPerSocketContext);
        }
      } //while
    

    위의 WSARecv 함수 호출에서 6번째 인자를 눈여겨 보기 바란다. WSAOVERLAPPED 구조체의 변수를 지정하는데 PER_IO_CONTEXT의 Overlapped 필드를 넘기고 있다. 3>에서 설명한 것처럼 이는 사실 pIOContext의 주소를 넘기는 것과 동일한 효과를 갖는다.

    아무튼 WSARecv로 인한 읽기 작업이 완료되면 이는 IOCP 큐에 들어간다. 이를 읽어들이는 작업은 앞에서 만든 스레드들에서 수행한다. 이 함수는 비동기 함수이기 때문에 바로 리턴하고 그리고나서 코드는 다시 while 루프로 선두로 가서 다른 클라이언트로부터의 연결을 대기한다.

      while (g_bEndServer == FALSE) 
      {
        // 클라이언트가 들어오기를 대기한다.
        sdAccept = WSAAccept(g_sdListen, NULL, NULL, NULL, 0);
        …
    

    즉, main 함수는 초기화 작업을 하고 난 뒤부터는 클라이언트로부터의 소켓연결이 맺어지기를 기다렸다가 만들어지면 이를 IOCP와 연결한 뒤에 WSARecv를 한번 호출하는 일만 한다. 실제 작업은 모두 스레드에서 이루어진다.

    참고로 WSASend와 WSARecv의 함수 원형을 살펴보자.

    int WSARecv(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, 
           LPDWORD  lpNumberOfBytesRecvd, LPDWORD lpFlags, 
           LPWSAOVERLAPPED lpOverlapped, 
           LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
    
    int WSASend(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, 
           LPDWORD lpNumberOfBytesSent, DWORD dwFlags, 
           LPWSAOVERLAPPED lpOverlapped, 
           LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
    

    이 두 함수는 비슷한 인자를 많이 갖고 있다. 먼저 모두 첫번째 인자는 소켓 핸들이다. 두 번째 인자는 WSABUF라는 구조체에 대한 포인터로 보낼 데이터에 대한 정보이거나 데이터를 받을 버퍼에 대한 정보이다. WSABUF는 다음과 같이 버퍼의 시작 주소와 버퍼의 크기를 지정하는 두개의 필드로 구성되어 있다.

    Typedef struct __WSABUF
    {
    u_long len; // 버퍼 크기
      char FAR *buf; // 버퍼 시작 주소
    } WSABUF, FAR *LPWASBUF;
    

    이 두 번째 인자로는 WSABUF 배열의 주소를 지정할 수도 있다. 그 경우 차례로 여러 버퍼의 데이터를 전송하거나 (WSASend의 경우) 받은 데이터를 여러 버퍼로 옮기는 역할(WSARecv의 경우)을 한다. 세 번째 인자는 이 두 번째 인자가 가리키는 WSABUF 변수의 수를 나타낸다. 배열을 지정했을 경우에는 그 크기를 이 인자로 지정해주면 된다. 배열이 아니라면 그냥 1을 지정하면 된다. 여기서 한가지 알아야 할 점은 이 두 함수 모두 지정한 크기만큼 입출력이 종료된 다음에 리턴되는 것이 아니란 점이다. WSARecv 같은 경우에는 읽어올 데이터가 생기면 지정된 크기와 관계없이 바로 작업을 종료한다. WSASend의 경우에는 소켓 버퍼가 꽉 차서 데이터를 지정된 크기만큼 보낼 수 없으면 일단 보낼 수 있는 만큼 보내고 만다.

    네 번째 인자는 각기 실제로 전송된 데이터(WSASend의 경우)와 실제로 읽어들인 데이터(WSARecv의 경우)의 크기가 들어간다. 그런데 이 함수들을 예제 프로그램에서처럼 비동기 모드로 사용할 경우에는 이 인자로 리턴되는 값은 함수 자체의 리턴값이 0인 경우에만 의미가 있다. 0인 경우는 바로 작업이 끝난 경우이다. 함수가 바로 끝나지 않을 경우에는 SOCKET_ERROR가 리턴되고 이 때 GetLastError 함수를 호출해보면 그 값이 WSA_IO_PENDING일 것이다.

    다섯 번째 인자는 약간 복잡한데 일단 대부분 0이 리턴되거나 (WSARecv의 경우) 0이 지정(WSASend의 경우)된다고 알아두기 바란다. 여섯 번째 인자는 WSAOVERLAPPED 구조체에 대한 포인터를 지정하는 부분이다. IOCP를 사용하는 경우에는 hEvent 필드의 값은 반드시 NULL이 지정되어야 한다. 마지막 인자는 콜백함수를 지정하는데 사용된다. 이 콜백함수의 원형은 다음과 같다.

    void CALLBACK CompletionROUTINE(DWORD dwError, DWORD cbTransferred,
        LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags);
    

    만일 여섯 번째 인자와 마지막 인자가 모두 NULL이면 이 함수들은 동기 모드로 동작한다. 여섯 번째 인자와 마지막 인자가 모두 지정되면 작업이 종료되었을 때 마지막 인자로 지정된 함수가 호출된다. 여섯 번째 인자만 지정되고 첫 번째 인자로 지정된 소켓이 IOCP와 연결되어 있으면 이 함수의 결과는 IOCP 큐에 들어간다. 사실 이 두 함수의 인자들을 제대로 이해할 수 있다면 윈도우 운영체제의 입출력 함수는 다 이해했다고 봐도 무방하다.

    5> 비동기 I/O 결과 읽기

    앞서 수행된 비동기 I/O의 결과를 읽어들이려면 GetQueuedCompletionPort라는 함수를 이용해야 한다. 이 함수 원형에 대한 설명은 참고 3에 있다. 이 함수는 IOCP 큐안에 읽어들일 비동기 I/O 결과가 있으면 이를 읽어가지고 리턴한다. 읽어올 것이 없으면 읽어올 것이 생길 때까지 리턴하지 않는다. 다음 코드처럼 이 함수는 무한루프안에서 계속적으로 호출되는 것이 일반적이다.

    While (1)
    {
      GetQueuedCompletionStatus(…);
      // 읽어들인 결과를 바탕으로 다음 일을 수행한다.
      …
    }
    

    예제 프로그램과 같은 에코우 서버에서는 특정 소켓에 대해 읽기 작업이 완료된 결과를 읽어들였으면 이를 비동기로 쓰는 작업을 하고, 쓰기 작업이 완료된 결과를 읽어들였으면 다시 비동기로 읽기 작업을 수행한다. 앞서 이야기한 것처럼 GetQueuedCompletionPort 함수의 세 번째 인자로는 현재 이 소켓에 대해 따로 할당된PER_SOCKET_CONTEXT 구조체의 포인터가 리턴되고 이 것의 pIOContext 필드를 보면 현재 진행중인 작업의 상태를 알 수 있다. pIOContext의IOOperation 필드의 값이ClientIoRead이면 지금 큐에서 읽어온 작업이 읽기 작업의 결과인 것이고 ClientIoWrite이면 쓰기 작업인 것이다.

    위의 코드를 좀더 예제 프로그램에 맞게 고쳐보면 다음과 같은 식이다.

    While (1)
    {
      GetQueuedCompletionStatus(…);
      // 읽어들인 결과를 바탕으로 다음 일을 수행한다.
      만일 읽어들인 결과가 읽기 작업이면
        읽어들인 데이터를 그대로 다시 서버로 보낸다 (물론 비동기 I/O)
      만일 읽어들인 결과가 쓰기 작업이면
        만일 앞서 쓰기 요청한 것이 다 전송되지 않았으면
          전송안 된 부분만 다시 전송한다
        다 전송되었으면
          읽기 비동기 작업을 소켓에 수행한다.
    }
    

    참고 3. GetQueuedCompletionStatus

    이 함수의 원형은 다음과 같다.

    BOOL GetQueuedCompletionStatus(
        HANDLE CompletionPort,       
        LPDWORD lpNumberOfBytes, 
        PULONG_PTR lpCompletionKey,
        LPOVERLAPPED *lpOverlapped,
        DWORD dwMilliseconds);
    

    첫 번째 인자인 CompletionPort로는 앞서 생성된 IOCP 객체의 핸들을 지정한다.

    두 번째 인자로는 지금 읽어온 I/O 작업의 결과로 읽거나 쓴 데이터의 크기가 바이트 단위로 지정된다. 즉 이 인자의 값은 운영체제에서 지정한다.

    세 번째 인자인 lpCompletionKey역시 운영체제에 의해 채워져 리턴되는 값이다. CreateIoCompletionPort 함수로 IOCP 객체를 생성할 때 세 번째 인자로 지정한 값이 여기로 리턴된다. 앞서 이야기한 것처럼 한 IOCP 객체로 둘 이상의 입출력 디바이스를 처리할 수 있기 때문에 이를 구분하는 값이 여기로 지정된다고 생각하면 된다.

    네 번째 인자인 lpOverlapped 역시 운영체제에 의해 값이 지정되는데 이는 한 입출력 디바이스내에서 각각의 입출력 작업을 구별하는 역할을 한다. 이 값은 사실 앞서 비동기 작업에서 사용된 OVERLAPPED 구조체의 주소가 그대로 들어온다. 그렇기 때문에 비동기 I/O 작업시에 OVERLAPPED 구조체를 스택에 있는 것을 사용하면 안 되고 각 작업마다 서로 다른 OVERLAPPED 구조체가 사용되어야 하는 것이다.

    마지막 인자인dwMilliseconds는 IOCP 큐에 결과가 없을 경우 얼마나 더 대기하다가 리턴할 것인지를 밀리세컨드 단위로 지정한다. 만일 타임아웃이 나서 리턴할 경우에는 GetQueuedCompletionStatus 함수의 리턴값은 FALSE가 되고 네 번째인자로는 NULL이 지정된다. 읽어올 것이 생길 때까지 대기하도록 하고 싶으면 이 인자로 INFINITE를 지정하면 된다.

    위의 플로우를 염두에 두고 이제 예제 프로그램의 스레드 코드를 실제로 살펴보자. 주석을 자세히 달아놓았으므로 주석과 함께 코드를 살펴보기 바란다.

    DWORD WINAPI EchoThread (LPVOID WorkThreadContext)
    {
      // 앞서 스레드 생성시 스레드 함수의 인자로 IOCP 핸들을 지정했었다.
      // 인자를 IOCP 핸들로 캐스팅한다.
      HANDLE hIOCP = (HANDLE)WorkThreadContext;
      BOOL   bSuccess = FALSE;
      int      nRet;
      LPOVERLAPPED    lpOverlapped = NULL;
      PPER_SOCKET_CONTEXT lpPerSocketContext = NULL;
      PPER_IO_CONTEXT     lpIOContext = NULL; 
      WSABUF buffRecv;
      WSABUF buffSend;
      DWORD  dwRecvNumBytes = 0;
      DWORD  dwSendNumBytes = 0;
      DWORD  dwFlags = 0;
      DWORD  dwIoSize;
        
      while (TRUE) 
      {
        // IOCP 큐에서 비동기 I/O 결과를 하나 읽어온다.
        bSuccess = GetQueuedCompletionStatus(hIOCP, &dwIoSize, 
                 (LPDWORD)&lpPerSocketContext, &lpOverlapped,INFINITE);
        if (!bSuccess) 
          printf("GetQueuedCompletionStatus: %d\n", GetLastError());
    
        // CleanUp 함수에 의해서 스레드의 강제 종료 명령이 내려지면.. 
        if (lpPerSocketContext == NULL)  return 0;
        if (g_bEndServer) return 0;
     
        // 클라이언트와의 소켓 연결이 끊어졌으면…
        if (!bSuccess || (bSuccess && (0 == dwIoSize))) 
        {
          // lpPerSocketContext를 메모리에서 제거한다.
          CloseClient(lpPerSocketContext); 
          continue;
        }
    
        /* 앞서 WSASend와 WSARecv에 의해 I/O 작업을 할 때 넘겼던 WSAOVERLAPPED    
    타입의 변수가 사실은 PER_IO_CONTEXT 타입의 시작이기도 하므로 이를 캐스팅하
    여 사용가능하다. */
        lpIOContext = (PPER_IO_CONTEXT)lpOverlapped;
        switch (lpIOContext->IOOperation) // 끝난 작업 종류가 무엇인가 ?
        {
            case ClientIoRead: // 읽기 작업인가 ?
            // --------------------------------------------
            // 받은 것을 그대로 보낸다. 즉, 다음 작업은 쓰기 작업이다.
            // --------------------------------------------
            printf("%s를 받았고 이를 재전송합니다.\n.", lpIOContext->wsabuf.buf);
              lpIOContext->IOOperation = ClientIoWrite; // 이제 쓰기 작업이 진행됨을 표시
              // 얼마큼 전송할 것인지 명시한다. 받은 만큼 보낸다. 이는 상태를 기록하기
              // 위함이지 WSASend 함수와는 관련없다.
              lpIOContext->nTotalBytes = dwIoSize; 
              // 전송된 데이터 크기. 아직 보낸 것이 없으므로 0
              lpIOContext->nSentBytes  = 0;
            // WSASend에게 보낼 데이터의 포인터와 크기를 지정한다.
            // 받은 데이터가 이미 lpIOContext->wsabuf.buf에 있다.
            lpIOContext->wsabuf.len  = dwIoSize; // 크기 지정
            dwFlags = 0;
            nRet = WSASend(lpPerSocketContext->Socket,
                  &lpIOContext->wsabuf, 1, &dwSendNumBytes,
                  dwFlags, &(lpIOContext->Overlapped), NULL);
            if (SOCKET_ERROR == nRet && (ERROR_IO_PENDING != WSAGetLastError())) 
            {
              printf("WSASend: %d\n", WSAGetLastError());
              CloseClient(lpPerSocketContext);
            }
            break;
    
          case ClientIoWrite: // 쓰기 작업인가 ?
            // ----------------------------------------------------
            // 전송이 다 되었는지 확인한다. 다 전송되지 않았으면 아직 전송되지 
            // 않은 데이터를 다시 보낸다. 다 전송되었으면 WSARecv를 호출해서
            // 다시 받기 모드로 진입한다.  
            // --------------------------------------------
            lpIOContext->nSentBytes  += dwIoSize; // 전송된 데이터 크기 업데이트
            dwFlags = 0;
            if (lpIOContext->nSentBytesnTotalBytes) // 다 전송되지 않았으면
            {
              // 마저 전송해야 하므로 아직 보내기모드
              lpIOContext->IOOperation = ClientIoWrite;
              // -----------------------
              // 전송되지 않은 부분을 보낸다. 
              // -----------------------
              // 버퍼 포인터를 업데이트하고
              buffSend.buf = lpIOContext->Buffer + lpIOContext->nSentBytes;
              // 보내야할 데이터의 크기를 남은 데이터의 크기만큼으로 줄인다.
              buffSend.len = lpIOContext->nTotalBytes - lpIOContext->nSentBytes;
              nRet = WSASend (lpPerSocketContext->Socket,
                         &buffSend, 1, &dwSendNumBytes,
                         dwFlags, &(lpIOContext->Overlapped), NULL);
              // SOCKET_ERROR가 리턴된 경우에는 반드시 WSAGetLastError의 리턴값이
              // ERROR_IO_PENDING이어야 한다.
              if (SOCKET_ERROR == nRet && (ERROR_IO_PENDING != WSAGetLastError())) 
              {
                printf ("WSASend: %d\n", WSAGetLastError());
                CloseClient(lpPerSocketContext);
              }
            }
            else // 데이터가 전부 전송된 경우
            {
              // 다시 이 소켓으로부터 데이터를 받기 위해 WSARecv를 호출한다.
              lpIOContext->IOOperation = ClientIoRead; 
              dwRecvNumBytes = 0;
              dwFlags = 0;
              buffRecv.buf = lpIOContext->Buffer; // 수신버퍼 지정
              // 읽어들일 데이터 크기 지정. 사실 이 크기만큼 데이터를 읽어들여야 
              // 그 결과가 IOCP큐에 들어가는 것은 아니다.  이 크기 이상 안 
              // 읽어들일 뿐이고 데이터가 이용가능한 만큼 IOCP큐에 넣는다.
              buffRecv.len = MAX_BUFF_SIZE;
              nRet = WSARecv(lpPerSocketContext->Socket,
                            &buffRecv, 1, &dwRecvNumBytes,
                            &dwFlags, &(lpIOContext->Overlapped), NULL);
              // SOCKET_ERROR가 리턴된 경우에는 반드시 WSAGetLastError의 리턴값이
              // ERROR_IO_PENDING이어야 한다.
              if (SOCKET_ERROR == nRet && (ERROR_IO_PENDING != WSAGetLastError())) 
              {
                printf ("WSARecv: %d\n", WSAGetLastError());
                CloseClient(lpPerSocketContext);
              }
            }
            break;
          } //switch
        } //while
        return(0);
    }
    

    자 이상으로 IOCP가 어떤 식으로 동작하는지 알아보았다. 단계별로 설명과 코드를 잘 살펴보면 어떻게 동작하는지 더 쉽게 이해할 수 있을 것이다.

    3. 예제 프로그램의 기타 코드 설명

    예제 프로그램에서 설명이 안 된 코드는 서버와 연결된 클라이언트의 리스트를 관리하는 함수들(CtxtAllocate, CtxtListFree, CtxtListAddTo, CtxtListDeleteFrom)과 청소 함수(CleanUp, CloseClient), 대기 소켓 생성함수(CreateListenSocket)등이다. 대기 소켓 생성 함수는 이미 지난 연재에서 살펴본 내용(사실 socket 대신에 WSASocket을 호출하는 부분만 다르다)이기 때문에 여기서는 다른 함수들에 대해서만 알아보겠다.

    클라이언트 리스트 관리 함수들

    접속하는 클라이언트가 생길 때마다 이는g_CtxtList에 기록된다. 이는CptrList 타입의 링크드 리스트 클래스이고 이 변수로의 접근은 모두g_CriticalSection이란 크리티컬 섹션에 의해 한번에 한 스레드로 제한된다.

    CtxtAllocate는 인자로 지정된 소켓에 PER_SOCKET_CONTEXT 구조체를 하나 할당하고 그 구조체를 초기화한 다음에 이를 리턴한다. 할당에 실패하면 NULL을 리턴한다. PER_SOCKET_CONTEXT 구조체의 IO_PER_CONTEXT 타입의 필드인 pIOContext의 필드를 초기화하는 부분을 눈여겨 봐두기 바란다.

    PPER_SOCKET_CONTEXT CtxtAllocate(SOCKET sd, IO_OPERATION ClientIO)
    {
      PPER_SOCKET_CONTEXT lpPerSocCon;
    
      lpPerSocCon = (PPER_SOCKET_CONTEXT)malloc(sizeof(PER_SOCKET_CONTEXT));
      if (lpPerSocCon)
      {
        lpPerSocCon->pIOContext = (PPER_IO_CONTEXT)
            malloc(sizeof(PER_IO_CONTEXT));
        if (lpPerSocCon->pIOContext) 
        {
          lpPerSocCon->Socket = sd;
          memset(&lpPerSocCon->pIOContext->Overlapped, 
             0, sizeof(OVERLAPPED));
          lpPerSocCon->pIOContext->IOOperation = ClientIO;
          lpPerSocCon->pIOContext->nTotalBytes = 0;
          lpPerSocCon->pIOContext->nSentBytes = 0;
          lpPerSocCon->pIOContext->wsabuf.buf = lpPerSocCon->pIOContext->Buffer;
          lpPerSocCon->pIOContext->wsabuf.len = MAX_BUFF_SIZE;
        }
        else 
        {
          free(lpPerSocCon);
          lpPerSocCon = NULL;
        }
      }
      return(lpPerSocCon);
    }
    

    나머지 세 함수들은 간단하다. CptrList 클래스를 사용해본 이라면 이 함수들의 소스를 이해하기가 아주 쉬울 것이다. 여기서는 CtxtListAddTo와 CtxtListDeleteFrom 함수만 살펴보겠다.

    // g_CtxtList에 lpPerSocketContext가 가리키는 항목을 추가한다
    VOID CtxtListAddTo (PPER_SOCKET_CONTEXT lpPerSocketContext)
    {
      EnterCriticalSection(&g_CriticalSection);
      g_CtxtList.AddTail(lpPerSocketContext); // 리스트의 끝에 붙인다.
      LeaveCriticalSection(&g_CriticalSection);
      return;
    }
    
    // g_CtxtList에서 lpPerSocketContext가 가리키는 항목을 제거한다.
    VOID CtxtListDeleteFrom(PPER_SOCKET_CONTEXT lpPerSocketContext)
    {
      EnterCriticalSection(&g_CriticalSection);
      if (lpPerSocketContext)
      {
        POSITION pos = g_CtxtList.Find(lpPerSocketContext);
        if (pos)
        {
          g_CtxtList.RemoveAt(pos);
          if (lpPerSocketContext->pIOContext)
            free(lpPerSocketContext->pIOContext);
          free(lpPerSocketContext);
        }
      }
      LeaveCriticalSection(&g_CriticalSection);
      return;
    }
    

    청소 함수들

    여기서는 CleanUp 함수의 코드를 보기로 하겠다. 이 함수를 프로그램이 종료될 때 호출되는 함수로 모든 스레드가 종료되기를 기다렸다가 클라이언트 리스트에 할당되었던 자료구조들을 제거하고 최종적으로 IOCP와 대기 소켓을 제거하는 일을 수행한다.

    void CleanUp()
    {
        if (g_hIOCP)        
        {
            // 스레드를 강제 종료하도록 한다. 
            // 참고 4와 EchoThread의 if (lpPerSocketContext == NULL)를 같이 보기 바란다.  
            for (DWORD i = 0; i < g_dwThreadCount; i++)
                PostQueuedCompletionStatus(g_hIOCP, 0, 0, NULL);
        }
    
        // 모든 스레드가 실행을 중지했는지 확인한다.
        if (WAIT_OBJECT_0 != WaitForMultipleObjects( g_dwThreadCount,  g_hThreads,
                         TRUE, 1000))
            printf("WaitForMultipleObjects failed: %d\n", GetLastError());
        else
            for (DWORD i = 0; i < g_dwThreadCount; i++) // 스레드 핸들을 모두 닫는다.
            {
                if (g_hThreads[i] != INVALID_HANDLE_VALUE) CloseHandle(g_hThreads[i]);
                    g_hThreads[i] = INVALID_HANDLE_VALUE;
            }
        // g_CtxtList에 들어있는 클라이언트들을 모두 제거한다.
        CtxtListFree();
        // IOCP를 제거한다.  
        if (g_hIOCP)    
        {
            CloseHandle(g_hIOCP);
            g_hIOCP = NULL;
        }
        // 대기 소켓을 제거한다.
        if (g_sdListen != INVALID_SOCKET) 
        {
            closesocket(g_sdListen); 
            g_sdListen = INVALID_SOCKET;
        }
    
        DeleteCriticalSection(&g_CriticalSection); // 크리티컬 섹션을 제거한다.
        WSACleanup(); // 윈속 라이브러리를 해제한다.
    }
    


    참고 4. PostQueuedCompletionPort

    앞에서 설명한 것처럼 이 함수는 IOCP 큐에 마치 비동기 작업이 끝나서 그 결과가 큐에 들어가는 것처럼 흉내내는 기능을 한다. 그렇기 때문에 이 함수의 인자들을 보면 GetQueuedCompletionStatus 함수에 있는 것과 동일하다. 이 함수의 원형은 다음과 같다.

    BOOL PostQueuedCompletionStatus(
    HANDLE CompletionPort,
    DWORD dwNumberOfBytesTransferred,
      ULONG_PTR dwCompletionKey, 
    LPOVERLAPPED lpOverlapped);
    

    첫 번째 인자인 CompletionPort로는 지금 만들어내는 I/O 작업의 결과가 들어갈 IOCP 객체의 핸들을 지정한다.

    두 번째 인자인 dwNumberOfBytesTransferred는 GetQueuedCompletionStatus 함수의 두 번째 인자로 넘어갈 값을 지정한다.

    세 번째 인자인 dwCompletionKey는 두 번째 인자와 마찬가지로 GetQueuedCompletionStatus 함수의lpCompletionKey 인자로 들어갈 값을 지정하는데 사용된다.

    네 번째 인자인 lpOverlapped는 앞서 인자들과 마찬가지로 GetQueuedCompletionStatus 함수의 네 번째 인자로 들어갈 OVERLAPPED 구조체의 값을 넘긴다.

    이 함수가 성공적으로 인자로 지정된 값들을 IOCP 큐에 넣으면 0이 아닌 값이 리턴된다. 실패시에는 0이 리턴되며 이 때는 GetLastError 함수를 호출해서 에러의 원인을 찾아볼 수 있다.

    예제 프로그램의 실행 화면은 그림 2와 같다.

    < 그림 2. 예제 프로그램의 실행화면 >

    이 것으로 IOCP에 대한 장황한 설명을 마치겠다. 아마 이해하기가 그리 쉽지 않을 것이다. 필자의 경우에도 이를 이해하는데 상당한 시간을 소모했으며 위의 예제 프로그램을 바탕으로 실제 환경하에서 동작하는 프로그램을 만드는데도 상당한 시간을 보냈다. 이해하기는 어렵지만 IOCP는 스레드을 최대한으로 활용할 수 있도록 해주는 메커니즘이다. 특히 소켓으로 다중 사용자의 요구를 처리해야 하는 프로그램을 만들어야 한다면 IOCP는 최적의 솔루션이 아닌가 싶다.

    참고문헌
    1. INFO: Design Issues When Using IOCP in a Winsock Server (Q192800) - http://support.microsoft.com/default.aspx?scid=kb;EN-US;q192800
    2. Programming Server-Side Applications for Microsoft Windows 2000, Chapter 2 Devico I/O and Interthreaded Communication 
    3. Writing Windows NT Server Applications in MFC Using I/O Completion Ports - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpic/html/msdn_servrapp.asp
    4. UNBUFCPY, SOCKSRV – Microsoft Platform SDK IOCP 윈속 예제 프로그램 
    5. Windows Sockets 2.0: Write Scalable Winsock Apps Using Completion Ports - http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnmag00/html/Winsock.asp

    파일다운로드 : iocp_source.zip

  • 'C/C++ > VC++ / MFC' 카테고리의 다른 글

    [ATL] ATL Com Programming  (0) 2013.05.24
    [COM] Com Event Handling  (0) 2013.05.24
    [VC++] Visual Studio Predefine Macro  (0) 2013.05.01
    [VC++] Tray Icon Animation  (0) 2013.04.26
    [VC++] Design Specifications and Guidelines - Visual Design  (0) 2013.03.14
    posted by 뚱2

    링크 : http://msdn.microsoft.com/ko-kr/library/yx7xezcf.aspx 

     

    '.Net > .Net' 카테고리의 다른 글

    [.Net] Regular Expression Quick Reference  (0) 2013.09.12
    [.Net] Thread Pool  (0) 2013.06.19
    [.Net] 닷넷프레임워크 버전 확인  (0) 2013.05.10
    [.Net] TransactedInstaller  (0) 2013.04.22
    [.Net] Windows Service Current Directory  (0) 2013.04.20
    posted by 뚱2

    [Netty] 네티 프레임워크

    Java/Netty 2013. 5. 20. 09:51
    http://atin.tistory.com/462 


    'Java > Netty' 카테고리의 다른 글

    [Netty] Get started with netty (한글예제)  (0) 2014.11.30
    [Netty] Netty Documentation  (0) 2013.11.27
    posted by 뚱2

    링크 : http://blog.naver.com/hcwha?Redirect=Log&logNo=70095361247 


    'C/C++ > FFmpeg' 카테고리의 다른 글

    [FFmpeg] Water Mark  (0) 2013.06.29
    [FFmpeg] 옵션  (0) 2013.06.11
    [FFmpeg] library document  (0) 2013.05.02
    [FFmpeg] thumbnail 추출  (0) 2013.04.29
    [FFmpeg] FFMpeg 윈도우 컴파일  (0) 2013.04.28
    posted by 뚱2

    [dex] Android Decompile

    Mobile/Android 2013. 5. 10. 17:53

    링크 : http://blog.naver.com/PostView.nhn?blogId=gigar&logNo=60115566766 

    'Mobile > Android' 카테고리의 다른 글

    [Android] YUV420 Format  (0) 2013.08.26
    [Json] 안드로이드 Json 처리  (0) 2012.06.15
    WebView.addJavascriptInterface 활용  (0) 2010.12.11
    [Android] 테트리스  (0) 2010.12.06
    posted by 뚱2

    [Java] 윈도우 JDK 버전 확인

    Java/Java 2013. 5. 10. 15:46

    링크 : http://jmnote.com/wiki/%EC%9C%88%EB%8F%84%EC%9A%B0_JDK_%EB%B2%84%EC%A0%84_%ED%99%95%EC%9D%B8 


    where /R C:\ javac.exe


    'Java > Java' 카테고리의 다른 글

    [Java] ThreadLocal  (0) 2013.08.21
    [Java] JDBC SQL Server 연결 URL  (0) 2013.07.18
    [Java] jar 파일 실행 시키기  (0) 2013.04.07
    [Java] JDK Download  (0) 2013.04.02
    [Java] Creating Custom Annotations and Using Them  (0) 2013.01.21
    posted by 뚱2

    링크 : http://jmnote.com/wiki/%EB%8B%B7%EB%84%B7%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC_%EB%B2%84%EC%A0%84_%ED%99%95%EC%9D%B8


    dir %windir%\Microsoft.NET\Framework | findstr DIR


    '.Net > .Net' 카테고리의 다른 글

    [.Net] Thread Pool  (0) 2013.06.19
    [.Net] 런타임에서 어셈블리를 찾는 방법  (0) 2013.05.20
    [.Net] TransactedInstaller  (0) 2013.04.22
    [.Net] Windows Service Current Directory  (0) 2013.04.20
    [.Net] Changing Start Mode of a Windows Service  (0) 2013.04.20
    posted by 뚱2

    링크 : https://code.google.com/p/zen-coding/downloads/list 


    소스가 Deprecated 되었네요.

    혹 IntelliJ IDEA에서 Zencoding 사용할수 있는 다른 플러그인 있으면 알려주세요.



    PS. 2013-11-05 추가

    현재 IntelliJ 버전이 12.1.6 인데 여기에 젠코딩 플러그인이 자체 내장되어 있습니다.

    그냥 사용하시면 됩니다.

    'IDE/Tool > IntelliJ IDEA' 카테고리의 다른 글

    [IntelliJ IDEA] IntelliJ 시작하기  (0) 2013.07.01
    [IntelliJ IDEA] Tutorial  (0) 2013.06.20
    [IntelliJ IDEA] Short Cut  (0) 2013.05.09
    [IntelliJ IDEA] VCS 연결하기 (SVN)  (0) 2013.01.05
    [IntelliJ IDEA] Eclipse FAQ  (0) 2013.01.02
    posted by 뚱2

    * 주석

    여러줄 주석 (토글) : Ctrl + ?

    한줄 주석    (토글) : Command + /


    * Auto Import

    Alt + Enter


    posted by 뚱2

    출처 : http://docs.scala-lang.org/ko/tutorials/scala-for-java-programmers.html 


    자바 프로그래머를 위한 스칼라 튜토리얼

       

    Michel Schinz, Philipp Haller 지음. 이희종 (heejong@gmail.com) 옮김.

    시작하면서

    이 문서는 Scala 언어와 그 컴파일러에 대해 간단히 소개한다. 어느 정도의 프로그래밍 경험이 있으며 Scala를 통해 무엇을 할 수 있는지를 빠르게 배우고 싶은 사람들을 위해 만들어 졌다. 여기서는 독자가 객체 지향 프로그래밍, 특히 Java에 대한 지식을 가지고 있다고 가정한다.

    첫 번째 예제

    첫번째 예제로 흔히 쓰이는 Hello world 프로그램을 사용하자. 이 프로그램은 그다지 멋지지는 않지만 언어에 대한 많은 지식 없이도 Scala 언어를 다루는데 필요한 도구들의 사용법을 쉽게 보여 줄 수 있다. 아래를 보자:

    1. object HelloWorld {
    2. def main(args: Array[String]) {
    3. println("Hello, world!")
    4. }
    5. }

    자바 프로그래머들은 이 프로그램의 구조가 익숙 할 것이다. 프로그램은 문자열 배열 타입의 명령줄 인자를 받는 이름이 main인 함수 하나를 가지고 있다. 이 함수의 구현은 하나의 또 다른 함수 호출로 이루어져 있는데 미리 정의 된 함수 println에 어디선가 많이 본 바로 그 환영 메시지를 넘겨주어 호출 한다. main 함수는 값을 돌려주지 않기 때문에 리턴 타입을 선언 할 필요가 없다.

    자바 프로그래머들에게 익숙하지 않은 부분은 main 함수를 감싸고 있는 object 선언일 것이다. 이 선언은 싱글턴 객체를 생성하는데, 이는 하나의 인스턴스만을 가지는 클래스라 할 수 있다. 따라서 위의 선언은 HelloWorld라는 클래스와 역시 HelloWorld라고 이름 붙인 이 클래스의 인스턴스를 함께 정의 하는 것이다. 이 인스턴스는 처음 사용 될 때에 필요에 따라 만들어 진다.

    똑똑한 독자들은 이미 눈치챘겠지만 위의 예제에서 main 함수는 static이 아니다. Scala에는 정적 멤버(함수든 필드든)라는 개념이 아얘 존재하지 않는다. 클래스의 일부로 정적 멤버를 정의하는 대신에 Scala 프로그래머들은 정적이기 원하는 멤버들을 싱글턴 객체안에 선언한다.

    예제를 컴파일 하기

    예제를 컴파일 하기 위하여 Scala 컴파일러인 scalac를 사용한다. scalac는 대부분의 컴파일러들과 비슷하게 동작한다. 소스파일과 필요에 따라 몇개의 옵션들을 인자로 받아 한개 또는 여러개의 오브젝트 파일을 생성한다. scalac가 생성하는 오브젝트 파일은 표준적인 Java 클래스 파일이다.

    위의 예제 프로그램을 HelloWorld.scala라는 이름으로 저장했다면, 아래의 명령으로 컴파일 할 수 있다 (부등호 >는 쉘 프롬프트이므로 함께 입력하지 말것) :

    1. > scalac HelloWorld.scala

    이제 현재 디렉토리에 몇개의 클래스 파일이 생성되는 것을 확인 할 수 있다. 그 중에 하나는HelloWorld.class이며 scala 명령을 통해 바로 실행 가능한 클래스를 포함하고 있다. 다음 장을 보자.

    예제를 실행하기

    일단 컴파일 되면 Scala 프로그램은 scala 명령을 통해 실행 할 수 있다. 사용법은 Java 프로그램을 실행 할 때 사용하는 java 명령과 매우 비슷하며 동일한 옵션을 사용 가능하다. 위의 예제는 아래의 명령으로 실행 할 수 있으며 예상한대로의 결과가 나온다.

    1. > scala -classpath . HelloWorld
    2. Hello, world!

    자바와 함께 사용하기

    Scala의 장점 중 하나는 Java 코드와 함께 사용하기 쉽다는 것이다. 사용하고 싶은 Java 클래스를 간단히 임포트 하면 되며, java.lang 패키지의 모든 클래스는 임포트 하지 않아도 기본적으로 사용 할 수 있다.

    아래는 Scala가 Java와 얼마나 잘 어울리는지를 보여주는 예제이다. 우리는 아래 예제에서 현재의 날짜를 구하여 특정 국가에서 사용하는 형식으로 변환 할 것이다. 이를테면 프랑스(불어를 사용하는 스위스의 일부 지역도 동일한 형식을 사용한다)라 하자.

    Java의 클래스 라이브러리는 Date와 DateFormat과 같은 강력한 유틸리티 클래스를 가지고 있다. Scala는 Java와 자연스럽게 서로를 호출 할 수 있으므로, 동일한 역할을 하는 Scala 클래스 라이브러리를 구현하기 보다는 우리가 원하는 기능을 가진 Java 패키지를 간단히 임포트하여 이용하자.

    1. import java.util.{Date, Locale}
    2. import java.text.DateFormat
    3. import java.text.DateFormat._
    4. object FrenchDate {
    5. def main(args: Array[String]) {
    6. val now = new Date
    7. val df = getDateInstance(LONG, Locale.FRANCE)
    8. println(df format now)
    9. }
    10. }

    Scala의 임포트 구문은 Java의 그것과 매우 비슷해 보이지만 사실 좀 더 강력하다. 위 예제의 첫번째 줄과 같이 중괄호를 사용하면 같은 패키지에서 여러개의 클래스를 선택적으로 불러 올 수 있다. Scala 임포트 구문의 또 한가지 특징은 패키지나 클래스에 속한 모든 이름들을 불러 올 경우 별표(*) 대신 밑줄(_) 을 사용 한다는 것이다. 별표는 Scala에서 합법적인 식별자(함수명 등에 사용 가능한)로 사용된다. 나중에 자세히 살펴 볼 것이다.

    따라서 세번째 줄의 임포트 구문은 DateFormat 클래스의 모든 멤버를 불러온다. 이렇게 함으로써 정적 함수 getDateInstance와 정적 필드 LONG이 바로 사용 가능하게 된다.

    main 함수 안에서 처음 하는 일은 Java 라이브러리에 속한 Date 클래스의 인스턴스를 생성하는 것이다. 이 인스턴스는 기본적으로 현재의 날짜를 가지고 있다. 다음으로 이전에 불러온 정적 함수getDateInstance를 통해 날짜 형식을 결정하고, 프랑스에 맞춰진 DateFormat 인스턴스를 사용하여 현재의 날짜를 출력한다. 이 마지막 줄은 Scala 문법의 재미있는 특성을 보여준다. 오직 하나의 인자를 갖는 함수는 마치 이항연산자 같은 문법으로 호출 가능하다. 이 이야기는 곧 아래의 표현식이:

    1. df format now

    아래 표현식과 동일한 의미를 가진 다는 것이다. 그저 좀 더 간단하게 표현 되었을 뿐이다.

    1. df.format(now)

    이러한 특성은 그저 별것 아닌 문법의 일부 인것 처럼 보이지만 여러 곳에서 중요하게 사용 된다. 그중에 하나가 다음 장에 나와있다.

    이번 장에서는 Java와 Scala가 얼마나 자연스럽게 서로 녹아드는지에 대해 배웠다. 이번 장에는 나타나지 않았지만, Scala 안에서 Java의 클래스들을 상속받고 Java의 인터페이스들을 바로 구현하는 것도 가능하다.

    모든 것은 객체다

    Scala는 순수한 객체지향적 언어이다. 이 말은 곧 숫자와 함수를 포함한 모든것이 객체라는 것이다. 이러한 면에서 Scala는 Java와 다르다. Java에서는 기본적인 타입(boolean이나 int 따위)과 참조 가능한 타입이 분리되어 있으며, 함수를 값과 동일하게 다룰 수도 없다.

    숫자도 하나의 객체다

    숫자는 객체이기 때문에 함수들을 포함하고 있다. 사실 아래와 같은 표현식은:

    1. 1 + 2 * 3 / x

    오직 함수 호출로만 이루어져 있다. 우리가 이전 장에서 보았듯이, 위의 표현식은 아래의 표현식과 동일하다.

    1. (1).+(((2).*(3))./(x))

    위의 표현식처럼 +* 등은 Scala에서 합법적인 식별자이다.

    위의 두번째 표현식에서 괄호는 꼭 필요하다. 왜냐하면 스칼라의 렉서(lexer)는 토큰들에 대하여 가장 긴 부분을 찾는 방법을 사용하기 때문이다. 아래의 표현식은:

    1. 1.+(2)

    세개(1.+2)의 토큰들로 분리된다. 이렇게 토큰들이 분리되는 이유는 미리 정의되어 있는 유효한 토큰 중에 1.이 1보다 길기 때문이다. 토큰 1.은 리터럴 1.0으로 해석 되어 Double 타입이 된다. 실제로 우리는 Int 타입을 의도 했음에도 말이다. 표현식을 아래와 같이 쓰면:

    1. (1).+(2)

    토큰 1이 Double로 해석 되는 것을 방지 할 수 있다.

    함수마저 객체다

    Java 프로그래머들에게는 놀라운 일이겠지만 Scala에서는 함수도 역시 객체이다. 따라서 함수에 함수를 인자로 넘기거나, 함수를 변수에 저장하거나, 함수가 함수를 리턴하는 것도 가능하다. 이처럼 함수를 값과 동일하게 다루는 것은 매우 흥미로운 프로그래밍 패러다임인 함수형 프로그래밍의 핵심 요소 중 하나이다.

    함수를 값과 같이 다루는 것이 유용함을 보이기 위해 아주 간단한 예제를 든다. 어떠한 행동을 매초 수행하는 타이머 함수를 생각해 보자. 수행 할 행동을 어떻게 넘겨 주어야 할까? 논리적으로 생각한다면 함수를 넘겨 주어야 한다. 함수를 전달하는 이런 종류의 상황은 많은 프로그래머들에게 익숙 할 것이다. 바로 유저 인터페이스 코드에서 어떤 이벤트가 발생하였을 때 불릴 콜백 함수를 등록하는 것 말이다.

    아래 프로그램에서 타이머 함수의 이름은 oncePerSecond이다. 이 함수는 콜백 함수를 인자로 받는다. 인자로 받는 함수의 타입은 () => Unit 인데, 이 타입은 인자를 받지 않고 아무 것도 돌려주지 않는 모든 함수를 뜻한다 (Unit 타입은 C/C++에서 void와 비슷하다). 이 프로그램의 메인 함수는 이 타이머 함수를 화면에 문장을 출력하는 간단한 콜백함수를 인자로 호출한다. 결국 이 프로그램이 하는 일은 일초에 한번씩 “time flies like an arrow”를 화면에 출력하는 것이 된다.

    1. object Timer {
    2. def oncePerSecond(callback: () => Unit) {
    3. while (true) { callback(); Thread sleep 1000 }
    4. }
    5. def timeFlies() {
    6. println("time flies like an arrow...")
    7. }
    8. def main(args: Array[String]) {
    9. oncePerSecond(timeFlies)
    10. }
    11. }

    우리는 문자열을 화면에 출력하기 위하여 Scala에 정의된 println을 사용 하였다. 이 함수는 Java에서 흔히 사용하는 System.out에 정의된 것과 다르다.

    이름없는 함수

    이 프로그램은 이해하기 쉽지만 조금 더 다듬을 수도 있다. 함수 timeFlies는 오직 함수oncePerSecond에 인자로 넘겨지기 위해 정의 되었다는 것에 주목하자. 이러한 한번만 사용되는 함수에 이름을 붙여 준다는 것은 필요 없는 일일 수 있다. 더 행복한 방법은 oncePerSecond에 함수가 전달 되는 그 순간 이 함수를 생성하는 것이다. Scala에서 제공하는 무명함수를 사용하면 된다. 무명함수란 말 그대로 이름이 없는 함수이다. 함수 timeFlies 대신에 무명함수를 사용한 새로운 버전의 타이머 프로그램은 아래와 같다:

    1. object TimerAnonymous {
    2. def oncePerSecond(callback: () => Unit) {
    3. while (true) { callback(); Thread sleep 1000 }
    4. }
    5. def main(args: Array[String]) {
    6. oncePerSecond(() =>
    7. println("time flies like an arrow..."))
    8. }
    9. }

    main 함수 안에 오른쪽 화살표 =>가 있는 곳이 무명함수이다. 오른쪽 화살표는 함수의 인자와 함수의 내용을 분리 해주는 역할을 한다. 위 예제에서 인자의 리스트는 비어있다. 화살표의 왼쪽을 보면 빈 괄호를 볼 수 있다. 함수의 내용은 timeFlies와 일치한다.

    클래스에 대하여

    지금까지 보았듯 Scala는 객체지향적 언어이며 클래스의 개념이 존재한다. (어떤 객체지향 언어는 클래스의 개념이 존재하지 않는다. 당연하게도 Scala는 이들에 속하지 않는다.) Scala의 클래스 정의는 Java의 클래스 정의와 유사하다. 한가지 중요한 차이점은 Scala 클래스의 경우 파라미터들을 가질 수 있다는 것인데 아래 복소수 예제에 잘 나타나 있다:

    1. class Complex(real: Double, imaginary: Double) {
    2. def re() = real
    3. def im() = imaginary
    4. }

    이 복소수 클래스는 두개의 인자를 받는다. 하나는 복소수의 실수 부분이고 다른 하나는 복소수의 허수 부분에 해당하는 값이 된다. 이 인자들은 Complex 클래스의 인스턴스를 생성 할 때 이처럼 반드시 전달 되어야 한다: new Complex(1.5, 2.3). 클래스는 re와 im라는 두 함수를 가지고 있는데 각각의 함수를 통해 복소수를 구성하는 해당 부분의 값을 얻을 수 있다.

    이 두 함수의 리턴타입은 명시적으로 나타나 있지 않다는 사실에 주목하자. 컴파일러는 이 함수들의 오른편을 보고 둘 다 Double 타입을 리턴 한다고 자동으로 유추해 낸다.

    하지만 컴파일러가 언제나 이렇게 타입을 유추해 낼 수 있는 것은 아니다. 그리고 불행하게도 어떤 경우 이러한 타입 유추가 가능하고 어떤 경우 불가능 한지에 관한 명확한 규칙도 존재하지 않는다. 일반적으로 이러한 상황은 별 문제가 되지 않는다. 왜냐하면 명시적으로 주어지지 않은 타입정보를 컴파일러가 자동으로 유추 해 낼 수 없는 경우 컴파일 시 에러가 발생하기 때문이다. 초보 Scala 프로그래머들을 위한 한가지 방법은, 주변을 보고 쉽게 타입을 유추 해 낼 수 있는 경우 일단 타입 선언을 생략하고 컴파일러가 받아 들이는지 확인하는 것이다. 이렇게 몇번을 반복하고 나면 프로그래머는 언제 타입을 생략해도 되고 언제 명시적으로 써주어야 하는지 감을 잡게 된다.

    인자 없는 함수

    함수 re와 im의 사소한 문제는 그들을 호출하기 위해 항상 뒤에 빈 괄호를 붙여 주어야 한다는 것이다. 아래를 보자:

    1. object ComplexNumbers {
    2. def main(args: Array[String]) {
    3. val c = new Complex(1.2, 3.4)
    4. println("imaginary part: " + c.im())
    5. }
    6. }

    실수 부분과 허수 부분에 접근 할 때에 마치 그들이 필드인 것 처럼 함수 마지막에 빈 괄호를 붙이지 않을 수 있다면 더욱 좋겠다. 놀라지 마시라, Scala는 이러한 기능을 완벽하게 제공한다. 그저 인자를 제외하고 함수를 정의하면 된다. 이런 종류의 함수는 인자가 0개인 함수와는 다른데, 인자가 0개인 함수는 빈 괄호가 따라 붙는 반면 이 함수는 정의 할 때도 사용 할 때도 이름 뒤에 괄호를 붙이지 않는다. 우리가 앞서 정의한 Complex 클래스는 아래와 같이 다시 쓸 수 있다:

    1. class Complex(real: Double, imaginary: Double) {
    2. def re = real
    3. def im = imaginary
    4. }

    상속과 재정의

    모든 Scala의 클래스들은 항상 상위 클래스로부터 상속된다. 만약 Complex 예제 처럼 상위 클래스가 존재하지 않을 경우는 묵시적으로 scala.AnyRef를 상속한다.

    Scala에서는 물론 상위 클래스에 정의된 함수를 오버라이드 하는 것도 가능하다. 그러나 의도하지 않는 실수를 방지하기 위하여 다른 함수를 오버라이드 하는 함수는 override 지시자를 꼭 적어주어야 한다. 예를 들면, 우리의 Complex 클래스에 대해 Object로 부터 상속된 toString 함수를 재정의 하는 법은 아래와 같다:

    1. class Complex(real: Double, imaginary: Double) {
    2. def re = real
    3. def im = imaginary
    4. override def toString() =
    5. "" + re + (if (im < 0) "" else "+") + im + "i"
    6. }

    케이스 클래스 그리고 패턴 매칭

    프로그램에 자주 등장하는 데이터 구조 중의 하나는 트리이다. 인터프리터와 컴파일러는 흔히 트리를 사용하여 내부 표현을 저장하고, XML 문서도 트리이며, 레드블랙 트리와 같은 저장구조 들도 트리에 기반을 두고 있다.

    작은 계산기 프로그램을 통해 Scala에서 이러한 트리들을 어떻게 표현하고 다루는지에 대해 알아 보자. 이 프로그램의 목표는 더하기와 상수인 정수 그리고 변수로 이루어진 간단한 산술 표현식을 다루는 것이다. 예를 들면, 1+2나 (x+x)+(7+y) 같은 식들 말이다.

    처음으로, 우리는 해당 산술 표현식들을 어떻게 표현 할지 결정해야 한다. 가장 자연스러운 방법은 트리를 사용하는 것이다. 노드는 연산(여기서는 덧셈)이 될 것이고, 리프는 값(여기서는 상수 또는 변수)가 되겠다.

    Java였다면 트리를 나타내기 위해, 트리에 대한 추상 상위 클래스와 노드와 리프 각각에 대한 실제 하위 클래스들을 정의 했을 것이다. 함수형 언어였다면 같은 목적으로 대수적 데이터 타입을 사용 했을 것이다. Scala는 케이스 클래스라 하는 이 둘 사이의 어디쯤에 놓여 질 수 있는 장치를 제공한다. 우리 예제의 트리 타입을 정의하기 위해 이 장치가 어떻게 사용 되는지 아래에서 실제적인 예를 보자:

    1. abstract class Tree
    2. case class Sum(l: Tree, r: Tree) extends Tree
    3. case class Var(n: String) extends Tree
    4. case class Const(v: Int) extends Tree

    클래스 SumVar 그리고 Const가 케이스 클래스로 선언되었다는 것은 이들이 여러가지 면에서 일반적인 클래스와 다르다는 의미이다:

    • 인스턴스를 생성 할 때 new 키워드를 생략 할 수 있다. 다른 말로, new Const(5)라 쓰는 대신Const(5)라 쓰면 된다.
    • 생성자 파라미터들에 대한 getter 함수가 자동으로 정의된다. 다른 말로, 클래스 Const의 인스턴스 c에 있는 생성자 파라미터 v의 값은 c.v로 접근 가능하다.
    • 함수 equals와 hashCode도 공짜로 제공된다. 이 함수들은 레퍼런스의 동일함 보다 구조의 동일함을 확인 하도록 구현되어 있다. 다른 말로, 생성 된 곳이 다르더라도 각각의 생성자 파라미터 값이 같다면 같은 것으로 여긴다.
    • 함수 toString에 대한 기본적 구현이 제공된다. 이 기본적인 구현은 “값이 생성 될 때”의 형태를 출력한다. 예를 들어 x+1의 트리 표현 을 출력 한다면 Sum(Var(x),Const(1))이 된다. - 케이스 클래스들의 인스턴스는 패턴 매칭을 통해 따로 사용 될 수 있다. 자세한 내용은 아래에서 다룬다.

    산술 표현식을 나타낼 수 있는 데이터 타입을 정의 했으므로 이제 그것들을 계산 할 연산자들을 정의 할 차례다. 일단, 어떤 환경안에서 표현식을 계산 해주는 함수부터 시작하자. 환경은 각각의 변수마다 주어진 값들을 저장 해 두는 곳이다. 컴퓨터에서 메모리의 역할과 비슷 하다고 생각하면 된다. 예를 들어, 변수 x에 5가 저장된 환경({ x -> 5 })에서 표현식 x+1을 계산하면 결과로 6이 나온다.

    환경은 어떻게 표현하는게 좋을까? 간단히 생각하면, 해쉬 테이블 같은 두 값을 묶어주는 데이터 구조를 사용 할 수 있겠다. 그러나 우리는 이러한 데이터를 저장하는 목적으로 함수를 직접 사용 할 수도 있다! 가만 생각해 보면 환경이라는 것은 변수명에서 값으로 가는 함수에 지나지 않는다. 위에서 사용한 환경 { x -> 5 } 은 Scala로 간단히 아래와 같이 쓴다:

    1. { case "x" => 5 }

    이 문법은 함수를 정의한다. 이 함수는 문자열 "x"가 인자로 들어 왔을 때 정수 5를 돌려주고, 다른 모든 경우에 예외를 발생시키는 함수이다.

    계산하는 함수를 작성하기 전에 환경 타입에 이름을 붙여 주는 것이 좋겠다. 물론 항상 환경 타입으로String => Int를 사용해도 되지만 보기 좋은 이름을 붙이는 것은 프로그램을 더 읽기에 명료하고 변경에 유연하게 해 준다. Scala에서는 아래와 같이 할 수 있다:

    1. type Environment = String => Int

    이제부터 타입 Environment는 String에서 Int로 가는 함수 타입의 다른 이름이다.

    지금부터 계산하는 함수를 정의하자. 개념으로 따지면 매우 간단하다: 두 표현식의 합은 각 표현식의 값을 구하여 더한 것이다. 변수의 값은 환경에서 바로 가져 올 수 있고, 상수의 값은 상수 자체이다. 이것을 Scala로 나타내는 것은 어렵지 않다:

    1. def eval(t: Tree, env: Environment): Int = t match {
    2. case Sum(l, r) => eval(l, env) + eval(r, env)
    3. case Var(n) => env(n)
    4. case Const(v) => v
    5. }

    이 계산 함수는 트리 t에 대해 패턴 매칭을 수행함으로써 동작한다. 위의 함수 정의는 직관적으로도 이해하기 쉽다:

    1. 처음으로 t가 Sum인지 확인한다. 만약 맞다면 왼쪽 서브트리를 새로운 변수 l에 오른쪽 서브트리를 새로운 변수 r에 할당 한다. 그리고 화살표를 따라 화살표의 오른편으로 계산을 이어 나간다. 화살표의 오른편에서는 화살표의 왼편에서 할당된 변수 l과 r을 사용 한다.
    2. 첫번째 확인이 성공하지 못하면 트리는 Sum이 아니라는 이야기이다. 다음으로는 t가 Var인지 확인한다. 만약 맞다면 Var 노드 안에 포함된 이름을 변수 n에 할당한다. 그리고 화살표의 오른쪽으로 진행한다.
    3. 두번째 확인 역시 실패하면 t는 Sum도 Var도 아니라는 뜻이다. 이제는 Const에 대해 확인 해본다. 만약 맞다면 Const 노드 안의 값을 변수 v에 할당하고 화살표의 오른쪽으로 진행한다. 4. 마지막으로 모든 확인이 실패하면 패턴 매칭이 실패 했음을 알리는 예외가 발생하게 된다. 이러한 상황은 확인 한 것 외에 Tree의 하위 클래스가 더 존재 할 경우 일어난다.

    패턴 매칭의 기본적인 아이디어는 대상이 되는 값을 여러가지 관심있는 패턴에 대해 순서대로 맞춰 본 후, 맞는 것이 있으면 맞은 값 중 관심 있는 부분에 대해 새롭게 이름 붙이고, 그 이름 붙인 부분을 사용하는 어떠한 작업을 진행하는 것이다.

    객체지향에 숙련된 프로그래머라면 왜 eval을 클래스 Tree와 그 하위 클래스에 대한 멤버 함수로 정의하지 않았는지 궁금 할 것이다. 사실 그렇게 할 수도 있었다. Scala는 일반적인 클래스 처럼 케이스 클래스에 대해서도 함수 정의를 허용한다. 패턴 매칭을 사용하느냐 멤버 함수를 사용하느냐는 사용자의 취향에 달린 문제다. 하지만 확장성에 관해 시사하는 중요한 점이 있다:

    • 멤버 함수를 사용하면 단지 Tree에 대한 하위 클래스를 새롭게 정의 함으로 새로운 노드를 추가하기 쉽다. 반면에 트리에 대한 새로운 연산을 추가하는 작업이 고되다. 새로운 연산을 추가하기 위해서는 Tree의 모든 하위 클래스를 변경해야 하기 때문이다. - 패턴 매칭을 사용하면 상황이 반대가 된다. 새로운 노드를 추가하려면 트리에 대해 패턴 매칭을 수행하는 모든 함수들을 새로운 노드도 고려하도록 변경해야 한다. 반면에 새로운 연산을 추가하는 것은 쉽다. 그냥 새로운 독립적인 함수를 만들면 된다.

    패턴 매칭에 대해 좀 더 알아보기 위해, 산술 표현식에 대한 또 다른 연산을 정의 해보자. 이번 연산은 심볼 추출이다. 트리에서 우리가 원하는 특정 변수만 1로 표시하는 일이다. 독자는 아래 규칙만 기억하면 된다:

    1. 더하기 표현식에서의 심볼 추출은 좌변과 우변의 심볼을 추출하여 더한 것과 같다. 2. 변수 v에 대한 심볼 추출은 v가 우리가 추출하기 원하는 심볼과 관련이 있다면 1이 되고 그 외의 경우 0이 된다. 3. 상수에 대한 심볼 추출 값은 0이다.

    이 규칙들은 거의 그대로 Scala 코드가 된다.

    1. def derive(t: Tree, v: String): Tree = t match {
    2. case Sum(l, r) => Sum(derive(l, v), derive(r, v))
    3. case Var(n) if (v == n) => Const(1)
    4. case _ => Const(0)
    5. }

    위의 함수는 패턴 매칭에 관한 두 가지 새로운 기능을 소개한다. 첫 번째로, case 표현은 가드를 가질 수 있다. 가드란 if 키워드 뒤에 오는 표현식을 뜻하는 말로 패턴 매칭에 추가적인 조건을 부여한다. 가드가 참이 되지 않으면 패턴 매칭은 성공하지 못한다. 여기서는, 매칭 된 변수의 이름이 우리가 추출하는 심볼 v와 같을 때만 상수 1을 리턴함을 보장하는 용도로 사용된다. 두 번째 새로운 기능은와일드카드이다. 밑줄 문자 _로 쓰며, 모든 값과 매치 되고 따로 이름을 붙이지 않는다.

    매턴 매칭의 뛰어난 기능들을 모두 살펴보지는 못했지만, 문서를 너무 지루하게 만들지 않기 위하여 이쯤에서 멈추기로 한다. 이제 위에서 정의한 두 개의 예제 함수가 실제로 동작하는 모습을 보자. 산술 표현식 (x+x)+(7+y)에 대해 몇가지의 연산을 실행하는 간단한 main 함수를 만들기로 한다. 첫번째로 환경 { x -> 5, y -> 7 }에서 그 값을 계산 할 것이고, 다음으로 x와 y에 대한 심볼 추출을 수행 할 것이다.

    1. def main(args: Array[String]) {
    2. val exp: Tree = Sum(Sum(Var("x"),Var("x")),Sum(Const(7),Var("y")))
    3. val env: Environment = { case "x" => 5 case "y" => 7 }
    4. println("Expression: " + exp)
    5. println("Evaluation with x=5, y=7: " + eval(exp, env))
    6. println("Derivative relative to x:\n " + derive(exp, "x"))
    7. println("Derivative relative to y:\n " + derive(exp, "y"))
    8. }

    이 프로그램을 실행하면, 예상된 결과를 얻을 수 있다:

    1. Expression: Sum(Sum(Var(x),Var(x)),Sum(Const(7),Var(y)))
    2. Evaluation with x=5, y=7: 24
    3. Derivative relative to x:
    4. Sum(Sum(Const(1),Const(1)),Sum(Const(0),Const(0)))
    5. Derivative relative to y:
    6. Sum(Sum(Const(0),Const(0)),Sum(Const(0),Const(1)))

    출력을 살펴 보면 심볼 추출의 결과가 사용자에게 좀 복잡하다는 생각이 든다. 패턴 매칭을 사용하여 이 결과를 단순화 하는 함수를 정의하는 것은 재미있는 문제이다(생각보다 복잡하기도 하다). 독자들에게 연습문제로 남겨두겠다.

    트레잇에 대하여

    Scala 클래스에서는 상위 클래스에서 코드를 상속 받는 것 뿐만이 아니라, 하나 또는 여러개의 트레잇(trait)에서 코드를 불러 올 수 있는 방법도 있다.

    Java 프로그래머들이 트레잇을 이해하는 가장 쉬운 길은 코드를 가질 수 있는 인터페이스라고 생각하는 것이다. Scala에서 어떤 클래스가 트레잇을 상속하면, 그 클래스는 트레잇의 인터페이스를 구현해야만 하고 동시에 트레잇이 가진 모든 코드들을 가져오게 된다.

    트레잇의 유용함을 보이기 위해 객체들에 순서를 붙이는 고전적인 예제 하나를 들어보기로 하자. 순서가 있는 객체들은 정렬문제 처럼 주로 그들 사이에 비교가 필요 할 경우 유용하다. Java에서는 비교가능한 객체들이 Comparable 인터페이스를 구현하게 된다. Scala에서는 이 Comparable을 트레잇으로 정의하여 더 나은 프로그램 디자인을 제공 할 수 있다. 여기서는 이를 Ord라 부를 것이다.

    객체를 비교 할 때, 여섯개의 서로 다른 관계가 주로 사용 된다: 작다, 작거나 같다, 같다, 같지 않다, 크거나 같다, 크다. 하지만 이 여섯개를 일일히 구현하는 것은 지루하고 의미 없는 일이 될 것이다. 게다가 이중 두 가지 관계만 정의 되어도 나머지 네가지 관계를 계산 할 수 있지 않은가. 예를 들어 같다와 작다만 결정 할 수 있어도 나머지 관계의 참 거짓을 쉽게 판단 할 수 있다. Scala에서는 이러한 논리들을 트레잇의 정의 안에 우아하게 표현 해 낼 수 있다:

    1. trait Ord {
    2. def < (that: Any): Boolean
    3. def <=(that: Any): Boolean = (this < that) || (this == that)
    4. def > (that: Any): Boolean = !(this <= that)
    5. def >=(that: Any): Boolean = !(this < that)
    6. }

    위의 정의는 Java의 Comparable 인터페이스와 같은 역할을 하는 Ord라고 불리는 새로운 타입을 만든다. 이 새로운 타입에는 세가지의 관계식이 기본적으로 구현이 되어 있으며 이 구현은 모두 하나의 추상 함수를 사용하고 있다. 모든 객체에 대해 기본적으로 존재하는 같다와 같지 않다에 대한 관계식은 빠져 있다.

    위에서 사용된 타입 Any는 Scala의 최상위 타입이다. Java의 Object 타입과 같으나, Int,Float과 같은 기본 타입의 상위 타입이라는 점에서 좀 더 일반화 된 버전이라 생각 할 수 있다.

    객체를 비교 가능하게 만들기 위해 정의해야 할 것은 같다와 작다 뿐이다. 나머지는 위의 Ord 트레잇을 삽입하여 처리한다. 하나의 예로 그레고리력의 날짜를 나타내는 Date 클래스를 만들어 보자. 이 날짜는 정수인 날, 월, 년으로 구성 된다. 일단 아래처럼 만든다:

    1. class Date(y: Int, m: Int, d: Int) extends Ord {
    2. def year = y
    3. def month = m
    4. def day = d
    5. override def toString(): String = year + "-" + month + "-" + day

    여기서 중요한 부분은 클래스 이름과 파라미터 뒤에 따라오는 extends Ord 선언이다. 이 선언은Date 클래스가 Ord 트레잇을 상속함을 뜻한다.

    다음으로 Object에서 상속된 equals 함수를 재정의 하여 각각의 일, 월, 년을 비교하여 같음을 올바르게 판단하도록 한다. equals의 기본 정의는 쓸모가 없다. 왜냐하면 Java와 같이 기본적인equals는 물리적 주소를 비교하기 때문이다. 최종적인 코드는 다음과 같다:

    1. override def equals(that: Any): Boolean =
    2. that.isInstanceOf[Date] && {
    3. val o = that.asInstanceOf[Date]
    4. o.day == day && o.month == month && o.year == year
    5. }

    이 함수는 미리 정의된 함수인 isInstanceOf와 asInstanceOf를 사용한다. 첫번째isInstanceOf는 Java의 instanceof 연산자와 동일한 일을 한다. 함수가 호출 된 객체가 함수의 인자로 들어온 타입의 인스턴스이면 참을 리턴한다. 두번째 asInstanceOf는 Java의 캐스트 연산자와 동일하다. 호출 된 객체가 인자로 들어온 타입의 인스턴스이면 그렇게 여겨지도록 변환하고 아니라면 ClassCastException을 발생시킨다.

    아래 마지막으로 정의된 함수는 작음을 판단하는 함수이다. 여기서는 error라는 또 다른 미리 정의된 함수가 쓰였는데, 이 함수는 주어진 에러 메시지와 함께 예외를 발생 시키는 역할을 한다.

    1. def <(that: Any): Boolean = {
    2. if (!that.isInstanceOf[Date])
    3. error("cannot compare " + that + " and a Date")
    4. val o = that.asInstanceOf[Date]
    5. (year < o.year) ||
    6. (year == o.year && (month < o.month ||
    7. (month == o.month && day < o.day)))
    8. }

    이걸로 Date 클래스의 정의가 완성되었다. 이 클래스의 인스턴스는 날짜로도 또는 비교가능한 어떤 객체로도 여겨질 수 있다. 이들은 위에서 언급한 여섯가지 비교연산을 모두 가지고 있는데,equals와 <는 Date 클래스의 정의 안에 직접 구현되어 있고 나머지는 Ord 트레잇에서 상속 받은 것이다.

    트레잇은 여기서 예로 든 경우 외에도 물론 다양하게 사용 될 수 있다. 하지만 다양한 경우들에 대하여 깊게 다루는 일은 이 문서의 범위 밖이다.

    제네릭함

    이 튜토리얼에서 다룰 Scala의 마지막 특징은 제네릭함이다. Java 프로그래머들은 Java의 제네릭 지원이 부족하기 때문에 발생한 여러가지 문제점들에 대해 잘 알고 있을 것이다. 이 문제점들은 Java 1.5에서 다뤄졌다.

    제네릭함이란 코드를 타입에 대하여 파라미터화 할 수 있는 능력이다. 이해를 돕기 위해 하나의 예를 들어 보자. 연결 리스트 라이브러리를 작성하는 프로그래머는 리스트의 원소 타입을 도대체 무엇으로 해야 할지 고민에 빠지게 된다. 이 연결 리스트는 서로 다른 많은 상황에서 사용 될 수 있기 때문에 원소의 타입이 반드시 Int 또는 반드시 Double이 될 것이라 미리 결정하는 것은 불가능하다. 이렇게 결정해 두는 일은 완전히 임의적이며 라이브러리의 사용에 있어 필요 이상의 심한 제약으로 작용 한다.

    Java 프로그래머는 어쩔 수 없이 Object를 사용하곤 한다. Object는 모든 객체의 상위 타입이기 때문이다. 하지만 이런 방법은 이상적이지 않다. intlongfloat등과 같은 기본 타입에 대해 동작하지 않으며, 연결 리스트에서 원소를 가져 올 때마다 많은 동적 타입 캐스트들을 프로그래머가 직접 삽입해 주어야 하기 때문이다.

    Scala는 이 문제를 해결하기 위한 제네릭 클래스와 제네릭 함수를 지원한다. 예제로 함께 살펴보자. 예제는 레퍼런스라는 간단한 저장구조 클래스이다. 이 클래스는 비어있거나 또는 어떤 타입의 객체를 가리키는 포인터가 된다.

    1. class Reference[T] {
    2. private var contents: T = _
    3. def set(value: T) { contents = value }
    4. def get: T = contents
    5. }

    클래스 Reference는 타입 T에 대해 파라미터화 되어있다. 타입 T는 레퍼런스의 원소 타입이다. 이 타입은 클래스 내부 여러 곳에서 나타나는데, contents 변수의 타입으로, set 함수의 인자 타입으로, 그리고 get 함수의 리턴 타입으로 사용 된다.

    위의 코드 샘플은 Scala에서 필드 변수를 만드는 내용이므로 따로 설명이 필요 없다. 한가지 흥미로운 점이 있다면 변수의 초기값이 _로 주어져 있다는 것인데, 여기서 _는 기본값을 뜻한다. 기본값은 수 타입에 대해서 0, Boolean 타입에 대해서 falseUnit 타입에 대해 (), 그리고 모든 객체 타입에 대해 null이다.

    Reference 클래스를 사용하려면 타입 파라미터 T에 대해 적당한 타입을 지정해 주어야 한다. 이 타입은 레퍼런스 안에 들어갈 원소의 타입이 된다. 예를 들어, 정수 값을 저장 할 수 있는 레퍼런스를 생성하고 사용하기 위해서는 다음과 같이 쓴다:

    1. object IntegerReference {
    2. def main(args: Array[String]) {
    3. val cell = new Reference[Int]
    4. cell.set(13)
    5. println("Reference contains the half of " + (cell.get * 2))
    6. }
    7. }

    위 예제에서 보듯 get 함수의 리턴값을 정수처럼 사용하기 위해 따로 캐스팅이 필요하지 않다. 여기서 정의된 레퍼런스는 정수를 포함하도록 선언이 되어 있으므로 정수 외에 다른 것은 넣을 수 없다.

    마치며

    우리는 지금까지 Scala 언어의 간략한 소개와 몇가지의 예제를 살펴 보았다. 흥미가 생겼다면 Scala By Example도 함께 읽어보자. 더 수준 높고 다양한 예제를 만날 수 있다. 필요 할 때마다 Scala Langauge Specification을 참고하는 것도 좋다.

    'Java > Scala' 카테고리의 다른 글

    [Scala] 스칼라 프로그래밍  (0) 2013.04.22
    posted by 뚱2

    [C#] XML Serialize Tutorial

    .Net/C# 2013. 5. 8. 16:33

    링크 : http://tech.pro/tutorial/798/csharp-tutorial-xml-serialization 

    링크 : http://msdn.microsoft.com/ko-kr/library/58a18dwa.aspx

     

    A long while ago we posted a tutorial on how to serialize objects to a binary file. While this is very useful, unfortunately the resulting file is not very human readable. In this tutorial, I'm going to demonstrate how to serialize your own objects to and from an XML file.

    Since .NET can use reflection to get property names, basic serialization is unbelievably simple. It only gets slightly difficult when you want to name your XML tags differently than your property names (but still not very hard). If you've ever used an XML serialization package in C++ like boost, tinyXML, or libXML2, you'll see how comparatively easy C# is to use.

    Let's start with a basic example. Below is an object that stores some information about a movie.

    public class Movie
    {
      public string Title
      { get; set; }
    
      public int Rating
      { get; set; }
    
      public DateTime ReleaseDate
      { get; set; }
    }

    All right, now that we have an object, let's write a function that will save it to XML.

    static public void SerializeToXML(Movie movie)
    {
      XmlSerializer serializer = new XmlSerializer(typeof(Movie));
      TextWriter textWriter = new StreamWriter(@"C:\movie.xml");
      serializer.Serialize(textWriter, movie);
      textWriter.Close();
    }

    The first thing I do is create an XMLSerializer (located in the System.Xml.Serialization namespace) that will serialize objects of type Movie. The XMLSerializer will serialize objects to a stream, so we'll have to create one of those next. In this case, I want to serialize it to a file, so I create a TextWriter. I then simply call Serialize on the XMLSerializer passing in the stream (textWriter) and the object (movie). Lastly I close the TextWriter because you should always close opened files. That's it! Let's create a movie object and see how this is used.

    static void Main(string[] args)
    {
      Movie movie = new Movie();
      movie.Title = "Starship Troopers";
      movie.ReleaseDate = DateTime.Parse("11/7/1997");
      movie.Rating = 6.9f;
    
      SerializeToXML(movie);
    }
    
    static public void SerializeToXML(Movie movie)
    {
      XmlSerializer serializer = new XmlSerializer(typeof(Movie));
      TextWriter textWriter = new StreamWriter(@"C:\movie.xml");
      serializer.Serialize(textWriter, movie);
      textWriter.Close();
    }

    After this code executes, we'll have an XML file with the contents of our movie object.

    <?xml version="1.0" encoding="utf-8"?>
    <Movie xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <Title>Starship Troopers</Title>
      <Rating>6.9</Rating>
      <ReleaseDate>1997-11-07T00:00:00</ReleaseDate>
    </Movie>

    If you noticed, all of the XML tag names are the same as the property names. If we want to change those, we can simply add an attribute above each property that sets the tag name.

    public class Movie
    {
      [XmlElement("MovieName")]
      public string Title
      { get; set; }
    
      [XmlElement("MovieRating")]
      public float Rating
      { get; set; }
    
      [XmlElement("MovieReleaseDate")]
      public DateTime ReleaseDate
      { get; set; }
    }

    Now when the same code is executed again, we get our custom tag names.

    <?xml version="1.0" encoding="utf-8"?>
    <Movie xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <MovieName>Starship Troopers</MovieName>
      <MovieRating>6.9</MovieRating>
      <MovieReleaseDate>1997-11-07T00:00:00</MovieReleaseDate>
    </Movie>

    Sometimes, in XML, you want information stored as an attribute of another tag instead of a tag by itself. This can be easily accomplished with another property attribute.

    public class Movie
    {
      [XmlAttribute("MovieName")]
      public string Title
      { get; set; }
    
      [XmlElement("MovieRating")]
      public float Rating
      { get; set; }
    
      [XmlElement("MovieReleaseDate")]
      public DateTime ReleaseDate
      { get; set; }
    }

    With this code, MovieName will now be an attribute on the Movie tag.

    <?xml version="1.0" encoding="utf-8"?>
    <Movie xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
        MovieName="Starship Troopers">
      <MovieRating>6.9</MovieRating>
      <MovieReleaseDate>1997-11-07T00:00:00</MovieReleaseDate>
    </Movie>

    Let's move on to something a little more interesting. Let's create another movie and serialize a List of them to our XML file. Here's the modified code to do just that:

    static void Main(string[] args)
    {
      Movie movie = new Movie();
      movie.Title = "Starship Troopers";
      movie.ReleaseDate = DateTime.Parse("11/7/1997");
      movie.Rating = 6.9f;
    
      Movie movie2 = new Movie();
      movie2.Title = "Ace Ventura: When Nature Calls";
      movie2.ReleaseDate = DateTime.Parse("11/10/1995");
      movie2.Rating = 5.4f;
    
      List<Movie> movies = new List<Movie>() { movie, movie2 };
    
      SerializeToXML(movies);
    }
    
    static public void SerializeToXML(List<Movie> movies)
    {
      XmlSerializer serializer = new XmlSerializer(typeof(List<Movie>));
      TextWriter textWriter = new StreamWriter(@"C:\movie.xml");
      serializer.Serialize(textWriter, movies);
      textWriter.Close();
    }

    Now we have XML that looks like this:

    <?xml version="1.0" encoding="utf-8"?>
    <ArrayOfMovie xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:xsd="http://www.w3.org/2001/XMLSchema">
      <Movie MovieName="Starship Troopers">
        <MovieRating>6.9</MovieRating>
        <MovieReleaseDate>1997-11-07T00:00:00</MovieReleaseDate>
      </Movie>
      <Movie MovieName="Ace Ventura: When Nature Calls">
        <MovieRating>5.4</MovieRating>
        <MovieReleaseDate>1995-11-10T00:00:00</MovieReleaseDate>
      </Movie>
    </ArrayOfMovie>

    Ok, so you can see how easy it is to get your objects into an XML document. Let's now look at how to read an XML document back into our objects - deserialization. The process of deserializing is very similar to what we did for serialization.

    static List<Movie> DeserializeFromXML()
    {
       XmlSerializer deserializer = new XmlSerializer(typeof(List<Movie>));
       TextReader textReader = new StreamReader(@"C:\movie.xml");
       List<Movie> movies; 
       movies = (List<Movie>)deserializer.Deserialize(textReader);
       textReader.Close();
    
       return movies;
    }

    Just like before, we first create an XmlSerializer that can deserialize objects of type List\. The XmlSerializer also deserializes from a stream, so we create a file stream from our XML file. We then simply call Deserialize on the stream and cast the output to our desired type. Now the movies List is populated with objects that we previously serialized to the XML file.

    The deserializer is very good at handling missing pieces of information in your XML file. Let's say the second movie didn't have the MovieName attribute on the Movie tag. When the XML file is deserialized, it simply populates that field with null. If MovieRating wasn't there, you'd receive 0. Since a DateTime object can't be null, if MovieReleaseDate was missing, you'd receive DateTime.MinValue (1/1/0001 12:00:00AM).

    If the XML document contains invalid syntax, like say the first opening Movie tag was missing, the Deserialize call will fail with an InvalidOperationException. It will also be kind enough to specify the location in the file where it encountered the error (line number, column number).

    One thing to remember is that the basic XML serialization won't maintain references. Let's say I populated my movies list with the same movie reference multiple times:

    Movie movie = new Movie();
    movie.Title = "Starship Troopers";
    movie.ReleaseDate = DateTime.Parse("11/7/1997");
    movie.Rating = 6.9f;
    
    List<Movie> movies = new List<Movie>() { movie, movie };

    Now I have a list containing two of the exact same movie reference. When I serialize and deserialize this list, it will be converted to two separate instances of the movie object - they would just have the same information. Along this same line, the XMLSerializer also doesn't support circular references. If you need this kind of flexibility, you should consider binary serialization.

    There's still a lot to cover when it comes to XML serialization, but I think this tutorial covers enough of the basics to get things rolling.

    '.Net > C#' 카테고리의 다른 글

    [C#] ref, out의 차이  (0) 2013.06.12
    [C#] Linq for xml tutorial  (0) 2013.05.23
    [C#] Mutex를 통한 다중 인스턴스 실행방지  (0) 2013.02.08
    [C#] Visual Studio TODO 만들기  (0) 2013.01.30
    [C#] Form close와 Dispose  (0) 2013.01.28
    posted by 뚱2

    /*

    * strTemp  : [필수] 크로스사이트 스크립팅을 검사할 문자열

    * level    : [옵션] 검사레벨

    *            0 (기본) -> XSS취약한 문자 제거

    *            1 (선택) -> 단순한 <, > 치환

    */

    function XSS_Check(strTemp, level) {     

    if ( level == undefined || level == 0 ) {

    strTemp = strTemp.replace(/\<|\>|\"|\'|\%|\;|\(|\)|\&|\+|\-/g,"");

    }

    else if (level != undefined && level == 1 ) {

    strTemp = strTemp.replace(/\</g, "&lt;");

    strTemp = strTemp.replace(/\>/g, "&gt;");

    }

    return strTemp;

    }


    posted by 뚱2

    [FFmpeg] library document

    C/C++/FFmpeg 2013. 5. 2. 00:54

    링크 : http://ffmpeg.org/doxygen/1.0/modules.html 

    'C/C++ > FFmpeg' 카테고리의 다른 글

    [FFmpeg] 옵션  (0) 2013.06.11
    [FFmpeg] 동영상의 기본적인 이해  (0) 2013.05.16
    [FFmpeg] thumbnail 추출  (0) 2013.04.29
    [FFmpeg] FFMpeg 윈도우 컴파일  (0) 2013.04.28
    [FFmpeg] FFmpeg을 이용한 동영상 인코딩  (0) 2013.04.28
    posted by 뚱2

    링크 : http://msdn.microsoft.com/en-us/library/b0084kay(v=vs.80).aspx

    posted by 뚱2

    [FFmpeg] thumbnail 추출

    C/C++/FFmpeg 2013. 4. 29. 04:30

    http://blog.daum.net/nbdjj1998/81 

    http://fendee.egloos.com/2232271


    'C/C++ > FFmpeg' 카테고리의 다른 글

    [FFmpeg] 동영상의 기본적인 이해  (0) 2013.05.16
    [FFmpeg] library document  (0) 2013.05.02
    [FFmpeg] FFMpeg 윈도우 컴파일  (0) 2013.04.28
    [FFmpeg] FFmpeg을 이용한 동영상 인코딩  (0) 2013.04.28
    [FFmpeg] FFmpeg 동영상 변환  (0) 2013.02.17
    posted by 뚱2

    VS 2010 컴파일 출처 : http://blog.naver.com/PostView.nhn?blogId=bsh0128&logNo=80162582453

    Windows ffmpeg : http://ffmpeg.zeranoe.com/builds/




    이전 버전

    ==============================================================================================================================

    출처 : http://blog.naver.com/meteoros21/130084661358

    아래 다운로드 안됨 : http://dev.naver.com/projects/npdf/download/note/777 


    1. 컴파일 환경을 위한 파일 준비

    MinGW-5.1.6.exe

    MSYS-1.0.11.exe

    coreutils-5.97-MSYS-1.0.11-snapshot.tar.bz2

    2. MinGW 설치

    설치 옵션에서 gcc 모듈 설치

    설치 위치 c:\MinGW

    C:\MinGW\bin 을 환경변수 PATH에 추가

    3. MSYS 설치

    설치 위치 C:\MSYS

    C:\MSYS\bin 을 환경변수 PATH에 추가

    VC++ 환경을 위해 C:\MSYS\msys.bat 파일 적당한 곳에 다음 라인 추가

        call "C:\Program Files\Microsoft Visual Studio 9.0\VC\bin\vcvars32.bat" 추가

    4. coreutils 설치

    압축을 푼 다음, bin 디렉터리에 존재하는 모든 파일을 C:\MSYS\bin 에 복사

    동일한 파일들이 존재하는데 덮어 쓰지말고 스킵

    5. ffmpeg 소스 준비

    http://ffmpeg.arrozcru.org/autobuilds/ 에서 32bit shared 최신 버전 다운

    압축을 풀어서 C:\MSYS\ffmpeg 디렉터리로 복사

    6. 컴파일

    msys.bat 실행하여 콘솔로 들어간다.

    $cd /ffmpeg

    $./configure --enable-shared --enable-memalign-hack

    $make

    $make install

     

    오류가 없다면 /local/bin 에 *.lib, *.dll 파일이, /local/include 아래에 *.h 파일이

    생성된다. 가져다가 윈도우 어플리케이션 개발 시 사용하면 된다.

    [출처] ffmpeg 컴파일|작성자 사진조아








    coreutils-5.97-MSYS-1.0.11-snapshot.tar.bz2


    MinGW-5.1.6.exe


    MSYS-1.0.11.exe


    'C/C++ > FFmpeg' 카테고리의 다른 글

    [FFmpeg] 동영상의 기본적인 이해  (0) 2013.05.16
    [FFmpeg] library document  (0) 2013.05.02
    [FFmpeg] thumbnail 추출  (0) 2013.04.29
    [FFmpeg] FFmpeg을 이용한 동영상 인코딩  (0) 2013.04.28
    [FFmpeg] FFmpeg 동영상 변환  (0) 2013.02.17
    posted by 뚱2

    링크 : http://helloworld.naver.com/helloworld/8794 


    NHN 동영상서비스개발2팀 김대웅

    Android는 오픈 플랫폼이어서 단말기가 제공하는 동영상 플레이어의 기능과 지원하는 포맷이 제조사마다 다릅니다. 그래서 더 효율적으로 동영상 서비스를 제공하려면 독자적인 동영상 플레이어가 있는 것이 좋습니다. 이 글에서는 오픈 소스인 FFmpeg의 레퍼런스 프로젝트인 FFPlay로 Android용 동영상 플레이어를 제작하기까지의 고민과 과정을 소개합니다.

  • Android 동영상 플레이어를 왜 만들어야 하는가?

    Android는 오픈 플랫폼이어서 다양한 제조사가 다양한 기기를 만들고 있다. 이러한 다양성은 소비자에게는 기기 선택의 폭을 넓혀 줄 수 있지만, NHN 같은 서비스 제공자 입장에서는 다양한 종류의 단말기에서 동일한 사용자 경험을 제공하려면 추가적인 노력을 들여야 한다. Android 단말기에서 기본으로 제공하는 동영상 플레이어마다 기능이 다르고, 지원하는 동영상 포맷도 다르기 때문이다.

    그래서 Android 동영상 서비스를 위해서는 독자적인 동영상 플레이어를 갖추고 있는 것이 좋다. 이를 위해 오픈 소스인 FFmpeg(http://www.ffmpeg.org/)을 이용해 Android 동영상 플레이어를 만들었다. 

  • 동영상 플레이어에 대한 배경 지식

  • 코덱, 컨테이너 그리고 플레이어

    일반적인 사용자 입장에서 생각한다면 동영상(비디오)에는 오디오가 포함되어 있지만, 전문적인 개발자 입장에서는 동영상과 오디오는 서로 다른 영역이다. MP3나 Vorbis와 같은 오디오 압축 코덱이 소리를 담당하고, 동영상을 담당하는 동영상 압축 코덱은 MPEG, MPEG-4 AVC(H.264), WMV9 등이다. 동영상 압축 코덱은 동영상 정보를 압축하는 알고리즘 종류를 지칭하는 것으로 이해하는 것이 좋다. 동영상이란 정지 영상(동영상 프레임)을 초당 수 개에서 수십 개까지 빠르게 보여 주는 것을 말하는 것이고, 압축 동영상이란 최소한의 정지 영상 정보만으로 완전한 동영상 정보를 만들어 내어 이를 재생해낼 수 있도록 하는 것이다.

    우리가 알고 있는 .mp4, .wmv, .asf, .3gpp 등의 파일은 Digital Container Format이라고 부르는 것이다. 엄밀히 말해 이들은 코덱이 아니다. Digital Container Format은 동영상/오디오 코덱을 이용하여 데이터를 저장하는 방식과 재생 동기화 정보 등의 부가 정보를 담고 있는 파일 형식을 말한다. 그리고 동영상 플레이어는 이 Digital Container Format을 읽어 동영상/오디오를 재생하는 프로그램이다.

    원본 영상 데이터로부터 특정 동영상 코덱으로 변환하는 것을 인코딩이라 하고, 변환한 데이터를 파일에 담는 것을 먹싱(muxing)이라고 한다. 동영상을 재생하려면 역순으로 해야 한다. 이때 동영상 파일로부터 비트스트림(bit-stream) 데이터를 추출하는 과정을 디먹싱(demuxing)이라고 한다. 이렇게 디먹싱하면 어떤 코덱으로 인코딩되었는지 알 수 있게 된다. 그리하여 적합한 코덱으로 디코딩하면 원본 데이터 영상을 얻을 수 있다.

  • FFmpeg

    FFmpeg은 크로스 플랫폼을 지원하는 오픈소스 멀티미디어 프레임워크이다. FFmpeg을 이용해 인코딩/디코딩, 트랜스코딩(transcode), 먹싱/디먹싱, 스트림(stream)은 물론 '재생'까지 멀티미디어와 관련한 거의 모든 기능을 다 갖추고 있다.

    FFmpeg의 라이선스는 GPL과 LGPL이다. FFmpeg에는 다음과 같은 여러 세부 라이브러리가 있다. 

    • libavcodec: 오디오/비디오의 인코더/디코더
    • libavformat: 오디오/비디어 컨테이너 포맷의 muxer/demuxer
    • libavutil: FFmpeg 개발 시 필요한 다양한 유틸리티
    • libpostproc: video post-processing
    • libswscale: 비디오의 image scaling, color-space, pixel-format 변환
    • libavfilter: 인코더와 디코더 사이에서 오디오/비디오를 변경하고 검사
    • libswresample: 오디오 리샘플링(audio resampling)

    FFmpeg을 이용하여 동영상 파일을 읽어 원본 데이터를 추출하는 과정은 다음 그림과 같다.

    ff01.png

    그림 1 FFmpeg을 이용한 demux/디코딩

    가장 먼저 libavformat을 이용하여 디먹싱을 한다. 이렇게 디먹싱하면 비트스트림 데이터를 얻는데, 이 것으로 어떤 코덱을 사용했는지 파악할 수 있다. 그 다음으로 libavcodec을 이용하여 디코딩을 한다. libavcodec은 추출한 코덱 정보를 바탕으로 적합한 디코더로 비트스트림에서 원본 데이터를 추출한다.

  • Android NDK

    Android NDK(Native Development Kit)는 C/C++와 같은 네이티브 언어로 개발한 라이브러리를 Android 앱에서 사용할 수 있게 하는 개발 도구이다. Android NDK의 구성 사항은 다음과 같다.

    • Cross-toolchains(compiler, linker 등)
    • Android Platform을 이용하기 위한 헤더 파일과 라이브러리
    • 문서와 샘플 코드

    현재 Android NDK가 지원하는 ARM instruction set은 다음과 같다.

    • ARMv5TE
    • ARMv7-A
    • X86 instructions 

    ARMv5TE machine code는 모든 ARM 기반의 Android 기기에서 동작한다. ARMv7-A는 호환되는 CPU가 탑재된 기기에서만 동작한다. 두 instruction set의 주요한 차이점은 ARMv7-A만 H/W FPU, Thumb-2, NEON instructions를 지원한다는 데 있다. Android NDK로 개발할 때에 두 instruction set 모두를 지원하거나 둘 중 하나만 지원하도록 할 수 있다.

  • FFmpeg 포팅

    FFmpeg은 C로 작성되었기 때문에 Android에서 동작시키려면 NDK를 이용해야 한다. 동영상 플레이어를 개발할 때에는 FFmpeg의 모든 library가 다 필요한 것은 아니기 때문에 필요한 libavformat과 libavcodec, libswscale만 NDK에서 빌드했다. NDK를 이용한 FFmpeg의 빌드 과정은 다음과 같다.

  • NDK 설치
  • Android 프로젝트 생성
  • FFmpeg 소스 코드 내려받기
  • FFmpeg 설정 및 config.h 파일 수정
  • Android.mk 파일 및 빌드 관련 기타 파일 작성
  • 컴파일
  • 각 과정의 자세한 설명은 아래의 링크를 참고하기 바란다.

  • Android 동영상 플레이어

    Android용으로 빌드한 FFmpeg 라이브러리로 동영상 플레이어를 개발하는 방법에는 크게 두 가지가 있다.

    첫 번째로 플레이어에 필요한 기능을 직접 개발하는 방법이다. 관련 지식을 쌓을 수 있고, 필요한 기능을 요구에 맞게 개발할 수 있는 장점이 있지만 데이터 큐(data queue), 스트림 싱크(stream sync) 등 많은 기능을 직접 모두 구현해야 하기 때문에 오랜 개발 기간이 필요하다는 단점이 있다.

    두 번째는 FFmpeg에서 레퍼런스로 제공하는 FFPlay를 Android용으로 포팅하는 방법이다. FFPlay는 FFMpeg 프로젝트의 일부로, SDL(Simple DirectMedia Layer)을 이용한 동영상 플레이어이다. SDL은 여러 그래픽, 사운드, 입력 디바이스에 대한 레이어 인터페이스를 제공하는 크로스 플랫폼 중 하나이다. Android용 SDL이 없기 때문에 동영상(비디오)/오디오 렌더링을 위해 FFPlay에서 SDL을 사용하는 부분을 수정해서 개발해야 하는 문제가 있다. 하지만 FFPlay에는 필요한 기능들이 이미 상당수가 있기 때문에 FFPlay의 Android 포팅 버전을 개발하는 것이 전체적으로 개발 시간을 줄일 수 있는 방법이다. 또한 FFPlay가 FFmpeg의 레퍼런스 프로젝트이기 때문에 FFmpeg 사용법을 잘 습득할 수 있는 기회가 될 수 있다.

    그래서 FFPlay를 Android 용으로 포팅하는 방식을 선택했다.

    ff02.png

    그림 2 FFPlay의 구조

  • Android에서 오디오 재생

    Android에서 PCM 데이터를 재생하기 위해 AudioTrack 클래스를 이용했다.

    AudioTrack 클래스는 PCM 데이터를 전달하면 JNI를 통해 AudioFlinger에 접근하여 오디오 디바이스로 소리를 출력해 준다. SDL에서는 오디오 재생(Audio rendering)에 콜백 인터페이스를 사용한다. 즉 오디오 디바이스에 출력할 데이터가 필요하면 콜백 함수가 호출되고, 필요한 만큼의 데이터를 디코딩하여 전달하는 방식이다. 하지만 AudioTrack은 콜백 인터페이스 기반이 아니기 때문에 지속적으로 데이터를 전달해 주어야 한다. 이를 위해 FFPlay에서 Audio Decoder 부분을 별도의 스레드로 동작시키고 지속적으로 PCM 데이터를 AudioTrack에 넘겨주도록 수정했다.

    ff03.png

    그림 3 Audio Rendering

  • Android에서 동영상(비디오) 렌더링

    Android에서 픽셀 데이터를 화면에 출력하기 위해 OpenGL을 이용했다.

    Android에서는 OpenGL을 이용하여 화면에 출력하려면 GLSurfaceView 클래스를 이용해야 한다. SDL에서는 화면을 그려야 할 때에 SDL에 픽셀 데이터를 넘겨 주도록 되어 있다. 그리고 Android에서 OpenGL을 이용할 때는 꼭 OpenGL 스레드에서 GLSurfaceView.Renderer.onDraw() 함수가 호출되어 화면을 출력하도록 해야 한다. 이를 위하여 기존의 Video Refresher가 스레드로 수행되면서 필요한 시점에 화면을 그렸던 방식에서, Video Refresher가 스레드로 수행되면서 필요한 시점에 GLSurfaceView 클래스에 요청(GLSurfaceView.requestRender() 함수 호출)을 보내어 GLSurfaceView.Renderer.onDraw() 함수를 호출하고 함수 내에서 픽셀 데이터를 가져와 화면에 그리는 방식으로 구조를 변경했다.

    ff04.png

    그림 4 Video Rendering

    OpenGL ES를 이용하여 화면을 그리는 과정은 다음 그림과 같다. 동영상 프레임(Video frame)을 디코딩하여 나온 결과인 픽셀 데이터는 원본 동영상 프레임보다 더 넓다(이것은 처리 성능을 향상시키기 위한 방법이고, 이렇게 넓어진 너비를 line-size라고 한다). 이렇게 나온 픽셀 데이터의 크기를 기준으로 해당 크기보다 큰 텍스처(texture)를 준비한다. 그 다음 텍스처에 픽셀 데이터를 그대로 복사하고, 원래의 동영상 프레임 크기만큼의 텍스처만 화면(screen)에 입힌다.

    ff05.png

    그림 5 OpenGL ES를 이용한 동영상 프레임 렌더링

  • Android 동영상 플레이어 최종 모습

    Android 동영상 플레이어의 개략적인 구조는 다음 그리과 같다.

    ff06.png

    그림 6 Android 동영상 플레이어의 구조

    FFPlay와 기본적인 구조는 동일하지만 앞서 설명한 대로 오디오와 비디오의 렌더링은 SDL을 이용하는 방식에서 AudioTrack과 OpenGL을 이용하는 방식으로 바꾼 것이 가장 큰 차이다.

    또 하나의 차이는 스레드 사용이다. 원래의 FFPlay는 SDL 스레드를 사용하는데, 이는 내부적으로 pthread를 사용하는 것이다. Android에서도 pthread를 이용할 수는 있지만 네이티브 코드에서 생성한 pthread의 존재를 JavaVM이 모르고 있기 때문에 여러 가지 예상치 못한 오류가 발생할 수 있고 JNI를 호출할(call) 수 없다.


    참조

    JNI와 pthread에 대한 자세한 내용은 "JNI Tips" 문서를 참조한다.

    이러한 문제점을 피하고 NDK에서 스레드를 사용하기 위해서는 생성된 pthread를 AttachCurrentThread() 함수를 사용하여 JavaVM에게 알려주거나, Java 스레드를 생성한 후 해당 스레드에서 수행하려하는 함수를 JNI를 이용하여 실행하도록 하는 방법이 있다. Android 동영상 플레이어는 개발 과정의 디버깅 편의성 등을 고려하여 Java 스레드를 이용하여 개발했다.


  • 성능 개선

    FFmpeg을 이용해 디코딩하여 나온 픽셀 데이터는 YUV 픽셀 데이터이다. 하지만 OpenGL을 이용해 렌더링을 하려면 RGB 픽셀 데이터가 필요하다. FFmpeg에서는 다음 그림과 같이 libswscale을 이용해 YUV 픽셀 데이터를 RGB 픽셀 데이터로 변환하는 색공간 변환 작업을 할 수 있다.

    ff07.png

    그림 7 libswscale을 이용한 픽셀 데이터 변환

    하지만 libswscale은 CPU를 이용하여 변환 작업을 진행하기 때문에, 비록 변환 계산은 단순해도 변환해야 할 데이터의 양이 많아 수행 성능이 좋지 않다. 이를 개선하기 위하여 Shader를 사용했다. Shader는 CPU가 아닌 GPU를 사용한다. Shader에는 vertex를 변환하는 vertex shader와 픽셀을 변환하는 fragment shader가 있다. 색 공간의 변환은 픽셀 단위로 이루어지기 때문에 fragment shader를 이용했다. 관련 코드는 다음과 같다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    uniform sampler2D sampler0;
    uniform sampler2D sampler1;
    uniform sampler2D sampler2;
     
     
     
     
    varying highp vec2 _texcoord;
     
     
     
     
    void main()
    {
    highp float y = texture2D(sampler0, _texcoord).r;
    highp float u = texture2D(sampler1, _texcoord).r;
    highp float v = texture2D(sampler2, _texcoord).r;
     
     
     
     
    y = 1.1643 * (y - 0.0625);
    u = u - 0.5;
    v = v - 0.5;
     
     
     
     
    highp float r = y + 1.5958 * v;
    highp float g = y - 0.39173 * u - 0.81290 * v;
    highp float b = y + 2.017 * u;
     
     
     
     
    gl_FragColor = vec4(r, g, b, 1.0);
    }
      

    Shader를 이용하여 색 공간을 변환하면 다음 그림과 같은 구조가 된다. GPU는 CPU와 달리 단순 반복 연산에서 더욱 좋은 성능을 내기 때문에 좀 더 빠른 변환이 가능하다. 또한 변환 과정 중 CPU는 다른 작업을 수행하도록 할 수 있는 장점이 있다. 그 결과 동영상 플레이어 재생 능력이 libswscale을 썼을 때보다 더 향상되었다.

    ff08.png

    그림 8 OpenGL ES의 shader를 이용한 pixel data 변환

  • 마치며

    더 나은 동영상 서비스를 준비하기 위하여 모바일 동영상 플레이어를 자체 개발할 필요가 있다고 판단했고, FFmpeg을 이용해 Android 동영상 플레이어를 개발했다. 동영상 플레이어를 개발하는 과정에서는 FFmpeg을 비롯한 많은 오픈 소스의 도움을 받았기 때문에 개발 과정을 공개하는 것이 좋다고 생각했다. 비록 이 글을 읽는 독자 분들이 하는 일이 동영상이나 모바일과는 크게 관계가 없어도, 사용한 기술과 의사 결정 과정 등이 다른 분야의 업무에도 도움이 될 수 있을 것이라 생각한다.

    앞으로도 개발 경험이나 새로운 기술에 대한 내용을 공유할 수 있는 기회가 더 있으면 한다. 궁금한 내용은 언제든 문의해 주기를 바란다.



  • 'C/C++ > FFmpeg' 카테고리의 다른 글

    [FFmpeg] 동영상의 기본적인 이해  (0) 2013.05.16
    [FFmpeg] library document  (0) 2013.05.02
    [FFmpeg] thumbnail 추출  (0) 2013.04.29
    [FFmpeg] FFMpeg 윈도우 컴파일  (0) 2013.04.28
    [FFmpeg] FFmpeg 동영상 변환  (0) 2013.02.17
    posted by 뚱2

    [VC++] Tray Icon Animation

    C/C++/VC++ / MFC 2013. 4. 26. 14:21

    링크 : http://ospace.tistory.com/169 




    posted by 뚱2

    이런 문서를 번역해 주신 이동국님께 감사드립니다.



    iBATIS-SqlMaps-2_ko.pdf



    MyBatis-3-User-Guide_ko.pdf


    posted by 뚱2

    링크 : http://ko.wikipedia.org/wiki/%EC%9D%98%EC%82%AC%EC%BD%94%EB%93%9C 

    의사코드(슈도코드, pseudocode)는 특정 프로그래밍 언어의 문법을 따라 씌여진 것이 아니라, 일반적인 언어로 코드를 흉내내어 알고리즘을 써놓은 코드를 말한다. 의사(疑似)코드는 말그대로 흉내만 내는 코드이기 때문에, 실제적인 프로그래밍 언어로 작성된 코드처럼 컴퓨터에서 실행할 수 없으며, 특정 언어로 프로그램을 작성하기 전에 알고리즘의 모델을 대략적으로 모델링하는 데에 쓰인다.

    의사코드는 실제 프로그래밍 언어처럼 엄밀한 문법을 따를 필요가 없기 때문에 다양한 변종이 존재한다. 그러나 보통 사용자가 많은 C나 리스프포트란 프로그래밍 언어등의 문법을 본딴 모양이 많다. 엄밀한 묘사가 불필요한 부분에는 자연어가 자유롭게 쓰이기도 한다.

    컴퓨터 과학의 전공 서적에서는 다양한 언어 구사자들이 모두 이해할 수 있도록 특히 의사코드를 많이 사용하여 설명한다. 또한 보통 의사코드는 저자마다 그 문법이 다르기 때문에, 의 서두에는 의사코드의 문법이 간략히 설명되어 있기도 하다.

    posted by 뚱2