[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

[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

[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

현재 윈도우가 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://msdn.microsoft.com/en-us/library/b0084kay(v=vs.80).aspx

    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

    [C++] C++11

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

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

    posted by 뚱2

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


    18 out of 34 rated this helpful Rate this topic

    This tutorial shows you several ways to customize the "out of the box" behavior and appearance of the WebBrowser Control. You'll see how to use the advanced hosting interfaces IDocHostUIHandlerIDocHostUIHandler2IDocHostShowUI, and ICustomDoc. This article will also look at other customization techniques such as download control through handling DISPID_AMBIENT_DLCONTROL in the host's IDispatch implementation, and using IHostDialogHelper.

    This article is divided into the following sections:

    Prerequisites and Requirements

    In order to understand and use this tutorial, you need:

    • A good understanding of C++ and the Component Object Model (COM).
    • Familiarity with the Active Template Library (ATL).
    • Microsoft Internet Explorer 6 or later installed.
    • Header files and libraries for Internet Explorer 6 or later for use in your development environment; in particular, you need Mshtmhst.h.

    Many of the WebBrowser customization features have been available since Internet Explorer 5 and Internet Explorer 5.5. Only a couple require Internet Explorer 6. One more requires Internet Explorer 6 for Windows XP Service Pack 2 (SP2). Check the reference documentation for versioning information.

    Introduction

    Hosting the WebBrowser Control is a powerful tool for rapid application development. Through hosting, you can take advantage of easy-to-use technologies—Dynamic HTML (DHTML), HTML, and XML—for displaying content and developing a UI. However, "out of the box," the WebBrowser Controlmay not behave quite the way you want. For instance, in its default state the user can view the source of a displayed page by right-clicking and choosing the View Source option on the shortcut menu. You might want to disable or eliminate that option. You might also want to go even further and replace the default shortcut menu entirely with your own custom menu.

    Aside from the customizations just mentioned, the advanced hosting features of Windows Internet Explorer enable you to:

    • Use buttons and other controls on a displayed page to call internal methods in your application, effectively extending the DHTML Object Model.
    • Change drag-and-drop behavior.
    • Control which pages your application can access, restricting navigation, for example, to specify pages, domains, or sites.
    • Intercept user keystrokes and process them however you want. For example, you might intercept CTRL+O to prevent the user from opening a new page in Internet Explorer rather than the host application.
    • Change default font and display settings.
    • Control the kinds of content that are downloaded and what the WebBrowser Control does with them once they are downloaded. For example, you can prevent videos from playing, script from running, or new windows from opening when users click links, or prevent Microsoft ActiveX controls from downloading or executing.
    • Restrict View Source.
    • Capture searches.
    • Capture navigation errors.
    • Replace or modify shortcut menus and shortcut menu entries—disabling, replacing, customizing, or adding to them.
    • Change registry settings for your application.
    • Intercept and change messages dialogs shown by the WebBrowser Control.
    • Control how new windows are created.

    In the following sections, we'll look at many, though not all, of these possibilities and discuss how to implement them.

    WebBrowser Customization Architecture

    Introducing IDocHostUIHandler, IDocHostUIHander2, IDocHostShowUI, and ICustomDoc

    Three interfaces are at the heart of WebBrowser Control UI customization: IDocHostUIHandlerIDocHostUIHandler2, and IDocHostShowUI. These are interfaces that you implement in your application when you want to modify the WebBrowser Control. There are also a couple of service interfaces.ICustomDoc is implemented by MSHTML and provides a means to enable WebBrowser Control customization in some scenarios. IHostDialogHelperprovides a means to open trusted dialog boxes, without identification that marks them as Internet Explorer dialog boxes.

    In addition to using these interfaces, there are two other things you can do. For one, you can control download by intercepting ambient property changes in your IDispatch implementation. Second, you can control how new windows are created by intercepting DISPID_NEWWINDOW2 in your IDispatchimplementation.

    How It Works

    The mechanism for WebBrowser Control customization is designed to be automated when a container provides support for ActiveX controls. Whenever theWebBrowser Control is instantiated, it attempts to find IDocHostUIHandlerIDocHostUIHandler2 and IDocHostShowUI implementations from the host, if they are available. The WebBrowser Control does this by a QueryInterface call on the host's IOleClientSite interface.

    This architecture works automatically for an application that implements an IOleClientSite interface and that passes an IOleClientSite pointer to theWebBrowser Control through the browser's IOleObject::SetClientSite method. A typical instantiation of the WebBrowser Control might look like this:

    // Error checking omitted for clarity
    CComPtr<IOleObject> spOleObj;
    
    // Create WebBrowser--store pointer in class member variable m_spWebBrowser
    CoCreateInstance(CLSID_WebBrowser, NULL, CLSCTX_INPROC, IID_IWebBrowser2, (void**)&m_spWebBrowser);
    
    // Query WebBrowser for IOleObject pointer
    m_spWebBrowser->QueryInterface(IID_IOleObject, (void**)&spOleObj);
    
    // Set client site
    spOleObj->SetClientSite(this);
    
    // In-place activate the WebBrowser control
    RECT rcClient
    GetClientRect(&rcClient);
    spOleObj->DoVerb(OLEIVERB_INPLACEACTIVATE, NULL, this, 0, GetTopLevelWindow(), &rcClient);
    
    // Register container to intercept WebBrowser events
    AtlAdvise(m_spWebBrowser, GetUnknown(), DIID_DWebBrowserEvents2, &m_dwCookie);
    
    // Navigate to start page
    m_spWebBrowser->Navigate(L"res://webhost.exe/startpage.htm", NULL, NULL, NULL, NULL);
    
    

    However, if your application doesn't have an IOleClientSite interface, all is not lost. Internet Explorer provides the ICustomDoc interface so you can pass Internet Explorer your IDocHostUIHandler interface yourself. You cannot use IDocHostUIHandler2 and IDocHostShowUI without providing anIOleClientSite interface on the object hosting the WebBrowser Control.

    When the WebBrowser Control holds a pointer to any of these interfaces, the methods of the interfaces are called at appropriate times during the lifetime of the WebBrowser Control. For instance, when the user right-clicks with the mouse anywhere in the WebBrowser Control's client area, your implementation of IDocHostUIHandler::ShowContextMenu will be called before Internet Explorer displays its default shortcut menu. This gives you a chance to display your own shortcut menu and prevent Internet Explorer from displaying its menu.

    There are a few more important points to remember when initializing the WebBrowser Control. Your application should use OleInitialize rather thanCoInitialize to start COM. OleInitialize enables support for the Clipboard, drag-and-drop operations, OLE, and in-place activation. Use OleUninitialize to close the COM library when your application shuts down.

    The ATL COM Wizard uses CoInitialize rather than OleInitialize to open the COM libraries. If you use this wizard to build an executable program, you need to change the CoInitialize and CoUninitialize calls to OleInitialize and OleUninitialize. For a Microsoft Foundation Classes (MFC) application, be sure that your application calls AfxOleInit, which calls OleInitialize, in its initialization process.

    If you don't want drag-and-drop support in your application, you can call IWebBrowser2::RegisterAsDropTarget, passing in VARIANT_TRUE, to prevent any drag-and-drop operations on your WebBrowser Control instance.

    An application hosting the WebBrowser Control will also need an implementation of IOleInPlaceSite, and since IOleInPlaceSite derives from IOleWindow, the application will also need an implementation of IOleWindow. You need these implementations so your application has a window in which to display theWebBrowser Control and so you can manage its display.

    The implementations of these interfaces and IOleClientSite can be minimal or nonexistent in many cases. The IOleClientSite methods can all return E_NOTIMPL. Some of the IOleInPlaceSite and IOleWindow methods need an implementation beyond their return value. See the code sample for an example of a minimal implementation of IOleInPlaceSite and IOleWindow.

    Now that we have covered the initialization preliminaries, let's take a look at each of the interfaces for WebBrowser Control customization.

    IDocHostUIHandler

    IDocHostUIHandler has been available since Internet Explorer 5. It provides fifteen methods. In general, some of the more important methods areIDocHostUIHandler::GetExternalIDocHostUIHandler::GetHostInfoIDocHostUIHandler::GetOptionKeyPathIDocHostUIHandler::ShowContextMenu, andIDocHostUIHandler::TranslateAccelerator. Of course, the methods that are most important to you will depend on your application.

    IDocHostUIHandler::GetHostInfo

    You use IDocHostUIHandler::GetHostInfo to tell MSHTML about your application's capabilities and requirements. With it you can control a variety of things, for instance:

    • You can disable the browser's 3-D border.
    • You can prevent scroll bars or change their appearance.
    • You can define how you want to handle double-clicks.

    IDocHostUIHandler::GetHostInfo has one parameter, a pointer to a DOCHOSTUIINFO structure allocated by MSHTML. Your job is to fill this structure with the information you want to pass to MSHTML.

    There are four members in the DOCHOSTUIINFO structure. The first member is cbSize, which is the size of the structure. You should set this yourself as the following code sample shows. The second member is dwFlags, which can take any number of flag values, combined with the bitwise OR operator, from theDOCHOSTUIFLAG enumeration. The third member is dwDoubleClick, which takes a value from the DOCHOSTUIDBLCLK enumeration. The fourth member ispchHostCss. You can set pchHostCss to a pointer to a string with Cascading Style Sheets (CSS) rules that you want to apply globally to any page displayed in the WebBrowser control. The final member of DOCHOSTUIINFO is pchHostNs. This is also a string pointer with which you can provide a semicolon-delimited list of namespaces. Use this member when you use custom tags on the pages you're displaying in the WebBrowser Control. This way you can have a global declaration for namespaces and don't have to declare them on each displayed page.

    Be sure to use CoTaskMemAlloc to allocate strings for pchHostCss or pchHostNS.

    HRESULT GetHostInfo(DOCHOSTUIINFO *pInfo)
    {
        WCHAR* szCSS = L"BODY {background-color:#ffcccc}";
        WCHAR* szNS = L"IE;MyTags;MyTags2='www.microsoft.com'";
    
        #define CCHMAX 256
        size_t cchLengthCSS, cchLengthszNS;
    
        HRESULT hr = StringCchLengthW(szCSS, CCHMAX, &cchLengthCSS);
        // TODO: Add error handling code here.
        OLECHAR* pCSSBuffer = (OLECHAR*)CoTaskMemAlloc((cchLengthCSS + 1) * sizeof(OLECHAR));
        // TODO: Add error handling code to make sure memory block was allocated successfully.
    
        hr = StringCchLengthW(szNS, CCHMAX, &cchLengthszNS);
        // TODO: Add error handling code here.
        OLECHAR* pNSBuffer = (OLECHAR*)CoTaskMemAlloc((cchLengthszNS + 1) * sizeof(OLECHAR));
        // TODO: Add error handling code to make sure memory block was allocated successfully.
    
        hr = StringCchCopyW(pCSSBuffer, cchLengthCSS + 1, szCSS);
        // TODO: Add error handling code here.
        hr = StringCchCopyW(pNSBuffer, cchLengthszNS + 1, szNS);
        // TODO: Add error handling code here.
    
        pInfo->cbSize = sizeof(DOCHOSTUIINFO);
        pInfo->dwFlags = DOCHOSTUIFLAG_NO3DBORDER | DOCHOSTUIFLAG_SCROLL_NO;
        pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT;
        pInfo->pchHostCss = pCSSBuffer;
        pInfo->pchHostNS = pNSBuffer;
    
        return S_OK;
    }
    

    If you have nothing special to say to MSHTML, you can return E_NOTIMPL from this method.

    IDocHostUIHandler::ShowContextMenu

    By implementing this method, you gain control over the shortcut menus displayed by the WebBrowser Control when a user right-clicks. You can prevent Internet Explorer from displaying its default menu by returning S_OK from this method. Returning some other value, like S_FALSE or E_NOTIMPL, allows Internet Explorer to go ahead with its default shortcut menu behavior.

    If you return S_OK from this method and nothing more, you can prevent any right-click behavior by the WebBrowser Control. This may be all you desire in many scenarios but you can do more. Often, you use this method to create and display your own shortcut menu before returning S_OK. If you know the resources from which the WebBrowser Control displays its menus, and how it chooses them, you can also effectively customize the default WebBrowser Control shortcut menus themselves.

    Refer to WebBrowser Customization (Part 2) for an implementation example of this method.

    IDocHostUIHandler::GetExternal: Extending the DOM

    IDocHostUIHandler provides you with a means to extend the Internet Explorer Document Object Model (DOM) with objects, methods, and properties of your own, implemented in your own application. You do this by providing MSHTML a pointer to the IDispatch interface for the COM automation object that implements your custom object, properties, and methods. These objects, properties, and methods will then be available to any page displayed in theWebBrowser Control through the document's external object.

    You can easily implement this method, assuming your IDispatch interface is on the same object that implements IDocHostUIHandler.

    HRESULT CBrowserHost::GetExternal(IDispatch **ppDispatch) 
    {
        *ppDispatch = this;
        return S_OK;
    }
    
    

    Once MSHTML has a pointer to your IDispatch, MSHTML will pass any calls on Web pages to automation methods on your application through the externalobject:

    <SCRIPT language="JScript">
    function MyFunc(iSomeData)
    {
        external.MyCustomMethod("Some text", iSomeData);
    }
    </SCRIPT>
    
    

    You can also use this technique to pass whole objects to a page. To do this, create a method in your IDispatch implementation that passes back the object you want to make available.

    <SCRIPT language="JScript">
    function MyFunc(iSomeData)
    {
        var oCustCalendarObj;
        external.GetCustomCalender(oCustCalenderObj);
        oCustCalerdarObj.doStuffWithIt();
        .
        .
        .
    }
    </SCRIPT>
    
    

    See the sample application for an example of IDispatch automation implementation using ATL.

    IDocHostUIHandler::GetOptionKeyPath

    IDocHostUIHandler::GetOptionKeyPath is a very powerful tool for customizing the WebBrowser Control. Many of the WebBrowser Control display and behavior settings are stored in the registry under the HKEY_CURRENT_USER key. IDocHostUIHandler::GetOptionKeyPath gives you an opportunity to override these registry settings for your specific instance of the WebBrowser Control. It does this by letting you supply an alternate registry location from which the WebBrowser Control will read in registry settings.

    An implementation of IDocHostUIHandler::GetOptionKeyPath passes a string to the WebBrowser Control for the registry location you want it to read from. The WebBrowser Control will look for this key under the HKEY_CURRENT_USER key.

    HRESULT CBrowserHost::GetOptionKeyPath(LPOLESTR *pchKey, 
                                           DWORD dwReserved) 
    {
        HRESULT hr;
    
        #define CCHMAX 256
        size_t cchLength;
    
        if (pchKey)
        {
            WCHAR* szMyKey = L"Software\\MyCompany\\MyApp";
            hr = StringCchLengthW(szMyKey, CCHMAX, &cchLength);
            // TODO: Add error handling code here.
            
            *pchKey = (LPOLESTR)CoTaskMemAlloc((cchLength + 1) * sizeof(WCHAR));
            if (*pchKey)
                hr = StringCchCopyW(*pchKey, cchLength + 1, szKey);
                // TODO: Add error handling code here.
    
            hr = (*pchKey) ? S_OK : E_OUTOFMEMORY;
        }
        else
            hr = E_INVALIDARG;
    
        return hr;
    }
    

    As with IDocHostUIHandler::GetHostInfo, be sure to allocate memory for your strings using CoTaskMemAlloc.

    Telling the WebBrowser Control where to look for your registry settings is the first step—actually, it's the second step as far as program execution is concerned. Your program must set the keys at the location specified by IDocHostUIHandler::GetOptionKeyPath so the WebBrowser Control can read them. There are a variety of ways to do this. One way would be with a registry script that runs when the application is installed. Another way might be to do it programmatically when the application starts. Here's a function that sets keys to change the default font, size, and color.

    HRESULT SetSomeKeys()
    {
        HKEY hKey = NULL;
        HKEY hKey2 = NULL;
        HKEY hKey3 = NULL;
        DWORD dwDisposition = NULL;
        LONG lResult = NULL;
        #define CBMAX 256
        size_t cbLength;
    	
        RegCreateKeyEx(HKEY_CURRENT_USER, _T("Software\\MyCompany\\MyApp"),
                       NULL, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE,
                       NULL, &hKey, &dwDisposition);
    
        RegCreateKeyEx(hKey, _T("Main"), NULL, NULL, REG_OPTION_NON_VOLATILE,
                       KEY_SET_VALUE, NULL, &hKey2, &dwDisposition);
    
        RegSetValueEx(hKey2, _T("Use_DlgBox_Colors"), NULL, REG_SZ,
                      (CONST BYTE*)_T("no"), sizeof(_T("no")));
    
        RegCloseKey(hKey2);
    
        RegCreateKeyEx(hKey, _T("Settings"), NULL, NULL, REG_OPTION_NON_VOLATILE,
                       KEY_SET_VALUE, NULL, &hKey2, &dwDisposition);
    
        RegSetValueEx(hKey2, _T("Anchor Color"), NULL, REG_SZ,
                      (CONST BYTE*)_T("0,255,255"), sizeof(_T("0,255,255")));
    
        RegSetValueEx(hKey2, _T("Text Color"), NULL, REG_SZ,
                      (CONST BYTE*)_T("255,0,255"), sizeof(_T("255,0,255")));
    
        RegCloseKey(hKey2);
    
        RegCreateKeyEx(hKey, _T("International\\Scripts"), NULL, NULL,
                       REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL,
                       &hKey2, &dwDisposition);
    
        BYTE bDefaultScript = 0x3;
    
        RegSetValueEx(hKey2, _T("Default_Script"), NULL, REG_BINARY,
                      &bDefaultScript, sizeof(bDefaultScript));
    
        RegCreateKeyEx(hKey2, _T("3"), NULL, NULL, REG_OPTION_NON_VOLATILE,
                       KEY_SET_VALUE, NULL, &hKey3, &dwDisposition);
    
        BYTE bSize = 0x4; // Value from 0 - 4. 2 is medium.
        TCHAR* szFontName = _T("Comic Sans MS");
        TCHAR* szFixedFontName = _T("Courier");
    	
        HRESULT hr = StringCbLength(szFontName, CBMAX, &cbLength);
        // TODO: Add error handling code here.
        RegSetValueEx(hKey3, _T("IEPropFontName"), NULL, REG_SZ,
                      (CONST BYTE*)szFontName, cbLength + sizeof(TCHAR));
    
        hr = StringCbLength(szFixedFontName, CBMAX, &cbLength);
        // TODO: Add error handling code here.
        RegSetValueEx(hKey3, _T("IEFixedFontName"), NULL, REG_SZ,
                      (CONST BYTE*)szFixedFontName, cbLength + sizeof(TCHAR));
    
        RegSetValueEx(hKey3, _T("IEFontSize"), NULL, REG_BINARY, &bSize, sizeof(bSize));
    
        RegCloseKey(hKey3);
        RegCloseKey(hKey2);
        RegCloseKey(hKey);
    
        return S_OK;
    }
    
    

    IDocHostUIHandler2

    IDocHostUIHandler2 has a single method, IDocHostUIHandler2::GetOverrideKeyPath. It performs a function very similar toIDocHostUIHandler::GetOptionKeyPath. It points your hosted WebBrowser to registry settings to modify the default Internet Explorer registry settings. An implementation of IDocHostUIHandler2::GetOverrideKeyPath will look much the same as an implementation of IDocHostUIHandler::GetOptionKeyPath.

    GetOptionKeyPath and GetOverrideKeyPath Compared

    The difference between IDocHostUIHandler::GetOptionKeyPath and IDocHostUIHandler2::GetOverrideKeyPath is subtle, but significant. If you implementIDocHostUIHandler::GetOptionKeyPath, your WebBrowser Control instance will ignore any user settings for Internet Explorer. These settings are stored in the registry under HKEY_CURRENT_USER/Software/Microsoft/Internet Explorer. If you implement IDocHostUIHandler2::GetOverrideKeyPath, yourWebBrowser Control instance will incorporate any user settings—font settings, menu extensions, and so forth—into the way it displays and behaves.

    To illustrate the difference between IDocHostUIHandler::GetOptionKeyPath and IDocHostUIHandler2::GetOverrideKeyPath, refer to the code example for adding extensions to the context menu in WebBrowser Customization (Part 2). Recall the line:

    spCT->Exec(&CGID_ShellDocView, SHDVID_ADDMENUEXTENSIONS, 0, &var1, &var2);
    

    If you've implemented IDocHostUIHandler::GetOptionKeyPath, this line would add none of the menu extension information that is stored in the registry for the current user. Instead, it would only add menu items that you have created. If you've implemented IDocHostUIHandler2::GetOverrideKeyPath, this line would add any extensions defined for the current user under HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\MenuExt  unless you explicitly supply an empty or alternative MenuExt key in your custom registry key.

    Controlling Navigation

    You may have wondered why the section on IDocHostUIHandler didn't mention IDocHostUIHandler::TranslateUrl as a method to implement when you wish to control page navigation. The reason is that this method is not the most general purpose technique with which to control navigation. Unless you are hosting MSHTML directly, this method will have no effect on navigation. Instead, you can control navigation by implementing your IDispatch::Invoke method to handle DISPID_BEFORENAVIGATE2. As an example, the following code prevents navigation to a particular URL, displaying the standard "Navigation Canceled" error page if the user attempts to do so.

    case DISPID_BEFORENAVIGATE2:
    {
        // Is navigation to specified Url disallowed? 
        ATLASSERT((*pDispParams).rgvarg[5].vt = VT_BYREF | VT_BSTR);
        CComBSTR url = ((*pDispParams).rgvarg)[5].pvarVal->bstrVal;
        if (url == "http://www.adatum.com" || url == "http://www.adatum.com/")
        {
            // If so, navigate the browser frame to standard resource page 
            CComQIPtr<IWebBrowser2> spBrowser = ((*pDispParams).rgvarg)[6].pdispVal;
            if (spBrowser != NULL)
            {
                static const CComBSTR newURL = L"res://ieframe.dll/navcancl.htm";
                spBrowser->Navigate(newURL, NULL, NULL, NULL, NULL);
                // Set Cancel parameter to TRUE to cancel the current event
                *(((*pDispParams).rgvarg)[0].pboolVal) = TRUE;
            }
        }
        break;
    }
    

    Keep in mind that event parameters are passed to Invoke in the opposite order as they appear in the event description. Therefore, the pDisp and Urlparameters are elements 6 and 5 of the rgvarg array, respectively. The Cancel parameter (the last parameter of the event) is the first element in the array.

    IDocHostShowUI

    This interface gives you control over the message boxes and help files displayed by the WebBrowser Control. It works the same way as IDocHostUIHandlerand IDocHostUIHandler2 in that you implement it so the WebBrowser Control can call your IDocHostShowUI methods before it displays any messages or help menus of its own. This gives you a chance to prevent the WebBrowser Control from displaying anything and enables you to display your own custom message or help instead. IDocHostShowUI has two methods, IDocHostShowUI::ShowMessage and IDocHostShowUI::ShowHelp.

    IDocHostShowUI::ShowMessage

    Return S_OK to disable WebBrowser Control messages. Any other return value, like S_FALSE or E_NOTIMPL, allows the WebBrowser Control to display with its message box.

    One nice thing you can do with this method is customize the message box captions for your application so they don't read "Microsoft Internet Explorer." You can do this by comparing the caption string in lpstrCaption with the string resource Internet Explorer uses, which is stored in Shdoclc.dll. It is identified by the symbol IDS_MESSAGE_BOX_TITLE, whose value is 2213. The following code snippet shows how you might do this.

    HRESULT CBrowserHost::ShowMessage(HWND hwnd,
                                      LPOLESTR lpstrText,
                                      LPOLESTR lpstrCaption,
                                      DWORD dwType,
                                      LPOLESTR lpstrHelpFile,
                                      DWORD dwHelpContext,
                                      LRESULT *plResult) 
    {
        USES_CONVERSION;
        TCHAR pBuffer[50];
    
        // resource identifier for window caption "Microsoft Internet Explorer"
        #define IDS_MESSAGE_BOX_TITLE 2213
    
        // Load Shdoclc.dll and the IE message box title string
        HINSTANCE hinstSHDOCLC = LoadLibrary(TEXT("SHDOCLC.DLL"));
    
        if (hinstSHDOCLC == NULL)
        {
            // Error loading module -- fail as securely as possible
            return;
        }
        LoadString(hinstSHDOCLC, IDS_MESSAGE_BOX_TITLE, pBuffer, 50);
    
        // Compare the IE message box title string with lpstrCaption
        // If they're the same, substitute your own Caption
        if (_tcscmp(OLE2T(lpstrCaption), pBuffer) == 0)
            lpstrCaption = L"Custom Caption";
    
        // Create your own message box and display it
        *plResult = MessageBox(OLE2T(lpstrText), OLE2T(lpstrCaption), dwType);
    
        // Unload Shdoclc.dll and return
        FreeLibrary(hinstSHDOCLC);
        return S_OK;
    }
    
    
    security note Security Alert  Using LoadLibrary incorrectly can compromise the security of your application by loading the wrong DLL. Refer to the LoadLibrarydocumentation for information on how to correctly load DLLs with different versions of Windows.

    IDocHostShowUI::ShowHelp

    This method is called whenever Internet Explorer Help would be shown, for instance when the F1 key is pressed, and works analogously toIDocHostShowUI::ShowMessage. Return S_OK to override Internet Explorer Help, or another HRESULT value to let Internet Explorer proceed with its Help.

    Controlling Download and Execution

    The WebBrowser Control gives you control over what it downloads, displays, and executes. To gain this control, you need to implement your host'sIDispatch so it handles DISPID_AMBIENT_DLCONTROL. When the WebBrowser Control is instantiated, it will call your IDispatch::Invoke with this ID. SetpvarResult to a combination of following flags, using the bitwise OR operator, to indicate your preferences.

    • DLCTL_DLIMAGES, DLCTL_VIDEOS, and DLCTL_BGSOUNDS: Images, videos, and background sounds will be downloaded from the server and displayed or played if these flags are set. They will not be downloaded and displayed if the flags are not set.
    • DLCTL_NO_SCRIPTS and DLCTL_NO_JAVA: Scripts and Java applets will not be executed.
    • DLCTL_NO_DLACTIVEXCTLS and DLCTL_NO_RUNACTIVEXCTLS : ActiveX controls will not be downloaded or will not be executed.
    • DLCTL_DOWNLOADONLY: The page will only be downloaded, not displayed.
    • DLCTL_NO_FRAMEDOWNLOAD: The WebBrowser Control will download and parse a frameSet, but not the individual frame objects within theframeSet.
    • DLCTL_RESYNCHRONIZE and DLCTL_PRAGMA_NO_CACHE: These flags cause cache refreshes. With DLCTL_RESYNCHRONIZE, the server will be asked for update status. Cached files will be used if the server indicates that the cached information is up-to-date. With DLCTL_PRAGMA_NO_CACHE, files will be re-downloaded from the server regardless of the update status of the files.
    • DLCTL_NO_BEHAVIORS: Behaviors are not downloaded and are disabled in the document.
    • DLCTL_NO_METACHARSET_HTML: Character sets specified in meta elements are suppressed.
    • DLCTL_URL_ENCODING_DISABLE_UTF8 and DLCTL_URL_ENCODING_ENABLE_UTF8: These flags function similarly to theDOCHOSTUIFLAG_URL_ENCODING_DISABLE_UTF8 and DOCHOSTUIFLAG_URL_ENCODING_ENABLE_UTF8 flags used withIDocHostUIHandler::GetHostInfo. The difference is that the DOCHOSTUIFLAG flags are checked only when the WebBrowser Control is first instantiated. The download flags here for the ambient property change are checked whenever the WebBrowser Control needs to perform a download.
    • DLCTL_NO_CLIENTPULL: No client pull operations will be performed.
    • DLCTL_SILENT: No user interface will be displayed during downloads.
    • DLCTL_FORCEOFFLINE: The WebBrowser Control always operates in offline mode.
    • DLCTL_OFFLINEIFNOTCONNECTED and DLCTL_OFFLINE: These flags are the same. The WebBrowser Control will operate in offline mode if not connected to the Internet.

    DISPID_AMBIENT_DLCONTROL and the flag values are defined in mshtmdid.h.

    Initially, when the function call to IDispatch::Invoke starts, the VARIANT to which the parameter pvarResult points is of type VT_EMPTY. You must switch the type to VT_I4 for any settings to have an effect. You can place your flag values in the lVal member of the VARIANT.

    Most of the flag values have negative effects, that is, they prevent behavior that normally happens. For instance, scripts are normally executed by theWebBrowser Control if you don't customize its behavior. But if you set the DLCTL_NOSCRIPTS flag, no scripts will execute in that instance of the control. However, three flags—DLCTL_DLIMAGES, DLCTL_VIDEOS, and DLCTL_BGSOUNDS—work exactly opposite. If you set flags at all, you must set these three for the WebBrowser Control to behave in its default manner vis-a-vis images, videos and sounds.

    The following code sample causes a WebBrowser Control instance to download and display images and videos, but not background sounds, since the DLCTL_BGSOUNDS is not explicitly set. Also, script execution on pages displayed by the WebBrowser Control is disabled.

    STDMETHODIMP CAtlBrCon::Invoke(DISPID dispidMember, REFIID riid,
                                   LCID lcid, WORD wFlags, 
                                   DISPPARAMS* pDispParams,
                                   VARIANT* pvarResult, 
                                   EXCEPINFO* pExcepInfo,
                                   UINT* puArgErr)
    {
        switch (dispidMember)
        {
        case DISPID_AMBIENT_DLCONTROL:
            pvarResult->vt = VT_I4;
            pvarResult->lVal = DLCTL_DLIMAGES | DLCTL_VIDEOS | DLCTL_NO_SCRIPTS;
            break;
    
        default:
            return DISP_E_MEMBERNOTFOUND;
        }
    
        return S_OK;
    }
    
    

    IHostDialogHelper

    IHostDialogHelper is an interface you can use to create dialog boxes according to your liking. This interface has one method,IHostDialogHelper::ShowHTMLDialog. This method provides the same service as the function ShowHTMLDialog, but it's a little easier to use.

    To use IHostDialogHelper, you create the dialog helper object from scratch. Here's how you would do it using CoCreateInstance. The interface and IDs are defined in mshtmhst.h.

    IHostDialogHelper* pHDH;
    IMoniker* pUrlMoniker;
    BSTR bstrOptions = SysAllocString(L"dialogHeight:30;dialogWidth:40");
    BSTR bstrPath = SysAllocString(L"c:\\dialog.htm");
    
    CreateURLMoniker(NULL, bstrPath, &pUrlMoniker);
    
    // Create the dialog helper object
    CoCreateInstance(CLSID_HostDialogHelper, 
                     NULL, 
                     CLSCTX_INPROC,
                     IID_IHostDialogHelper, 
                     (void**)&pHDH);
    
    // Call ShowHTMLDialog to create your dialog box
    pHDH->ShowHTMLDialog(NULL, 
                         pUrlMoniker, 	
                         NULL, 
                         bstrOptions,
                         NULL, 
                         NULL);
    
    // Free resources
    SysFreeString(bstrPath);
    SysFreeString(bstrOptions);
    pUrlMoniker->Release();
    pHDH->Release();
    
    

    Controlling New Windows

    One important way to take control of the WebBrowser Control is to control navigation. You saw earlier how you can intercept DISPID_BEFORENAVIGATE2 in an IDispatch::Invoke implementation to control where your WebBrowser Control will navigate. Another important aspect of navigation is to control how the navigation occurs, especially when opening new windows. Let's say, for instance, that the user clicks the right mouse button over a link and chooses "Open in New Window" or that a page contains a script like this:

    window.open("www.msn.com");
    
    

    By default, the WebBrowser Control deals with this code by opening a new instance of Internet Explorer to display the page. This may be fine for your application. But then again, it may not. Perhaps you'll want all links to open in your current WebBrowser Control instance. Or perhaps you'll want to open a link in a new WebBrowser Control instance under your control, with your user interface and with your branding.

    You can intercept an event, DWebBrowserEvents2::NewWindow2, in your IDispatch implementation to control this. Your control needs to connect to theDWebBrowserEvents2 connection point to intercept this event.

    Once you're connected to DWebBrowserEvents2, implement your IDispatch::Invoke so that it handles DISPID_NEWWINDOW2. During the IDispatch::Invokefunction call for DISPID_NEWWINDOW2, the array pDispParams contains two parameters. The first one, at index zero, is a Boolean value that tells theWebBrowser Control whether to cancel the new window or not. By default, it is FALSE and a new window will open. If you want to cancel new window creation completely, set the flag to TRUE.

    The parameter at index one is a pointer to an IDispatch interface. You can set this parameter to the IDispatch of a WebBrowser Control that you've created. When you pass back an IDispatch like this, MSHTML will use the control you've given it to open the link.

    Information Bar

    Internet Explorer 6 for Windows XP SP2 introduced a new security UI element called the Information bar. The Information bar displays a UI element to users of Internet Explorer when certain actions are prevented. Specifically, it displays when the following are blocked.

    • Pop-up window instantiation (see Pop-up Blocker)
    • File downloads (see File Download Restrictions)
    • ActiveX control installation (see ActiveX Restrictions)
    • ActiveX control security prompts because of the user's security settings or because the control is not marked safe for scripting
    • Files that have a mismatch between the file name extension and the MIME type (see MIME Handling)
    • Content restricted by the network protocol lockdown (see Protocols)
    • Internet Explorer 7 or later. Windows opened by the Microsoft JScript prompt method and the Microsoft Visual Basic Scripting Edition (VBScript) InputBox Function.

    The Information bar is one of the feature controls introduced in Internet Explorer 6 for Windows XP SP2. Like the other feature controls, it is managed through a registry key (FEATURE_SECURITYBAND). Internet Explorer (iexplorer.exe) and Windows Explorer (explorer.exe) run under this feature control by default. The following shows the registry key and enabled processes.

    HKEY_LOCAL_MACHINE (or HKEY_CURRENT_USER)
         SOFTWARE
              Microsoft
                   Internet Explorer
                        Main
                             FeatureControl
                                  FEATURE_SECURITYBAND
                                       iexplorer.exe = 0x00000001
                                       explorer.exe = 0x00000001
                                       process name.exe = 0x00000001

    The FEATURE_SECURITYBAND feature control only affects whether Internet Explorer displays the Information bar, which alerts the user that an action has been mitigated. It does not control the mitigation of the action.

    An application hosting the WebBrowser Control can enable the Information bar by adding its process to this registry key. This can be done programmatically by using the CoInternetSetFeatureEnabled function. If an application does not run under this feature control, the WebBrowser Controlbehaves the same as Internet Explorer 6 SP1b.

    There is no access to this feature through script.

    Applications running under the FEATURE_SECURITYBAND and related feature controls can also use the Information bar APIs to customize the UI displayed when a URL action is disallowed. There are several new OLECMDID commands for the Information bar. The first three are part of the CGID_DocHostCommandHandler group. Hosting applications should implement IOleCommandTarget on their implementation of IDocHostUIHandler to receive IOleCommandTarget::Exec calls from the WebBrowser Control.

    • OLECMDID_PAGEACTIONBLOCKED
    • OLECMDID_PAGEACTIONUIQUERY
    • OLECMDID_FOCUSVIEWCONTROLS

    Hosting applications can use the following two new OLECMDID commands to make IOleCommandTarget::Exec calls on the WebBrowser Control.

    • OLECMDID_FOCUSVIEWCONTROLSQUERY
    • OLECMDID_SHOWPAGEACTIONMENU
    This example uses IWebBrowser2::ExecWB to execute the OLECMDID_SHOWPAGEACTIONMENU command.
       POINT pt = { 0 };
       GetCursorPos(&pt);
    
       CComVariant varHwnd((LONG)hwnd);
       CComVariant varX(pt.x);
       CComVariant varY(pt.y);
    
       SAFEARRAY* psa = SafeArrayCreateVector(VT_VARIANT, 0, 3);
    
       LONG lIndex = 0;
       SafeArrayPutElement(psa, &lIndex, &varHwnd);
       lIndex++;
       SafeArrayPutElement(psa, &lIndex, &varX);
       lIndex++;
       SafeArrayPutElement(psa, &lIndex, &varY);
    
       CComVariant varArgIn;
       V_VT(&varArgIn) = VT_ARRAY | VT_I4;
       V_ARRAY(&varArgIn) = psa;
    
       pBrowser-&lt;>*Fix pointer operator!!ExecWB(OLECMDID_SHOWPAGEACTIONMENU, (OLECMDEXECOPT)dwPageActionFlags, &varArgIn, NULL);
    

    Also, an application can override the default security zone settings by implementing IInternetSecurityManager. See Implementing a Custom Security Managerfor more information.

    Conclusion

    You now have a number of techniques at your disposal to customize the WebBrowser Control. This article is by no means exhaustive, but hopefully you also now have a sense of some of the possibilities you may discover on your own beyond the techniques in this article. Check out the Internet Explorer registry to see the kinds of information stored there that you can modify with IDocHostUIHandler::GetOptionKeyPath or IDocHostUIHandler2::GetOverrideKeyPath. Keep in mind that many registry settings are interdependent with others. You may have to do some experimentation to discover how particular registry settings can be customized effectively. You can also look into IDocHostUIHandler::GetDropTarget if you want to control what the WebBrowser Control does during drag-and-drop operations.

    Related Topics

    8 out of 10 rated this helpful Rate this topic

    Part 1 of this tutorial introduced several of the standard hosting interfaces for the WebBrowser ControlIDocHostUIHandlerIDocHostShowUI, andICustomDoc, to name a few. Part 2 of this article explains how to retrieve a reference to the client site while processing IPersistMoniker::Load, and how to use the IServiceProvider and IOleCommandTarget interfaces to send messages between the Active document (DocObject) and its host.

    IPersistMoniker and IBindCtx

    When the WebBrowser Control determines that a hyperlinked resource is associated with a helper application, it creates an instance of the target application and prompts the Active document (DocObject) to load the resource by using the IPersistMoniker interface. Before the browser navigates to a document, it registers a reference to its IOleClientSite interface in the binding context by calling IBindCtx::RegisterObjectParam. This reference allows the DocObject to retrieve and use the client site during the call to IPersistMoniker::Load.

    The following example demonstrates how to retrieve and set the client site from the binding context:

    HRESULT CDoc::Load(BOOL fFullyAvailable, IMoniker *pimkName, LPBC pibc, DWORD grfMode)
    {
        HRESULT hr = S_OK;
        CComPtr<IUnknown> spUnk;
        CComPtr<IBindCtx> spBindCtx;
    
        // SHDOCVW puts the client site in the bind context.
        if (pibc)
        {
            pibc->GetObjectParam(SZ_HTML_CLIENTSITE_OBJECTPARAM, &spUnk);
            if (spUnk)
            {
                CComQIPtr<IOleClientSite> spSite(spUnk);
                
                if (spSite)
                {
                    hr = SetClientSite(spSite);
                }
            }
        }
    }
    

    IServiceProvider

    The IServiceProvider interface is a generic access mechanism to locate a GUID-identified service that is provided through a control or any other object. Using IServiceProvider::QueryService, the caller specifies a service identifier (SID), the IID of the desired interface, and the address of the interface pointer variable that is set upon a successful return.

    In many ways, IServiceProvider::QueryService functions like QueryInterface. However, the more adaptable IServiceProvider mechanism enables an implementer to delegate the query to one of its member objects, or to hand off the request to a callback hierarchy that searches for an object to support the requested IID. In this way, the implementer is not required to recognize the requested interfaces. In common usage, IServiceProvider::QueryService is used to improve the discoverability of some interfaces.

    QueryService Example

    For example, a call to QueryService with SID_SShellBrowser retrieves the nearest shell browser, such as the Windows Internet Explorer WebBrowser Control. If the DocObject is hosted in the search pane, this technique will prevent it from mistakenly executing commands on the main browser window.SID_SShellBrowser service is specific to the WebBrowser control; any DocObject that implements the IBrowserService interface should respond to queries for this service.

    The following example demonstrates this basic scenario:

        CComPtr<IServiceProvider> pSvc; 
        CComPtr<IShellBrowser>    pShb;
        HRESULT                   hr;
        
        hr = m_pUnk->QueryInterface(IID_IServiceProvider, &pSvc);
        if (S_OK == hr && pSvc)
        {
            hr = pSvc->QueryService(SID_SShellBrowser, IID_IShellBrowser, &pShb);
            if (E_NOINTERFACE == hr)
                return;   // pSvc released automatically.
    
            // . . . Use the interface here.
        }
    

    CLSID_CMarkup

    The special CLSID_CMarkup GUID is used to determine whether an object is a native MSHTML markup object. No code except Internet Explorer should use this CLSID. Your QueryService or QueryInterface implementation should return E_NOINTERFACE if invoked with this GUID.

    DEFINE_GUID(CLSID_CMarkup, 0x3050F4FB, 0x98B5, 0x11CF, 0xBB, 0x82, 0, 0xAA, 0, 0xBD, 0xCE, 0x0B);
    
    Note   The MSHTML implementation uses a non-standard QueryInterface for CLSID_CMarkup to obtain an object pointer without adding a reference. In other words, the out parameter ppv does not receive an interface pointer; it contains the address of the object behind pUnknown.

    IOleCommandTarget

    The IOleCommandTarget interface enables objects and their containers to dispatch commands to each other. Available commands are defined by integer identifiers from a command group, which is itself identified by command group ID (also a GUID). The interface enables a caller to query for one or more supported commands within a group and to issue a command to the object.

    The WebBrowser Control uses the IOleCommandTarget interface extensively. The following sections highlight just a few of the ways that the client site can communicate with its control.

    Showing a Certificate Dialog

    With Microsoft Internet Explorer 6 for Windows XP Service Pack 2 (SP2) and later, you can show the Certificate dialog box when the user is viewing a valid Secure Hypertext Transfer Protocol (HTTPS) site. This has the same result as the user clicking the lock icon in Internet Explorer. You can use theDWebBrowserEvents2::SetSecureLockIcon event to show your own icon.

    #define SHDVID_SSLSTATUS 33
    
    IOleCommandTarget *pct;
    if (SUCCEEDED(pWebBrowser2->QueryInterface(IID_IOleCommandTarget, (void **)&pct)))
    {
       pct->Exec(&CGID_ShellDocView, SHDVID_SSLSTATUS, 0, NULL, NULL);
       pct->Release();
    }
    

    Controlling Navigation (Revisited)

    If the MSHTML control is aggregated, the controlling DocObject is in a position to regulate navigation events. The fact that a document can navigate on its own implies that it will also take care of updating the navigation history.

    In Internet Explorer 6 and later, the DocObject can indicate to the client site that it can navigate using CGID_DocHostCmdPriv (a privately defined command group GUID) and the DOCHOST_DOCCANNAVIGATE command. A pointer to the object that implements the IHTMLWindow2 interface is passed with the command in the VARIANTARG* parameter pvaIn. (Set pvaIn to NULL if the document cannot perform its own navigation.)

    DEFINE_GUID(CGID_DocHostCmdPriv, 0x000214D4L, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
    #define DOCHOST_DOCCANNAVIGATE	0
    
    // Create variant, and initialize with a pointer to our IUnknown.
    VARIANTARG var;
    
    V_VT(&var)      = VT_UNKNOWN;
    V_UNKNOWN(&var) = (IUnknown*)this;
    
    // Execute IOleCommandTarget with command group and ID.
    m_pCmdTarg->Exec(&CGID_DocHostCmdPriv, DOCHOST_DOCCANNAVIGATE, 0L, &var, NULL);
    

    Specifying the Zone Icon

    In conjunction with CGID_Explorer, the SBCMDID_MIXEDZONE command is used to determine the zone icon and text displayed in the Internet Explorer status bar.

    Active document objects hosted in Internet Explorer 6 for Windows XP SP2 or later can respond to the SBCMDID_MIXEDZONE command to determine the zone icon and text displayed in the Internet Explorer status bar. The document object should implement IOleCommandTarget, and Internet Explorer callsIOleCommandTarget::Exec with the CGID_Explorer command group.

    To specify the zone icon:

    1. Define SBCMDID_MIXEDZONE.

      #define SBCMDID_MIXEDZONE 39
      
    2. Initialize pvaOut with VariantInit.

    3. Determine the zone you want to specify and then set pvaOut as follows. To specify:

      • Internet, Intranet, Trusted or Untrusted zones: set pvaOut->vt to VT_UI4, and set pvaOut->ulVal to URLZONE_INTERNET, URLZONE_INTRANET, URLZONE_TRUSTED or URLZONE_UNTRUSTED respectively.
      • A mixed zone: set pvaOut->vt to VT_NULL.
      • An unknown zone: set pvaOut->vt to VT_EMPTY.

    If an active document does not handle SBCMDID_MIXEDZONE, the default behavior will show Unknown Zone in the status bar.

    security note Security Alert  If you cannot reliably determine your document's zone, you should not handle this command.

    More Examples of Commands

    The IDM_CONTEXT command (CGID_EditStateCommands command group) is used to determine whether editing commands should be routed to the host first. An IOleCommandTarget implementation returns S_OK to indicate that editing commands should be routed to the host first.

    If the host is designed to handle editing commands, it will respond to the SID_SEditCommandTarget service identifier by providing an IOleCommandTargetinterface to process editing commands. The editing commands are defined in the CGID_MSHTML command group, as shown in the following code.

    DEFINE_GUID(CGID_MSHTML, 0xde4ba900, 0x59ca, 0x11cf, 0x95, 0x92, 0x44, 0x45, 0x53, 0x54, 0, 0);
    

    For more information on editing commands, see Introduction to MSHTML Editing and MSHTML Command Identifiers reference.

    Context Menus and Extensions

    The IDocHostUIHandler::ShowContextMenu method was introduced in part one of this article. The IDocHostUIHandler interface is designed to be overridden by applications that host the WebBrowser control. It is not intended for use by Browser Helper Objects (BHOs) because, in addition to the problem of appending items to the standard menu discussed below, only one add-on at a time can override IDocHostUIHandler and multiple add-ons can easily conflict with each other.

    Important    In Internet Explorer 7, the technique for overriding the context menu from a DocObject host is the same as Internet Explorer 6; however, the host must implement its own menu resources. The internal resources of Internet Explorer should not be used as they may change or move (as has been done in Internet Explorer 7). See Appending Extensions to Your Custom Menu below.

    The following examples demonstrate how to use the IOleCommandTarget interface with SHDVID_GETMIMECSETMENU and SHDVID_ADDMENUEXTENSIONS (CGID_ShellDocView command group) to add and remove custom menu options from the standard Internet Explorer context menu:

    #define SHDVID_GETMIMECSETMENU   27
    #define SHDVID_ADDMENUEXTENSIONS 53 
    

    In Internet Explorer 6 and earlier, the WebBrowser Control gets its context menu resources from Shdoclc.dll. The following code loads the WebBrowser Control shortcut menu resource from Shdoclc.dll, chooses the correct menu for the context, loads language and extension resources from the registry, removes the menu item corresponding to the IDM_VIEWSOURCE command, and then displays the pop-up menu.

    HRESULT CBrowserHost::ShowContextMenu(DWORD dwID,
                                         POINT *ppt,
                                         IUnknown *pcmdTarget,
                                         IDispatch *pdispObject) 
    {
       #define IDR_BROWSE_CONTEXT_MENU  24641
       #define SHDVID_GETMIMECSETMENU   27
       #define SHDVID_ADDMENUEXTENSIONS 53
    
       HRESULT hr;
       HINSTANCE hinstSHDOCLC;
       HWND hwnd;
       HMENU hMenu;
       CComPtr<IOleCommandTarget> spCT;
       CComPtr<IOleWindow> spWnd;
       MENUITEMINFO mii = {0};
       CComVariant var, var1, var2;
    
       hr = pcmdTarget->QueryInterface(IID_IOleCommandTarget, (void**)&spCT);
       hr = pcmdTarget->QueryInterface(IID_IOleWindow, (void**)&spWnd);
       hr = spWnd->GetWindow(&hwnd);
    
       hinstSHDOCLC = LoadLibrary(TEXT("SHDOCLC.DLL"));
       
       if (hinstSHDOCLC == NULL)
       {
           // Error loading module -- fail as securely as possible.
           return;
       }
    
       hMenu = LoadMenu(hinstSHDOCLC,
                        MAKEINTRESOURCE(IDR_BROWSE_CONTEXT_MENU));
    
       hMenu = GetSubMenu(hMenu, dwID);
    
       // Get the language submenu.
       hr = spCT->Exec(&CGID_ShellDocView, SHDVID_GETMIMECSETMENU, 0, NULL, &var);
    
       mii.cbSize = sizeof(mii);
       mii.fMask  = MIIM_SUBMENU;
       mii.hSubMenu = (HMENU) var.byref;
    
       // Add language submenu to Encoding context item.
       SetMenuItemInfo(hMenu, IDM_LANGUAGE, FALSE, &mii);
    
       // Insert Shortcut Menu Extensions from registry.
       V_VT(&var1) = VT_INT_PTR;
       V_BYREF(&var1) = hMenu;
    
       V_VT(&var2) = VT_I4;
       V_I4(&var2) = dwID;
    
       hr = spCT->Exec(&CGID_ShellDocView, SHDVID_ADDMENUEXTENSIONS, 0, &var1, &var2);
    
       // Remove View Source.
       DeleteMenu(hMenu, IDM_VIEWSOURCE, MF_BYCOMMAND);
    
       // Show shortcut menu.
       int iSelection = ::TrackPopupMenu(hMenu,
                                         TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD,
                                         ppt->x,
                                         ppt->y,
                                         0,
                                         hwnd,
                                         (RECT*)NULL);
    
       // Send selected shortcut menu item command to shell.
       LRESULT lr = ::SendMessage(hwnd, WM_COMMAND, iSelection, NULL);
    
       FreeLibrary(hinstSHDOCLC);
       return S_OK;
    }
    
    security note Security Alert  Using LoadLibrary incorrectly can compromise the security of your application by loading the wrong DLL. Refer to the LoadLibrarydocumentation for information on how to correctly load DLLs with different versions of Windows.

    Appending Extensions to Your Custom Menu

    In the previous example, the menu (hMenu) and menu context identifier (dwID) are passed as arguments to the IOleCommandTarget with the SHDVID_ADDMENUEXTENSIONS command. This operation inserts the registered menu extensions into the menu.

    If you choose to replace the standard menu with your own, you can still append menu extensions to your custom menu. Simply include a blank IDM_MENUEXT_PLACEHOLDER menu option in your menu definition to indicate where the custom commands are to be inserted. Then pass your own menu as the command target. Menu extensions will be inserted just before the placeholder.

    You can also add your own custom command to the standard menu by inserting the menu option before IDM_MENUEXT_PLACEHOLDER, as shown in the following example.

    #define IDM_MENUEXT_PLACEHOLDER	 6047
    
    // If the placeholder is gone or was never there, then just exit.
    if (GetMenuState(hMenu, IDM_MENUEXT_PLACEHOLDER, MF_BYCOMMAND) != (UINT) -1)
    {
       InsertMenu(hMenu,                    // The Context Menu
           IDM_MENUEXT_PLACEHOLDER,         // The item to insert before
           MF_BYCOMMAND|MF_STRING,          // Insert by item ID and str value
           IDM_MENUEXT_FIRST__ + nExtCur,   // The command ID
           (LPTSTR)aOpts[nExtCur].pstrText);// Some menu command text
    	
       // Remove placeholder.
       DeleteMenu(hMenu, IDM_MENUEXT_PLACEHOLDER, MF_BYCOMMAND);
    }
    

    The menu IDs for extensions fall between IDM_MENUEXT_FIRST__ and IDM_MENUEXT_LAST__ for a maximum of 32 custom commands.

    Enabling and Disabling Menu Items

    The menus in Internet Explorer use resource identifiers from mshtmcid.h to specify which command is executed when the menu item is clicked. Using the same resource IDs, you can easily determine whether the command has been enabled or disabled by calling IOleCommandTarget::QueryStatus, as shown in the following code snippet.

    for (i = 0; i < GetMenuItemCount(hMenu); i++)
    {
        OLECMD olecmd.cmdID = GetMenuItemID(hMenu, i);
        if (olecmd.cmdID > 0)
        {
            UINT mf;
            spCmdTarget->QueryStatus(&CGID_MSHTML, 1, &olecmd, NULL);
            switch (olecmd.cmdf)
            {
            case OLECMDSTATE_UP:
            case OLECMDSTATE_NINCHED:
                mf = MF_BYCOMMAND | MF_ENABLED | MF_UNCHECKED;
                break;
    
            case OLECMDSTATE_DOWN:
                mf = MF_BYCOMMAND | MF_ENABLED | MF_CHECKED;
                break;
    
            case OLECMDSTATE_DISABLED:
            default:
                mf = MF_BYCOMMAND | MF_DISABLED | MF_GRAYED;
                break;
            }
            CheckMenuItem(hMenu, olecmd.cmdID, mf);
            EnableMenuItem(hMenu, olecmd.cmdID, mf);
        }
    }
    

    References

    The following table indicates where the identifiers described in this article are defined:

    IdentifierHeader File 
    CGID_EditStateCommandsMshtml.h
    CGID_ExplorerShlguid.h
    CGID_MSHTMLMshtmhst.h
    CGID_ShellDocViewShlguid.h
    IDM_CONTEXTMshtmcid.h
    SID_SEditCommandTargetMshtml.h
    SID_SShellBrowserShlguid.h
    SZ_HTML_CLIENTSITE_OBJECTPARAM Mshtmhst.h

    posted by 뚱2
    Visual Studio 2008 MFC 프로그램에서 Excel Automation을 사용(Add Class From Typelib)할려고 디폴트로 사용할려고 하니

    알수 없는 에러가 발생했다.





    #해결1
    http://www.ms-news.net/f3295/ms-office-type-library-problem-2533193.html

    참조 : http://support.microsoft.com/kb/308292 
             http://support.microsoft.com/kb/311407/EN-US 


    #해결2
    에러는 해결했는데 Warning이 발생한것도 있고 Typelib를 이용해서 클래스 만들어주는게 다 만들어주는것도 아니고해서
    결국직접 typelib를 임포트 해서 스마트포인터를 사용해서 해결했다.
    #import "C:\\Program Files (x86)\\Common Files\\Microsoft Shared\\OFFICE11\\MOS.DLL" \
        rename("RGB", "excelRGB")
    #import "C:\\Program Files (x86)\\Common Files\\Microsoft Shared\\VBA\\VBA6\\VBE6EXT.OLB"
    #import "C:\\Program Files (x86)\\Microsoft Office\\OFFICE11\\excel.exe" \
        rename("DialogBox", "excelDialogBox") \
        rename("RGB", "excelRGB") \
        rename("CopyFile", "excelCopyFile") \
        no_dual_interface
    using namespace Excel;
    

    * office11은 MS Office 2003 버전이다.
    * 32bit 버전이라면 'Program Files (x86)'이 아니라 'Program Files' 이다.

         

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

    [C++] C++11  (0) 2013.01.29
    [COM] WebBrowser Customization  (0) 2012.12.29
    [MFC] MFC에서 Token 분리  (0) 2011.07.21
    [MFC] CListCtrl 현재 행 선택하기  (0) 2011.03.20
    COM Automation 에서 옵션인자 설정 방법  (0) 2011.03.03
    posted by 뚱2

    * This global function can be used to extract a substring from a given source string.

    BOOL AFXAPI AfxExtractSubString (
       CString& rString,
       LPCTSTR lpszFullString,
       int iSubString,
       TCHAR chSep = '\n'
    );
    

    * Sample Source
    
    // The following example extracts a series of name, value pairs from a
    // given source string:
    
    // Input string consisting of a number of name, value pairs
    LPCTSTR lpszSource = _T("\"Name\"=\"John Smith\"\n")
       _T("\"Company\"=\"Contoso, Ltd\"\n\"Salary\"=\"25,000\"");
    
    CString strNameValue; // an individual name, value pair
    
    int i = 0; // substring index to extract
    while (AfxExtractSubString(strNameValue, lpszSource, i))
    {
       // Prepare to move to the next substring
       i++;
    
       CString strName, strValue; // individual name and value elements
    
       // Attempt to extract the name element from the pair
       if (!AfxExtractSubString(strName, strNameValue, 0, _T('=')))
       {
          // Pass an error message to the debugger for display
          OutputDebugString(_T("Error extracting name\r\n"));
          continue;
       }
    
       // Attempt to extract the value element from the pair
       if (!AfxExtractSubString(strValue, strNameValue, 1, _T('=')))
       {
          // Pass an error message to the debugger for display
          OutputDebugString(_T("Error extracting value element\r\n"));
          continue;
       }
    
       // Pass the name, value pair to the debugger for display
       CString strOutput = strName + _T(" equals ") + strValue + _T("\r\n");
       OutputDebugString(strOutput);
    }
    
    
     
    posted by 뚱2

    프로그램 업데이트 목록을 만들다 보면은 ListCtrl에 업데이트 목록을 넣어두고
    업데이트를 할때마다 리스트 현재 행을 변경시킬때가 있다 그럴때 유용하다.

        // 먼저 현재 선택상태를 해제합니다
        m_listResult.SetItemState( -1, 0, LVIS_SELECTED|LVIS_FOCUSED );
        // 원하는 아이템을 선택합니다
        m_listResult.SetItemState(m_nCurrentItem, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
        // 선택된 아이템을 표시합니다
        m_listResult.EnsureVisible(m_nCurrentItem, false);
        // 리스트 컨트롤에 포커스를 맞춥니다
        m_listResult.SetFocus(); 
    
    posted by 뚱2

    ADO를 이용해서 Recordset 개체를

    Open하는데 두번째 인자(ActiveConnection)는 옵션인자입니다.

    'Visual Basic
    recordset.Open Source, ActiveConnection, CursorType, LockType, Options
    



    이걸 그냥 NULL 이렇게 주면은 실행시 예외를 떨굽니다.
    이럴떼 NULL 대신
    // Visual C++
    _variant_t vOPTION;
    vOPTION.vt = VT_ERROR;
    vOPTION.scode = DISP_E_PARAMNOTFOUND;
    


    해주시면 됩니다.

    p.s. 2011-08-06 추가
    MFC에서 컴관련 클래스로 COleVariant가 있습니다.
    COleVariant vOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
    
     

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

    [MFC] MFC에서 Token 분리  (0) 2011.07.21
    [MFC] CListCtrl 현재 행 선택하기  (0) 2011.03.20
    #pragma message  (0) 2011.02.25
    Predefines Macros  (0) 2011.02.16
    [링크] 일정 시간이 흐른후 메세지 박스 종료하기  (0) 2011.02.15
    posted by 뚱2

    #pragma message

    C/C++/VC++ / MFC 2011. 2. 25. 13:15
    참 재미있는 기능입니다.
    이걸로 TODO를 만들면 VC++에서 점프가 가능합니다.


    위와 같이 Output 창에서 해당 라인을 더블클릭하면 실제 소스로 이동합니다.

    // with line number
    #define STRING2(x) #x
    #define STRING(x) STRING2(x)
    #define TODO(x) message(__FILE__"("STRING(__LINE__)") : ▶"x"◀")
    


    사용할때는
    // Used
    #pragma TODO("테스트")
    


    이렇게 사용하시면 됩니다.
    posted by 뚱2

    Predefines Macros

    C/C++/VC++ / MFC 2011. 2. 16. 17:59
    컴파일시 변수와 비슷하게 사용할수 있는 Macro들이 있습니다.
    그중 __FILE__, __LINE__, __FUNCTION__은  디버깅시 유용합니다.

    __FILE__           : 현재 소스의 파일 경로 문자열을 리턴
    __LINE__          : 소스 파일의 라인번호 숫자를 리턴
    __FUNCTION__ : 현재 위치의 함수 이름 문자열을 리턴

    참고 ( http://msdn.microsoft.com/en-us/library/b0084kay(VS.71).aspx )

    posted by 뚱2
    출처 ( http://www.devpia.com/Maeul/Contents/Detail.aspx?BoardID=51&MAEULNO=20&no=8514&page=1 )

    데브피아 살펴보다가 유용한 팁이 있어서 링크 없어질것 대비하여 블로그에 옮겨 놓습니다.

    나중에 써먹어야지!!


    글 내용
    ---------------------------------------------------------------------------------------------------
    팁이 될지 모르겠네요. 모르는분들을 위한 팁이에요...양해 바랍니다. ^^
    어떤분이 질문을 올리셨기에...
    질문의 내용은 A프로그램에서 B프로그램 실행후 B프로그램이 끝날때까지 A프로그램은 계속 대기
    B프로그램에서 메시지박스가 떠 있기때문에 무한정 기다려야 하는 문제가 있을 경우 사용하면 될것 같습니다.
    또는 그냥 시간되면 메시지 박스를 종료하고 싶을때 사용하면 될것 같습니다.
    사용 방법은 SetTime를 이용합니다. 
    ---------------------------------------------------------------------------------------------------
    SetTimer(101, 1000, NULL);
    if(AfxMessageBox("박스다..") == IDOK)
    {
        // AfxMessageBox("OK");
    }
    

    //OnTimer() 안에서 사용하시면 됩니다.
    HWND wndDlg = ::GetLastActivePopup(m_hWnd);
    if(wndDlg && wndDlg != m_hWnd)
    {
        char buffer[256] = {0};
        ::GetClassName(wndDlg, buffer, 256);
        if(CString("#32770") == buffer) //메시지 박스는 분명히 다이얼로그이며 클래스명이 #32770
        {
            ::EndDialog(wndDlg, IDOK);
        }
    }
    

    posted by 뚱2

    물론 MultiByteToWideChar API를 사용하면  Ansi->Unicode로 전환할수 있습니다.

    MultiByteToWideChar를 보시면 알겠지만 인자가 많습니다. ㅡㅡ;

    int MultiByteToWideChar(
      UINT CodePage, 
      DWORD dwFlags,         
      LPCSTR lpMultiByteStr, 
      int cbMultiByte,       
      LPWSTR lpWideCharStr,  
      int cchWideChar        
    );
    

    이럴때 MFC에서만 사용할수 있는 꼼수

    char szBuffer[] = "Ansi 스트링 입니다.";
    CString strUnicode = (CString)szBuffer;
    

    이렇게만 하면 끝났습니다.
    다만 그냥 컨버팅 되는게 아니라
    프로그램 베이스가 Unicode 기반으로 작성된 프로그램에서
    Ansi 문자열을 Unicode 기반으로 컨버팅 하실때 편하게 사용하실수 있습니다.

    소스를 쫓아 들어가보면 알겠지만

    형변환 내부적으로 MultiByteToWideChar 함수를 호출하고 있습니다.

    // 데이터 길이 가져오는 함수
    static int __cdecl GetBaseTypeLength( _In_z_ LPCSTR pszSrc ) throw()
    {
        // Returns required buffer size in wchar_ts
        return ::MultiByteToWideChar( _AtlGetConversionACP(), 0, pszSrc, -1, NULL, 0 )-1;
    }
    

    // 데이터 변환 하는 함수
    static void __cdecl ConvertToBaseType( _Out_cap_(nDestLength) LPWSTR pszDest
    , _In_ int nDestLength, _In_z_ LPCSTR pszSrc, _In_ int nSrcLength = -1) throw()
    {
        // nLen is in wchar_ts
        ::MultiByteToWideChar( _AtlGetConversionACP()
                              , 0, pszSrc, nSrcLength, pszDest, nDestLength );
    }
    

    그런데 막상 해보면 글자가 한글이 깨집니다.
    이유는 http://msdn.microsoft.com/ko-kr/library/w1sc4t4k(VS.80).aspx
    문자열 변환 보시면 알겠지만 기본 코드 페이지가 변경되었습니다.


    문자열 변환

    Visual C++ 6.0의 ATL 3.0 및 그 이전 버전에서는 atlconv.h의 매크로를 사용하는 문자열 변환이 항상 시스템의 ANSI 코드 페이지(CP_ACP)를 사용하여 수행되었습니다. Visual C++ .NET의 ATL 7.0부터는 _CONVERSION_DONT_USE_THREAD_LOCALE이 정의되지 않은 경우 문자열 변환이 현재 스레드의 기본 ANSI 코드 페이지를 사용하여 수행됩니다. _CONVERSION_DONT_USE_THREAD_LOCALE이 정의된 경우에는 이전과 같이 시스템의 ANSI 코드 페이지가 사용됩니다.

    CW2AEX 등의 문자열 변환 클래스를 사용하면 변환에 사용할 코드 페이지를 해당 생성자에 전달할 수 있습니다. 코드 페이지를 지정하지 않으면 해당 클래스에서는 매크로와 동일한 코드 페이지를 사용합니다.

    자세한 내용은 ATL and MFC String Conversion Macros를 참조하십시오.


    결국 컨버전 할때 내부적으로

    inline UINT WINAPI _AtlGetConversionACP() throw()
    {
    #ifdef _CONVERSION_DONT_USE_THREAD_LOCALE
        return CP_ACP;
    #else
        return CP_THREAD_ACP;
    #endif
    }
    

    이 함수를 호출하는데 _CONVERSION_DONT_USE_THREAD_LOCALE 매크로가 없기 때문에 CP_THREAD_ACP 코드
    페이지가 작성됩니다.

    그래서 매크로를 프로젝트에 추가(Property Pages->Configuration Properties->C/C++->Preprocessor->Preprocessor Definitions)해주고

    Rebuild All 해서 사용하시면 됩니다.

    posted by 뚱2
    데이터를 파일로 저장하는 프로그램을 만들다 보면은 꼭 필요한게
    데이터 저장 폴더가 유효한지 아닌지 판단한는 일입니다.
    그럴대 유용한 API 입니다.

    PathIsDirectory Function


    Verifies that a path is a valid directory.


    Syntax

    BOOL PathIsDirectory(LPCTSTR pszPath);
    


    Parameters

    pszPath
    [in] A pointer to a null-terminated string of maximum length MAX_PATH that contains the path to verify.


    Return Value

    Returns TRUE if the path is a valid directory, or FALSE otherwise.


    Function Information

    Minimum DLL Version shlwapi.dll version 4.71 or later
    Custom Implementation No
    Header shlwapi.h
    Import library shlwapi.lib
    Minimum operating systems Windows 2000, Windows NT 4.0 with Internet Explorer 4.0, Windows 98, Windows 95 with Internet Explorer 4.0
    Unicode Implemented as ANSI and Unicode versions.

    posted by 뚱2

    MFC를 사용 할 때 전역적으로 사용할수 있는 API 앞머리에 Afx가 붙습니다.
    그중 현재 프로그램의 Instance Handle을 구할수 있는 API입니다.

    AfxGetInstanceHandle();
    

    그런데 이걸 Winapi로 하면 어떻게 될까요?  우선 GetWindowLong API를 이용하면 해결됩니다.
    // 원형
    LONG GetWindowLong(          
        HWND hWnd,
        int nIndex
    );
    

    아래와 같이 호출해 주시면 됩니다.  hWnd는 호출하는 쪽의 윈도우 핸들 입니다.
    GetWindowLong(hWnd, GWL_HINSTANCE);
    

    posted by 뚱2