------

[ AD ] Port Monitor ( Try to use a Best WebSite Monitoring Tool )

------

1.main.c                         2.ClassA.h                  3.ClassZ.h

#include "ClassA.h"        class ClassA               class ClassZ
#inlcude "ClassZ.h"        {                                {
                                          int ax;                         int zx;
int main()                             int ay;                         int zy;
{                                         ClassZ * pz;               ClassA *pa;
     int ma;                       }                                }
     ClassA a;
     ClassZ z;
     int mb;
}

자..정상적인 부분을 이해하셨다면 뭐가 문제인지 아실겁니다~


자..정상적인 부분을 이해하셨다면 뭐가 문제인지 아실겁니다~

main.c에 정체불명의 "ClassA a;"를 만났습니다. ClassA.h를 쫓아갑니다.

ClassA.h에 정체불명의 ClassZ * pz;를 만났습니다. ClassZ.h를 쫓아갑니다.
ClassZ.h에 정체불명의 ClassA * pa;를 만났습니다. ClassA.h를 쫓아갑니다.

ClassA.h에 정체불명의 ClassZ * pz;를 만났습니다. ClassZ.h를 쫓아갑니다.
ClassZ.h에 정체불명의 ClassA * pa;를 만났습니다. ClassA.h를 쫓아갑니다.

다행일지 몰라도 컴파일러는 나름 똑똑한지 계속 무한루프를 돌진 않군요~

"ClassZ * pz;가 뭡니까? 식별할 수 있는게 필요합니다. 이런식으로 나오믄 크기를 걍 int형으로 생각해버리겠습니다"
이렇게 경고를 줍니다~젤 위에 사진의 내용이 이런 식입니다^^


우선 문제가 제가 예전에 무언가 만들다가 상호참조가 자주 발생했었는데
현재는 어떻게 저렇게 만드는지 이해가 가질 않는군요^^

간략히 상호 참조를 해결하려면 ClassA나 ClassZ의 헤더파일에 해더를 인클루드하지말고 간단히 Class가 있다고 정의만 내려줍니다~

ClassZ.h 이파일을 수정해보겠습니다.

class ClassA;

class ClassZ
{
     int zx;
     int zy;
     ClassA * pa;
}

ClassZ.h가 있다면 ClassZ.c파일이 있겠죠~
ClassZ.c파일에서 간단히 #include "ClassA.h"로 인클루드만 하시면 회피합니다.

class ClassA;를 선언함으로써 "글쎄 obj가 없어도 일단 이런 클래스가 있다는 가정하에 님부터 크기를 파악해줘"라는게 됩니다.

그럼 일단 ClassZ.obj를 "ClassA"라는 클래스가 있다는 가정하에 만들게 되고, 오브젝트를 모두 만든후에 링크를 걸때는 classZ.c에 선언된 헤더를 보고 링크를 걸게 되는거죠~


, , , , , , , ,

'온라인게임 > vc++' 카테고리의 다른 글

사용 클래스의 싱글턴으로 구성  (0) 2010.10.08
줄번호 visual studio c++ 2010  (0) 2010.10.08
visual studio 2010 동영상 강좌 / 강의  (0) 2010.10.07
단축키 visual studio 2010  (0) 2010.10.07
Pre-processor(전처리기)  (0) 2010.10.07

7. 사용 클래스의 싱글턴으로 구성

싱글턴 메크로 추가

 

//싱글턴을위한

#define DECLEAR_SINGLETON( className )\

public:\

             static className* Instance();\

             virtual void releaseInstance();\

private:\

             static className* m_pInstance;

 

#define CREATE_FUNCTION( className , funcName ) \

             static className* ##funcName()\

             {\

             return className::Instance();\

             };

 

#define IMPLEMENT_SINGLETON( className )\

             className* className::m_pInstance = NULL;\

             className* className::Instance()\

             {\

             if ( NULL == m_pInstance )\

                           {\

                           m_pInstance = new className;\

                           }\

                           return m_pInstance;\

             }\

             void className::releaseInstance()\

             {\

             if ( NULL != m_pInstance )\

                           {\

                           delete m_pInstance;\

                           m_pInstance = NULL;\

                           }\

             }

.h 파일

class CMsgView : public CListView

{

             DECLEAR_SINGLETON( CMsgView);

.

}

CREATE_FUNCTION( CMsgView, LogView );

 

.cpp 파일 위에  IMPLEMENT_SINGLETON( CMsgView ); 추가

 

// CMsgView

IMPLEMENT_SINGLETON( CMsgView );

IMPLEMENT_DYNCREATE(CMsgView, CListView)

 

실제 사용 클래스 객체 생성

 

CMainFrame::CMainFrame()

{

             LogView();

}

 

CMainFrame::~CMainFrame()

{

              LogView()->releaseInstance();

}

 


'온라인게임 > vc++' 카테고리의 다른 글

error C2143  (2) 2010.10.11
줄번호 visual studio c++ 2010  (0) 2010.10.08
visual studio 2010 동영상 강좌 / 강의  (0) 2010.10.07
단축키 visual studio 2010  (0) 2010.10.07
Pre-processor(전처리기)  (0) 2010.10.07
줄번호 보이게 하기
visual studio c++ 2010

Tools->Options
도구 > 옵션을 선택한 후

TextEditor->
왼쪽의 트리에서 텍스트 편집기 > 모든 언어를 선택하면 아래의 화면이 표시된다.




'온라인게임 > vc++' 카테고리의 다른 글

error C2143  (2) 2010.10.11
사용 클래스의 싱글턴으로 구성  (0) 2010.10.08
visual studio 2010 동영상 강좌 / 강의  (0) 2010.10.07
단축키 visual studio 2010  (0) 2010.10.07
Pre-processor(전처리기)  (0) 2010.10.07

  • 3:20
    대기열에 추가됨
  • 3:16
    대기열에 추가됨
  • 3:33
    대기열에 추가됨
  • 3:39
    대기열에 추가됨
  • 2:49
    대기열에 추가됨
  • 3:41
    대기열에 추가됨
  • 3:04
    대기열에 추가됨
  • 3:07
    대기열에 추가됨
  • 3:00
    대기열에 추가됨
  • 4:46
    대기열에 추가됨
  • 3:34
    대기열에 추가됨
  • 3:14
    대기열에 추가됨
  • 3:09
    대기열에 추가됨
  • 4:08
    대기열에 추가됨
  • 3:44
    대기열에 추가됨
  • 3:34
    대기열에 추가됨
  • 3:13
    대기열에 추가됨
  • 3:20
    대기열에 추가됨
  • 4:42
    대기열에 추가됨
  • 3:15
    대기열에 추가됨
  • 3:13
    대기열에 추가됨
  • 2:30
    대기열에 추가됨
  • 3:28
    대기열에 추가됨
  • 4:48
    대기열에 추가됨
  • 4:06
    대기열에 추가됨
  • 4:12
    대기열에 추가됨
  • 3:23
    대기열에 추가됨
  • 4:00
    대기열에 추가됨
  • 4:10
    대기열에 추가됨
  • 3:56
    대기열에 추가됨
  • 4:25
    대기열에 추가됨
  • 4:21
    대기열에 추가됨
  • 4:29
    대기열에 추가됨
  • 4:11
    대기열에 추가됨
  • 3:56
    대기열에 추가됨
  • 4:28
    대기열에 추가됨
  • 4:06
    대기열에 추가됨
  • 4:51
    대기열에 추가됨
  • 5:02
    대기열에 추가됨
  • 5:14
    대기열에 추가됨
  • 4:23
    대기열에 추가됨
  • 5:26
    대기열에 추가됨
  • 5:47
    대기열에 추가됨
  • 5:27
    대기열에 추가됨
  • 5:38
    대기열에 추가됨
  • 5:30
    대기열에 추가됨
  • 5:50
    대기열에 추가됨
  • 5:16
    대기열에 추가됨

  • http://goo.gl/riMq  다운로드 PDF visual studio 2010 c++ 단축키

    문자열 검색 Edit.IncrementalSearch

    1. Ctrl + I 를 친다.
    2. 검색할 단어를 친다. note. 첫번째 단어로 이동하는 모습을 볼수있을 것입니다.
    3. 이어서 다음(아래로) 검색은 Ctrl + I
    4. 이전(위로) 검색은 Ctrl + Shift + I 입니다.
    5. 검색 종료는 ESC를 누르세요.

    - 코드 분석시 정의 찾아가기

    코드 리뷰를 하기 위해서 소스를 보고 있는데 틈틈이 봐뒀으면 더 좋을 걸 한 번에 보자니 이거 영 진도도 안나가고 끙끙...

    아무튼 비주얼 스튜디오에서 코드 읽을 때 기본적으로 딱 두 가지 단축키만 알고 있으면 편하게 소스의 숲을 헤집고 다닐 수가 있다. 한 절반은 먹고 들어간달까.

    F12 : Edit.GoToDefinition
        CTRL+F12  : Edit.Go-To-Declation

    Ctrl+Shift+8: View.PopBrowseContext
      -> Ctrl + HYPHEN  <-> CTRL+SHIFT + HYPHEN


    GoToDefinition을 해서 소스를 보면서 막 돌아다니다가 PopBrowseContext를 하면 바로 전에 GoToDefinition했던 곳으로 이동한다. GoToDefinition을 여러 번 수행했다면 PopBrowseContext도 여러 번 수행할 수 있다. BrowseContext라는 개념이 뭔지는 잘 모르겠는데 스택으로 관리되는 것 같다.

    IntelliSense
    - 단어 완성 : CTRL+SPACEBAR       or     ALT+RightArrow


    -  기능키 

    Ctrl + Tab : 열어놓은 파일 왔다 갔다

    Ctrl + L :한줄을 잘라내기

     

    Tab 들여쓰기
    Shift+Tab 내어쓰기
    F4 다음 에러메세지로 이동
    Alt+F8 들여쓰기 자동 조정
    Ctrl+Shift+F8 열 블럭
    Alt+E+I 프로젝트 내의 모든 파일에서 찾기
    Ctrl+] 괄호 짝 찾기
    Ctrl+F2 북마크 설정 (Ctrl+K K)
    F2 설정된 북마크로 이동
    F9 현재 커서위치에 BreakPoint를 설정/해제
    Ctrl+Shift+F9 모든 브레이크 포인트 해제 

    Ctrl+B 브레이크 포인트 관리 메뉴
    Ctrl+Spacebar 멤버목록 팝업창
    Ctrl+U 모두 소문자로
    Ctrl+Shift+U 모두 대문자로
    Ctrl+Shift+8 Tab은 ^ 로 공백은 . 으로 보여줌
    Ctrl+Shift+R 매크로 기록
    Ctrl+Shift+P 매크로 실행

    실행 : 컨트롤 + F5

    컴파일 : 컨트롤+F7 입니다.

    F8 찾기에서 다음 항목으로 이동

    Ctrl + H 찾아 바꾸기

    Ctrl + F : 찾기
    F3 : 다음 찾기
    Ctrl + A 전체선택


    새 프로젝트 :Ctrl + N 


    Ctrl + Space : 인텔리센스 출력
    Ctrl + F5 : 빌드 후 프로그램 실행
    F5 : Debugging 모드로 작동
    F9 : Break Point
    Ctrl + F2 : 북마크
    F2 : 북마크로 이동
    F10 : Debugging 모드로 작동하되 엔트리 포인트부터 시작
    Ctrl + F10 : Debugging 모드로 작동하되 커서의 위치까지
    Alt + F8 : 들여쓰기 정리
    Ctrl + Shite + Space : 현재 가르키고 있는 함수의 매개변수 보기
    Alt + B -> E : Clean
    Alt + B -> R : Rebuild All
    Ctrl + E : 현재 괄호랑 맞는 괄호를 찾아준다.
    Alt + F7 : Project Setting
    Ctrl + H : 바꿈
    Ctrl + F : 찾기
    Alt + E -> I : 여러파일에서 찾기
    Ctrl + W : 클래스 위자드 (MFC 코딩시에만 사용)
    Alt + 2 : output 윈도우
    Alt + 0 : Workspace 윈도우

    Alt + MouseMove : 컬럼 Selection

    Tab 들여쓰기
    Shift+Tab 내어쓰기
    F4 다음 에러메세지로 이동
    Alt+F8 들여쓰기 자동 조정
    Ctrl+Shift+F8 열 블럭
    Ctrl+F 찾기
    Ctrl+F3 현재 커서에 있는 단어로 찾기
    Ctrl+H 찾아 바꾸기
    F3 다음 단어 찾기
    Ctrl+Shift+F3 이전 단어 찾기
    Alt+E+I 프로젝트 내의 모든 파일에서 찾기
    Ctrl+] 괄호 짝 찾기
    Ctrl+F2 북마크 설정
    F2 설정된 북마크로 이동
    F9 현재 커서위치에 BreakPoint를 설정/해제
    Ctrl+B 브레이크 포인트 관리 메뉴
    Ctrl+Spacebar 멤버목록 팝업창
    Ctrl+T 현재 커서에 있는 단어의 툴팁정보 보기
    Ctrl+U 모두 소문자로
    Ctrl+Shift+U 모두 대문자로
    Ctrl+Shift+8 Tab은 ^ 로 공백은 . 으로 보여줌

    Debug 모드 작동중
    F10 : Step Over (다음줄로)
    F11 : Step Into (함수 안으로 들어감)
    F5 : 다음 BreakPoint 까지 작동
    Alt + 7 : Call Stack 윈도우
    Alt + 3 : Watch 윈도우
    Shift + F11 : 현재 루틴에서 빠져나온다.
    Shift + F5 : 강제로 디버깅 모드 종료


    Pre-processor(전처리기)

     

    컴파일을 시도할 경우 컴파일이 실행되기 전에 전처리기 명령부터 처리된다.

    전처리기는 # 로 시작하고  ; 를 붙이지 않으며 보이지 않게 소스 코드를 변경하며

    컴파일러에게 지시를 내릴 수도 있다.

     
    #include <xxxx.h>
    #define
    Inclusion Guard
    Macro Function
    ASSERT()

    매크로 함수
    문자열 조작
    문자열 결합
    내장매크로 __DATE__ __TIME__ __LINE__ __FILE__
    #error
    #pragma

    각각은 여기서는 명령어라는 용어를 사용했지만 정확하게는 directive(지시자) 라고 하는 것이 나을 지 모르겠다.

     

    #include <header.h>

     

    가장 흔히 볼 수 있는 전처리기이다. 해당 파일을 찾아서 컴파일러가 그 파일이 마치 현재 컴파일하는

    소스 코드에 포함되어 있는 것같이 해준다. <> 는 표준 헤더 파일일 경우에 설정되어 있는 폴더에서

    헤더 파일을 찾으며 “” 는 그 외 폴더에서 찾을 수 있는데 최우선으로 현재 프로젝트 폴더에서 찾게 된다.

     

    #define

     

    define 문은 여러 경우에 사용될 수 있는데 일반적으로 문자열 대치에 사용된다.

     

    #define MAX 512

     

    라고 하면 소스 코드에 있는 MAX 라는 문자를 모두 512 로 대치한다.

     

    int Arr[MAX];

     

    는 실제 컴파일러가 컴파일할 때는

     

    int Arr[512];

     

    로 대치될 것이다. 이것은

     

    #define MAX 512

    int Arr[MAX];

     

     

    int Arr[512];

     

    로 바뀐다고 생각하면 된다.

    대치되는 문자 없이 그냥

     

    #define MAX

     

    로 사용되는 경우도 흔하다. 이것은 MAX 라는 문자열을 정의하지만 대치시키지는 않는다.

    이렇게 사용하는 경우는 #ifdef ~ #elif ~ #else ~ #endif 문과 같이 사용되면 상당히 유용하게 사용된다.

     

    #ifdef MAX

           cout << “Max is defined!” << endl;

    #else

           cout << “Max is not defined!” << endl;

    #endif

     

    이렇게 사용하면 #define MAX 줄로 여러 버전의 실행 파일을 만들 수도 있다.

    이런 것을 조건부 컴파일이라고 한다.

    마지막 #endif 넣는 것도 잊지 말자.

     

    #ifdef MAX

    #elif defined(MIN)

    #else

    #endif

     

    문이나

     

    #if defined(MAX)

    #elif defined(MIN)

    #else

    #endif

     

    문등의 예제를 봐두기 바란다.

     

    ¨       관련된 주요 전처리기 명령어

     

    ¨       #ifdef 식별자

    ¨       #ifndef 식별자

    ¨       #undef 식별자

    ¨       #if defined(식별자)

    ¨       #if !defined(식별자)

    ¨       #elif

    ¨       #else

    ¨       #endif

     

    #ifdef 등으로 식별자를 사용했으면

     

    #undef 식별자

     

    문으로 undef 하는 습관을 기르자. #undef 이후부터 식별자는 정의되지 않은 것으로 간주된다.

     

    예제) http://blog.naver.com/xtelite/50018568445

     

    포함감시(Inclusion Guard)

     

    여러 개의 헤더 파일(.h 또는 .hpp)과 구현 파일(.c 또는 .cpp) 들이 있을 경우 헤더 파일이 중복 포함되는

    경우가 많다. 특히 C/C++에 입문한 지 얼마 안되는 사람들에게서 자주 나타나는 데 이런 경우 포함감시를

    사용한다. 사실 포함감시 기능은 만드는 모든 헤더파일에 적용하도록 하자.

     

    #ifndef FILENAME_H

    #define FILENAME_H

     

    // 여기에 헤더 파일의 모든 내용을 넣는다

     

    #endif

     

    이렇게 해 두면 두 번째 포함될 때 #ifndef 가 거짓이 되어 이 헤더 파일이 포함되지 않는다.

    ,

     

    #include “filename.h”

    #include “filename.h”

     

    와 같이 하더라도 두 번째 헤더파일은 포함되지 않을 것이다. FILENAME_H 부분은 관례상 파일명을 이용해서

    이렇게 명명한다. _FILENAME_H __FILENAME_H__ 등 잘 사용되지 않는 문자열을 파일이름을 사용해서 만드는

    것이다.

     

    매크로 함수(Macro function)

     

    매크로 함수도 전처리기로 흔히 사용된다. 다만 디버깅이 힘들다는 단점 때문에 점점 사용되지 않고 있기도 하다.

    특히, C++의 경우 엄격한 형 검사를 하게 되는데 매크로 함수를 사용하게 되면 그 기능을 사용할 수 없으니

    피해야 한다.

     

    #define CUBE(x) ((x)*(x)*(x))

     

    int x, y;

    y = CUBE(x);

     

     

    int x, y;

    y = (x) * (x) * (x);

     

    로 대치될 것이다. 매크로 함수를 사용한다면 ()를 남발하는 습관을 키워야 한다.

    왜 그런지는

     

    y = CUBE(3+4);

     

    와 같은 경우에 직접 대치해 보면 알게 될 것이다.

     

    C++ 사용자는 매크로 함수보다는 template이나 inline 함수를 사용해야 한다.

     

    문자열 조작

     

    #define SAY(x) printf(#x)

     

    SAY(Hello, world!);

     

    와 같이 식별자 앞에 # 를 붙이게 되면 자동으로 “x” 와 같이 “”로 둘러 싸 준다.

    결과적으로

     

    printf(“Hello, world!”);

     

    로 대치될 것이다.

     

    문자열 결합

     

    ## 는 두 개의 문자열을 결합해 준다.

     

    #define Print(x) Print ## x

     

    와 같이 된 경우

     

    Print(One) 을 사용하면 PrintOne 이라는 문자열로 대치되고 Print(Two) PrintTwo 라는 문자열로 대치된다.

    잘 사용하면 아주 유용한 기능이 된다.

     

    ASSERT()

     

    대부분의 컴파일러는 ASSERT() 매크로를 가지고 있다. 여기서는 직접 하나 만들어 보자.

     

    #ifndef DEBUG

    #define ASSERT(x)

    #else

    #define ASSERT(x) \

           if ( ! (x) ) \

           { \

                 printf(#x); \

                 printf(“ is NULL on line %d in file %s”, __LINE__, __FILE__); \

           }

    #endif

     

    이 코드의 위에

     

    #define DEBUG

     

    한 줄 포함하면

     

    #define ASSERT(x)

     

    로 아무 일도 하지 않고 DEBUG가 정의되지 않으면 그 아래 함수가 정의된다.

    , 디버그때만 코드가 생성되고 릴리즈시에는 코드가 생성되지 않게 할 수 있는 것이다.

     

    여러 줄이 필요할 때는 \ 가 사용되었다는 것에 유의하자. 또한 내장 매크로인 __LINE__ 이나 __FILE__

    다음에 설명한다.

     

    내장 매크로

     

    컴파일 시에 컴파일러가 미리 정의하고 있는 매크로들이 있다.

    각각 오른 쪽에 있는 내용으로 대치된다.

     

    __DATE__ : 컴파일하는 날짜

    __TIME__ : 컴파일하는 시간

    __LINE__ : 현재 컴파일하고 있는 줄 번호

    __FILE__ : 현재 컴파일하고 있는 파일의 이름

     

    #error

     

    컴파일러는 이 명령을 만나게 되면 해당 메지시를 출력하고 컴파일을 중지한다.

    C++ 컴파일러에서만 동작하게 하는 다음 코드를 참조하자.

     

    #if !defined(__cplusplus)

    #error C++ compiler required.

    #endif

     

    __cplusplus C++ 컴파일러일 경우에 정의되는 내장 매크로이다.

     

    #pragma

     

    #pragma 는 컴파일러마다 고유하게 사용할 수 있는 명령어이다. 따라서 그 문법은 컴파일러마다

    다르고 그 종류도 많다.

     

    예를 들어

     

    #pragma once

     

    같은 경우 위의 포함감시 기능을 컴파일러가 알아서 해 준다.
    , 한 번 include 된 헤더 파일은 중복해서
    포함되지 않도록 컴파일러가 처리해 준다.

     

    Visual Studio 2010에서 변경된 MSDN

    Visual Studio 2010에서는 Help Library (Microsoft Help System) 라는 이름으로 변경된것 같습니다.

    Microsoft의 url은 언제든지 변경될 수 있으니 유의 하십시요.

     

    설치하는 방법은 Visual Studio 2010 설치를 끝낸 다음에 보면 어쩌고 저쩌고 Document 라고 써있는 파란색 버튼이 있을겁니다. (기억이..)

    아니면 벌써 설치를 끝내신 분들은

    - 시작 (윈도우 버튼) -> 모든 프로그램 -> Microsoft Visual Studio 2010 -> Microsoft Visual Studio 2010 Documentation

    - 시작 (윈도우 버튼) -> 모든 프로그램 -> Microsoft Visual Studio 2010 -> Visual Studio Tools -> Manage Help Settings - ENU

    위에 두개의 메뉴중 하나를 누르시면 설치할 수 있으니 참고 하시기 바랍니다.

     


    '온라인게임 > vc++' 카테고리의 다른 글

    단축키 visual studio 2010  (0) 2010.10.07
    Pre-processor(전처리기)  (0) 2010.10.07
    DLL 내에서 정의된 Function을 export  (0) 2010.10.06
    IO Completion Port  (0) 2010.10.06
    vs 2010 팁 ( visual studio 추가 설치 파일 )  (0) 2010.10.05
    DLL 내에서 정의된 Function을 export하기 위해서는 “__declspec(dllexport)” 를 사용한다.

    “__declspec”은 MS만의 C, C++의 확장된 syntax로서, 확장된 storage-class 정보를 정의한다.

    “dllexport”는 storage-class의 한 속성으로, DLL의 Function, Data, Object를 export할 수 있도록 하여준다.

    반대로 DLL내의 Function을 import하기 위해서는 “dllimport” 속성을 사용한다.

    Win32 환경에서는 Function Export/Import를 위하여 이것을 이용하며,

    Win32 이전의 Windows 환경에서 사용되던 module-definition file (.DEF)을 사용하지 않는다.
    단, VB와 호환가능한 DLL을 제작하는 경우, module-definition file을 사용하도록 한다.


    #ifdef DLLTEST_EXPORTS
    #define DLLFunction __declspec(dllexport)
    #elseif
    #define DLLFunction __declspec(dllimport)
    #endif


    DLL의 Project Settings에 Preprocessor definitions에 “프로젝트명_EXPORTS”의 형식으로 정의 되어 있다

    IO Completion Port 작성 하기



    1. MFC 프로젝트 - 다이얼로그 방식

    프로젝트명 : IOCompletionPort 

    생성 : 
    자동 ( IOCompletionPortDlg.h / IOCompletionPortDlg.cpp / class CIOCompletionPort() {} /
            로 생김(자동으로 C가 붙음) )


    2. 다이얼로그에  ListBox Control 추가

    ---- 흠

    3. 버튼을 옮겨서 : 서버 시작 버튼을 만들고.....클릭시 함수처리부분에서 호출 및 사용될  class 정의


    4.  class 정의 :  cIOCompletionPort

    5. 클래스 마법사에서 ListBox 멤버 변수 작성 - IOCompletionPortDlg.cpp
    6. ListBox에 문자 출력하는 함수 작성 - IOCompletionPortDlg.cpp
    IOCompletionPortDlg.h
    // 흠 흠 흠
    #include "afxwin.h"
    #include "resource.h"
    
    #include "cIOCompletionPort.h"
    
    #define	LISTEN_PORT		8000
    
    ///..........
    
    public:
    	void OutputMsg(char * szOutputString,...);
    	CListBox m_ctOutput;
    	boolean		m_bServerStarted;
    	cIOCompletionPort m_IOCompletionPort;
    
    	afx_msg void OnBnClickedStartserver();
    	afx_msg void OnBnClickedCancel();
    
    
    

    IOCompletionPortDlg.cpp
    
    /// 사용자 추가
    void CIOCompletionPortDlg::OutputMsg(char * szOutputString,...)
    {
    
    	char szOutStr[1024];
    	va_list argptr;
    
    	va_start(argptr, szOutputString) ;
    	vsprintf( szOutStr, szOutputString, argptr );
    	va_end(argptr);
    
    	
    	//  CListBox에 추가
    
    	// 속성 - > 구성속성->일반 -> 멀티 바이트 문자 조합 MBCS
    	//m_ctOutput.SetCurSel( m_ctOutput.AddString( szOutStr ) );
    
    	m_ctOutput.InsertString(0, szOutStr) ;
    
    }
    
    
    
    void CIOCompletionPortDlg::OnBnClickedStartserver()
    {
    	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
    
    		if ( ! m_bServerStarted  ) {
    			OutputMsg("0.----서버 ");
    
    			m_IOCompletionPort.SetMainDlg(this);
    			bool bRet = m_IOCompletionPort.InitSocket();
    
    			OutputMsg("1.----포트:%d", LISTEN_PORT );
    			m_IOCompletionPort.BindandListen(LISTEN_PORT);
    
    			OutputMsg("2.----가동 " );
    			m_IOCompletionPort.StartServer();
    
    			m_bServerStarted=true;
    		} else {
    			OutputMsg("****************서버 가동중 : 누르지 마세요 ******************");
    		}
    
    }
    
    
    void CIOCompletionPortDlg::OnBnClickedCancel()
    {
    	// TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
    
    	m_IOCompletionPort.DestroyThread();
    
    	CDialogEx::OnCancel();
    }
    
    

    cIOCompletionPort.h
    
    
    // 1. 패킷 사이즈
    #define MAX_SOCKBUF		1024
    // 2. 클라이언트 수
    #define		MAX_CLIENT	1024
    // 3. 쓰레스 수
    #define		MAX_WORKERTHREAD	4
    
    
    enum	enumOperation {
    	OP_RECV,
    	OP_SEND
    };
    
    /// WSAOVERLAPPED 구조체를 확장시켜 필요한 정보 추가
    struct stOverlappedEx {
    	WSAOVERLAPPED		m_wsaOverlapped;	// Overlapped I/O 구조체
    	SOCKET				m_socketClient;		// client socket
    	WSABUF				m_wsaBuf;			// Overlapped I/O 작업 버퍼
    	char				m_szBuf[MAX_SOCKBUF];	// 데이타 버퍼
    	enumOperation		m_eOperation;			// 작업 동작 종류
    };
    /// client 정보를 담기 위한 구조체
    struct	stClientInfo {
    	SOCKET				m_socketClient;		// 클라이언트와 연결되는 소켓
    	stOverlappedEx		m_stRecvOverlappedEx;	// Recv Overlapped I/O 작업을 위한 변수
    	stOverlappedEx		m_stSendOverlappedEx;	// Send Overlapped I/O 작업을 위한 변수
    
    	/// 생성자에서 멤버 변수들을 초기화
    	stClientInfo() 
    	{
    		m_socketClient = INVALID_SOCKET;
    		ZeroMemory( &m_stRecvOverlappedEx, sizeof(m_stRecvOverlappedEx) );
    		ZeroMemory( &m_stSendOverlappedEx, sizeof(m_stSendOverlappedEx) );
    	}
    };
    
    
    class CIOCompletionPortDlg;
    
    class cIOCompletionPort
    {
    public:
    	cIOCompletionPort(void);
    	~cIOCompletionPort(void);
    	bool InitSocket(void);
    
    	bool BindandListen(int nPort);
    	bool StartServer(void);
    	bool CreateWorkerThread(void);
    	bool CreateAccepterThread(void);
    	stClientInfo * GetEmptyClientInfo(void);
    	bool BindIOCompletionPort(stClientInfo * pClientInfo);
    
    	bool BindRecv(stClientInfo * pClientInfo);
    	bool SendMsg(stClientInfo * pClientInfo, char * pMsg, int nLen);
    	void WorkerThread(void);
    	void AccepterThread(void);
    	void SetMainDlg(CIOCompletionPortDlg * pMainDlg);
    	void DestroyThread(void);
    	void CloseSocket(stClientInfo * pClientInfo, bool bIsForce=false);
    private:
    	// 1. 클라이언트 정보 저장 구조체
    	stClientInfo *	m_pClientInfo;
    
    	// 2. 클라이언트 접속을 받기위한 리슨 소켓
    	SOCKET			m_socketListen;
    
    	// 3. 접속 되어 있는 클라이언트 수
    	int				m_nClientCnt;
    
    	// 4. 메인 윈도우 포인터
    	CIOCompletionPortDlg *		m_pMainDlg;
    
    	// 5. 작업 스레드 핸들
    	HANDLE			m_hWorkerThread[MAX_WORKERTHREAD];
    
    	// 6. 접속 스레드 핸들
    	HANDLE			m_hAccepterThread;
    
    	// 7. CompletionPort 객체 핸들
    	HANDLE			m_hIOCP;
    
    	// 8. 작업 스레드 동작 플래그
    	bool			m_bWorkerRun;
    
    	// 9. 접속 스레드 동작 플래그
    	bool			m_bAccepterRun;
    
    	// 10. 소켓 버퍼
    	char			m_szBuf[1024];
    
    };
    
    
    

    cIOCompletionPort.cpp
    #include "StdAfx.h"
    
    #include "cIOCompletionPort.h"
    
    //// 흠
    #include "IOCompletionPortDlg.h"
    
    /// 쓰레드 만들기 
    /// WSARecv , WSASend의 Overlapped I/O 작업을 위한 
    unsigned int WINAPI	CallWorkerThread(LPVOID p)
    {
    	cIOCompletionPort * pOverlappedEvent = (cIOCompletionPort *)p;
    
    	pOverlappedEvent->WorkerThread();
    
    	return 0;
    }
    unsigned int WINAPI	CallAccepterThread(LPVOID p)
    {
    	cIOCompletionPort * pOverlappedEvent = (cIOCompletionPort *)p;
    
    	pOverlappedEvent->AccepterThread();
    
    	return 0;
    }
    /// 
    cIOCompletionPort::cIOCompletionPort(void)
    {
    	/// 모든 멤버 변수들의 초기화
    	m_pMainDlg = NULL;
    	m_bWorkerRun	= true;
    	m_bAccepterRun	= true;
    	m_nClientCnt = 0;
    	m_hAccepterThread = NULL;
    	m_hIOCP	= NULL;
    	m_socketListen = INVALID_SOCKET;
    	ZeroMemory(m_szBuf, 1024);
    	for ( int i=0; i < MAX_WORKERTHREAD; i++ ) {
    		m_hWorkerThread [i] = NULL;
    	}
    	m_pClientInfo = new stClientInfo[MAX_CLIENT];
    }
    
    
    cIOCompletionPort::~cIOCompletionPort(void)
    {
    	// 윈속 사용 해재
    	WSACleanup();
    	// 다 사용한 객체 삭제
    	if ( m_pClientInfo ) 
    	{
    		delete[] m_pClientInfo;
    		m_pClientInfo = NULL;
    	}
    }
    
    
    bool cIOCompletionPort::InitSocket(void)
    {
    	WSADATA	wsaData;
    
    	// 윈속 버젼 2.2
    	int nRet = WSAStartup( MAKEWORD(2,2) , &wsaData);
    	if ( 0 != nRet ) {
    
    		m_pMainDlg->OutputMsg("[에러]WSAStartup() 실패:%d:",WSAGetLastError() );
    
    		return false;
    	}
    	m_socketListen = WSASocket(AF_INET, SOCK_STREAM,
    					IPPROTO_TCP, NULL, NULL, WSA_FLAG_OVERLAPPED );
    
    	if ( INVALID_SOCKET == m_socketListen ) {
    
    		m_pMainDlg->OutputMsg("[에러]WSASocket() 실패:%d:",WSAGetLastError() );
    
    		return false;
    
    	}
    	m_pMainDlg->OutputMsg(" InitSocket() 성공" );
    	///
    	return true;
    }
    
    
    
    
    bool cIOCompletionPort::BindandListen(int nPort)
    {
    
    	SOCKADDR_IN		stServerAddr;
    
    	stServerAddr.sin_family = AF_INET;
    
    	stServerAddr.sin_port = htons(nPort);
    
    	stServerAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
    
    	int nRet = bind( m_socketListen, (SOCKADDR *)&stServerAddr, sizeof(SOCKADDR_IN) );
    	if ( 0 != nRet ) {
    
    		m_pMainDlg->OutputMsg("[에러] bind() 실패:%d:",WSAGetLastError() );
    
    		return false;
    
    	}
    	nRet = listen( m_socketListen, 5 ) ;
    	if ( 0 != nRet ) {
    
    		m_pMainDlg->OutputMsg("[에러] listen() 실패:%d:",WSAGetLastError() );
    
    		return false;
    
    	}
    	m_pMainDlg->OutputMsg(" BindandListen() 성공" );
    	///
    	return true;
    }
    
    
    
    
    
    bool cIOCompletionPort::CreateWorkerThread(void)
    {
    
    	unsigned int uiThreadId = 0;
    
    	/// Waiting Thread Queue에 대기 상태로 넣을 쓰레드들 생성
    	/// 권장하는 개수 : cpu *2 +1
    
    	for ( int i =0; i < MAX_WORKERTHREAD; i++ ) 
    	{
    
    		m_hWorkerThread[i] = (HANDLE)_beginthreadex(NULL, 0,
    							&CallWorkerThread,
    							this,
    							CREATE_SUSPENDED,
    							&uiThreadId);
    		if ( m_hWorkerThread[i] == NULL ) 
    		{
    			m_pMainDlg->OutputMsg("[에러] CreateWorkerThread() 실패:%d:",GetLastError() );
    
    			return false;
    		}
    		ResumeThread( m_hWorkerThread[i] );
    	}
    
    
    	m_pMainDlg->OutputMsg(" CreateWorkerThread() 성공" );
    	///
    	return true;
    }
    
    
    bool cIOCompletionPort::CreateAccepterThread(void)
    {
    
    	unsigned int uiThreadId = 0;
    
    	/// 클라이언트 접속 요청을 받은 쓰레드 생성
    	m_hAccepterThread = (HANDLE)_beginthreadex(NULL, 0,
    						&CallAccepterThread,
    						this,
    						CREATE_SUSPENDED,
    						&uiThreadId);
    	if ( m_hAccepterThread == NULL ) 
    	{
    		m_pMainDlg->OutputMsg("[에러] CreateAccepterThread() 실패:%d:",GetLastError() );
    
    		return false;
    	}
    	ResumeThread( m_hAccepterThread );
    
    
    	m_pMainDlg->OutputMsg(" CreateAccepterThread() 성공" );
    	///
    	return true;
    }
    ////
    bool cIOCompletionPort::BindIOCompletionPort(stClientInfo * pClientInfo)
    {
    
    	HANDLE hIOCP;
    
    	/// socket 과 pClientInfo를  CompletionPort객체와 연결 시킨다.
    	hIOCP = CreateIoCompletionPort( (HANDLE)pClientInfo->m_socketClient,
    			m_hIOCP,
    			reinterpret_cast( pClientInfo ),
    			0);
    	if ( NULL == hIOCP || m_hIOCP != hIOCP ) 
    	{
    		m_pMainDlg->OutputMsg("[에러] CreateIoCompletionPort() 실패:%d:",GetLastError() );
    
    		return false;
    	}
    
    
    	m_pMainDlg->OutputMsg(" BindIOCompletionPort() 성공" );
    	///
    	return true;
    }
    
    bool cIOCompletionPort::StartServer(void)
    {
    
    
    	m_hIOCP = CreateIoCompletionPort( INVALID_HANDLE_VALUE,
    								NULL,
    								NULL,
    								0);
    	if ( NULL == m_hIOCP ) 
    	{
    		m_pMainDlg->OutputMsg("[에러] CreateIoCompletionPort() 실패:%d:",GetLastError() );
    
    		return false;
    	}
    
    	
    	bool bRet = CreateWorkerThread();
    	if ( false == bRet ) 
    	{
    		m_pMainDlg->OutputMsg("[에러] CreateWorkerThread() 실패:%d:",GetLastError() );
    
    		return false;
    	}
    
    	bRet = CreateAccepterThread();
    	if ( false == bRet ) 
    	{
    		m_pMainDlg->OutputMsg("[에러] CreateAccepterThread() 실패:%d:",GetLastError() );
    
    		return false;
    	}
    
    	m_pMainDlg->OutputMsg(" StartServer() 성공" );
    	///
    	return true;
    }
    
    	
    bool cIOCompletionPort::BindRecv(stClientInfo * pClientInfo)
    {
    	DWORD	dwFlag = 0;
    	DWORD	dwRecvNumBytes = 0;
    
    
    	// Overlapped I/O Setting
    	pClientInfo->m_stRecvOverlappedEx.m_wsaBuf.len = MAX_SOCKBUF;
    	pClientInfo->m_stRecvOverlappedEx.m_wsaBuf.buf = 
    		pClientInfo->m_stRecvOverlappedEx.m_szBuf;
    	pClientInfo->m_stRecvOverlappedEx.m_eOperation = OP_RECV;
    
    	
    	//// 입력 버퍼 클리어 ???????????????
    	//ZeroMemory(pClientInfo->m_stRecvOverlappedEx.m_szBuf, 1024);
    
    	int nRet = WSARecv( pClientInfo->m_socketClient,
    						&(pClientInfo->m_stRecvOverlappedEx.m_wsaBuf),
    						1,
    						&dwRecvNumBytes,
    						&dwFlag,
    						(LPWSAOVERLAPPED)&(pClientInfo->m_stRecvOverlappedEx),
    						NULL);
    	/// socket_error 이면 client socket이 끊어 진걸로 처리한다.
    	if ( nRet ==  SOCKET_ERROR && ( ERROR_IO_PENDING != WSAGetLastError()  )  ) 
    	{
    		m_pMainDlg->OutputMsg("[에러] BindRecv WSARecv() 실패 WSAGetLastError:%d:",WSAGetLastError() );
    
    		return false;
    	}
    	m_pMainDlg->OutputMsg("[알림] BindRecv WSARecv() 성공");
    	return true;
    }
    
    
    bool cIOCompletionPort::SendMsg(stClientInfo * pClientInfo, char * pMsg, int nLen)
    {
    	DWORD	dwRecvNumBytes = 0;
    
    	//전송될 메시지를 복사
    	CopyMemory( pClientInfo->m_stSendOverlappedEx.m_szBuf, pMsg, nLen );
    
    
    	// Overlapped I/O Setting 정보
    	pClientInfo->m_stSendOverlappedEx.m_wsaBuf.len = nLen;
    
    	pClientInfo->m_stSendOverlappedEx.m_wsaBuf.buf =
    							pClientInfo->m_stSendOverlappedEx.m_szBuf;
    
    	pClientInfo->m_stSendOverlappedEx.m_eOperation = OP_SEND;
    
    	int nRet = WSASend( pClientInfo->m_socketClient, 
    						&(pClientInfo->m_stSendOverlappedEx.m_wsaBuf),
    						1,
    						&dwRecvNumBytes,
    						0,
    						(LPWSAOVERLAPPED)&(pClientInfo->m_stSendOverlappedEx),
    						NULL);
    	if ( nRet == SOCKET_ERROR ) {
    		m_pMainDlg->OutputMsg("[에러] SendMsg WSASend() nRet:%s:","SOCKET_ERROR" );
    	}
    	/// socket_error 이면 client socket이 끊어 진걸로 처리한다.
    	if ( nRet ==  SOCKET_ERROR && ( WSAGetLastError() != ERROR_IO_PENDING )  ) 
    	{
    		m_pMainDlg->OutputMsg("[에러] SendMsg WSASend() 실패 WSAGetLastError:%d:",WSAGetLastError() );
    
    		return false;
    	}
    			
    	m_pMainDlg->OutputMsg("[알림] SendMsg WSASend() 성공");
    
    	return true;
    }
    // 할당
    stClientInfo * cIOCompletionPort::GetEmptyClientInfo(void)
    {
    	for(int i = 0; i < MAX_CLIENT; i++ ) 
    	{
    		if(INVALID_SOCKET == m_pClientInfo[i].m_socketClient) 
    		{
    			return &m_pClientInfo[i];
    		}
    	}
    	return NULL;
    }
    
    
    
    // 사용자 접속 받는 쓰레드
    void cIOCompletionPort::AccepterThread(void)
    {
    	SOCKADDR_IN		stClientAddr;
    
    	int nAddrLen = sizeof(SOCKADDR_IN);
    	while ( m_bAccepterRun)
    	{
    		// 접속 받을 구조체 인덱스 얻기
    		stClientInfo * pClientInfo = GetEmptyClientInfo();
    		if ( NULL == pClientInfo )
    		{
    			m_pMainDlg->OutputMsg("[에러] NULL == pClientInfo :%s:","Client FULL");
    
    			return ;
    		}
    		// 클라이언트 접속 요청까지 대기
    		pClientInfo->m_socketClient = accept ( m_socketListen,
    							(SOCKADDR *)&stClientAddr, &nAddrLen );
    		if ( INVALID_SOCKET == pClientInfo->m_socketClient ) {
    			continue;
    		}
    		// I/O Completion Port객체와 소켓을 연결 시킨다.
    		bool bRet = BindIOCompletionPort( pClientInfo );
    		if ( false == bRet ) {
    			return;
    		}
    		// Recv Overlapped I/O 작업을 요청한다
    		bRet = BindRecv(pClientInfo);
    		if ( false == bRet ) {
    			return;
    		}
    
    		m_pMainDlg->OutputMsg("[클라이언트 접속] ip(%s) SOCKET(%d)",
    			inet_ntoa( stClientAddr.sin_addr) ,
    			pClientInfo->m_socketClient);
    
    		m_nClientCnt ++;
    
    	}
    }
    
    
    void cIOCompletionPort::WorkerThread(void)
    {
    	// CompletionKey를 받을 포인터 변수
    	stClientInfo * pClientInfo = NULL;
    
    	// 함수 호출 성공여부
    	BOOL bSuccess = TRUE;
    
    	// Overlapped I/O작업에서 전송된 데이타 크기
    	DWORD dwIoSize = 0;
    
    	// I/O 작업을 위해 요청한 Overlapped 구조체를 받을 포인터
    	LPOVERLAPPED lpOverlapped = NULL;
    
    	while ( m_bWorkerRun ) 
    	{
    		/**
    		이 함수로 인해 쓰래들들은 WaitingThread Queue에 대기상태로 들어간다
    		완료된 Overlapped I/O 작업이 발생하면 IOCP Queue에서 완료된 작업을 가져와 뒤처리
    		그리고 PostQueuedCompletionStatus()함수에 의해 사용자 메시지가 도착되면 쓰레드 종료
    		**/
    		bSuccess = GetQueuedCompletionStatus( m_hIOCP,
    				&dwIoSize,							// 실제 전송된 바이트
    				(LPDWORD)&pClientInfo,				// Completionkey
    				&lpOverlapped,						// Overlappped I/O 객체
    				INFINITE);							// 대기할 시간(무한대기)
    		
    		// 클라이언트가 접속 끊었을 때
    		//
    		//  FALSE == bSuccess
    		//
    		if ( FALSE == bSuccess && 0 == dwIoSize ) 
    		{
    			m_pMainDlg->OutputMsg("[클라이언트] SOCKET(%d) 접속 끊김",	pClientInfo->m_socketClient);
    			CloseSocket(pClientInfo);
    			continue;
    		}
    
    		// 사용자 스레드 종료 메시지 처리
    		//
    		//  TRUE == bSuccess
    		//
    
    		if ( TRUE == bSuccess && 0 == dwIoSize && NULL == lpOverlapped )
    		{
    			//
    			// WorkerThread 종료
    			//
    			m_bWorkerRun = false ;
    			continue;
    		}
    
    		if ( NULL == lpOverlapped ) {
    			continue;
    		}
    		stOverlappedEx * pOverlappedEx =(stOverlappedEx *)lpOverlapped;
    
    		// Overlapped I/O Recv 작업 결과 뒤 처리
    		// 
    		//	OP_RECV
    		//
    		if ( OP_RECV == pOverlappedEx->m_eOperation ) 
    		{
    			pOverlappedEx->m_szBuf[dwIoSize] = NULL;
    			m_pMainDlg->OutputMsg("[수신] ( %d ) bytes , msg : %s ",dwIoSize,pOverlappedEx->m_szBuf);
    
    			// 클라이언트에 메시지를 에코한다.
    			//BindRecv( pClientInfo );
    
    			//SendMsg(pClientInfo, pOverlappedEx->m_szBuf, dwIoSize );
    
    			//pOverlappedEx->m_eOperation = OP_SEND;
    			//BindRecv( pClientInfo );
    
    			SendMsg( pClientInfo, pOverlappedEx->m_szBuf, dwIoSize);
    
    		}
    		// Overlapped I/O Send 작업 결과 뒤 처리
    		// 
    		//	OP_SEND
    		//
    		else if ( OP_SEND == pOverlappedEx->m_eOperation ) 
    		{
    			m_pMainDlg->OutputMsg("[송신] ( %d ) bytes , msg : %s ",dwIoSize,pOverlappedEx->m_szBuf);
    			
    			//// 입력 버퍼 클리어 ???????????????
    			ZeroMemory(pOverlappedEx->m_szBuf, 1024);
    
    			BindRecv( pClientInfo );
    
    		}
    		else 
    		{
    			m_pMainDlg->OutputMsg("[클라이언트] SOCKET(%d) 예외 상황 ",pClientInfo->m_socketClient);
    		}
    		lpOverlapped = NULL;
    	}
    }
    
    void cIOCompletionPort::SetMainDlg(CIOCompletionPortDlg * pMainDlg)
    {
    	/// .h에서 .cpp로 옮겨 놓았는데? 문제 없나?
    	m_pMainDlg = pMainDlg;
    }
    
    
    void cIOCompletionPort::DestroyThread(void)
    {
    	for(int i=0; i< MAX_WORKERTHREAD; i++)
    	{
    		// WaitingThreadQueue에서 대기중인 쓰레드에 사용자 종료 메시지 보내기
    		PostQueuedCompletionStatus( m_hIOCP,0,0,NULL);
    	}
    	for(int i=0; i< MAX_WORKERTHREAD; i++)
    	{
    		CloseHandle( m_hWorkerThread[i] );
    		WaitForSingleObject ( m_hWorkerThread[i], INFINITE );
    	}
    
    	m_bAccepterRun = false;
    	// Accepter Thread 종료
    	closesocket( m_socketListen );
    	// Thread 종료
    	WaitForSingleObject( m_hAccepterThread, INFINITE );
    
    
    }
    
    
    void cIOCompletionPort::CloseSocket(stClientInfo * pClientInfo, bool bIsForce)
    {
    	struct linger stLinger = {0,0};
    
    	if ( true ) {
    		// timeout=0으로 설정되어 강제 종료. 주의 : 데이타 손실 가능성
    		// right now !!!
    		stLinger.l_onoff = 1;
    	}
    	// 데이타 송수신 모두 중단
    	shutdown( pClientInfo->m_socketClient, SD_BOTH );
    
    	// 소켓 옵션
    	setsockopt( pClientInfo->m_socketClient, SOL_SOCKET, SO_LINGER,
    			(char *)&stLinger, sizeof(stLinger) );
    	// 소켓 연결 종료
    	closesocket(pClientInfo->m_socketClient);
    	pClientInfo->m_socketClient = INVALID_SOCKET;
    }
    
    


    Windows Automation API 3.0이 설치되어 있을 때 Visual Studio 2010이 더 빠르게 실행됨



    Windows Automation API를 사용하는 응용 프로그램은 Windows Automation API 3.0이 설치되어 있지 않을 때 Microsoft Visual Studio IntelliSense 성능을 크게 떨어뜨립니다. 예를 들어 Windows 펜 및 터치 서비스는 Windows Automation API 3.0이 설치되어 있지 않을 때 Visual Studio IntelliSense 성능을 크게 떨어뜨릴 수 있습니다

    http://support.microsoft.com/kb/981741/ko

    + Recent posts