string Hex to bytes

CString str;

std::string hex((LPCTSTR)str);

//std::vector<char> HexToBytes(const std::string& hex)

std::vector<char> bytes;

for (unsigned int i = 0; i < hex.length(); i += 2)

{

std::string byteString = hex.substr(i, 2);

char byte = (char) strtol(byteString.c_str(), NULL, 16);

bytes.push_back(byte);

}

16개 Mask값으로 변환

int nCardMask = pWiegandConfig->wiegandCardMask;

int nCardMask = pWiegandConfig->wiegandCardMask;

unsigned char* pucBinStr = new unsigned char[16];

memset(pucBinStr, 0x00, sizeof(unsigned char)*16); long binary(0), k(0); while(nCardMask > 0)

{

binary = nCardMask % 2;

pucBinStr[k] = binary;

nCardMask = nCardMask / 2; k++;

}

delete []pucBinStr;

순수가상함수 vs 가상함수

[ 가상함수(Virtual Function) ]

1. 가상함수 개요

■ 가상함수(Virtual Function)란?

– 실행시 사용된 객체에 의해 실행 코드가 결정되는 함수

– 지정자 virtual로 선언됨 지정자 virtual은 선언문에 존재함.

class Animal {

  public :

virtual void breathe();

};


class Fish : public Animal {

  public :

virtual void breathe();

};

 

■ 가상 함수의 호출

– 기반클래스의 포인터가 파생클래스의 객체를 가리킬 때, 그 포인터를 통해 가상함수를 호출하면 파생 클래스에서 재정의한 함수가 호출됨.

Animal* a = new Fish;

a -> breathe();    // Fish::breathe 호출

→ a->breathe(); 는 Fish::breathe 를 호출함. 즉, a->breathe(); 는 함수명으로는 Animal::breathe를 호출하지만, 내용으로는 Fish::breathe 가 호출되는 것임.

 

■ 기반클래스의 멤버함수가 virtual 을 갖고 있을 때

– 파생클래스에서 재정의한 함수는 virtual 을 생략해도 저절로 가상함수가 됨.

– 그러나 기반클래스의 가상 함수를 파생클래스에서 재정의할  때도 virtual 을 명시해주는 것이 관례임. (소스코드 이해가 수월하기 위해서…)

 

2. 가상함수의 특징

■ 동적바인딩(Dynamic Binding)

– 가상함수는 동적결합(Dynamic Binding)을 함. (코드만 봐서는 어떤 코드가 실행될지 정해지지 않은 상태. 즉, 객체를 생성하는 시점에 생성조건에 따라 실행코드가 결정된다.)

– 일반함수는 정적 결합을 하는 반면, 가상 함수는 동적 결합을 함.

 

■ 가상함수(동적 바인딩)의 장단점

– 장점 : 실행시 다형성(Runtime Polymorphism) 이라는 융통성을 갖게 됨. (상황에 생성하는 조건에 따라 실행 코드를 변경할 수 있다.)

– 단점 : 가상함수를 사용하면 실행속도에서는 손해

3. 가상 함수 테이블 (Virtual Function Table)

■ vtable (Virtual Function Table) 이란?

– 가상 함수에 대한 포인터 배열

– 가상 함수를 사용하는 클래스의 각 객체는 vtable을 가리키는 vptr이라는 숨겨진 포인터를 갖고 있음.

– 가상함수를 포함한 클래스의 객체를 생성할 때 vtable이 만들어짐.

– 객체에서 가상함수를 호출하면 vptr를 통해 vtable에 있는 해당함수를 찾아 수행시킴.

 

■ 파생클래스에서의 vtable

– 기반클래스의 객체로부터 vtable을 물려 받아 수정 및 확장함.

– 파생클래스에서 재정의한 가상함수가 있다면 기반클래스의 가상 함수에 대한 포인터가 들어 있던 곳을 파생클래스에서 재정의한 가상함수에 대한 포인터로 변경함.

– 기반클래스의 포인터가 파생클래스의 오브젝트를 가리킬 때 그 포인터를 통해 가상 함수를 호출하면 파생클래스에서 수정한 vtable 을 사용하게 되므로 파생 클래스에서 재정의한 함수가 호출됨.

 

4. 가상 소멸자

■ 상속 관계에서 기반클래스의 포인터가 파생클래스의 동적 객체를 가리킬 때 그 포인터를 통해 동적 객체를 제거하면…?

기본적으로 기반 클래스의 소멸자만 호출되고 파생 클래스의 소멸자는 호출되지 않음.

class Animal {

};


class Fish : public Animal {

};


Animal* a = new Fish;

delete a;

 

■ 기반클래스의 포인터가 파생클래스의 동적 객체를 가리키고, 그 포인터를 통해 동적 객체를 제거했을 때 파생클래스의 소멸자까지 호출되도록 하려면…?

기반 클래스의 소멸자를 다음과 같이 가상 소멸자(Virtual Destructor)로 만들어야 함.

class Animal {

  public :

Animal();

virtual ~Animal();

};


class Fish : public Animal {

  public :

Fish();

virtual ~Fish();

};


Animal* a = new Fish;

delete a;

→ delete a;는 Animal::~Animal 뿐만 아니라 Fish::~Fish 도 호출함.

 

[ 순수가상함수(Pure Virtual Function) ]

1. 순수 가상 함수

■ 순수 가상 함수 (Pure Virtual Function) 란?

– 선언만 있고 정의가 없는 가상 함수

– 파생 클래스에서 재정의할 것으로 예상되는 함숭에 대해 미리 호출 계획을 세워 두기 위해 정의.

 

■ 가상함수를 순수 가상함수로 만드는 방법

– 다음의 예와 같이 선언시 0을 지정하면 됨.

– 여기서 ‘=0’은 Pure Specifier임.

class Animal {

  public :

virtual void breathe() = 0;

};


class Fish : public Animal {

  public :

Fish();

virtual void breathe();

};

→ Animal::breathe 는 순수 가상함수임. Animal::breathe 는 함수명만 제공하고, 내용은 Fish::breathe 에서 제공하게 됨.

 

2. 추상클래스 (Abstract Class)

■ 추상클래스란?

– 순수 가상함수를 포함하고 있는 클래스.

– 파생클래스의 행동양식을 미리 설계하기 위한 기반클래스임.

– 일반적으로 순수 가상함수를 통해 인터페이스를 제공하고 파생클래스에서 그 인터페이스를 구현하게 됨.

class Animal { // 추상클래스 Animal

  public :

virtual void breathe() = 0;

};

 

■ 추상클래스의 특징

추상클래스의 객체를 생성하는 것은 불가능. ∵ 정의되지 않은 함수를 가지고 있기 때문…

– 추상클래스는 파생클래스를 정의하여 순수가상함수를 구현하였을 때 객체를 생성할 수 있다.

– 추상클래스의 포인터를 선언하는 것은 가능함.

– 추상클래스의 포인터는 흔히 파생 클래스의 객체를 가리키게 되는데, 추상클래스의 포인터가 파생클래스의 객체를 가리킬 때 그 포인터를 통해 가상함수를 호출하면 파생클래스에서 재정의한 함수가 호출됨.

Animal* a = new Fish;

a -> breathe(); // 추상클래스 Animal

delete a;

 

2. 순수 가상함수의 재정의

■ 순수 가상함수의 재정의

– 순수 가상함수는 사용하기 전에 반드시 재정의해야 함.

– 만일 기반 클래스가 순수 가상 함수를 포함하고 있는데 파생 클래스에서 그것을 재정의하지 않는다면 그 파생 클래스도 추상 클래스가 됨.

→ 기반클래스 뿐만 아니라, 그 파생클래스의 객체를 생성하는 것도 불가능 함.

class Animal { // 추상클래스 Animal

  public :

virtual void breathe() = 0;

};

class Fish : public Animal {

  public :

void swim();

};


Animal* a = new Animal; // error

Animal* f = new Fish;  // error

MFC 종료에 관하여(WM_CLOSE/WM_DESTROY/WM_QUIT)

WM_CLOSE

윈도우가 닫히기 전에 이 메시지가 전달되며 메인 윈도우인 경우는 응용 프로그램이 종료된다는 신호이다.
이 메시지를 처리하지 않고 DefWindowProc으로 보내면 DestroyWindow 함수를 호출하여 윈도우를 파괴하도록 한다.
이 메시지가 전달되었을 때는 아직 윈도우가 파괴된 것이 아니므로 윈도우가 파괴되는 것을 중간에 차단할 수 있다.
미저장 파일이 있거나 프로그램을 종료할 상황이 되지 않을 때 사용자에게 메시지 박스를 통해 종료 사실을 확인시킬
수 있으며 이 메시지를 가로채서 단순히 return하면 DestroyWindow가 호출되지 않도록 할 수 있다.
예를 들어 프로그램을 사용자가 닫을 때 “저장할까요?”라는 확인이 필요한 경우 등에 사용할 수 있다.

WM_DESTROY

윈도우가 파괴될 때 이 메시지가 전달된다.
사용자가 Alt+F4 또는 닫기 버튼을 누를 경우 WM_CLOSE 메시지가 전달되며 이 메시지를 별도로 처리하지 않으면
DefWindowProc은 DestroyWindow 함수를 호출하여 윈도우를 파괴한다. 또는 프로그램 코드 내부에서 명시적으로
DestroyWindow 함수를 호출할 때도 윈도우가 파괴되는데 이 함수 호출 결과로 WM_DESTROY 메시지가 전달된다.
이 메시지를 받은 윈도우는 윈도우의 종료를 위한 처리를 해야 하는데 예를 들어 열어 놓은 파일을 닫고 할당한
메모리를 해제하는 등의 정리 작업을 한다. WM_CREATE에서의 초기화 처리의 반대 동작이 이 메시지에 작성되는
것이 일반적이며 그외 레지스트리에 미보관 정보를 저장하는 등의 작업을 할 수 있다. 만약 파괴되는 윈도우가
클립보드 체인에 속해 있으면 자신을 클립보드 체인에서 제거해야 한다.
DestroyWindow 함수는 파괴할 윈도우를 화면에서 숨긴 후 이 메시지를 보내므로 이 메시지를 받은 시점에서는
윈도우 자체가 파괴되지 않은 상태이다. 또한 DestroyWindow 함수는 자식 윈도우에게도 이 메시지를 차례대로
보내주는데 부모 윈도우가 먼저 이 메시지를 받고 자식 윈도우에게로 이 메시지가 보내진다. 따라서 부모 윈도우가
이 메시지를 처리하는 동안은 모든 자식 윈도우가 아직 파괴되기 전이므로 자식 윈도우를 프로그래밍할 수 있다.
파괴되는 윈도우가 메인 윈도우일 경우 PostQuitMessage 함수를 반드시 호출하여 프로세스의 메시지 루프를
종료하도록 해야 한다. 만약 이 처리를 생략하면 윈도우만 파괴되고 메시지 루프는 계속 실행중인 상태가 되므로
프로세스가 종료되지 않는다.

WM_QUIT

응용 프로그램을 종료하라는 신호이다. PostQuitMessage 함수 호출에 의해 발생하며 GetMessage 함수가 0을
리턴하도록 함으로써 메시지 루프를 종료시키는 역할을 한다. GetMessage 함수는 WM_QUIT 이외의 모든 메시지에
대해 0이 아닌 값을 리턴하므로 계속 루프를 돌지만 WM_QUIT에 대해서만 0을 리턴한다.
그래서 메시지 루프는 통상 다음과 같이 작성된다.

while(GetMessage(&Message, 0, 0, 0))
{
TranslateMessage(&Message);
DispatchMessage(&Message);
}
return (int)Message.wParam;

GetMessage 함수가 0이 아닌 값을 리턴하는 동안 무한히 이 루프를 도는데 단 WM_QUIT가 전달될 때는
while문이 종료되며 따라서 WinMain이 종료된다. 메인 윈도우의 WM_DESTROY에서는 반드시 PostQuitMessage
함수를 호출하여 메시지 루프가 종료될 수 있도록 해 주어야 한다. 그렇지 않으면 메인 윈도우는 파괴되었으나
프로세스는 계속 실행중인 상태가 된다.

PeekMessage 함수는 WM_QUIT 메시지와 상관없이 메시지 큐에 메시지가 있는지만 리턴하므로 메시지 루프를
구성할 때 따로 WM_QUIT 메시지를 점검해야 한다.
for(;;)
{
if(PeekMessage(&Message, NULL, 0, 0, PM_REMOVE))
{
if (Message.message == WM_QUIT)
{
break;
}

TranslateMessage(&Message);
DispatchMessage(&Message);
}
else
{
// 백그라운드 작업
}
}

조사한 메시지가 WM_QUIT이면 메시지 루프를 탈출하는 별도의 코드가 필요하다.
WM_QUIT는 윈도우에게 전달되는 메시지가 아니므로 윈도우 프로시저는 이 메시지를 받을 수 없다.
윈도우 프로시저까지 전달되기 전에 메시지 루프에서 이 메시지를 차단하여 루프를 탈출하게 된다.

WM_NCDESTROY
비작업 영역이 파괴될 때 보내진다.
윈도우와 그 차일드들이 먼저 파괴된 후에 비작업 영역이 파괴되므로 이 메시지는 윈도우가 가장 마지막으로 받는 메시지이다.
WM_DESTROY보다 뒤에 발생되며 이 메시지를 받았을 때는 모든 차일드가 이미 파괴된 후이다.
반면 WM_DESTROY 메시지는 차일드가 아직 파괴되기 전이다.
종료 처리가 필요할 경우는 일반적으로 WM_DESTROY 메시지에 코드를 작성하므로 이 메시지는 실용적인 가치가 거의 없는
셈이며 처리하는 경우가 극히 드물다.

LPSTR과 LPCTSTR에 관해서

LPSTR, LPCSTR, LPTSTR, LPCTSTR , LPWSTR, LPCWSTR
뭔가 다 비슷 비슷해보이죠?

원래 c와 c++은 string이라는 똑똑한 자료구조형을 compiler차원에서 지원하고 있지 않습니다.

그대신 가장 많이 사용하는 string을 어떻게 저장해야 할지에 대해 고심한 결과…
결국 배열의 끝에 ”또는 0 또는 NULL값을 넣어 string을 표현하도록 했습니다.
결국 가장 적은 용량의 string처리와 가장 골치아픈 string처리가 탄생하는 순간이였죠.

어쨌거나 요점은…
Windows에서는 이런 string처리를 위해서 char* 형을 그대로 쓰기 보다는 LPCSTR등의 표현으로 대치해 사용함으로써, 개발의 편의성을 돕고 있습니다.

자… 그럼 서론이 길었고…
위의 골치아픈 형을 살펴보면..

같은 글자들이 여러번 반복해 나옴니다.

LP, C, STR 등이 거의 자주 반복되고,
어떤놈들은 T 나 W를 사용하기도 하죠.

글자를 하나씩 살펴볼까요.

LP는 long pointer를 나타내는 약어로서 16bit시절의 윈도우의 유산입니다.
과거 windows3.1까지의 시절에는 포인터는 모두 16bit였고, 24bit 메모리를 long pointer라는 것을 통해서 extended memory라는 이름으로 관리했었거든요..
현재 LP(long pointer)는 .Net에서는 64bit pointer를, VC++6.0과 그 이전 버전에서는 32bit pointer를 나타냅니다.

C는 constant, 즉 함수의 내부에서 인자값을 변경하지 말라는 뜻입니다.

STR은 말그대로 string자료가 될것이라는 뜻으로 내부적으로는 char형 배열에 null값 종료를 의미하고 있죠.

자… 그럼 해석해 봅시다..
LPSTR = long pointer string = char *
LPCSTR = long pointer constant string = const char *
결과적으로는 맨 마지막과 같은 형이라는 거죠.

그런데…
LPCTSTR!! 요넘은 무었이냐!!
LPCTSTR = long pointer constant t_string = const tchar *
앗! 오타입니다. t라는 놈이 들어갔네요..
오타일까요? ^^ 아닙니다. t라는 놈은 우리나라를 위해 아주 중요한 역할을 하는 놈이죠.. 이것은 잠시 이후에 살펴보겠습니다.

그럼 먼저..
W라는 넘을 살펴보죠…

W 이넘은 wide char를 나타냅니다. 쉽게 말하면 unicode죠..
win9x에서 사용하던 multibyte와는 다릅니다. 물론 한글 조합형 코드도 아니고…
unicode를 나타냅니다.

자 그럼 다시 해석을 해보죠.
LPWSTR = long pointer wide string = w_char *
LPCWSTR = long pointer constant wide string = const w_char *

위와 같이 해석됩니다.

그런데 t_char(‘티캐릭터’라고 읽습니다.)는 무었이냐!!

마이크로소프트가 세계 각국에 제품을 판매하면서..
각국의 언어에 맞추어 개발하는 것에 환멸을 느끼다가..
드디어 windows를 unicode기반으로 개발하는 작업에 착수했습니다.

그런데… 문제는 char는 1Byte이고 wide char는 2Byte이므로..
포인터 연산을 많이하는 c, c++코드는 호환성에 치명적인 문제가 있었죠.
그래서 컴파일러가 precompile option을 보고. 환경에 맞게 동작하는 코드를 작성할 수 있는 새로운 변수 모양의 Macro를 선언하게 되었습니다.
그것이 바로 TCHAR, t_char라는 변수죠.
이놈들은 자신의 운영체제가 multi-byte환경이면, char형으로,
unicode환경이면, w_char, wide char형으로 type casting됩니다.

그래서… 보통 windows 9x, 2000계열의 환경이라면,
LPTSTR = LPSTR = char *
LPCTSTR = LPCSTR = const char *가 됩니다.

그런데..
아마 저 코드에서..
(LPSTR)(LPCTSTR) 형변환을 할때 자세히 보면..
const 라는 키워드만 떼내는거지요…
그러니까 사실은 (char *)(const char *)와 같은 말입니다.
웃기는 형변환이죠..
그럼 없어도 될까요?
^^

없으면 당연히 오류가 나게됩니다.
왜냐면…(LPSTR)CString을 하면…. CString형 자료의 맨 처음 주소부터 char * 형으로 형변환하기 때문이죠.
CString형은 앞의 16Byte를 자료형을 표현하기 위해서 사용하기 때문에, 여기서부터 형 변환을 해주면 엉뚱한 값이 표현되게 됩니다.

따라서 MFC에서 지원하는 CString class는 LPCTSTR라는 함수를 통해서 일단 안전하게 const char * 형으로 바뀐 자료형을 얻어오게 하는거죠.

CString myString;
(LPCTSTR)myString;이라고 해주면..
myString내부의 string 값을 꺼내오게 도와주는 연산자 또는 함수를 사용하게 된겁니다.
즉 (LPCTSTR)이란 놈이 반환값이 const char* 인 함수입니다.
정확하게 표현하면 operator overloading이라는 거지요.

결과적으로 (LPSTR)(LPCTSTR)myString은
myString의 내부 string 자료를 함수를 통해 자료를 꺼내온뒤에, char* type으로 안전하게 바꾸어주는 역할을 하게 되는 거지요.

참고로, 함수의 인자가 char * 인곳에 const char* 형을 넣으면 컴파일 오류가 발생하기 때문에 (LPSTR)을 한번더 앞에 써주어서 강제 type casting을 한 것입니다.

그럼 도움이 되셨길 바래요.

출처 : Tong – lcyblue님의 C++통

소켓 입출력 모델 : WSAEventSelect

WSAEventSelect 모델은 WSAEventSelect() 함수가 핵심적인 역할을 한다는 뜻에서 붙인
이름이다.
WSAEventSelect 모델에서는 이벤트 객체를 통해 네트워크 이벤트를 감지한다.
각 소켓에 대해 이벤트 객체를 생성하고, 이 이벤트 객체를 관찰함으로써 멀티스레드를
사용하지 않고도 여러 개의 소켓을 처리할 수 있다.

1. 동작 원리
각 소켓마다 이벤트 객체를 하나씩 생성하여 짝지어두면, 네트워크 이벤트가 발생할 때마다
이벤트 객체는 신호 상태가 된다.
따라서 이벤트 객체의 신호 상태를 통해 네트워크 이벤트 발생을 감지할 수 있다.
그러나 이것만으로는 구체적으로 어떤 종류의 이벤트가 발생했는지 혹은 어떤 오류가
발생했는지 알 수 없다는 문제가 있다.

*WSAEventSelect 모델이 제대로 동작하기 위해서는 다음과 같은 기능이 반드시 필요하다.
-이벤트 객체 생성과 제거: WSACreateEvent(), WSACloseEvent()
-소켓과 이벤트 객체 짝짓기: WSAEventSelect()
-이벤트 객체의 신호 상태 감지하기: WSAWaitForMultipleEvents()
-구체적인 네트워크 이벤트 알아내기: WSAEnumNetworkEvents()

*WSAEventSelect 모델을 이용한 소켓 입출력 절차
-소켓을 생성할 때마다 WSACreateEvent()함수를 이용하여 이벤트 객체를 생성한다.
-WSAEventSelect() 함수를 이용하여 소켓과 이벤트 객체를 짝지음과 동시에, 처리할
네트워크 이벤트를 등록한다.
예를 들면, 소켓을 통해 데이터를 보내거나 받을 수 있는 상황이 되면 이벤트 객체를
신호 상태로 변경하라는 내용을 등록한다.
-WSAWaitForMultipleEvents()함수를 호출하여 이벤트 객체가 신호 상태가 되기를
기다린다.
등록한 네트워크 이벤트가 발생하면 해당 소켓과 연관된 이벤트 객체가 신호 상태가 된다.
-WSAEnumNetworkEvents()함수를 호출하여 발생한 네트워크 이벤트를 알아내고,
적절한 소켓 함수를 호출하여 처리한다.

이벤트 객체 생성과 제거
WSACreateEvent() 함수는 이벤트 객체를 생성하는 역할을 한다. 이때 생성되는
이벤트 객체는 항상 수동 리셋 이벤트며,
비신호 상태로 시작한다. 사용이 끝난 이벤트 객체는 WSACloseEvent() 함수를
호출하여 제거한다.

WSAEVENT WSACreateEvent();
성공: 이벤트 객체 핸들, 실패: WSA_INVALID_EVENT

BOOL WSACloseEvent(WSAEVENT hEvent);
성공: TRUE, 실패: FALSE

소켓과 이벤트 객체 짝짓기
WSAEventSelect() 함수는 소켓과 이벤트 객체를 짝지음과 동시에, 처리할 네트워크
이벤트를 등록하는 역할을 한다.

int WSAEventSelect(
SOCKET s, //1
WSAEVENT hEventObject, //2
long lNetworkEvents //3
);
성공: 0, 실패: SOCKET_ERROR

1. SOCKET s
처리하고자 하는 소켓이다.

2. WSAEVENT hEventObject
소켓과 연고나시킬 이벤트 객체의 핸들값이다.

3. long lNetworkEvents
처리할 네트워크 이벤트 종류를 비트 마스크조합으로 나타낸다.
WSAAsyncSelect 모델과 동일하다.
*네트워크 이벤트 상수값
FD_ACCEPT: 클라이언트가 접속하면 윈도우 메시지를 발생시킨다.
FD_READ: 데이터 수신이 가능하면 윈도우 메시지를 발생시킨다.
FD_WRITE: 데이터 송신이 가능하면 윈도우 메시지를 발생시킨다.
FD_CLOSE: 상대가 접속을 종료하면 윈도우 메시지를 발생시킨다.
FD_CONNECT: 접속이 완료되면 윈도우 메시지를 발생시킨다.
FD_OOB: OOB 데이터가 도착하면 윈도우 메시지를 발생시킨다.

//소켓 s와 이벤트 객체를 짝짓고, FD_READ와 FD_WRITE 이벤트를 등록하는 예
WSAEVENT hEvent = WSACreateEvent();
WSAEventSelect(s, hEvent, FD_READ|FD_WRITE);

WSAEventSelect() 함수 사용 시 유의할 점은 다음과 같다.
-WSAEventSelect() 함수를 호출하면 해당 소켓은 자동으로 넌블로킹 모드로 전환된다.
-socket()함수가 리턴하는 소켓은 연결 대기 소켓(ListeningSocket)과 동일한
속성을 지니게 된다.
연결 대기 소켓은 직접 데이터 송수신을 하지 않으므로 FD_READ, FD_WRITE
이벤트를 처리하지 않는다.
반면 accept() 함수가 리턴하는 소켓은 FD_READ, FD_WRITE 이벤트를 처리해야
하므로 ,
다시 WSAEventSelect()함수를 호출해서 속성을 변경해야 한다.
-WSAEWOULDBLOCK오류 코드를 체크해야 한다.
-네트워크 이벤트 발생 시 적절한 소켓 함수를 호출하지 않으면, 다음 번에는 같은
네트워크 이벤트가 발생하지 않는다.
따라서 네트워크 이벤트가 발생하면 대응 함수를 호출해야 하며, 그렇지 않을 경우
애플리케이션이 네트워크 이벤트
발생 사실을 기록해두고 나중에 대응 함수를 호출해야 한다.
*네트워크 이벤트 대응 함수
FD_ACCEPT: accept()
FD_READ: recv(), recvfrom()
FD_WRITE: send(), sendto()
FD_CLOSE: 없음
FD_CONNECT: 없음
FD_OOB: recv(), recvfrom()

이벤트 객체의 신호 상태 감지하기
WSAWaitForMultipleEvents() 함수는 여러 이벤트 객체를 동시에 관찰할 수 있는
기능을 제공한다.

DWORD WSAWaitForMultipleEvents(
DWORD cEvents, //1
const WSAEVENT* lphEvents, //2
BOOL fWaitAll, //3
DWORD dwTimeout, //4
BOOL fAlertable //5
);
성공: WSA_WAIT_EVENT_0 ~ WSA_WAIT_EVENT_0 + cEvents-1 또는 WSA_WAIT_TIMEOUT
실패: WSA_WAIT_FAILED

1,2. DWORD cEvnets, const WSAEVENT* lphEvents
WSAWaitForMultipleEvents() 함수를 사용할 때는 이벤트 객체 핸들을 모두 배열에
저장해야 한다.
cEvents는 배열 원소 개수, lphEvents는 배열의 시작 주소를 나타낸다.
cEvents 최대값은 WSA_MAXIMUM_WAIT_EVENTS(64)다.

3. BOOL fWaitAll
TRUE면 모든 이벤트 객체가 신호 상태가 될 때까지 대기한 후 리턴한다.
FALSE면 한 이벤트 객체가 신호 상태가 되는 즉시 리턴한다.

4. DWORD dwTimeout
대기 시간으로 밀리초 단위를 사용한다. 이 시간 내에 조건이 만족되지 않더라도
WSAWaitForMultipleEvents() 함수는 리턴한다.
대기 시간으로 WSA_INFINITE 값을 사용하면 조건이 만족될 때까지 무한히 대기한다.

5. BOOL fAlertable
입출력 완료 루틴과 관련된 부분이다.
WSAEventSelect 모델에서는 항상FALSE를 사용한다.

구체적인 네트워크 이벤트 알아내기
WSAEnumNetworkEvents() 함수는 소켓과 관련하여 발생한 구체적인 네트워크
이벤트를 알려주는 역할을 한다.

int WSAEnumNetworkEvents(
SOCKET s, //1
WSAEVENT hEventObject, //2
LPWSANETWORKEVENTS lpNetworkEvents //3
);
성공:0, 실패: SOCKET_ERROR

1. SOCKET s
대상 소켓을 나타낸다.

2. WSAEVENT hEventObject
대상 소켓s와 짝지어둔 이벤트 객체 핸들을 넘겨주면 자동으로 이벤트 객체가 비신호
상태로 된다.
이 인자는 선택 사항이므로 사용하지 않으려면 NULL값을 넘겨주면 된다.

3. LPWSANETWORKEVENTS lpNetworkEvents
WSANETWORKEVENTS 구조체 변수 주소값을 넘겨주면, 발생한 네트워크 이벤트와
오류 정보가 이 변수에 저장된다.
//WSANETWORKEVENTS 구조체 정의
typedef struct _WSANETWORKEVENTS{
long lNetworkEvents; //발생한 네트워크 이벤트를 알려준다.
int iErrorCode[FD_MAX_EVENTS]; //네트워크 이벤트와 연관된 오류 정보가
//저장된다.
} WSANETWORKEVENTS, *LPWSANETWORKEVENTS;

//————————————–
// EventSelectServer.cpp
// WSAEventSelect 모델을 이용한 에코서버
//————————————–

#include
#include
#include

#define BUFSIZE 512

// 소켓 정보 저장을 위한 구조체
struct SOCKETINFO
{
SOCKET sock;
char buf[BUFSIZE+1]; //”을 위한 추가공간 +1
int recvbytes; // 받은 바이트 수, 받은 데이터를 그대로 보내기 위해서
//필요하다.
int sendbytes; // 보낸 바이트 수
};

int g_nTotalSockets = 0;
SOCKETINFO* g_SocketInfoArray[WSA_MAXIMUM_WAIT_EVENTS]; //64
WSAEVENT g_EventArray[WSA_MAXIMUM_WAIT_EVENTS];

// 소켓 관리 함수
BOOL AddSocketInfo(SOCKET sock);
void RemoveSocketInfo(int nIndex);

// 오류 출력 함수
void ErrQuit(char* msg);
void ErrDisplay(char* msg);
void ErrDisplay(int errcode);

int main(int argc, char* argv[])
{
int retval;

// 윈속 초기화
WSADATA wsa;
if(WSAStartup(MAKEWORD(2,2), &wsa) != 0)
ErrQuit(“윈속 초기화 실패”);

// socket()
SOCKET ListenSock = socket(AF_INET, SOCK_STREAM, 0);
if(ListenSock == INVALID_SOCKET)
ErrQuit(“socket()”);

// 소켓 정보 추가
if(AddSocketInfo(ListenSock) == FALSE)
return -1;

// WSAEventSelect()
// 연결 대기 소켓과 이벤트 객체를 짝짓는다.
// 연결 대기 소켓은 FD_ACCEPT와 FD_CLOSE 두 개의 네트워크 이벤트만 처리하면 된다.
retval = WSAEventSelect(ListenSock, g_EventArray[g_nTotalSockets-1],
FD_ACCEPT|FD_CLOSE);
if(retval == SOCKET_ERROR)
ErrQuit(“WSAEventSelect()”);

// bind()
SOCKADDR_IN ServerAddr;
ZeroMemory(&ServerAddr, sizeof(ServerAddr));
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(9000);
ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);
retval = bind(ListenSock, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr));
if(retval == SOCKET_ERROR)
ErrQuit(“bind()”);

// listen()
retval = listen(ListenSock, SOMAXCONN);
if(retval == SOCKET_ERROR)
ErrQuit(“listen()”);

// 데이터 통신에 사용할 변수
int nIndex;
WSANETWORKEVENTS NetworkEvents;
SOCKET ClientSock;
SOCKADDR_IN ClientAddr;
int nAddrLen;

while(1){
// 이벤트 객체 관찰
// 이벤트 객체가 신호 상태가 될 때까지 대기한다.
// WSAWaitForMultipleEvents() 함수의 리턴값은 신호 상태가 된 이벤트 객체의
// 배열 인덱스 + WSA_WAIT_EVENT_0 값이다.
// 그러므로 실제 인덱스 값을 얻으려면 WSA_WAIT_EVENT_0값을 빼야 한다.
nIndex = WSAWaitForMultipleEvents(g_nTotalSockets, g_EventArray,
FALSE, WSA_INFINITE, FALSE);
if(nIndex == WSA_WAIT_FAILED){
ErrDisplay(“WSAWaitForMultipleEvents()”);
continue;
}
nIndex -= WSA_WAIT_EVENT_0;

// 구체적인 네트워크 이벤트 알아내기
retval = WSAEnumNetworkEvents(g_SocketInfoArray[nIndex]->sock,
g_EventArray[nIndex], &NetworkEvents);
if(retval == SOCKET_ERROR){
ErrDisplay(“WSAEnumNetworkEvents()”);
continue;
}

// FD_ACCEPT 이벤트 처리
if(NetworkEvents.lNetworkEvents & FD_ACCEPT){
if(NetworkEvents.iErrorCode[FD_ACCEPT_BIT] != 0){
ErrDisplay(NetworkEvents.iErrorCode[FD_ACCEPT_BIT]);
continue;
}

nAddrLen = sizeof(ClientAddr);
ClientSock = accept(g_SocketInfoArray[nIndex]->sock,
(SOCKADDR*)&ClientAddr, &nAddrLen);
if(ClientSock == INVALID_SOCKET){
ErrDisplay(“accept()”);
continue;
}
printf(“[TCP 서버] 클라이언트 접속: IP주소=%s, 포트 번호=%d\n”,
inet_ntoa(ClientAddr.sin_addr), ntohs(ClientAddr.sin_port));
if(g_nTotalSockets >= WSA_MAXIMUM_WAIT_EVENTS){
printf(“[오류] 더 이상 접속을 받아들일 수 없습니다!\n”);
closesocket(ClientSock);
continue;
}

// 소켓 정보를 추가한다.
if(AddSocketInfo(ClientSock) == FALSE)
continue;

// 새로 접속한 클아이언트 소켓에 FD_READ, FD_WRITE, FD_CLOSE
//이벤트들을 등록한다.
retval == WSAEventSelect(ClientSock, g_EventArray[g_nTotalSockets-1],
FD_READ|FD_WRITE|FD_CLOSE);
if(retval == SOCKET_ERROR)
ErrQuit(“WSAEventSelect()”);
}

// FD_READ, FDWRITE 이벤트 처리
if(NetworkEvents.lNetworkEvents & FD_READ
|| NetworkEvents.lNetworkEvents & FD_WRITE)
{
if(NetworkEvents.lNetworkEvents & FD_READ &&
NetworkEvents.iErrorCode[FD_READ_BIT] != 0)
{
ErrDisplay(NetworkEvents.iErrorCode[FD_READ_BIT]);
continue;
}
if(NetworkEvents.lNetworkEvents & FD_WRITE &&
NetworkEvents.iErrorCode[FD_WRITE_BIT] != 0)
{
ErrDisplay(NetworkEvents.iErrorCode[FD_WRITE_BIT]);
continue;
}

SOCKETINFO *pSocketInfo = g_SocketInfoArray[nIndex];

// 받은 바이트 수가 0일 경우에만 recv() 함수를 호출하여 데이터를 읽는다.
if(pSocketInfo->recvbytes == 0){
// 데이터 받기
retval = recv(pSocketInfo->sock, pSocketInfo->buf, BUFSIZE, 0);
if(retval == SOCKET_ERROR){
if(WSAGetLastError() != WSAEWOULDBLOCK){
ErrDisplay(“recv()”);
RemoveSocketInfo(nIndex);
}
continue;
}
pSocketInfo->recvbytes = retval;
// 받은 데이터 출력
pSocketInfo->buf[retval] = ”;
nAddrLen = sizeof(ClientAddr);
getpeername(pSocketInfo->sock, (SOCKADDR*)&ClientAddr, &nAddrLen);
printf(“[TCP/%s:%d] %s\n”, inet_ntoa(ClientAddr.sin_addr),
ntohs(ClientAddr.sin_port), pSocketInfo->buf);
}

// 받은 바이트 수가 보낸 바이트 수보다 클 경우 send() 함수를 호출하여
//데이터를 보낸다.
// 모두 보냈다면 받은 바이트 수와 보낸 바이트 수를 다시 0으로 초기화한다.
if(pSocketInfo->recvbytes > pSocketInfo->sendbytes){
// 데이터 보내기
retval = send(pSocketInfo->sock, pSocketInfo->buf +
pSocketInfo->sendbytes, pSocketInfo->recvbytes – pSocketInfo->sendbytes,
0);
if(retval == SOCKET_ERROR){
if(WSAGetLastError() != WSAEWOULDBLOCK){
ErrDisplay(“send()”);
RemoveSocketInfo(nIndex);
}
continue;
}
pSocketInfo->sendbytes += retval;
// 받은 데이터를 모두 보냈는지 체크
if(pSocketInfo->recvbytes == pSocketInfo->sendbytes)
pSocketInfo->recvbytes = pSocketInfo->sendbytes = 0;
}
}

// FD_CLOSE 이벤트 처리
if(NetworkEvents.lNetworkEvents & FD_CLOSE){
if(NetworkEvents.iErrorCode[FD_CLOSE_BIT] != 0)
ErrDisplay(NetworkEvents.iErrorCode[FD_CLOSE_BIT]);
RemoveSocketInfo(nIndex); // 소켓 정보를 삭제한다. 이벤트 객체도
// 이 함수내에서 제거한다.
}
}

// 윈속 종료
WSACleanup();
return 0;
}

// 소켓 정보 추가
BOOL AddSocketInfo(SOCKET sock)
{
if(g_nTotalSockets >= WSA_MAXIMUM_WAIT_EVENTS){
printf(“[오류] 소켓 정보를 추가할 수 없습니다!\n”);
return FALSE;
}

SOCKETINFO* pSockInfo = new SOCKETINFO;
if(pSockInfo == NULL){
printf(“[오류] 메모리가 부족합니다!\n”);
return FALSE;
}

WSAEVENT hEvent = WSACreateEvent();
if(hEvent == WSA_INVALID_EVENT){
ErrDisplay(“WSACreateEvent()”);
return FALSE;
}

pSockInfo->sock = sock;
pSockInfo->recvbytes = 0;
pSockInfo->sendbytes = 0;
g_SocketInfoArray[g_nTotalSockets] = pSockInfo;
g_EventArray[g_nTotalSockets] = hEvent;
g_nTotalSockets++;

return TRUE;
}

// 소켓 정보 제거
void RemoveSocketInfo(int nIndex)
{
SOCKETINFO* pSocketInfo = g_SocketInfoArray[nIndex];

// 클라이언트 정보 얻기
SOCKADDR_IN ClientAddr;
int nAddrLen = sizeof(ClientAddr);
getpeername(pSocketInfo->sock, (SOCKADDR*)&ClientAddr, &nAddrLen);
printf(“[TCP 서버] 클라이언트 종료: IP주소=%s, 포트 번호=%d\n”,
inet_ntoa(ClientAddr.sin_addr), ntohs(ClientAddr.sin_port));

closesocket(pSocketInfo->sock);
delete pSocketInfo;
WSACloseEvent(g_EventArray[nIndex]);

for(int i=nIndex; i<g_nTotalSockets; i++){
g_SocketInfoArray[i] = g_SocketInfoArray[i+1];
g_EventArray[i] = g_EventArray[i+1];
}
g_nTotalSockets++;
}

// 소켓 함수 오류 출력 후 종료
void ErrQuit(char *pMsg)
{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER|
FORMAT_MESSAGE_FROM_SYSTEM,
NULL, WSAGetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf, 0, NULL);
MessageBox(NULL, (LPCTSTR)lpMsgBuf, (LPCTSTR)pMsg, MB_ICONERROR);
LocalFree(lpMsgBuf);
exit(-1);
}

// 소켓 함수 오류 출력
void ErrDisplay(char *pMsg)
{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER|
FORMAT_MESSAGE_FROM_SYSTEM,
NULL, WSAGetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf, 0, NULL);
printf("[%s] %s", pMsg, (LPCTSTR)lpMsgBuf);
LocalFree(lpMsgBuf);
}

// 소켓 함수 오류 출력
void ErrDisplay(int errcode)
{
LPVOID lpMsgBuf;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER|
FORMAT_MESSAGE_FROM_SYSTEM,
NULL, errcode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpMsgBuf, 0, NULL);
printf("[오류] %s", (LPCTSTR)lpMsgBuf);
LocalFree(lpMsgBuf);
}
[출처] 소켓 입출력 모델 : WSAEventSelect|작성자 쿠아

스레드 정리2

1. Process와 Thread

쓰레드라는 놈을 이해하기 위해선 프로세스란 놈을 먼저 이해해야한다. 프로세스는 EXE란 확장명을 가지고 파일을 실행시켰을 때 실질적으로 컴퓨터의 메모리에 자리잡고 앉아서 실행되는 형태를 말한다. 예를 들면 프로그램을 마우스로 더블클릭하면 OS가 그 프로그램을 실행하기 위해, 알맞게 메모리상에 그 프로세스가 실행가능한 영역을 잡아주고(이런 영역에는 여러가지 영역이 있다.코드부 or 스택부), 프로그램은 거기에 들어앉아서 온갖 사용자의 명령을 다 받으며 자기의 일을 수행한다.
즉, 프로세스는 메모리에 적재된 상태로 동작하는 프로그램이다.

프로세스를 이루는 기본단위가 쓰레드라 볼 수 있다. 프로세스는 적게는 수십개, 많게는 수백개의 Thread로 이루어져 있다. 즉 이런 쓰레드들이 실처럼 일률적인 순서대로 늘어져 원하는 작업을 수행하고 있는 것이죠. 쉽게 보면 쓰레드 하나가 특정한 작업을 하나 수행한다고 생각 할 수 있다.

프로그램을 짜다보면 여러가지 상황이 발생하는데, 블록킹모드, 논블록킹 모드, 동기모드, 비동기모드 등등..
예를 하나 들어 보면,
while( true )
{
int count;
printf( “Current number %d”, count );
count++;
}
위의 루틴에 들어가게 되면 프로그램은 블록(무한 Loop에 빠짐)됩니다. 사용자는 어떤 입력도 프로그램에 가 할 수 없습니다. 그래서 사람들은 저런 반복적으로 작업하는 계산 루틴 등을 쓰레드로 만들어 버린다. 즉 계산과정을 쓰레드로 만들어서 하나의 작은 작업으로 처리해 버리는 것이다. 그러므로 저렇게 반복적인 작업이나 시간이 오래 걸리는 작업, 또는 다른 작업과 병행해서 일을 해야 하는 경우는 쓰레드를 사용하게 된다.

물론 처리 속도 문제는 조금 다른 문제이다. 단지 쓰레드로 프로그램을 작성하면, OS차원에서 쓰레드가 CPU를 사용할 시간을 분배(Schedule)하는 것뿐이다. 단지 이 시간의 간격이 무지 짧기 때문에 사용자는 느끼지 못하고, 마치 순간적으로 동시에 작동하는 것처럼 보인다.

쓰레드는 사용자에게 융통성 있는 사용환경을 만들어 주기 위해서 개발자가 충분히 검토하고 개발에 적용해야할 테크닉이다.

2. 쓰레드의 장점

A. 애플리케이션의 일부가 비동기적이고 병렬적으로 실행가능하게 함
B. 코드의 복사본을 여러 개 수행하여 여러 개의 클라이언트에게 동일한 서비스 제공가능
C. 블록(죽어버릴 수 있는)될 가능성이 있는 작업을 수행할 때 방지할 수 있음
D. 멀티프로세서 시스템을 효과적으로 사용 할 수 있음

A. 애플리케이션의 일부가 비동기적이고 병렬적으로 실행가능하게 함
동기적이란 어떤 작업이 순차적으로 실행되어야 함을 말한다. 예를 들어 어떤 함수 두개가 있다고 가장하자.

int a = 2, b = 3, c;
int d = 4, e = 5, f;
funcA { c = a+b;}
funcB { f = d+e;}
단일 쓰레드 환경에서는 함수A를 호출한 다음에 함수B를 호출한다.
funcA()
funcB()
그런데 함수A와 함수B가 서로 완전히 다른 데이터를 가지고 작업을 한다면, 함수가 실행되는 순서에는 상관이 없다. 즉 함수의 호출 순서가 바뀌어도 상관이 없다.
funcB()
funcA()

그러나 새로운 함수 C를 만들어 보자.
in funcC { return c + f;}
위의 함수는 함수A와 함수B의 결과값을 가지고 연산을 하기 때문에, 함수A와 함수B가 완전히 실행이 끝난 다음에야 호출할 수가 있다. 위와 같이 순차적으로 실행을 해야만 하는 순차적인 프로그램 방식을 동기화(Synchronization)라고 합니다. 비동기화는 위와 같이 순차적으로 실행을 안 해도 되는 것이다.

단일 쓰레드를 지원하는 OS에서는 반드시 위와 같은 절차를 가지고 프로그래밍을 해야 하지만 멀티 쓰레드를 사용하는 OS에서는 프로그램을 작성할 때 각 부분 사이에 상호관계를 반영하여 설계를 할 수 있도록 하는 것이다.

B. 코드의 복사본을 여러 개 수행하여 여러 개의 클라이언트에게 동일한 서비스 제공가능
Network 프로그램 같은 경우 사용자의 접속을 계속 처리하기 위해서 쓰레드를 사용합니다. 즉 사용자가 접속 요청을 하면 하나의 쓰레드와 연결시켜준다. 여러 사용자가 단일 쓰레드의 서버 프로그램에 접속해서 서비스를 받는다고 가정하면, 한 명이 접속해서 그 사람이 접속을 끊을 때까지 다른 사용자는 서비스를 받을 수 없다.

C. 블록(죽어버릴 수 있는)될 가능성이 있는 작업을 수행할 때 방지할 수 있음
Network 프로그래밍에서 블록 되는 코드가 있다고 가정하자. 대표적으로 보면 accept()가 있는데, 이 함수는 사용자가 접속요청을 할 때까지 멈춰 있는다. 사용자 접속요청이 없으면 무한정 기다리게 된다. 그래서 접속요청을 받는 부분을 쓰레드로 만들어서 프로그램이 블록되지 않게 하는 것이다.
또 다른 예로, 탐색기에서 덩치가 큰 파일을 복사를 걸어놓고도 곧바로 탐색기를 다시 있는데, 이는 복사하는 루틴을 쓰레드로 만들었기 때문이다.

D. 멀티 프로세서 시스템을 효과적으로 사용 할 수 있음
CPU가 많은 시스템에서 쓰레드를 돌리면 좀더 효율적이다.

3. 멀티쓰레드 애플리케이션 구조

멀티쓰레드를 이용한 애플리케이션을 작성하는 구조에는 3가지 방법이 있다.
A. boss/worker모델.
B. work crew모델.
C. 파이프라이닝 모델.

A. Boss/Worker Model
쓰레드(주쓰레드)가 필요에 따라 작업자 쓰레드를 만들어 내는 경우에, 위에서 예를 든 것과 같이 접속받는 부분을 주쓰레드(boss)로 돌리고, 접속요청이 오면 새로운 쓰레드(worker)를 만들어 사용자와 연결시켜 주는 방법입니다.

B. Work Crew Model
어떤 한 작업을 여러 개의 쓰레드가 나눠서 하는 방식이다. 예를 들어, 집을 청소하는 작업에 대한 쓰레드를 여러 개 돌려, 방바닥 닦는 사람, 먼지 터는 사람 이런 식이다. 특정한 청소를 하는 작업이 쓰레드 하나가 되는 것이다.

C. Pipe Lining Model
이 구조에서는 순서대로 실행되어야할 작업이 여러 개가 있을 경우, 작업 1은 작업 2에게 전달하고, 작업2는 작업3에게 전달하는 방식으로 순차적으로 진행되어야 하지만 최종결과를 여러 개 만들어내기 위해서 동시에 수행될 필요가 있을 경우 멀티쓰레딩을 사용한다. 예를 들면 공장의 라인에서는 서로 다른 작업들을 수행하지만, 결국 하나의 결과물을 만들어 내기 위한 과정이다.

4. 쓰레드의 생성

쓰레드는 UI( User Interface ) Thread와 Worker( 작업자 ) 쓰레드로 나뉜다.
UI Thread는 사용자 메시지 루프를 가지고 있는(즉 어떤 메시지가 날라오면 일하는 쓰레드를 말한다.) 쓰레드이다.
Worker 쓰레드는 보통 오래 걸리는 작업이나 무한루프를 가지는 작업을 하는 사용자 정의 함수를 위한 것이다.

윈도우에서 쓰레드를 생성하는 방법은 여러가지가 있다. 즉 윈도우라는 OS에서 제공해주는 라이브러리함수를 가지고 쓰레드를 만드는 것입니다.

Wokrer 스레드를 만드는 방법을 알아보자.
어떤 프로그램이 Dialog 기반으로 돌아가며, 화면에 2초에 한번씩 숫자를 더하면 화면에 바뀐 숫자를 표시한다고 가정한다.

① 화면에 무한적으로 숫자를 뿌리는 함수를 만듦
UINT ThreadFunc( void* pParam )
{
int count;
while(true)
{
count++;
GetDlgItem( IDC_STATIC )‐>SetWindowText( atoi(count) );
Sleep( 2000 );
}
}
//정확한 코드는 아님
// IDC_STATIC는 static_control 의 resource id 임

이 함수는 2초에 한번씩 count를 증가시키면서 Dialog box에 증가된 값을 화면에 뿌린다. 이 함수는 쓰레드로 돌리려 한다.

② Worker 쓰레드 생성하기
작업자 쓰레드로 특정한 작업을 하는 사용자 정의 함수를 만들기 위해서, 윈도우에서는 여러가지 쓰레드 생성 함수를 제공해 준다.
1. CreateThread()
2. _beginthread(), _beginthreadex()
3. AfxBeginThread(), AfxBeginThreadEx()

1. CreateThread()

win32함수로써, 중요한 parameter만 살펴본다.
HANDLE handle;
Handle = CreateThread( Threadfunc(), Param );
첫번째 parameter는 위에서와 같이 사용자가 쓰레드로 돌려야할 작업함수를 써 주며, 두 번째 parameter는 작업함수에 parameter값으로 전달할 값이다. 이 parameter는 VOID*으로 되어 있기 때문에 4BYTE이내의 값은 어떤 값이든 들어갈수 있다. 대신 TYPE CASTING을 해주어야 합니다.(ex. (int) 3). 이 함수가 올바르게 실행이 되면 쓰레드에 대한 핸들을 반환하는데, 이 핸들을 가지고 쓰레드를 조작할 수 있게 된다.
대표적으로 쓰레드를 닫을 때 CloseHandle()함수를 사용해서 쓰레드 핸들을 넣어주고 쓰레드를 닫아 준다. 그러나 이 함수로 생성된 쓰레드를 닫을때는 ExitThread()면 된다.

2. _beginthread(), _beginthreadex()

CreateThread는 쓰레드에서 win32 API함수만 호출할 수 있다. 즉, 사용자가 어떤 작업을 하는 함수를 만들 때 CreateThread로 만들게 되면, 그 함수안에서는 win32 API만 사용할 수 있고, C함수나 MFC는 못쓴다.
그러나 _beginthread() 함수는 win32 API와 C 런타임 함수를 사용할 때 사용한다.
주의할 점은 이 함수도 쓰레드 핸들을 리턴하는데, 이 핸들을 가지고 쓰레드에 조작을 가 할 수 있다. 대신 이 함수를 사용하면 C 런타임 라이브러리가 핸들을 자동으로 닫으므로 이를 직접 닫을 필요는 없다. 하지만, _beginthreadex는 스레드 핸들을 직접 닫아야 합니다. 그리고 이 쓰레드를 닫을 때는 _endthread(), _endthreadex()를 사용하면 된다.

3. AfxBeginThread(), AfxBeginThreadEx()

실질적으로 가장 자주 사용하는 쓰레드 생성함수 이다. 이 함수를 이용하면 사용자 정의 함수내에서 MFC, win32 API, C 런타임 라이브러리등 여러가지 라이브러리 함수들을 전부 사용할 수 있다. 이 함수는 리턴값이 CwinThread* 형을 리턴하는데, 이 객체를 사용하면, 생성된 쓰레드에 여러가지 조작을 가할 수 있다. AfxEndThread()를 사용해서 종료 시킬 수 있다. 쓰레드가 종료되면 MFC는 쓰레드 핸들을 닫고 리턴값으로 받은 CwinThread*객체를 제거합니다.

5. MFC에서 작업자 쓰레드 생성하기..

MFC에서 쓰레드를 만드는 방법은 두 가지가 있다. CwinThread의 멤버 함수인 CreateThread를 사용하는 방법과 AfxBeginThread()를 사용하는 방법이다. 후자는 CwinThread객체를 만들 수 있다.

또한 MFC에서는 AfxBeginThread의 서로 다른 버전 두개를 정의 하고 있다. 하나는 작업자 쓰레드를 위한 것이고, 하나는 UI쓰레드를 위한 것인데, 이 두 가지 정의대한 소스코드는 Thrdcore.cpp에서 볼 수 있다.

CwinThread* pThread = AfxBeginThread( Threadfunc, &threadinfo );

▪ nProiority는 쓰레드의 우선순위를 지정한다. 기본적으로 THREAD_PRIORITY_NORMAL이 들어가는데 이는 평범한 순서로 작동시키겠다는 말이다. 이 우선순위는 나중에 리턴값으로 받은 CwinThread* 의 멤버 함수인 SetThreadPriority를 가지고 변경해 줄 수 있다.
▪ dwCreateFlags는 0 아니면 CREATE_SUSPEND이다. 기본값은 0이 들어가는데 0이면 바로 쓰레드가 실행되는 것이고 두 번째이면 쓰레드는 정지된 상태로 만들어지고 리턴값으로 받은 객체의 멤버함수인 ResumeThread를 호출하면 쓰레드를 시작한다.
▪ lpSecurityAttrs는 보안속성이다.

AfxBeginThread() 함수의 첫번째 인자로 들어갈 작업함수의 원형은 다음과 같다.

UINT ThreadFunc ( LPVOID pParam )

이 함수는 static 클래스 멤버 함수 이거나 클래스 외부에서 선언한 함수여야 한다.
즉, 어떤 함수를 클래스 멤버함수로 선언해서 쓰레드로 사용하려면 클래스내부에 함수 프로토타입 선언시 static로 선언해 주어야 한다는 건데, static 멤버 함수는 static 멤버 변수에만 접근 할 수 있기 때문에 제약이 많다.

인자값은 AfxBeginThread에서 두번째 인자로 들어갔던 32bit값의 파라미터 인데, 일반적으로 그냥 데이터형을 넘겨줄 수도 있고, 많은 데이터가 있을 때에는 구조체를 만들어서 포인터로 넘겨주어서 함수내부에서 다시 풀어서 사용한다.

쓰레드 함수 내부에서 만들어진 객체나 변수들은 쓰레드 자신의 내부 스택에 생성되기 때문에 다른 쓰레드에서 데이터를 변경하거나 하는 그런 문제들은 없다.

6. 쓰레드 멈추고 다시 실행하기

AfxBeginThread() 함수로 작업을 할 함수를 스레드로 돌리고 그 스레드가 올바르게 작동이 되면 CwinThread*형을 리턴값으로 받는다는 것을 알게 되었다. 이렇게 되면 우리는 이 실행시킨 스레드를 사용자가 멈추고 싶을 때 멈추게 할 수 있고, 또 멈췄으면 언젠가 다시 실행을 시켜야 한다.
우선 실행중인 스레드함수를 멈추게 할때는 우리가 리턴값으로 돌려받은 CwinThread*의 변수를 가지고 작업을 할 수 있다.
CwinThread::SuspendThread()
위의 함수가 바로 실행중인 스레드를 멈출 수 있게 하는 함수이다. 이 함수는 현재 실행중인 스레드 내부에서 호출할 수도 있고, 또한 다른 쓰레드 내에서도 호출할 수가 있다.
CwinThread::ResumeThread()
위 함수는 중지된 쓰레드를 깨우는 함수이다. 이 함수는 위의 SuspendThread같이 스레드 자기자신의 내부에서는 호출할 수가 없다. 다른 쓰레드나 메시지 Handler 함수들을 이용해서 ResumeThread()를 호출해야지 멈춰진 쓰레드를 다시 실행 할 수 있다.

(참고) 스레드 내부로 복잡하게 들어가면 내부적으로 각 스레드 함수는 Suspend count란 것을 유지하고 있어서 이 값이 0일 때만 스레드가 CPU시간을 할당받아 실행이 된다. 즉 AfxBeginThread함수에 dwCreateFlags에 CREATE_SUSPEND를 넣어줘서 실행한다거나 CwinThread::SuspendThread()를 실행하거나 하면 Suspend count는 1이 증가가 된다. 그래서 스레드는 멈추게 되고 다시 CPU로부터 시간을 할당받아 스레드가 활동을 재개하게 하려면 ResumeThred()를 호출해서 suspend count를 0으로 만들어 주어 CPU시간을 할당받게 해서 스레드를 다시 시작한다. 내부적으로 이런 메커니즘을 가지고 스레드는 동작하기 때문에 SuspendThread()를 두 번 호출하면 suspend count가 2가 되고, RecumeThread()도 두 번 호출해 줘야 스레드가 다시 활동할 수가 있다.

7. 스레드 잠재우기

쓰레드를 잠재우는 이유는 크게 두 가지가 있다.
하나는 에니메이션을 구현하거나 아날로그 시계의 바늘 같이 시간이 경과함에 따라 화면에 그림을 그린다든지 하는 경우, 즉 시간 경과에 따라 실행하여야 하는 프로그램의 경우이고,
두 번째는 어떤 스레드가 현재 실행되어야 하는 시점에서 다른 쓰레드에게 양보를 할 경우에 사용된다.
첫 번째의 경우는, 한마디로 쓰레드 내부에서만 사용하는 쓰레드 타이머라고 생각할 수 있다. 즉 일정시간이 지나면 반복적으로 작업을 할 때 유용하다.

쓰레드 내부에서도 Sleep()라는 걸 이용하면 쓰레드 내부 루틴을 타이머를 사용했을 때와 비슷하게 만들 수 있다.
쓰레드를 잠재울 때는 다음의 함수를 사용한다.
::Sleep( 0 );
위 함수는 win32 API함수이다. 따라서 사용할 때 :: 를 이용해서 호출한다. 만약 위의 함수를
::Sleep( 5000 );
하면 이 쓰레드는 5초 동안 CPU시간을 쓰지 않게 되며, 이렇게 잠들어 있을 때는 다른 쓰레드가 CPU시간을 얻어서 활동하게 된다.

두번째는 위의 첫 번째 예와 같이 인자로 0을 주는 경우인데, 이 경우에 현재 스레드를 정지하고(0 이면 0.001초인 이데..멈추다니..) 이 스레드가 CPU시간을 풀어줄 때까지 기다리던 다른 스레드가 실행된다. 그런데 다른 스레드는 이 스레드와 우선순위가 같아야 한다. 그리고 같은 우선순위의 스레드가 없다면 현재 sleep(0)을 실행했던 스레드가 계속 실행이 된다.
스레드 안에서 sleep()를 쓰는 경우는 시간의 지연을 두고 작업을 해야 하는 경우에 많이 사용된다. (ex. 아날로그 시계)

CwinThread* pThread = AfxBeginThread( TimeFunc, pParam );

UINT TimeFunc( void* pParam )
{
void* param = (void*) pParam; // 사용자가 넘겨준 데이터를 사용하기 위함
while( true )
{
::Sleep( 1000 ); // 이곳에서 1초가 멈춰진다.
DrawTime(); // 초침을 그리는 함수
}
}

7. 스레드 죽이기

쓰레드를 죽이는 이유는 자기의 일을 다 마치고 나면 당연히 쓰레드는 없어져야 하기 때문이다. 그렇지 않으면, 쓰레드는 여전히 자기가 들어앉은 메모리 속에 앉아서 컴퓨터 리소스만 잡아먹고 있게 된다.
스레드를 죽이는 방법엔 두 가지가 있다.
① 스레드 내부에서 return을 시킬 때.
② AfxEndThread를 호출할 때.

안전한 방법은 스레드 내부 자체에서 return문을 이용하는 것이 안전합니다. 탐색기도 이와 같은 방법을 사용합니다. 복사가 다 끝나면 자동적으로 return을 시키기 때문에 스레드가 알아서 죽는다. 이런 방법은 쓰레드가 오랜 시간을 작업하는경우에 사용한다. 즉 언젠가는 결과를 얻어서 끝 날수 있는 일을 할 때 사용하게 되고, 대개 무한루프를 돌면서 하는 일은 AfxEndThread를 사용해서 끝낸다.

위의 첫 번째 방법과 같이 return을 받았을때는 GetExitCode를 이용해서 검색할 수 있는 32bit의 종료 코드를 볼수 있다.

DWORD dwexitcode;
::GetExitCodeThread( pThread‐>m_hThread, &dwExitCode );
// pThread는 CwinThread* 객체의 변수이고..
// m_hThread 는 CwinThread내부의 생성된 쓰레드 핸들

이 문장은 실행이 종료된 스레드의 상태를 보여주는 문장이다. dwExitCode에 스레드가 return 하면서 나온 32bit의 종료값이 들어가게 된다. 만약 실행중인 스레드를 대상으로 저 코드를 쓰게 된다면 dwExitCode에는 STILL_ACTIVE라는 값이 들어가게 된다.
그런데, 위의 코드를 사용함에 있어 제약이 있다. CwinThread*객체는 스레드가 return 되어서 스스로 종료가 되면 CwinThread 객체 자신도 혼자 제거되어 버린다. 따라서 delete시켜주지 않아도 메모리에서 알아서 없어진다.
return이 되어서 이미 죽어버린 스레드를 가지고 pThread‐>m_hThread를 넣어주면, 이것은 이미 return되어 죽어버린 스레드의 핸들을 가리키고, Access위반이란 error메시지가 나오게 된다.
이런 문제를 해결하려면 CwinThread* 객체를 얻은 다음 이 객체의 멤버 변수인 m_hAutoDelete를 FALSE로 설정하면 스레드가 return을 해도 CwinThread객체는 자동으로 제거 되지 않기 때문에 위의 코드는 정상적을 수행된다.

이런 경우에 CwinThread*가 더 이상 필요가 없어지면 개발자 스스로 CwinThread를 delete시켜 주어야 합니다. 또 다른 방법으로 스레드가 가동이 되면 CwinThread*의 멤버변수인 m_hThread를 다른 곳으로 저장을 해놓고 이 것을 직접GetExitCode()에 전달을 하면 그 쓰레드가 실행중인지 한때는 실행되고 있었지만 죽어버린 스레드인지 확인이 가능하다.

int a = 100; // 파라미터로 넘겨줄 전역변수.
CwinThread* pThread // 전역 쓰레드 객체의 포인터 변수.
HANDLE threadhandle; // 스레드의 핸들을 저장할 핸들변수.

Initinstance() // 프로그램초기화.
{
// 프로그램 실행과 동시에 스레드 시작.
1번방법:pThread = AfxBeginThread( func, (int) a );
// 스레드가 리턴되면 자동으로 CwinThread객체가 자동으로 파괴되지 않게 설정.
2번방법:pThread‐>m_hAutoDelete = FALSE;
// 쓰레드 핸드를 저장. 위의 m_hAutoDelete를 설정하지않았을경우..
threadhandle = pThread‐>m_hThread;
}

MessageFunction() // 어떤 버튼을 눌러서 스레드의 상태를 알고 싶다..
{
char* temp;
DWORD dwExitcode;
// 스레드 객체의 m_hAutoDelete를 fasle로 설정해서 스레드가 return되어도
// 객체가 자동으로 파괴되지 않아서 핸들을 참조 할수 있다.
1번방법: ::GetExitCode( pThread‐>m_hThread, &dwExitcode);
// 스레드가 종료되고 미리 저장해둔 핸들을 이용할경우..
2번방법: ::GetExitCode(threadhandle, &dwExitcode);
sprintf( temp, “Error code : %d”, dwExitcode );
// 스레드 객체 삭제..
1번방법: delete pThread;
AfxMessageBox( temp );
}

func( void* pParam )
{
int b = (int) pParam;
for( int I = 0; I m_hThread; //m_hThread는 생성된 스레드의 핸들

BContinue = FALSE;
::WaitForSingleObject( hthread, INFINITE ); //졸라리 중요한 함수임다

// 스레드 B
UINT func( LPVOID pParam )
{
BOOL* pContinue = (BOOL*) pParam;
While( *pContinue )
{
// 어떤일 수행.
}
return 0;
}

WaitForSingleObject란 Win32함수의 프로토타입은 다음과 같다.
DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilisecond );
첫번째 인자로 주어진 핸들에 위의 예문에서 실행시킨 스레드의 핸들값을 저장한다. 그리고 두번째 인자로 시간값을 받는데, 이 값은 어떤 행동이 일어나기까지 기다릴 시간을 말한다. 이 함수의 목적은 첫번째 핸들값으로 주어진 그 핸들에 대한 해당하는 어떤객체(?)가 “신호를 받은 상태” 가 될 때까지 기다린다. 예제를 보면 스레드A가 스레드B를 종료시키려고 종료플래그를 FALSE로 셋팅하고 waitforsingleobject()를 호출하고 있는데, 이때 신호를 받아야하는 객체로 스레드B의 핸들을 지정하는 것이다. 이 말은 스레드 B가 정상적이든, 혹은 종료신호가 올 때까지 기다린다는 의미이다.
네트웍 프로그램에서 어떤 파일을 받는 스레드가 있고, 이 받은 데이터로 먼가를 작업하는 스레드 2개가 존재한다면 모든 데이터가 다 받아 질 때까지 받은 데이터로 작업하는 스레드는 멈춰 있어야한다. 이 경우 저 함수를 이용해서 받은 데이터로 작업을 수행하는 스레드를 멈춰 둠으로써 안정적으로 작동하게 하는 것이다.

이때 WaitForSingleObject에 두번째 인자에 INFINITE를 넣지 않고 5000이라는 값을 넣으면 무한정 기다리는 대신 그 스레드가 신호를 받기를 기다리는 시간을 5초를 지정한 겁니다. 여기서 5초가 경과된 후에도 이 객체가 신호를 받지 못하면, WaitForSingleObject는 반환이 되는데 이 반환된 값을 가지고도 프로그래머가 스레드의 상황을 파악할 수 있다.
WAIT_OBJECT_0 : 객체가 신호를 받았음
WAIT_TIMEOUT : 객체가 신호를 받지 못했음

//스레드 A(받은 데이터를 가지고 어떤 작업을 한다..)
………
if(::WaitForSingleObjcet(hThread, 5000) == WAIT_TIMEOUT )
{
AfxMesageBox(“5초가 지나도록 데이터 못 받았다..에러다..”)
}
else
{
//데이터 다 받았으니까..알아서..해라..
}

//스레드 B(데이터를 받는다..)
…작업중…

다른 방법으로는 WaitForSingleObjct의 두번째 인자에 0을 지정함으로써 이 스레드 함수가 실행중인지 혹은 종료가 되었는지도 알 수 있다.

if( ::WaitForSingleObjcet( hThread, 0 ) == WAIT_OBJCET_0 )
{
// 스레드가 종료가 됬당..
}
else
// 스레드가 아직 실행중이다..
즉 두 번째 인자에 0이란 값을 주면, 그 스레드에 대한 상태를 바로 반환해 주게 된다.

CwinThread*의 멤버변수중에 하나인 m_bAutoDelete를 false로 설정해 놓지도 않고, CwinThread가 신호를 받았는지 알기위해서 WaitforSingObject를 호출하는 것은 말이 안됩니다. 이유는 그 스레드가 끝이나면 해당 CwinThread*객체 또한 자동으로 사라지기 때문에, m_bAutoDelete를 false로 해주지 않으면, 말이 안된다.

③ ================================================================================
마지막 방법은 ::TerminateThread( pThread‐>m_hThread , 0 ); 이다.
이것은 어떤 스레드에서 다른 스레드를 종료시킬 때 최후의 수단으로 쓸 때 요긴하다. 위의 함수를 사용하면 pThread‐>m_hThread를 종료하고, 그 스레드에 종료코드 0을 할당한다. 이 함수는 사용함에 있어 제약이 많이 따른다.
예를 들어 동기화 객체를 예로 들수 있는데, 어떤 동기화 객체를 공유하는 스레드들이 있는데.. 어떤 스레드가 공유하는 동기화 객체를 Locking하고 작업을 수행하던중,(이때 다른 스레드들은 공유 동기화 객체를 얻기 위해 대기상태에 있다) 위의 TerminateThread함수를 사용해서 작업중인 스레드를 죽이면, 공유된 동기화 객체를 UnLocking하기 전에 죽어 버릴 수 있게 된다. 이러면 공유된 동기화 객체는 영원히 Locking되어 있기 때문에 그 동기화 객체를 공유하는 다른 스레드들은 실행이 안 되게 된다.

스레드 정리

1. 개요

현재 대부분의 OS는 프로세스 스케쥴링에 의해 프로그램의 멀티태스킹(Multi-tasking)을 지원하고 있다.
멀티태스킹이란 실행되고있는 프로그램을 일정 단위로 잘라서(slice) 순서대로 CPU를 사용하게끔 하는 것 인데,
사용자는 마치 동시에 여러 개의 프로그램이 실행되는 것처럼 느낄 수 있게 된다.
즉, CPU 사용률을 최대화 하고, 대기시간과 응답시간의 최소화를 가능케 해주는 방법이다.

이번에는 프로세스 한 개만 놓고 보자.
한 프로세스는 구성면에서 [텍스트]-[데이터]-[스택] 영역으로 구성되어있고, 기능면에서는 텍스트의 모듈들은 각각의 역할을 가지고 있다.
프로세스에서의 공유메모리영역을 제외한 부분끼리 묶어서 쓰레드로 만든 후, 이것들을 멀티태스킹처럼 동작시키면 멀티쓰레딩이 되는 것이다.

멀티쓰레드 프로그램을 작성할 경우의 장점은 다음처럼 요약될 수 있다.
1) 병렬화가 증가되어
2) CPU사용률이 극대화되며,
3) 전체적 처리율이 빨라지고,
4) 사용자에대한 응답성이 향상된다.
5) 또한, 완벽에 가까운 기능별 구분에 의한 모듈작성을 함으로써 설계가 단순해져서,
6) 프로그램의 안정성이 향상된다.
7) 코드의 복사본을 여러 개 수행하여 여러 개의 클라이언트에서 동일한 서비스를 제공할수 있다.
8) 블록될 가능성이 있는 작업을 수행할 때 프로그램이 블록되지 않게 한다.

하지만, 쓰레드를 사용하면 오히려 불리한 경우도 있다. 대표적인 예로, 교착상태(deadlock)와 기아(starvation)이다.
쓰레드 기법을 사용할 때 주의사항을 정리하자면,
1) 확실한 이유를 가지고 있지 않는 경우에는 쓰레드를 사용하면 안 된다. 즉 쓰레드는 명확히 독립적인 경우에 사용해야 한다.
2) 명확히 독립적인 쓰레드라 하여도 오히려 나눔으로 인해 OS가 쓰레드를 다루는데에 따른 부하(overload)가 발생하게 된다.
즉, 실제 쓰레드에 의해 수행되는 작업량보다 클 경우에는 사용하지 않도록한다.

멀티쓰레드를 이용한 애플리케이션을 작성하는 구조에는 3가지 방법이 있다..
1. boss/worker 모델..
2. work crew 모델.
3. pipeline 모델.

1. 첫번째 쓰레드(주쓰레드)가 필요에 따라 작업자 쓰레드를 만들어 내는 경우.
이런 경우는 C/S 환경에서 접속받는 부분을 쓰레드로 돌리고, 접속요청이 오면 새로운 쓰레드를 만들어 사용자와 연결시켜 주는 방법이다.
이때 접속 받는 쓰레드가 주 쓰레드(boss Thread) 라고 하고, 사용자와 연결된 다른 쓰레드..
즉 주 쓰레드로부터 실행된 쓰레드는 작업자 쓰레드(worker Thread) 라고 한다..
2. 두번째 방식은 어떤 한 작업을 여러 개의 쓰레드가 나눠서 하는 방식이다.
즉 집을 청소한다는 개념의 작업이 있으면, 청소하는 작업에 대한 쓰레드를 여러 개 돌리는 거..
3. 공장라인을 생각…

쓰레드는 UI(User Interface) Thread와 Worker(작업자) Thread로 나뉜다.
UI Thread는 사용자 메시지 루프를 가지고 있는(즉 어떤 메시지가 날라오면 일하는.. )쓰레드이고..
Worker Thread는, 보통 오래 걸리는 작업이나 무한루프를 가지는 작업을 하는 사용자 정의 함수의 경우 사용.
UI Thread를 사용하려면, CWinThread 파생 클래스를 만들어 사용한다.

MFC에서는 AfxBeginThread의 서로 다른 버전 두 개를 정의 하고 있다..
하나는 작업자 쓰레드를 위한 것이고, 하나는 UI쓰레드를 위한 것이져..

원형은 다음과 같다..
UINT ThreadFunc(void* pParam)

이함수는 정적(static)클래스 멤버 함수 이거나 클래스 외부에서 선언한 함수여야 한다.

2. 쓰레드의 기본

1) 쓰레드 생성

WM_CREATE 에서 쓰레드를 만들면 되는데 함수는 다음과 같다.
HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter,
DWORD dwCreationFlags, LPDWORD lpThreadId);

+lpThreadAttributes : 쓰레드의 보안속성 지정. 자식 프로세스로 핸들을 상속하지 않은 한 NULL
+dwStackSize : 쓰레드의 스택 크기 지정. 안정된 동작을 위해 쓰레드마다 별도의 스택 할당.
0으로 설정하면 주 쓰레드(CreateThread를 호출한 쓰레드)와 같은 크기를 갖으며, 스택이 부족할 경우 자동으로 스택크기를 늘려주므로 0으로 지정하면 무리가 없다.
+lpStartAddress : 쓰레드의 시작함수를 지정. 가장 중요한 인수.
+lpParameter : 쓰레드로 전달할 작업 내용이되 인수가 없을경우 NULL임.
+dwCreationFlags : 생성할 쓰레드의 특성 지정. 0이면 아무 특성없는 보통 쓰레드가 생성되고
CREATE_SUSPENDED 플래그를 지정하면 쓰레드를 만들기만 하고 실행은 하지 않도록하고 실행을 원하면 ResumeThread함수를 호출하면 된다.
+lpThreadId : 쓰레드의 ID를 넘겨주기 위한 출력용 인수이므로 DWORD형의 변수 하나를 선언한 후 그 변수의 번지를 넘기면 됨.

**** 작업자 쓰레드 생성하기 ****

작업자 쓰레드로 특정한 작업을 하는 사용자 정의 함수를 맹글기 위해서, 윈도우에서는 여러가지 쓰레드 생성 함수를 제공해 준다.
그 함수의 종류를 알아보도록 하져..

1. CreateThread()
2. _beginthread(), _beginthreadex()
3. AfxBeginThread(), AfxBeginThreadEx()

이렇게 약 5가지의 쓰레드 생성함수가 존재한다.
이제부터 저 5가지 함수의 특징을 알아보도록 하져…..
그럼 첫번째 CreateThread()함수. 이 함수는 보통 사용할때 다음과 같이 사용한다.

HANDLE handle;
Handle = CreateThread( Threadfunc(), Param );

첫번째 인자는 사용자가 쓰레드로 돌려야할 작업함수를 써주는 곳이고, 두번째는 작업함수에 인자값으로 전해줄 값이 들어간다..
이 인자값 형은 VOID*으로 되어 있기 때문에 4BYTE 이내의 값은 어떤 값이든 들어갈수 있져..대신 TYPE CASTING을 해주어야 하져..
그리고 받는 쪽에서도 type casting를 해서 받아야 한다.
이함수가 올바르게 실행이 되면 쓰레드에 대한 핸들을 반환하는데.. 이 핸들을 가지고 쓰레드를 조작할 수가 있져..
대표적으로 쓰레드를 닫을 때 CloseHandle()함수를 사용해서 쓰레드 핸들을 넣어주고 쓰레드를 닫아 주어야 한다..
이함수로 생성된 쓰레드를 닫을때는 ExitThread() 면 됩니다.

그럼..두번째 _beginthread를 알아보도록 하져..CreateThread는 쓰레드에서 win32 API함수만 호출할수 있다..
즉, 사용자가 어떤작업을 하는 함수를 만들 때 그 함수 안에서 win32API만 사용할수 있다는 말이다..
즉 C함수나 MFC는 저얼대~~ 못 쓴다….
_beginthread 함수는 win32 API아 C 런타임 함수를 사용할 때 사용한다.
이 함수를 사용하면 C런타임 라이브러리가 핸들을 자동으로 닫으므로 이를 직접할 필요는 없다.
대신 _beginthreadex는 스레드 핸들을 직접 닫아야 한다. 그리고 이 쓰레드를 닫을 때는 _endthread(), _endthreadex()를 사용하면 된다.

세번째 AfxBeginThread()와 AfxBeginThreadEx()..
실질적으로 가장 자주 사용하는 쓰레드 생성함수이다..
이 함수를 이용하면 사용자 정의 함수내에서 MFC, win32 API, C 런타임 라이브러리등 여러가지 라이브러리 함수들을 전부 사용할수 있다..
주로 프로젝트를 MFC로 만들 때 사용하죠..
이 함수는 리턴값이 CWinThread* 형을 리턴하며, 이 함수와 매칭되는 종료함수는 AfxEndThread()이다…
해서 쓰레드가 종료되면 MFC는 쓰레드 핸들을 닫고 리턴값으로 받은 CWinThread*객체를 제거한다.

CWinThread* pThread = AfxBeginThread( Threadfunc, &threadinfo );

첫번째 인자는 사용자 정의 함수이고, 두번째는 첫번째 인자의 쓰레드 함수에 인자값으로 들어갈 파라미터이다..
이 형은 void* 형으로 4byte를 가지므로 어떤 형으로 넣어줄 때 type casting하면 된다….

그 예는 다음과 같다.

int nNumber = 1000;

CWinThread *pThread = ::AfxBeginThread(ThreadFunc, &nNumber);

UINT ThreadFunc(LPVOID pParam)
{
int j = (int)pParam;
for (int i=0; ipoint = m_ptPoint;
pThreadParams->pContinue = &m_bExec; // 쓰레드 실행 플래그
pThreadParams->pFriend = &m_bYield; // 쓰레드 양보 플래그
pThreadParams->pWnd = this;
m_pThread = AfxBeginThread(ThreadFunc, pThreadParams);

UINT ThreadFunc(LPVOID pParam)
{
// 넘어온 인자를 복사
THREADPAPAMS *pThreadParams = (THREADPAPAMS *)pParam;
CPoint point = pThreadParams->point;
CWnd *pWnd = pThreadParams->pWnd;
BOOL *pContinue = pThreadParams->pContinue;
BOOL *pFriend = pThreadParams->pFriend;
delete pThreadParams; // delete로 해제

// “실행” 플래그가 TRUE인 동안 스레드가 실행됨
while(*pContinue)
{
// 수행할 작업

// “양보” 플래그가 TRUE이면 다른 스레드에 CPU를 양보
if(*pFriend) Sleep(0);
}
return 0;
}

자 그럼..정리해 보도록 하져…..쓰레드를 생성하는 함수들은 크게 3가지가 있고..(확장된것까지 생각하면 5개..^^ ) 이들 함수의 특징은 다음과 같다.

쓰레드가 win32 API만을 사용한다면 CreateThread()를 사용하면 되고, C런타임 라이브러리를 사용하다면 _beginthread()를 사용하고,
전부다 사용한다면 AfxBeginThread()를 사용하면 된다.

2) 쓰레드 종료

작업 쓰레드가 종료되었는지 조사하는 함수는 다음과 같다.
BOOL GetExitCodeThread(HANDLE hThread, PDWORD lpExitCode);

+hThread : 쓰레드의 핸들
+lpExitCode : 쓰레드의 종료코드.
+Return : 계속 실행중 : STILL_ACTIVE, 쓰레드 종료 : 스레드 시작함수가 리턴한 값 or ExitThread 함수의 인수

쓰레드가 무한루프로 작성되어 있다해도 프로세스가 종료되면 모든 쓰레드가 종료되므로 상관이 없다.
백그라운드 작업을 하는 쓰레드는 작업이 끝나면 종료되는데 때로는 작업도중 중지해야 할 경우에는 다음 두 함수가 사용된다.

VOID ExitThread(DWORD dwExitCode);
BOOL TerminateThread(HANDLE hThread, DWORD dwExitCode);

ExitThread는 스스로 종료할 때 사용.인수로 종료코드를 넘김. 종료코드는 주 쓰레드에서 GetExitCodeThread함수로 조사할 수 있다.
이것이 호출되면 자신의 스택을 해제하고 연결된 DLL을 모두 분리한 후 스스로 파괴된다.

TerminateThread는 쓰레드 핸들을 인수로 전달받아 해당 쓰레드를 강제종료시킨다.
이 함수는 쓰레드와 연결된 DLL에게 통지하지 않으므로 DLL들이 제대로 종료처리를 하지 못할 수 있고 리소스도 해제되지 않을 수 있다.
그래서 이 작업 후 어떤일이 발생할지를 정확히 알때에만 사용하도록한다.

스레드를 죽이는 방법엔 두가지가 있져..
1. 스레드 내부에서 return을 시킬 때.
2. AfxEndThread를 호출할 때.
안전한 방법은 스레드 내부 자체에서 return문을 이용해서 죽여주는게 안전하다. 위의 예와 같이…

다음은 쓰레드를 종료하는 함수의 예이다.
if(m_pThread != NULL)
{
HANDLE hThread = m_pThread->m_hThread; // CWinThread *m_pThread;
m_bExec = FALSE; // 실행 플래그를 FALSE로 하여 쓰레드 종료시킴..
::WaitForSingleObject(hThread, INFINITE);
// 이후 정리작업…
}

위의 첫번째 방법과 같이 return을 받았을때는 GetExitCodeThread를 이용해서 검색할수 있는 32bit의종료 코드를 볼수 있다..

DWORD dwexitcode;
::GetExitCodeThread( pThread->m_hThread, &dwExitCode );
// pThread는 CWinThread* 객체의 변수..

만약 실행중인 스레드를 대상으로 저 코드를 쓰게 된다면 dwExitCode에는 STILL_ACTIVE라는 값이 들어가게 된다.

근데..위의 코드를 사용함에 있어 제약이 좀 있다.
CWinThread*객체는 스레드가 return 되어서 종료가 되면 CWinThread객체 자신도 제거되어 버린다..즉 동반자살이져..
delete시켜주지 않아도 메모리에서 알아서 없어진다는 말이져..
즉…return이 되어서 이미 죽어버린 스레드를 가지고 pThread->m_hThread를 넣어주면, Access위반이란 error메시지가 나오게 되져..

이런 문제를 해결할라면 CWinThread* 객체를 얻은 다음 이 객체의 멤버 변수인 m_hAutoDelete를 FALSE로 설정하면
스레드가 return을 해도 CWinThread객체는 자동으로 제거 되지 않기 때문에 위의 코드는 정상적으로 수행이 된다..

이런 경우에 CWinthread*가 더 이상 필요가 없어지면 개발자 스스로 CWinThread를 delete시켜 주어야 한다.

또다른 방법으로 스레드가 가동이 되면 CWinThread*의 멤버변수인 m_hThread를 다른 곳으로 저장을 해놓고
이 것을 직접GetExitCodeThread()에 전달을 하면 그 쓰레드가 실행중인지 한때는 실행되고 있었지만 죽어버린 스레드인지 확인이 가능하다.

int a = 100; // 파라미터로 넘겨줄 전역변수.
CWinThread* pThread // 전역 쓰레드 객체의 포인터 변수.
HANDLE threadhandle; // 스레드의 핸들을 저장할 핸들변수.

Initinstance() // 프로그램초기화.
{
// 프로그램 실행과 동시에 스레드 시작.
1번방법:pThread = AfxBeginThread( func, (int) a );

// 스레드가 리턴되면 자동으로 CWinThread객체가 자동으로 파괴되지 않게 설정.
2번방법:pThread->m_hAutoDelete = FALSE;

// 쓰레드 핸드를 저장. 위의 m_hAutoDelete를 설정하지않았을경우..
threadhandle = pThread->m_hThread;
}

MessageFunction() // 어떤 버튼을 눌러서 스레드의 상태를 알고 싶다..
{
char* temp;
DWORD dwExitcode;
// 스레드 객체의 m_hAutoDelete를 fasle로 설정해서 스레드가 return되어도
// 객체가 자동으로 파괴되지 않아서 핸들을 참조 할수 있다.
1번방법: ::GetExitCode( pThread->m_hThread, &dwExitcode);

// 스레드가 종료되고 미리 저장해둔 핸들을 이용할경우..
2번방법:::GetExitCode(threadhandle, &dwExitcode);
sprintf( temp, “Error code : %d”, dwExitcode );

// 스레드 객체 삭제..
1번방법: delete pThread;
AfxMessageBox( temp );
}

func( void* pParam )
{
int b = (int) pParam;
for( int i = 0; i m_hThread, INFINITE))
{
// 쓰레드가 종료된 후 해야 할 작업들
}

(쓰레드 종료를) 어느 정도 기다리다가 프로그램을 진행시키려면 다음과 같이 한다.
DWORD dwRetCode;
dwRetCode = ::WaitForSingleObject(pThread->m_hThread, 2000);
if (dwRetCode == WAIT_OBJECT_0)
{
// 쓰레드가 종료된 후 해야 할 작업들
}
else if(dwRetCode == WAIT_TIMEOUT)
{
// 2초 동안 쓰레드가 종료되지 않았을 때 해야 할 에러 처리
}

다음과 같이 하면, 어떤 쓰레드가 현재 실행 중인지 아닌지를 알 수 있다.
if (::WaitForSingleObject(pThread->m_hThread, 0) == WAIT_TIMEOUT)
{
// 현재 쓰레드가 실행 중.
}
else
// 실행 중인 상태가 아니다.

// WaitForMultipleObjects() sample…

// 쓰레드 함수의 원형
DWORD WINAPI increment(LPVOID lParam);
DWORD WINAPI decrement(LPVOID lParam);

int main()
{
// char* ps[] = {“increment”, “decrement”};
DWORD threadID;
HANDLE hThreads[2];

// hThreads[0] = CreateThread( NULL, 0, increment, (LPVOID)ps[0], 0, &threadID);
// hThreads[0] = CreateThread( NULL, 0, increment, NULL, 0, &threadID);

for (int i=0; i<2; ++i)
{
hThreads[i] = CreateThread( NULL, 0, increment, (void *)i, 0, &threadID);
}

// 모든 쓰레드가 종료할 때 까지 기다린다.
// WaitForMultipleObjects(2, hThreads, TRUE, INFINITE);

int ret;
ret = WaitForMultipleObjects(2, hThreads, FALSE, INFINITE);
switch(ret)
{
case WAIT_OBJECT_0: // handle hThreads[0] is signaled..
break;
case WAIT_OBJECT_0+1:
break;
}

CloseHandle(hThreads[0]);
CloseHandle(hThreads[1]);
return 0;
}

DWORD WINAPI increment(LPVOID lParam)
{
while (1)
{

}

return 0;
}

DWORD WINAPI decrement(LPVOID lParam)
{
while (1)
{

}

return 0;
}

4) 쓰레드 일시중지 – 재개

DWORD SuspendThread(HANDLE hThread); – 1
DWORD ResumeThread(HANDLE hThread); – 2

둘 다 내부적으로 카운터를 사용하므로 1을 두번 호출했다면 2도 두번 호출해야한다. 그래서 카운터가 0 이되면 쓰레드는 재개하게된다.

5) 우선순위 조정

향상된 멀티태스킹을 지원하기 위해서는 시분할 뿐만 아니라 프로세스의 우선순위를 지원해야 한다.
마찬가지로 프로세스 내부의 쓰레드들도 우선순위를 갖아야 하며 우선순위 클래스, 우선순위 레벨 이 두 가지의 조합으로 구성된다.

우선순위 클래스는, 스레드를 소유한 프로세스의 우선순위이며
CreateProcess 함수로 프로세스를 생성할 때 여섯번째 파라미터 dwCreationFlag로 지정한 값이다.
디폴트는 NORMAL_PRIORITY_CLASSfh 보통 우선순위를 가지므로 dwCreationFlag를 특별히 지정하지 않으면 이 값이 전달된다.

우선순위 레벨은 프로세스 내에서 쓰레드의 우선순위를 지정하며 일단 쓰레드를 생성한 후 다음 두 함수로 설정하거나 읽을 수 있다.

BOOL SetThreadPriority(HANDLE hThread, int nPriority);
Int GetThreadPriority(HANDLE hThread);

지정 가능한 우선순위 레벨은 총 7가지 중 하나이며 디폴트는 보통 우선순위인 THREAD_PRIORITY_NORMAL 이다.

우선순위 클래스와 레벨값으로부터 조합된 값을 기반우선순위(Base priority)라고 하며 쓰레드의 우선순위를 지정하는 값이 된다.
기반우선순위는 0~31 중 하나이며 0은 시스템만 가질 수 있는 가장 낮은 우선순위 이다. (낮을수록 권한이 높음)

우선순위를 높이는(에이징)방법과 낮추는 방법을 동적 우선순위 라고하며, 우선순위 부스트(Priority Boost)라고 한다.
단 이 과정은 기반 우선순위 0~15 사이의 쓰레드에만 적용되며 16~31 사이의 쓰레드에는 적용되지 않는다.
또한 사용자입력을 받거나(인터럽트) 대기상태에서 준비상태가 되는 경우에는 우선순위가 올라가고,
쓰레드가 할당된 시간을 다 쓸 때마다 우선순위를 내려 결국 다시 기반 우선순위와 같아지게 되는데,
어떠한 경우라도 동적 우선순위가 기반 우선순위보다는 더 낮아지지 않는다.

win32 프로그램 exe 외부 프로그램에서 호출

redir

//UI 부분

void CRedirDemoDlg::OnBnClickedButtonRun()
{
UpdateData();
if (m_strCommand.IsEmpty())
return;

m_redir.m_pWnd = (CEdit*) GetDlgItem(IDC_EDIT_OUTPUT);
m_redir.m_pWnd->SetWindowText(“”);
m_redir.Close();
m_redir.Open(m_strCommand);
}

void CRedirDemoDlg::OnBnClickedButtonInput()
{
UpdateData();
m_redir.Printf(“%s\r\n”, m_strInput);
}

//아래는 CRedirector 헤더와 구현부분(.h/cpp)

//////////////////////////////////////////////////////////////////////
//
// Redirector – to redirect the input / output of a console
//
// Developer: Jeff Lee
// Dec 10, 2001
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_REDIR_H__4FB57DC3_29A3_11D5_BB60_006097553C52__INCLUDED_)
#define AFX_REDIR_H__4FB57DC3_29A3_11D5_BB60_006097553C52__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

class CRedirector : public CObject
{
public:
CRedirector();
virtual ~CRedirector();

private:
HANDLE m_hThread; // thread to receive the output of the child process
HANDLE m_hEvtStop; // event to notify the redir thread to exit
DWORD m_dwThreadId; // id of the redir thread
DWORD m_dwWaitTime; // wait time to check the status of the child process

protected:
HANDLE m_hStdinWrite; // write end of child’s stdin pipe
HANDLE m_hStdoutRead; // read end of child’s stdout pipe
HANDLE m_hChildProcess;

BOOL LaunchChild(LPCTSTR pszCmdLine,
HANDLE hStdOut, HANDLE hStdIn, HANDLE hStdErr);
int RedirectStdout();
void DestroyHandle(HANDLE& rhObject);

static DWORD WINAPI OutputThread(LPVOID lpvThreadParam);

protected:
// overrides:
virtual void WriteStdOut(LPCSTR pszOutput);
virtual void WriteStdError(LPCSTR pszError);

public:
BOOL Open(LPCTSTR pszCmdLine);
virtual void Close();
BOOL Printf(LPCTSTR pszFormat, …);

void SetWaitTime(DWORD dwWaitTime) { m_dwWaitTime = dwWaitTime; }
};

#endif // !defined(AFX_REDIR_H__4FB57DC3_29A3_11D5_BB60_006097553C52__INCLUDED_)

//////////////////////////////////////////////////////////////////////
//
// Redirector – to redirect the input / output of a console
//
// Developer: Jeff Lee
// Dec 10, 2001
//
//////////////////////////////////////////////////////////////////////

#include “stdafx.h”
#include “Redir.h”

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

#define _TEST_REDIR

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CRedirector::CRedirector() :
m_hStdinWrite(NULL),
m_hStdoutRead(NULL),
m_hChildProcess(NULL),
m_hThread(NULL),
m_hEvtStop(NULL),
m_dwThreadId(0),
m_dwWaitTime(100)
{
}

CRedirector::~CRedirector()
{
Close();
}

//////////////////////////////////////////////////////////////////////
// CRedirector implementation
//////////////////////////////////////////////////////////////////////

BOOL CRedirector::Open(LPCTSTR pszCmdLine)
{
HANDLE hStdoutReadTmp; // parent stdout read handle
HANDLE hStdoutWrite, hStderrWrite; // child stdout write handle
HANDLE hStdinWriteTmp; // parent stdin write handle
HANDLE hStdinRead; // child stdin read handle
SECURITY_ATTRIBUTES sa;

Close();
hStdoutReadTmp = NULL;
hStdoutWrite = hStderrWrite = NULL;
hStdinWriteTmp = NULL;
hStdinRead = NULL;

// Set up the security attributes struct.
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;

BOOL bOK = FALSE;
__try
{
// Create a child stdout pipe.
if (!::CreatePipe(&hStdoutReadTmp, &hStdoutWrite, &sa, 0))
__leave;

// Create a duplicate of the stdout write handle for the std
// error write handle. This is necessary in case the child
// application closes one of its std output handles.
if (!::DuplicateHandle(
::GetCurrentProcess(),
hStdoutWrite,
::GetCurrentProcess(),
&hStderrWrite,
0, TRUE,
DUPLICATE_SAME_ACCESS))
__leave;

// Create a child stdin pipe.
if (!::CreatePipe(&hStdinRead, &hStdinWriteTmp, &sa, 0))
__leave;

// Create new stdout read handle and the stdin write handle.
// Set the inheritance properties to FALSE. Otherwise, the child
// inherits the these handles; resulting in non-closeable
// handles to the pipes being created.
if (!::DuplicateHandle(
::GetCurrentProcess(),
hStdoutReadTmp,
::GetCurrentProcess(),
&m_hStdoutRead,
0, FALSE, // make it uninheritable.
DUPLICATE_SAME_ACCESS))
__leave;

if (!::DuplicateHandle(
::GetCurrentProcess(),
hStdinWriteTmp,
::GetCurrentProcess(),
&m_hStdinWrite,
0, FALSE, // make it uninheritable.
DUPLICATE_SAME_ACCESS))
__leave;

// Close inheritable copies of the handles we do not want to
// be inherited.
DestroyHandle(hStdoutReadTmp);
DestroyHandle(hStdinWriteTmp);

// launch the child process
if (!LaunchChild(pszCmdLine,
hStdoutWrite, hStdinRead, hStderrWrite))
__leave;

// Child is launched. Close the parents copy of those pipe
// handles that only the child should have open.
// Make sure that no handles to the write end of the stdout pipe
// are maintained in this process or else the pipe will not
// close when the child process exits and ReadFile will hang.
DestroyHandle(hStdoutWrite);
DestroyHandle(hStdinRead);
DestroyHandle(hStderrWrite);

// Launch a thread to receive output from the child process.
m_hEvtStop = ::CreateEvent(NULL, TRUE, FALSE, NULL);
m_hThread = ::CreateThread(
NULL, 0,
OutputThread,
this,
0,
&m_dwThreadId);
if (!m_hThread)
__leave;

bOK = TRUE;
}

__finally
{
if (!bOK)
{
DWORD dwOsErr = ::GetLastError();
char szMsg[40];
::sprintf(szMsg, “Redirect console error: %x\r\n”, dwOsErr);
WriteStdError(szMsg);
DestroyHandle(hStdoutReadTmp);
DestroyHandle(hStdoutWrite);
DestroyHandle(hStderrWrite);
DestroyHandle(hStdinWriteTmp);
DestroyHandle(hStdinRead);
Close();
::SetLastError(dwOsErr);
}
}

return bOK;
}

void CRedirector::Close()
{
if (m_hThread != NULL)
{
// this function might be called from redir thread
if (::GetCurrentThreadId() != m_dwThreadId)
{
ASSERT(m_hEvtStop != NULL);
::SetEvent(m_hEvtStop);
//::WaitForSingleObject(m_hThread, INFINITE);
if (::WaitForSingleObject(m_hThread, 5000) == WAIT_TIMEOUT)
{
WriteStdError(_T(“The redir thread is dead\r\n”));
::TerminateThread(m_hThread, -2);
}
}

DestroyHandle(m_hThread);
}

DestroyHandle(m_hEvtStop);
DestroyHandle(m_hChildProcess);
DestroyHandle(m_hStdinWrite);
DestroyHandle(m_hStdoutRead);
m_dwThreadId = 0;
}

// write data to the child’s stdin
BOOL CRedirector::Printf(LPCTSTR pszFormat, …)
{
if (!m_hStdinWrite)
return FALSE;

CString strInput;
va_list argList;

va_start(argList, pszFormat);
strInput.FormatV(pszFormat, argList);
va_end(argList);

DWORD dwWritten;
return ::WriteFile(m_hStdinWrite, (LPCTSTR)strInput,
strInput.GetLength(), &dwWritten, NULL);
}

BOOL CRedirector::LaunchChild(LPCTSTR pszCmdLine,
HANDLE hStdOut,
HANDLE hStdIn,
HANDLE hStdErr)
{
PROCESS_INFORMATION pi;
STARTUPINFO si;

ASSERT(::AfxIsValidString(pszCmdLine));
ASSERT(m_hChildProcess == NULL);

// Set up the start up info struct.
::ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.hStdOutput = hStdOut;
si.hStdInput = hStdIn;
si.hStdError = hStdErr;
si.wShowWindow = SW_HIDE;
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;

// Note that dwFlags must include STARTF_USESHOWWINDOW if we
// use the wShowWindow flags. This also assumes that the
// CreateProcess() call will use CREATE_NEW_CONSOLE.

// Launch the child process.
if (!::CreateProcess(
NULL,
(LPTSTR)pszCmdLine,
NULL, NULL,
TRUE,
CREATE_NEW_CONSOLE,
NULL, NULL,
&si,
&pi))
return FALSE;

m_hChildProcess = pi.hProcess;
// Close any unuseful handles
::CloseHandle(pi.hThread);
return TRUE;
}

// redirect the child process’s stdout:
// return: 1: no more data, 0: child terminated, -1: os error
int CRedirector::RedirectStdout()
{
ASSERT(m_hStdoutRead != NULL);
for (;;)
{
DWORD dwAvail = 0;
if (!::PeekNamedPipe(m_hStdoutRead, NULL, 0, NULL,
&dwAvail, NULL)) // error
break;

if (!dwAvail) // not data available
return 1;

char szOutput[256];
DWORD dwRead = 0;
if (!::ReadFile(m_hStdoutRead, szOutput, min(255, dwAvail),
&dwRead, NULL) || !dwRead) // error, the child might ended
break;

szOutput[dwRead] = 0;
WriteStdOut(szOutput);
}

DWORD dwError = ::GetLastError();
if (dwError == ERROR_BROKEN_PIPE || // pipe has been ended
dwError == ERROR_NO_DATA) // pipe closing in progress
{
#ifdef _TEST_REDIR
WriteStdOut(“\r\n<TEST INFO>: Child process ended\r\n”);
#endif
return 0; // child process ended
}

WriteStdError(“Read stdout pipe error\r\n”);
return -1; // os error
}

void CRedirector::DestroyHandle(HANDLE& rhObject)
{
if (rhObject != NULL)
{
::CloseHandle(rhObject);
rhObject = NULL;
}
}

void CRedirector::WriteStdOut(LPCSTR pszOutput)
{
TRACE(“%s”, pszOutput);
}

void CRedirector::WriteStdError(LPCSTR pszError)
{
TRACE(“%s”, pszError);
}

// thread to receive output of the child process
DWORD WINAPI CRedirector::OutputThread(LPVOID lpvThreadParam)
{
HANDLE aHandles[2];
int nRet;
CRedirector* pRedir = (CRedirector*) lpvThreadParam;

ASSERT(pRedir != NULL);
aHandles[0] = pRedir->m_hChildProcess;
aHandles[1] = pRedir->m_hEvtStop;

for (;;)
{
// redirect stdout till there’s no more data.
nRet = pRedir->RedirectStdout();
if (nRet <= 0)
break;

// check if the child process has terminated.
DWORD dwRc = ::WaitForMultipleObjects(
2, aHandles, FALSE, pRedir->m_dwWaitTime);
if (WAIT_OBJECT_0 == dwRc) // the child process ended
{
nRet = pRedir->RedirectStdout();
if (nRet > 0)
nRet = 0;
break;
}
if (WAIT_OBJECT_0+1 == dwRc) // m_hEvtStop was signalled
{
nRet = 1; // cancelled
break;
}
}

// close handles
pRedir->Close();
return nRet;
}

키워드 비밀(Exception C++ Style 28장)

int main() {

if(true);      //OK

if(42);        //OK
}

//빈 문장 수행

calss if {  // if라는 이름의 클래스(실제로 적합하지 않지만, 적법하다고 가정)

public

if(bool) {}      // 생성자임

}

int main() {

if(true);                //어떤 의미인지 명확한가?

if(42);    // 그리고 이건?

}

조건문? if라는 클래스의 이름 없는 임시 객체들을 생성?

//사람이 결정할 수 없다면 컴파일러 역시 마찬가지

 

calssSomeFunctor {

public

if operator()(bool) { return 42; }

}

SomeFunctor if;            //if 라는 변수(실제로는 적법하지 않지만 적법하다고 가정)

int main() {

if(true);                //조건문? 아니면 if.operator()(true); ?

if(42);    // 조건문? 아니면 if.operator()(42); ?

}

일부 이름들을 특별하게 취급하고 보존해야 하는 이유는 명확하다.

언어는 언어 자체가 사용할 이름들을 예약할 필요가 있으며,

그런 키워드들을 예약어(reserved word)

 

표준 C++ 키워드들

asm  do  if  return  typedef

auto  double  inline  short  typeid

bool  dynmaic_cast  int  signed  typename

break  else  long  sizeof  union

case  enum  mutable  static  unsigned

catch  explicit  namespace  static_cast  using

char  export  new  struct  virtual

class  extern  operator  switch  void

const  false  private  template  voliatile

const_cast  float  protected  this  wchar_t

continue   for  public  throw  while

default  friend  register  true

delete  goto  reinterpret_cast  try

and  and_eq  bitand  bitor  compl

not  not_eq  or  or_eq  xor

xor_eq

 

auto는 쓰지 말것, 공백이나 마찬가지이다

auto는 없어도 되는 저장 부류 지정자

코드 블록 안에 선언된 객체 이름 앞에만 올 수 있으며,

그 객체가 해당 함수 또는 블록이 끝날 때 자동으로 파괴된다는 점. 그러나 auto를 붙이지 않아도 파괴된다.

inti;

int j;

int main() {

int(i);                  //::i에 대한 참조가 아니라 I 선언

auto int(j);  //역시 j를 선언

int f();  //함수 선언

auto int f();  //

}

//  auto가 모호성을 해소하는데 도움이 되지 않는다.

 

Vetor<SomeNamespace::SomeType>::const_iterator i = v.begin();

-àauto i = v.begin(); // 차후 C++에서는 가능할수도 있음

(C++위원회 준비이며, auto를 자동적으로 형식 유도를 위한 키워드로 재사용하는 것을 고려중)

register쓰지 말 것(컴파일러를 잘 알지 못하는 한, 그리고 코드 안에서 실제로 어떤 부분이 도움이 되는지를 예측할 수 없느). 대부분 컴파일러에서 register는 공백이나 마찬가지

어떤 변수가 많이 쓰인다면, 가능한 한 그것을 물리적인 CPU 레지스터에 집어놓는 것이 더 느린 캐시 메모리나 메인 메모리에 넣는 것보다 수행속도에 도움이 된다는 취지

변수를 레지스터에 할당하는 것으로 코드를 더 빠르게 만드는 것이 불가능

-자신의 코드가 실행될 CPU을 미리 알고 있다고 하더라도

-컴파일러의 내부적인 작동 방식을 세세히 알고 있다고 하더라도

–>객체들을 레지스터에 집어넣는 일을 좋은 컴파일러보다 더 잘할 수는 없다.

컴파일러는 다른 여러 최적화들(인라인화, 루프 펼치기, 가망 없는 분기 제거, 변수 접기 등)을수행한 후의 문맥을 아는 상황에서 그런 결정을 내린다.

그러나 사람은 그런 정보를 얻지 못하기 때문에 사람이 할만한 일이 아니다.