[C++] chilkatsoft

C/C++/VC++ / MFC 2014. 8. 10. 22:12

링크: http://www.chilkatsoft.com/refdoc/


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

[C++] Crypto++  (0) 2014.07.25
[WinDBG] 사용시 참고할만한 사이트  (0) 2014.01.03
[VC++] dependency walker  (0) 2013.08.07
[VC++] Device Info  (0) 2013.06.13
[VC++] Getting the PropertyData of ManagementObject in C++  (0) 2013.06.12
posted by 뚱2

[C++] Crypto++

C/C++/VC++ / MFC 2014. 7. 25. 22:23

링크: http://cdecl.tistory.com/277

링크: http://maytrees.tistory.com/83

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

[C++] chilkatsoft  (0) 2014.08.10
[WinDBG] 사용시 참고할만한 사이트  (0) 2014.01.03
[VC++] dependency walker  (0) 2013.08.07
[VC++] Device Info  (0) 2013.06.13
[VC++] Getting the PropertyData of ManagementObject in C++  (0) 2013.06.12
posted by 뚱2

링크 : http://chogar.blog.me/80204671432

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

[C++] chilkatsoft  (0) 2014.08.10
[C++] Crypto++  (0) 2014.07.25
[VC++] dependency walker  (0) 2013.08.07
[VC++] Device Info  (0) 2013.06.13
[VC++] Getting the PropertyData of ManagementObject in C++  (0) 2013.06.12
posted by 뚱2

1. 디먹싱 (Demuxing)
    : 파일(DCF)에서 비디오, 오디오 데이터 스트림을 추출
2. 디코딩 (Deconding)
    : 특정 동영상 코덱을 사용하여 원본 영상 데이터를 얻는 것
3. 인코딩 (Encoding)
    : 원본 영상 데이터로부터 특정 동영상 코덱으로 변환하는 것
2. 먹싱 (Muxing)
    : 변환한 데이터(오디오, 비디오)를 파일(DCF)에 담는 것을 먹싱

* DCF (Digital Container Format)
  동영상/오디오 코덱을 이용하여 데이터를 저장하는 방식과 재생 동기화 정보등의 부가 정보를 담고 있는 파일 형식


결국 H.264로 변환한다는건

          디먹싱                               디코딩                                    인코딩                                                먹싱           
.wmv                                                                            
.asf     ------> 비트스트림 데이터 추출 ------> 원본 데이터 (동영상,오디오) ------> 특정 동영상 코텍(H.264)으로 변환  ------> .mp4      
.avi                                                                              

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

[FFmpeg] FFmpeg x264EncodingGuide  (0) 2013.12.06
[FFmpeg] 동영상 정보 확인 하는 프로그램  (0) 2013.08.15
[FFmpeg] Water Mark  (0) 2013.06.29
[FFmpeg] 옵션  (0) 2013.06.11
[FFmpeg] 동영상의 기본적인 이해  (0) 2013.05.16
posted by 뚱2

링크 : https://trac.ffmpeg.org/wiki/x264EncodingGuide

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

[FFmpeg] 인코딩 과정 정리  (0) 2014.01.03
[FFmpeg] 동영상 정보 확인 하는 프로그램  (0) 2013.08.15
[FFmpeg] Water Mark  (0) 2013.06.29
[FFmpeg] 옵션  (0) 2013.06.11
[FFmpeg] 동영상의 기본적인 이해  (0) 2013.05.16
posted by 뚱2

링크 : http://mediaarea.net/ko/MediaInfo/Download/Windows

 

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

[FFmpeg] 인코딩 과정 정리  (0) 2014.01.03
[FFmpeg] FFmpeg x264EncodingGuide  (0) 2013.12.06
[FFmpeg] Water Mark  (0) 2013.06.29
[FFmpeg] 옵션  (0) 2013.06.11
[FFmpeg] 동영상의 기본적인 이해  (0) 2013.05.16
posted by 뚱2

[VC++] dependency walker

C/C++/VC++ / MFC 2013. 8. 7. 08:50

링크 : http://www.dependencywalker.com/

 

해당프로그램이 사용하는 참조 모듈들을 알려주는 프로그램

 

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

[C++] Crypto++  (0) 2014.07.25
[WinDBG] 사용시 참고할만한 사이트  (0) 2014.01.03
[VC++] Device Info  (0) 2013.06.13
[VC++] Getting the PropertyData of ManagementObject in C++  (0) 2013.06.12
[VC++] 윈도우 OS Bit 판별하기  (0) 2013.05.31
posted by 뚱2

[FFmpeg] Water Mark

C/C++/FFmpeg 2013. 6. 29. 18:30

링크 : http://www.idude.net/index.php/how-to-watermark-a-video-using-ffmpeg/ 


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

[FFmpeg] FFmpeg x264EncodingGuide  (0) 2013.12.06
[FFmpeg] 동영상 정보 확인 하는 프로그램  (0) 2013.08.15
[FFmpeg] 옵션  (0) 2013.06.11
[FFmpeg] 동영상의 기본적인 이해  (0) 2013.05.16
[FFmpeg] library document  (0) 2013.05.02
posted by 뚱2

[VC++] Device Info

C/C++/VC++ / MFC 2013. 6. 13. 11:07

장치관리자 Device Tree: http://www.codeproject.com/Articles/6597/CDeviceTree 

Enumerate Properties: http://www.codeproject.com/Articles/6866/Enumerate-Properties-of-an-Installed-Device

장치 열거하기 (한글) : http://blog.naver.com/cra2yboy?Redirect=Log&logNo=90165203152

posted by 뚱2

링크 : http://social.msdn.microsoft.com/Forums/en-US/vcgeneral/thread/1ba183a5-71bb-4601-beb6-73ba20b087cd/

 

IWbemService Interface : http://msdn.microsoft.com/en-us/library/aa392093(v=vs.85).aspx

MSDN 예제 : http://msdn.microsoft.com/en-us/library/aa390425(v=VS.85).aspx

Query : http://msdn.microsoft.com/en-us/library/aa392902(v=vs.85).aspx

WMI 데이터판독기 작업 :  http://sqlmvp.kr/140163038957

 

WMI를 이용한 프로세스 감시 : http://blog.naver.com/adsloader?Redirect=Log&logNo=50142012166

posted by 뚱2

[FFmpeg] 옵션

C/C++/FFmpeg 2013. 6. 11. 15:27

링크 : http://blog.daum.net/nbdjj1998/83 


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

[FFmpeg] 동영상 정보 확인 하는 프로그램  (0) 2013.08.15
[FFmpeg] Water Mark  (0) 2013.06.29
[FFmpeg] 동영상의 기본적인 이해  (0) 2013.05.16
[FFmpeg] library document  (0) 2013.05.02
[FFmpeg] thumbnail 추출  (0) 2013.04.29
posted by 뚱2

현재 윈도우가 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

[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

[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://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

    [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

    링크 : http://msdn.microsoft.com/en-us/library/ms997619.aspx 


    Design Specifications and Guidelines - Visual Design

    4 out of 7 rated this helpful Rate this topic

    Layout

    Size, spacing, and placement of information are critical in creating a visually consistent and predictable environment. Visual structure is also important for communicating the purpose of the elements displayed in a window. In general, follow the layout conventions for how information is read. In Western countries, this means left-to-right, top-to-bottom, with the most important information located in the upper left corner.

    The system defines the size and location of user interface elements in a window based on dialog units (DLUs), not pixels. A dialog unit is the device-independent measure to use for layout. One horizontal dialog unit is equal to one-fourth of the average character width for the current system font. One vertical dialog unit is equal to one-eighth of an average character height for the current system font. The default height for most single-line controls is 14 DLUs. Be careful if you use a pixel-based drawing program, because it may not provide an accurate representation when you translate your design into dialog units. If you do use a pixel-based drawing tool, you may want to take screen snapshots from a development tool that supports dialog units and use those images.

    Cross ReferenceMore Information

    Your application can retrieve the number of pixels per base unit for the current display using the GetDialogBaseUnits function. For more information about this function, see the Microsoft Platform SDK on the MSDN Online Web site.

    Size

    The following table lists the typical height and width of common dialog box controls.

    Size of Common Dialog Box Controls
    ControlHeight (DLUs)Width (DLUs)
    Dialog boxes and property sheets263 max. (for 640 x 480 screen resolution)
    218 
    215 
    188
    263 max. (for 640 x 480 screen resolution)
    252 
    227 
    212
    (For property sheets, heights include 25 DLUs for property sheet button bars.)
    Command buttons1450
    Check boxes10As wide as needed
    Drop-down combo box and drop-down list10Size to match other drop-down combo boxes and text boxes
    Option buttons10As wide as needed
    Text boxes14Size to match other drop-down combo boxes and text boxes
    Text labels8 per line of textAs wide as needed
    Other screen text8 per line of textAs wide as needed

    Cross ReferenceMore Information

    To support localization, you should make controls wider than just enough to display the labels. For more information, see Chapter 15, "Special Design Considerations."

    Toolbars and their buttons use pixels instead of dialog units for their measurement. The recommended sizes are shown in the following table.

    Size of Toolbars and Toolbar Buttons
    ControlHeight (pixels)Width (pixels)
    Toolbars in small button mode23Width of toolbar area or window
    Toolbars in large button mode28Width of toolbar area or window
    Small toolbar buttons21Depends on content; 22 if the button includes only an image
    Large toolbar buttons26Depends on content; 28 if the button includes only an image

    When you cannot reasonably apply the size guidelines for secondary windows, try to maintain a width within a task. This can provide a smooth transition, making it easier for a user to focus on the task. Also, always check to make sure that the window will fit in the minimum screen resolution set by your application's users. Typically, this means using a 640 x 480 resolution screen to ensure that it fits completely. You must also take into account the possible space taken up by the task bar and other desktop toolbars.

    Make buttons a consistent length for readability. However, if maintaining this consistency greatly expands the space required for a set of buttons, it may be reasonable to have one button larger than the rest.

    Similarly, if you use tabs, try to maintain a consistent width for all tabs in the same window (and in the same dimension). However, if a particular tab's label makes this unworkable, size it larger and maintain a smaller, consistent size for the other tabs. If a tab's label contains variable text, you can size the tab to fit the label, up to some reasonable maximum, after which you truncate the text and add an ellipsis.

    Try to maintain a consistent width between text boxes and the list boxes they appear near, using only one or two different widths per group or window. If you localize your application, you should extend text, option button labels, and check box labels to be as wide as the group or window, where possible. This will reduce the work necessary to localize your interface.

    Spacing and Positioning

    Maintain a consistent margin from the edge of the window — seven dialog units is recommended. Use spacing between groups within the window, as shown in Figure 14.27.

    Recommended layout and spacing of controls and text

    Figure 14.27 Recommended layout and spacing of controls and text (click to enlarge image)

    The following table lists the typical items found in an interface and the recommended spacing between them.

    Spacing Between Interface Items
    Interface itemsUse this spacing (DLUs)
    Dialog box margins7 on all sides
    Between paragraphs of text7
    Between text labels and their associated controls (for example, text boxes and list boxes)3
    Between related controls4
    Between unrelated controls7
    First control in a group box11 down from the top of the group box; align vertically to the group box title
    Between controls in a group box4; align vertically to the group box title
    Between horizontally or vertically arranged buttons4; align vertically to the group box title
    From the left edge of a group box9; if the group box is left-aligned, controls are 16 from the left edge of the dialog box or property page
    Last control in a group box7 above the bottom of the group box
    Smallest space between controls2
    Text label beside a button3 down from the top of the button
    Check box, list box, or option button beside a button2 down from the top of the button

    Toolbars and their buttons use pixels instead of DLUs. The following table provides spacing for toolbar buttons.

    Spacing for toolbar buttons
    Button SizeSpacing
    Small (16 x 16 pixel image) toolbar buttons3 pixels between a button and its text label 
    2 pixels above the toolbar image 
    3 pixels below the toolbar image
    Large (20 x 20 pixel image) toolbar buttons3 pixels between a button and its text label 
    2 pixels above the toolbar image 
    2 pixels below the toolbar image

    In general, for controls that do not contain their own labels, place the label to the left or above the related control. This makes it easier for users to associate the label with the corresponding control.

    When a text box is the first item in the group box, use a smaller measurement so the visual spacing above and to the right looks equal. In cases where there are controls below a group box, align the controls to the edge of the group box above and use seven DLUs between the bottom edge of the group box and the control (or text), as shown in Figure 14.28.

    Example of group box spacing

    Figure 14.28 Example of group box spacing (click to enlarge image)

    Position controls in a toolbar so that there is at least a window's border width from the edges of the toolbar, as shown below.

    Minimum border width

    (click to enlarge image)

    Use at least 4 DLUs between controls, except for between a set of related toolbar buttons. There should be no space between adjacent toolbar buttons, such as a set of related option buttons.

    For wizard design, Figure 14.29 shows suggested positioning and spacing.

    Positioning and spacing in a wizard

    Figure 14.29 Positioning and spacing in a wizard (click to enlarge image)

    Grouping

    Group related components — you can use group box controls, separator lines, or spacing. Although you can also use color to visually group objects, it is not a common convention and could result in undesirable effects if the user changes color schemes.

    A group box provides a strong visual element for related items. However, avoid using a group box when you have only one set of related items or where the group box may take too much space or add visual clutter rather than structure. Instead, consider using separators to group related items. Property sheets for files and folders are a good illustration of the use of separators rather than group boxes.

    Stack the main command buttons in a secondary window in the upper right corner or in a row along the bottom, as shown in Figure 14.30. If there is a default button, it is typically the first button in the set. Place OK and Cancel buttons next to each other. If there is no OKbutton but there are command buttons that initiate action, place the Cancel button at the end of the buttons but before a Help button. If a particular command button applies only to a particular field, group it with that field.

    Cross referenceMore Information

    For more information about button placement in secondary windows, see Chapter 9, "Secondary Windows."

    Layout of buttons

    Figure 14.30 Layout of buttons (click to enlarge image)

    Group controls so that their location helps users understand the associated context or scope. For tabbed pages, follow these guidelines:

    • When command buttons and other controls apply only to that page, place them within the border of the tabbed page.
    • When command buttons and other controls apply to the entire window, place them outside the tabbed page.

    Alignment

    When information is positioned vertically, align fields by their left edges (in western countries). This usually makes it easier for the user to scan the information. Text labels are usually left-aligned and placed above or to the left of the areas to which they apply. When placing text labels to the left of text box controls, align the top of the text with text displayed in the text box.

    In group boxes, controls should be left-aligned with the text label of the group. However, command buttons in the group should be right-aligned.

    Align command buttons in most secondary windows at the top right or right-align them with the bottom. The exception is for message boxes, where command buttons should be centered. In toolbar arrangements, buttons and other controls are typically left- or top-aligned, depending on the layout of the area.

    Required and Optional Input

    For input form design, you may want to require certain input fields or controls and make others optional. To help users distinguish required input from optional input, provide some form of visual differentiation. The best way to do this is to separate the two sets of input into separate windows, panes, or groups and label the fields accordingly. However, this may not always work with the type of information you are presenting. The next best way is to label the individual fields with the words "required" or "optional" in parentheses. You can also use fonts, symbols, or graphics; however, such conventions require the user to learn the convention in order to use the application effectively. In scenarios where you cannot rely on training the user, use a more obvious form of identification. Do not use color unless you are using some other form of feedback as well. Color may attract the user's attention, but the perception of color can vary. Therefore, do not rely on it as the only means of identification.

    Preview and Sample Boxes

    In some situations, you may want to provide an area for a visual example of changes a user is making to an item, as shown in Figure 14.31.

    Preview or sample box

    Figure 14.31 Preview or sample box (click to enlarge image)

    sample is a representation of what might show up on screen, but it does not show the actual data that the user is working on. In contrast, a preview shows the user's actual data.

    Include text, graphics, or both in your preview or sample boxes. The preview can be illustrative and interactive. If the preview is interactive, include instructions or some visual cue to let the user know that it is interactive.

    Include a label for your preview or sample box, and keep the wording for the label brief. A one- or two-word label (often Preview or Sample) is usually sufficient unless the user needs to interact with the preview to update it. Use sentence-style capitalization for the label, but do not include ending punctuation unless the user can interact with the preview, in which case end the label with a colon.

    Appendixes and References

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

    [VC++] Visual Studio Predefine Macro  (0) 2013.05.01
    [VC++] Tray Icon Animation  (0) 2013.04.26
    [VC++] 모듈 정의 파일(.def)을 이용해서 EXPORTS 시키기  (0) 2013.03.08
    [C++] C++11  (0) 2013.01.29
    [COM] WebBrowser Customization  (0) 2012.12.29
    posted by 뚱2

    외부 프로그램과 연동하는 dll을 만들어야 했는데 .h, .lib를 같이 배포했기 때문에 __declspec을 사용했었습니다.

     

    다른 방법으로는 모듈 정의 파일을 이용하는 방법도 있습니다.

     

     

     

     

     

    프로젝트 속성 -> 구성속성 -> 링커 -> 입력 -> 모듈 정의 파일

    posted by 뚱2

    링크 : http://ffmpeg.org/ffmpeg.html 

     

    튜토리얼 : http://dranger.com/ffmpeg/tutorial01.html 

     

    위키 : http://ko.wikipedia.org/wiki/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.04.28
    posted by 뚱2

    [C++] C++11

    C/C++/VC++ / MFC 2013. 1. 29. 09:22

    링크 : http://ko.wikipedia.org/wiki/C++11 

    posted by 뚱2