펌 ) Devpia VC++ 강좌 엘키(sunghun84)
C 프로그래머가 알아야할 것들 - Chapter 1. 진법

김성훈 (sunghun84@nate.com)

Chapter 1. 진법

(1) 왜 진법에 대하여 배워야 하는가?
(2) 2진법
(3) 8진법
(4) 16진법

(1) 왜 진법에 대하여 배워야하는가?

진법이 뭘까요? 바로 수를 세는 단위입니다.

진법중에는 우리가 흔히 사용하고 있는것은 60진법(초,분을 재는데에 쓰이는 방식. 0~59까지 세고, 60이 되었을떄 자리올림하는 수체계)이나, 10진법(0~9까지 세고, 10이 되었을 때는 자리올림을 하는 우리가 기본적으로 수를 세는방식)등도 있습니다.

그 중에서도 여기서 배울 것은 2진법,8진법,16진법이 있는데요, 이 진법들에 공통점은, 모두다 2진수와 연관이 있는 진법이라는 겁니다.

왜 2진수(혹은 2진수와 관련이 깊은 진법에 대해 알아야 될까요? 그 이유는 컴퓨터는 0과 1밖에 구분하지 못하기 때문입니다. 컴퓨터는 전류가 흐를 때, 흐르지 않을때를 통해 구분하는것으로써, 2진수와 일맥 상통하게 됩니다. 2진수는 또한 참과, 거짓으로 상태를 표기하는 부울대수와도 연관이 깊죠.

그런데 게임과, 메신져 이런것들이 0과 1만으로 실행 되는거라고? 거짓말 아냐? 라고 생각하실분들도 계실텐데요, 그것에 대해서는 2번째 챕터 비트의 법칙에서 자세히 설명하도록하겠습니다.

16진수는 2진수 매우 연관이 깊어 2진수를 4개 단위로 읽을 때 유용하게 이용되기도 합니다. 8진수는 16진수와 비슷하게 2진수를 3개 단위로 읽을 때 혹은, 10진수에서의 상호 변환등에서 자주 사용되게 됩니다.

또한, 컴퓨터는 수를 계산(연산)을 하는 기계입니다. 그렇기 때문에, 수를 표현하는 방식(그 중에서도 컴퓨터가 사용하는 2진수와 관련이 깊은)에 대해서 알아둬야할 필요가 있는 것입니다.

(2) 2진법

아까 언급했듯이, 컴퓨터는 0과 1밖에 수를 인식하지 못합니다.

2진수는 0과 1로써 수를 세는 방법입니다.

자리수가 2가 되었을때 자리 올림을 하여 10으로 표시하는 수체계죠.

2진수는 표현할 때

0011 (2)

위와같이 표현하곤합니다. 괄호는 반드시 써야하는건 아니고, 혼돈의 여지가 있을 때만 사용하곤합니다.

2진수
10진수

0000
0

0001
1

0010
2

0011
3

0100
4

0101
5

0110
6

0111
7

1000
8

1001
9

1010
10


위의 표를 보시면, 대강 어떤지 감이 오실겁니다.
10진수가 11일때 2진수로는 몇이 될까요?
네 그렇죠~ 1011이 되는겁니다.


그런데...2진수가 너무 읽기 힘들다구요?

8 4 2 1
1 0 0 1 (자릿수가 1일 때마다, 머리위에 위치한 수만큼 더해줍니다)



8에 위치한 2진수가 1이니 8, 4와 2자리에 위치한 수는 0이니 그냥 내버려두고, 1의 자리에 위치한 수가 1이니 1. 그래서 나온수인 8과 1을 더하면 9가 되는거죠. 쉽죠?

마찬가지로 이진수가 커질때에도

128 64 32 16 8 4 2 1
0 0 0 0 0 0 0 0

이런식으로, 수가 하나 증가할때마다 수가 배로 증가하게 됩니다. 이렇게 하니 2진수 읽는 법은 감이 오죠?


그런데...음수를 표현할땐 어떻게 해야 할까요?

-0011 이런식으론 안되냐구요?

아쉽게도, 10진수와는 달리 -부호를 사용할수 없습니다.

그래서 최상위 비트(가장 앞에 있는 수를 최상위 비트=MSB라 부르게 되죠. 비트란 다음 강좌에서 배우게 될 바이너리 디지트=이진수의 약자입니다)를 부호 비트로 사용하게 되는것이죠.

최상위 비트가 0이면 양수, 1이면 음수로 표현 하기로 했습니다.

0001은 1, 1001은 -1 이렇게 말이죠.


그런데 이것은 문제가 있습니다.

2진수의 계산이 힘들어진것이죠.

1001 + 0001 = 1010

(-1) (1) (0 이어야 함)



2진수의 덧셈에서 자리 올림수가 발생하면 일반 덧셈에서 자리 올림수와 마찬가지로 처리해주면 됩니다.

그건 그렇고, 뭔가 이상하죠? 1010이라니...최상위 비트가 1이니 음수인거 같고, 뒤가 010이니 -2인거 같은데... 어? 0이 아니네요?

그래서 나온 것이 1의 보수입니다.


1의 보수란, 음수 표현시에 0을 1로, 1을 영으로 모든 수를 반전 시키는것이죠.

1의 보수로 표현하자면,

0001은 1, 1110은 -1이 되는거죠.


자..아까 계산이 잘못되었던 -1에서 1을 더해볼까요?

1110 + 0001 = 1111

(-1) (1) (0이어야 함)

어? 여전히 뭔가 이상하죠?? 최상위 비트가 1이니 음수 인거 같고....111이라면 -7? 왜 또 0이 아니지? 또 문제가 있었습니다.


그래서 2의 보수란 것이 나오게 된것입니다.

2의 보수란 0을 1로, 1을 0으로 모두 바꿔준후에 1을 더하는 방법입니다.

0001은 1, 1111은 -1이되는것이죠.



그럼 다시...계속 잘못되었던계산인 -7에서 7을 더해봅시다.

1111 + 0001 = 10000

(-1) (1) (0이어야 함)

음...이번에도 최상위 비트가 1이군요. 그럼 음순가? 그런데 뒤가 0000이네요? 하지만 이 계산은 맞습니다. 맨앞에 초과된 1은 무시하기 때문입니다. 그래서 0000 즉 0이 되는것이죠.



왜 2의 보수가 사용되는지 아셨겠죠?

참고로 1의 보수의 또다른 문제점은 0이 두 개이기 때문입니다.

1111 1111과, 0000 0000 각각 음수 0 양수 0을 의미합니다.

이에 비해 2의 보수는 0000 0000은 당연히 0이고, 0의 1의 보수인 1111 1111에서 1을 더하면 1 0000 0000이 되고, 이때 초과된 1은 무시하게 되므로 0은 양수 0 하나만 남게 되는것이죠. 이로써 아까 -1에서 1을 더하는 계산에서의 초과된 1을 무시하는 이유도 함께 이해가 되시죠?



(3) 8진법

8진법이란 0~7로 수를 세는 방법입니다.

8진수는 자릿수가 7이 넘어 8이 되었을때는 자리 올림하여서, 10으로 표현하는 수체계입니다.

8진수를 표기할때

12 (8)

이런식으로 작은 괄호로 8을 붙여서 표현하곤 합니다. 2진수와 마찬가지로 꼭 표기해줄 필요는 없습니다.
C언어등의 고급 언어에서 사용시에는 0(숫자 0)을 붙여서 사용하죠.

아래 표를 보시면, 2진수, 8진수, 10진수의 관계를 아실수 있을겁니다.

2진수
8진수
10진수

0000
0
0

0001
1
1

0010
2
2

0011
3
3

0100
4
4

0101
5
5

0110
6
6

0111
7
7

1000
10
8

1001
11
9

1010
12
10




8진수는 그 자체적인 의미도 중요하지만, 2진수에서의 변환이 매우 편리하다는 장점이 있습니다.

바로 수를 세 개단위로 묶어서 표현하면 되는것이죠.



4 2 1
1 1 1 (7입니다)

8진수로 바꿀때는, 4+2+1=7이 되는거죠.



이런식으로

4 2 1 4 2 1
1 1 0 0 1 1

4+2=6 2+1=3

8진수로 63이 되는겁니다.


10진수 524을 8진수로 구해봅시다.

524 / 8 = 65 나머지 4

65 / 8 = 8 나머지 1

8 / 8 = 1 나머지 0

1 / 8 = 0 나머지 1

1014(8)

즉, 나머지를 역순으로 나열하면, 10진수에서, 8진수를 구할 수 있습니다.



다시 8진수 1014를 10진수로 바꿔봅시다.

1014(8) = 4 x 8에 0승 + 1 x 8에 1승 + 1 x 8에 3승
= 4 x 1 + 1 x 8 + 1 x 512
= 4 + 8 + 512
= 524

524(10)

각 자리수에 맞춰서 곱해주면, 8진수에서 10진수로 변환되는겁니다.


(4) 16진법

16진법이란, 수를 0부터 15로 세다가, 16이 되어 자리올림할땐 10으로 표현하는 방법을 말합니다.

그런데...뭔가 조금 이상하죠? 0~15로 수를 센다면, 0~9는 괜찮겠지만, 10부터 15는 자리 올림된 10~15와 같게 되니까요.

그래서 10~15는 A~F로 표현하게 됩니다.

16진수도 8진수 2진수와 마찬가지로,

AF (16)

위와 같이 표기할 수 있습니다.

C언어등 고급언어에서 16진수 표현시 숫자 앞에 0x (숫자 0과 알파벳 x)를 붙여서 사용합니다.


아래 표를 보시면 진법에 따른 수 표현 방식을 이해하실수 있을겁니다.

2진수
8진수
16진수
10진수

0000
0
0
0

0001
1
1
1

0010
2
2
2

0011
3
3
3

0100
4
4
4

0101
5
5
5

0110
6
6
6

0111
7
7
7

1000
10
8
8

1001
11
9
9

1010
12
A
10

1011
13
B
11

1100
14
C
12

1101
15
D
13

1110
16
E
14

1111
17
F
15


16진수도 8진수처럼 2진수에서의 변환이 쉽다고 했었죠?

16진수도 2진수를 8진수로 변환하는 것과 비슷합니다. 2진수 4개씩 묶어서 16진수로 변환하면 되는것이죠.

8 4 2 1
1 1 0 0 (12입니다)

8+4=12인데, 12는 알파벳 C로 표현하기로 했으니,

8+4=C로 표현할수 있는것이죠.

8 4 2 1 8 4 2 1
1 0 1 0 0 0 1 1

8+2=A 2+1=3

A3으로 4자리씩 묶어서 변환함으로써 2진수를 좀 더 읽기 쉽게 하는것이죠.

16진수는 또한 니블(Nibble)에 기본 단위가 됩니다. 니블이란 비트를 4개씩 묶어서 표현하는 방식으로써, 16진수와 같은 방식으로 표현할 수 있게 되죠.

이것으로 각 진법에 대하여 알아봤는데요, 2진수, 8진수, 16진수가 모두 연관이 있다는 것을 알수 있었습니다. 특히나 2진수는 어떤수로나 변환이 쉽다는 것도 말이죠.
메인 윈도우 맹글고
메인 프로시저에서
MDICLIENT 윈도를 맹글다.

자식창은
MDICLIENT 윈도우에다가 WM_MDICREATE메세지를
보내는디 lParam에다가

MDICREATESTRUCT 구조체를 집어 넣는다.

메인 프로시저는 wndProc가 아니고 DefFrameProc
자식은 DefMDIChildProc
 

'프로그래밍 > MFC' 카테고리의 다른 글

CCombobox Edit 창 크기 설정  (0) 2015.03.26
MFC 디렉토리 생성  (0) 2013.08.14
쓰레드 생성 (펌글)  (0) 2013.08.14
쓰레드 생성.  (0) 2013.08.14
유니코드  (0) 2013.08.14
API 에서 지원되는 함수들은 어느정도 제약이 있기 떄문에......



좀 짜증났거든요.....  



물론 만드는게 어렵진 않지만 자주 사용해야 하므로 아예 보관하시고 카피해서 사용하시면 편하실거 같아서요...







#include <windows.h>

#include <stdio.h>

#include <string>



using namespace std;



/*



기존 디렉토리가 있을경우 안만들어지고 없으면 만든다. 부모디렉토리가 없어도 생성가능



*/

void CreateDir(char* Path)

{

  char DirName[256];  //생성할 디렉초리 이름

  char* p = Path;     //인자로 받은 디렉토리

  char* q = DirName;  



  while(*p)

  {

      if (('\\' == *p) || ('/' == *p))   //루트디렉토리 혹은 Sub디렉토리

      {

          if (':' != *(p-1))

          {

              CreateDirectory(DirName, NULL);

          }

      }

      *q++ = *p++;

      *q = '\0';

  }

  CreateDirectory(DirName, NULL);  

}





/*



하위디렉토리를 제외한 해당 디렉토리 모든 파일들을 제거



*/

void DeleteAllFiles(char* folderPath)

{

  char fileFound[256];

  WIN32_FIND_DATA info;

  HANDLE hp;



  sprintf(fileFound, "%s\\*.*", folderPath);

  hp = FindFirstFile(fileFound, &info); //디렉토리에 파일이 있는지 첫번째 파일만.

  do

  {

      sprintf(fileFound,"%s\\%s", folderPath, info.cFileName);

      DeleteFile(fileFound);



  }while(FindNextFile(hp, &info));  //다른 파일이 있을때 까지



  FindClose(hp);

}





/*



해당 하는 디렉토리에 파일이 존재해도  디렉토리가 비어있지 않아도 지울수 있다 .



*/



void EmptyDirectory(char* folderPath)

{

  char fileFound[256];

  WIN32_FIND_DATA info;

  HANDLE hp;



  sprintf(fileFound, "%s\\*.*", folderPath);

  hp = FindFirstFile(fileFound, &info);   //디렉토리에 파일이 있는지 첫번째 파일만.

  do

  {

      if (!((strcmp(info.cFileName, ".")==0)||(strcmp(info.cFileName, "..")==0)))

      {

          if((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)==FILE_ATTRIBUTE_DIRECTORY)  //Sub디렉토리가 존재하는경우

          {

              string subFolder = folderPath;

              subFolder.append("\\");

              subFolder.append(info.cFileName);

              EmptyDirectory((char*)subFolder.c_str()); /// {return (_Ptr == 0 ? _Nullstr() : _Ptr); }

              RemoveDirectory(subFolder.c_str());

          }

          else

          {

              sprintf(fileFound,"%s\\%s", folderPath, info.cFileName);

              BOOL retVal = DeleteFile(fileFound);

          }

      }



  }while(FindNextFile(hp, &info));



  FindClose(hp);

}

/////////////
아래의 글을 보다가 우연히 제가 가지고 있던 팁중에서도 아래와 비슷하다고 생각하는 내용이 있어,



생각난김에 하나 올려봅니다.



아래의 디렉토리 생성과 유사한데...



만드려고하는 폴더를 full path로 그냥 넣어주기만하면 상위가 없으면 같이 만들어줍니다.



#include <io.h>



BOOL CreateDir(char* pszDir)

{

  BOOL bRet = false;

  int nLen = strlen(pszDir);

  char* pszSubDir = NULL;



  _finddata_t fdata;

  long hFind;

  for (int i = nLen - 1; i >= 0; i--)

  {

      if (pszDir[i] == '\\')

      {

          pszSubDir = new char[i+1];

          memset(pszSubDir, 0, i+1);

          memcpy(pszSubDir, pszDir, i);

          if (hFind = _findfirst(pszSubDir, &fdata) == -1L)

          {

              if (!CreateDir(pszSubDir))

              {

                  delete pszSubDir;

                  return bRet;

              }

          }

          delete pszSubDir;

          break;

      }

  }

   

  bRet = ::CreateDirectory(pszDir, NULL);



  return bRet;

}
////////////////////////////////
Shell 명령을 쓰면 더욱 간단해 집니다.
제가 만든 코드입니다.

bool CheckDirectory(LPCTSTR dir)
{
if (::PathFileExists(dir) && ::PathIsDirectory(dir))
return true;

TCHAR parent[MAX_PATH] = _T("");
lstrcpy(parent, dir);
::PathRemoveFileSpec(parent);
if (CheckDirectory(parent))
return (::CreateDirectory(dir, NULL) != FALSE);
return false;
}
/////////////////////
int SHCreateDirectory(HWND hwnd, LPCWSTR pszPath);

'프로그래밍 > MFC' 카테고리의 다른 글

CCombobox Edit 창 크기 설정  (0) 2015.03.26
WIN32 API프로그래밍에서 MDI에서 자식 생성.  (0) 2013.08.14
쓰레드 생성 (펌글)  (0) 2013.08.14
쓰레드 생성.  (0) 2013.08.14
유니코드  (0) 2013.08.14
// 데브피아(devpia) 가욱현, 정대원 님의 글을 토대로 함다.
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; i<j; i++)
{
// 수행할 작업
}
}


작업자 스레드 함수에 4바이트 이상의 정보를 넘겨주어야 할 경우에는
다음과 같이 작업자 스레드 함수에 넘겨주어야 할 모든 값을 포함하는 구조체를 선언하고,

typedef struct tagTREADPARAMS {
CPoint point;
BOOL *pContinue;
BOOL *pFriend;
CWnd *pWnd;
} THREADPAPAMS;

// 그런 다음 구조체에 필요한 값들을 설정하고, 이 구조체의 포인터를 넘겨준다.
THREADPAPAMS *pThreadParams = new THREADPAPAMS; // new로 할당
pThreadParams->point = 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 < b; i++)
{
// 어떤일을 한다.
}
return;  // 작업이 끝나면 리턴한다. 이때 스레드 자동으로 종료.
}


1번째 방법은 스레드를 생성하고 m_hAutoDelete를 false로 해서
스레드가 return해서 자동종료해도 CWinthread를 자동파괴하지 않게 하고, GetExitCodeThread()를 호출하져..
밑에서 delete해 주는 거 꼭 해야되고요..안그럼 메모리 누수가 되져..

2번째는 m_hThread를 다른 핸들변수에 저장해 놓고..스레드가 return되면 CWinThread*도 같이 파괴가 되는데..
원래 저장한 핸들을 가지고 GetExitcodeThread()를 호출해서 한때 존재했지만 종료된 쓰레드를 검사하는 것이져….이해 OK?????


3) 대기 함수

WaitForSingleObject(), WaitForMultipleObjects()의 원형은 다음과 같다.

DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);

DWORD WaitForMultipleObjects(
DWORD nCount,             // number of handles in array
CONST HANDLE *lpHandles,  // object-handle array
BOOL bWaitAll,            // wait option
DWORD dwMilliseconds      // time-out interval
);


쓰레드 종료를 위한 플래그를 설정한 후, 쓰레드가 완전히 종료된 것을 확인 후에 어떤 작업을 하고 싶으면 다음과 같이 한다.
if (::WaitForSingleObject(pThread->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 사이의 쓰레드에는 적용되지 않는다.
또한 사용자입력을 받거나(인터럽트) 대기상태에서 준비상태가 되는 경우에는 우선순위가 올라가고,
쓰레드가 할당된 시간을 다 쓸 때마다 우선순위를 내려  결국 다시 기반 우선순위와 같아지게 되는데,
어떠한 경우라도 동적 우선순위가 기반 우선순위보다는 더 낮아지지 않는다.

3. 쓰레드간 동기화

멀티쓰레드는 개요에서 말했듯이 한 프로세스를 여러 역할에 따라 여러 개의 쓰레드로 나뉘어 작업하는 방식이므로 각 쓰레드간의 동기화가 필요하다.
동시에 복수개의 코드가 같은 주소영역에서 실행됨으로써 서로 간섭하고 영향을 주는 경우가 빈번하기 때문이다.

멀티쓰레드의 가장 큰 문제점은 공유자원(주로 메모리의 전역변수)을 보호하기가 어렵다는 점이다.
그리고 쓰레드간의 실행순서를 제어하는 것도 쉽지 않은 문제이다.

이런 여러가지 문제점을 해결하기 위하여 쓰레드간의 실행 순서를 제어할 수 있는 여러가지 방법들을 동기화라고 한다.
동기화 방법에는, Interlocked, 임계영역, 뮤텍스, 세마포어, 이벤트등의 기법을 사용한다.

1) 임계영역 (Critical Section)

동기화문제를 해결하는 방법들 중 가장 쉬운반면 동일한 프로세스 내에서만 사용해야 하는 제약이 있다.
임계영역(Critical Section)이란 공유자원의 독점을 보장하는 코드의 영역을 가리킨다. 이는 아래 두 함수로 시작하고 끝낸다.

VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
VOID DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

CRITICAL_SECTION형의 포인터형은 복수개의 쓰레드가 참조해야 하므로 반드시 전역변수로 선언해야한다. 사용법은 다음과 같다.

CRITICAL_SECTION crit1, crit2;

함수 {

EnterCriticalSection(&crit1);
//공유자원1을 액서스한다.
LeaveCriticalSection(&crit1);

EnterCriticalSection(&crit2);
//공유자원2을 액서스한다.
LeaveCriticalSection(&crit2);

}

주의할것은 가급적 임계영역 내부의 코드가 빨리 끝날 수 있도록 짧은 시간을 사용하도록 작성해야 한다.
만약 Leave를 호출하지않고 쓰레드를 빠져나와버리면 이후부터는 다른 쓰레드는 이 임계영역에 들어갈 수 없게된다.
만약 이부분에서 예외가 발생하여 Leave함수가 호출되지 못하게 될 수도 있다.
그래서 임계영역을 쓸 때는 반드시 구조적 예외 처리구문에 포함시켜주는 것이 좋다.

Try {
EnterCriticalSection(&crit);

}
finally {
LeaveCriticalSection(&crit);
}

이렇게하면 설사 예외가 발생하더라도 Leave함수는 반드시 호출되므로 훨씬 안전해진다.

다음은 MFC 에서의 사용 예이다.
CCriticalSection g_critical; // 전역 변수로 선언

function()
{
AfxBeginThread(ThreadFuncA, NULL);
AfxBeginThread(ThreadFuncB, this);
}

UINT ThreadFuncA(LPVOID pParam)
{
while(1)
{
g_critical.Lock();

// ThreadFuncA가 할 일....

g_critical.Unlock();
}
return 0;
}

UINT ThreadFuncB(LPVOID pParam)
{
while(1)
{
g_critical.Lock();

// ThreadFuncB가 할 일....

g_critical.Unlock();
}
return 0;
}


2) 뮤텍스(Mutex)

임계영역은 앞서 말했듯 동일한 프로세스 내에서만 사용할 수 있다.
그러나, 뮤텍스(Mutex; Mutual Exclusion;상호배제)는 임계영역이 사용된 곳에 대신 사용될 수 있으며, 프로세스 간에도 사용할 수 있다.
뮤텍스를 사용하려면 다음 함수로 생성해야 한다.

HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL blInitialOwner, LPCTSTR lpName);

lpMutexAttributes : 보안속성. 대개 NULL
blInitialOwner : 뮤텍스 생성과 동시에 소유할 것인지 지정.
lpName: 뮤텍스의 이름을 지정하는 문자열.
뮤텍스는 프로세스간의 동기화에도 사용되므로 이름이 필요하고, 이 이름은 프로세스간 뮤텍스를 공유할 때 사용된다.

뮤텍스 소유를 해지하여 다른 쓰레드가 이것을 가질 수 있도록 하려면 임계영역의 LeaveCriticalSection 에 해당하는 다음 함수를 호출하면 된다.

BOOL ReleaseMutex(HANDLE hMutex);

만일 프로세스가 다른 프로세스의 쓰레드에 의해서 이미 생성된 뮤텍스의 핸들을 얻기를 원하거나,
뮤텍스가 존재하지 않는 경우에 뮤텍스를 생성하기 원한다면 다음 함수를 사용한다.

HANDLE OpenMutex(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName);

3) 세마포어 (Semaphore)

세마포어도 뮤텍스와 유사한 동기화 객체이나 다른점은, 뮤텍스는 하나의 공유자원을 보호하기 위해 사용하지만,
세마포어는 제한된 일정 개수를 가지는 자원(HW, 윈도우, 프로세스, 쓰레드, 권한, 상태 등 컴퓨터에서의 모든 자원)을 보호하고 관리한다.
세마포어는 사용 가능한 자원의 개수를 카운트하는 동기화 객체이다.
세마포어와 관련된 함수는 다음과 같다.

HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG IlInitialCount,
LONG lMaximumCount, LPCTSTR lpName);

HANDLE OpenSemaphore(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName);

BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount);

4) 이벤트 (Event)

임계영역, 뮤텍스, 세마포어는 주로 공유자원을 보호하기 위해 사용되는 데 비해
이벤트는 이보다는 스레드간의 작업순서나 시기를 조정하기 위해 사용한다.
특정한 조건이 만족될 때까지 대기해야 하는 쓰레드가 있을 경우 이 쓰레드의 실행을 이벤트로 제어할 수 있다.
이벤트는 자동리셋과 수동리셋이 있다.

+자동 리셋 이벤트 : 대기상태가 종료되면 자동으로 비신호상태가 된다.
+수동 리셋 이벤트 : 쓰레드가 비신호상태로 만들어줄 때까지 신호상태를 유지한다.

++신호상태 (Signaled): 쓰레드 실행가능상태. 신호상태의 동기화 객체를 가진 쓰레드는 계속 실행할 수 있다.

HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset,
BOOL bInitialState, LPCTSTR lpName);

HANDLE OpenEvent(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName);

bManualReset은 이벤트가 수동리셋 이벤트(manual)인지 자동리셋 이벤트(automatic)인지 지정하는데 TRUE이면 수동리셋 이벤트가 된다.
bInitialState가 TRUE이면 이벤트를 생성함과 동시에 신호상태로 만들어 이벤트를 기다리는 쓰레드가 곧바로 실행을 하도록 해준다.
이벤트도 이름(lpName)을 가지므로 프로세스간의 동기화에 사용될 수 있다.

또한 이벤트가 임계영역이나 뮤텍스와 다른점은
대기함수를 사용하지 않고도, 쓰레드에서 임의적으로 신호상태와 비신호상태를 설정할 수 있다는 점이다. 다음 함수를 사용한다.

BOOL SetEvent(HANDLE hEvent);
BOOL ResetEvent(HANDLE hEvent);

SetEvent는 신호상태로 만들고 ResetEvent는 비신호상태로 만든다.

다음은 MFC 에서의 사용 예이다.
CEvent g_event; // 전역변수로 선언

FunctionA()
{
AfxBeginThread(ThreadFunc, this);
}

FunctionB()
{
g_event.SetEvent(); // Lock() 함수에서 더 이상 진행하지 못하고 잠자고 있는 쓰레드를 깨워서 일을 시키려면 SetEvent()를 호출.
}

// ThreadFunc() 함수는 이벤트가 발생할 때마다 while문을 한번씩 실행.
UINT ThreadFunc(LPVOID pParam)
{
while(1)
{
g_event.Lock();  // SetEvent()가 호출되면, Lock()함수에서 실행이 중단된 쓰레드가 다음 코드를 실행.

// ThreadFunc가 할 일....

g_event.Unlock();
}
return 0;
}

'프로그래밍 > MFC' 카테고리의 다른 글

WIN32 API프로그래밍에서 MDI에서 자식 생성.  (0) 2013.08.14
MFC 디렉토리 생성  (0) 2013.08.14
쓰레드 생성.  (0) 2013.08.14
유니코드  (0) 2013.08.14
CEvent 클래스  (0) 2013.08.14
윈도우 쓰레드 생성 방법

_beginthread
- C 라이브러리를 사용할수 있는 장점 있음
- 런타임 라이브러리 지정시 다중 쓰레드 옵션을 주면 사용할수 있음.
- 종료시 _endthread 호출해야함

CreateThread
- 종료시 함수 리턴, 혹은 ExitThread함수 호출
- UI 관련 함수 , 윈도우 컨트롤 사용시 알수 없는 문제가 많이 발생한다. 이럴경우
  AfxBeginThread로 사용한다.
 

MFC를 이용한 쓰레드 프로그래밍
- 메시지 펌프가 있어서 메시지 처리 가능함.
- 작업 쓰레드와 UI 쓰레드로 구분.
0 작업쓰레드 생성시 AfxBeginThread 사용.
- 작업 쓰레드 종료시 AfxThreadEnd() 함수 처리.
- 외부에서 종료 처리할수 있음
- TerminateThread API 함수를 사용.
0 UI 쓰레드 생성시 AfxBeginthread 호출 인자값 pfnThreadProc는 NULL이
되는 AfxBeginthread호출함.
 

'프로그래밍 > MFC' 카테고리의 다른 글

MFC 디렉토리 생성  (0) 2013.08.14
쓰레드 생성 (펌글)  (0) 2013.08.14
유니코드  (0) 2013.08.14
CEvent 클래스  (0) 2013.08.14
JPG to BMP , BMP to JPG 로 변환 코드  (0) 2013.08.14
1. 유니코드

ASCII로는 영문(1Byte)밖에 표현 못한다. 한글을 사용하려 하거나 다른 나라 언어를
사용하기 위해선 255로써는 표현을 못했다. 그래서 보통 한글을 사용하기 위해서
2Byte로 처리를 해서 조합,완성형이라는 한글이라는 방식이 생기게 되었다.

다른 나라도 각자 알아서 2Byte라든지 다른 방식으로 처리해서 표현하게 되었는데, 이게 너무 다 다르다는 거다. 그래서 "모든 나라의 글자셋을 하나로 묶어 버리자!"라는 취지 하에 만들어 진게 유니 코드이다.

그런데 유니코드를 실제 바이트로 변환이 필요하게 되었고 인코딩 방식이 생기게 되었는데 하나의 글자 표현에 2바이트를 사용하는 UCS-2(2바이트) 또는 UTF-16(16비트) ,
영어는 1바이트 그외 다른 언어는 2바이트~6바이트까지 할당하는 UTF-8등이 있다.

-유니코드 : Wide Character로 16비트 코드를 가진다. wchar_t, wchar_t*로 표현됨.

- MBCS/DBCS : MBCS(Multi-Byte Character Set)은 하나의 문자가 한 바이트 이상 으로 구성된 문자열 셋을 말한다.  정확한건 모르겠고, 한 글자가 1바이트 이상으로 표현될수 있는 문자열셋을 말하나보당.

-ANSI : 8비트로 구성된거. 설명 필요 읍지.

-TCHAR : 그냥 MS에서 유니코드로 구성된 프로젝트일때는 WCHAR로 아니면 CHAR로 변환해주는 매크로라고 보면 쉽겠지.

유니코드로 되었있든 그냥 일반 MBCS, ANSI로 되어있든 TCHAR형으로 써주면 알아서 변경 되게 한다 라는 취지겠지 모.

ANSI               UNICODE                 TCHAR
strlen               wcslen                    _tcslen
strcat               wcscat                    _tcscat
strchr               wcschr                    _tcschr
strcmp             wcscmp                    _tcscmp
strcpy              wcscpy                    _tcscpy
strstr               wcsstr                    _tcsstr
strrev               _wcsrev                    _tcsrev
printf                wprintf                       _tprintf
sprintf               wsprintf                    _tsprintf
scanf               wscanf                     _tscanf



사용 예 :
TCHAR szTemp[10] = _T("유니코드");
int Len = _tcslen(szTemp);

MBCS에서 유니코드 변환시 필요 함수 : mbstowcs, MultiByteToWideChar
유니코드에서 MBCS 변환시 함수 : wcstombs, WideCharToMultiByte

ATL에서 제공하는 A2W , W2A 도 있다.

'프로그래밍 > MFC' 카테고리의 다른 글

MFC 디렉토리 생성  (0) 2013.08.14
쓰레드 생성 (펌글)  (0) 2013.08.14
쓰레드 생성.  (0) 2013.08.14
CEvent 클래스  (0) 2013.08.14
JPG to BMP , BMP to JPG 로 변환 코드  (0) 2013.08.14
이 글의 목적은 CEvent의 사용 목적 및 방법과 WaitForSingleObject(wait function)와의 사용관계에 대한 간단한 설명입니다.



CEvent는 보통 이벤트 객체라고 불리며, 동기화 객체로 사용됩니다. 이 이벤트 객체는 딱 2가지 상태(signaled, non-signaled 또는 unsignaled)로 동기화를 지원합니다.

예를 들어 TRUE, FALSE 와 같은 개념입니다. 그럼 이 두 상태를 바탕으로 우리가 할 수 있는 일은 대부분 동기화 , 즉 둘 이상의 관계에서 서로간의 상태에 따라 작업을

구분지어 실행 순서에 제어를 두기 위함입니다. 예를 들어 두 스레드간에 서로 같은 데이터를 접근하여 사용하는 경우이겠죠.



CEvent의 생성자를 살펴보면, 다음과 같습니다.

CEvent::CEvent

(

  BOOL bInitiallyOwn = FALSE,

  BOOL bManualReset = FALSE,

  LPCTSTR lpszName = NULL,

  LPSECURITY_ATTRIBUTES lpsaAttribute = NULL

);



여기서 주의 깊게 보아야 할 부분이 bInitiallyOwn, bManualReset 입니다.

bInitiallyOwn 을 FALSE로 지정할 경우 생성되는 event 객체는 non-signaled된 상태입니다. 반대일 경우는 signaled로 생성이 됩니다.



그럼 bManualReset의 상황에 따라 WaitForSingleObject의 수행이 어떻게 다른가 살펴보겠습니다. (단, bInitiallyOwn 는 FALSE)



1. bManualReset = TRUE 일 경우 ( 수동 event 객체)



WaitforSingleObject를 통해서 스레드나 프로세스가 대기하게 됩니다. 물론 같은 event 객체에 대한 WaitForSingleObject가 수행된 곳은 모두 대기 상태입니다. 더 이상 실행이

진행이 안되는 것이죠. 이 상황에서 SetEvent()를 호출하면 내부적으로 event 객체가 signaled로 변경됩니다. 각 대기 스레드나 프로세스들은 (현재 대기하는 모든 스레드나 프로세스) 가 대기상태 에서 진행상태로 바뀌게 됩니다  SetEvent()로 인하여 event 객체는 계속 signaled된 상태를 지속하게 됩니다. 이 event 객체의 상태를 non-signaled로 바꾸려면, ResetEvent()를 호출시켜줍니다. 이 경우는 같은 event 객체를 사용하고 , waitForSIngleObject를 통해서 대기하는 모든 스레드나 프로세스가 같이 대기 상태에서

진행상태로 바꾸게 됩니다.  그리고, ResetEvent()를 통해 다시 대기 상태로 변경시킬 수 있습니다. 이 경우 PulseEvent()함수는 Event의 상태를 signaled로 변경하고 ,

모든 대기 스레드나 프로세스를 대기상태에서 벗어나게 합니다. 그리고 나서 자동으로 non-signaled로 변경시켜줍니다.



2. bManualReset = FALSE 일 경우 ( 자동 event 객체)



WaitforSingleObject를 통해서 스레드나 프로세스가 대기하게 됩니다. 물론 같은 event 객체에 대한 WaitForSingleObject가 수행된 곳은 모두 대기 상태입니다.

이 상황에서 SetEvent()를 호출하면 내부적으로 event 객체가 signaled로 변경됩니다. 현재 대기하는 모든 스레드나 프로세스 중에서 하나의 스레드나 프로세스만이 대기상태에서 진행상태로 바뀌게 됩니다. 그리고 자동으로 non-signaled 상태가 되어 다른 스레드나 프로세스가 진행상태로 되는 것을 방지해줍니다. 물론 꼭 하나의 스레드나 프로세스가 대기상태에서 진행 상태로 변경되어야만 non-signaled 상태가 됩니다. ResetEvent()는 자동 event 객체일때는 사용할 수 없습니다. 이 경우 PulseEvent()함수는 Event의 상태를 signaled로 변경하고 , 현재 대기하는 모든 스레드나 프로세스 중에서 하나의 스레드나 프로세스만이 대기상태에서  벗어나게 합니다. 그리고 나서 자동으로 non-signaled로 변경시켜줍니다.



보통 다중 스레드에서 하나의 데이터에 접근할 때 쓰는 방법은 2번이 되겠습니다. 1번의 경우는 각 스레드들이 작업을 진행하다가 어떤 이벤트에 의해서 모두 정지되거나 진행되어야 할 때 유용하게 사용할 수 있습니다.



예제) bInitiallyOwn = FALSE, bManualReset = FALSE일 경우

Thread1()

{

  while(condition)

  {  

      0.  WaitForSingleObject( event, INFINITE);

      2. // 작업...                                              

      3. SetEvent();

  }

}



Thread2()

{

  while(condition)

  {

      0. WaitForSingleObject(event, INFINITE);

      4. //작업

      5. SetEvent();

  }

}



otherFunction()

{

  1.  SetEvent();

}

위의 예제에서는 //작업이라는 구역에는 동시에 두 스레드가 접근할 수 없습니다. 이해가 되실꺼에요. 3. 5번으로 인하여 두 스레드는 계속 루프를 돌게 됩니다. while이 있지만, 서로가 서로에게 계속 이벤트를 발생시켜줌으로서 운영체제의 스레드 스케쥴링에 의해서 계속 루프를 돌면서 수행되는 이야기입니다. 만약 스레드 안에 3. 5번을 없애면 1번 처럼

이벤트를 날려줌으로 해서 스레드의 작업을 제어할 수 있습니다.

'프로그래밍 > MFC' 카테고리의 다른 글

MFC 디렉토리 생성  (0) 2013.08.14
쓰레드 생성 (펌글)  (0) 2013.08.14
쓰레드 생성.  (0) 2013.08.14
유니코드  (0) 2013.08.14
JPG to BMP , BMP to JPG 로 변환 코드  (0) 2013.08.14
/*

CIMAGE 안에 있는 JPEG 라이브러리를 이용해서

DC를 캡쳐한것을 JPEG, BMP로 저장하는 소스코드

긁어다 쓰는것도 엄청 복잡하구만 ㅠ.ㅠ



*JPEG 을 컴파일할때 NOT USING MFC 항목을 체크할것

*BMP는 MSDN을 참조

*JPG는 CODEGURU  참조



*ppt 를 전체화면으로 뛰웠을때 파워포인트를 캡쳐해서

저장하는 프로그램이라고 하면 될것임;;

*/

#include <windows.h>
#include <stdio.h>

extern "C" {
#include "jpeglib.h"
}

#include <setjmp.h>

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

struct ima_error_mgr {
struct jpeg_error_mgr pub; /* "public" fields */
 jmp_buf setjmp_buffer; /* for return to caller */
};

typedef ima_error_mgr *ima_error_ptr;

void CreateBMPFile(HWND hwnd, LPTSTR pszFile, PBITMAPINFO pbi, HBITMAP hBMP, HDC hDC) ;
void errhandler(LPTSTR pszFile, HWND hwnd);

////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
//Header file code
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
HANDLE DDBToDIB(HBITMAP bitmap, DWORD dwCompression);

//In the relevant header file (up near the top):
BOOL DibToSamps(HANDLE          hDib,
               int                         nSampsPerRow,
               struct jpeg_compress_struct cinfo,
               JSAMPARRAY                  jsmpPixels,
               LPTSTR                    pcsMsg);

BOOL JpegFromDib( HANDLE    hDib,     //Handle to DIB
                int        nQuality, //JPEG quality (0-100)
                LPTSTR    csJpeg,   //Pathname to target jpeg file
                LPTSTR   pcsMsg);  //Error msg to return

BOOL BuildSamps(HANDLE                      hDib,
               int                         nSampsPerRow,
               struct jpeg_compress_struct cinfo,
               JSAMPARRAY                  jsmpArray,
               LPTSTR                    pcsMsg);

RGBQUAD QuadFromWord(WORD b16);

////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
//Source file code
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////
//This function takes the contents of a DIB
//and turns it into a JPEG file.
//
//The DIB may be monochrome, 16-color, 256-color, or 24-bit color.
//
//Any functions or data items beginning with "jpeg_" belong to jpeg.lib,
//and are not included here.
//
//The function assumes 3 color components per pixel.
/////////////////////////////////////////////////////////////////////////////
BOOL JpegFromDib(HANDLE hDib,     //Handle to DIB
                int        nQuality, //JPEG quality (0-100)
                LPTSTR    csJpeg,   //Pathname to jpeg file
                LPTSTR   pcsMsg)   //Error msg to return
{
   //Basic sanity checks...
   if (nQuality < 0 || nQuality > 100 ||
       hDib   == NULL ||
       pcsMsg == NULL ||
       csJpeg == "")
   {
       if (pcsMsg != NULL)
           pcsMsg = "Invalid input data";

       return FALSE;
   }

   pcsMsg = "";

   LPBITMAPINFOHEADER lpbi = (LPBITMAPINFOHEADER)hDib;

   byte *buf2 = 0;

   //Use libjpeg functions to write scanlines to disk in JPEG format
   struct jpeg_compress_struct cinfo;
   struct jpeg_error_mgr       jerr;

   FILE*      pOutFile;     //Target file
   int        nSampsPerRow; //Physical row width in image buffer
   JSAMPARRAY jsmpArray;    //Pixel RGB buffer for JPEG file

   cinfo.err = jpeg_std_error(&jerr); //Use default error handling (ugly!)

   jpeg_create_compress(&cinfo);

   if ((pOutFile = fopen(csJpeg, "wb")) == NULL)
   {
       pcsMsg = "Cannot open ";
//  pcsMsg += csJpeg;
       jpeg_destroy_compress(&cinfo);
       return FALSE;
   }

   jpeg_stdio_dest(&cinfo, pOutFile);

   cinfo.image_width      = lpbi->biWidth;  //Image width and height, in pixels
   cinfo.image_height     = lpbi->biHeight;
   cinfo.input_components = 3;              //Color components per pixel
                                            //(RGB_PIXELSIZE - see jmorecfg.h)
   cinfo.in_color_space   = JCS_RGB;       //Colorspace of input image

   jpeg_set_defaults(&cinfo);

   jpeg_set_quality(&cinfo,
                    nQuality, //Quality: 0-100 scale
                    TRUE);    //Limit to baseline-JPEG values

   jpeg_start_compress(&cinfo, TRUE);

   //JSAMPLEs per row in output buffer
   nSampsPerRow = cinfo.image_width * cinfo.input_components;

   //Allocate array of pixel RGB values
   jsmpArray = (*cinfo.mem->alloc_sarray)
               ((j_common_ptr) &cinfo,
                JPOOL_IMAGE,
                nSampsPerRow,
                cinfo.image_height);

   if (DibToSamps(hDib,
                  nSampsPerRow,
                  cinfo,
                  jsmpArray,
                  pcsMsg))
   {
       //Write the array of scan lines to the JPEG file
       (void)jpeg_write_scanlines(&cinfo,
                                  jsmpArray,
                                  cinfo.image_height);
   }

   jpeg_finish_compress(&cinfo); //Always finish

   fclose(pOutFile);

   jpeg_destroy_compress(&cinfo); //Free resources

   if (pcsMsg != "")
       return FALSE;

   else
       return TRUE;
}

////////////////////////////////////////////////////////////////
//This function fills a jsmpArray with the RGB values
//for the CBitmap.
//
//It has been improved to handle all legal bitmap formats.
//
//A jsmpArray is a big array of RGB values, 3 bytes per value.
//
//Note that rows of pixels are processed bottom to top:
//The data in the jsamp array must be arranged top to bottom.
////////////////////////////////////////////////////////////////
BOOL DibToSamps(HANDLE                      hDib,
               int                         nSampsPerRow,
               struct jpeg_compress_struct cinfo,
               JSAMPARRAY                  jsmpPixels,
               LPTSTR                    pcsMsg)
{
  //Sanity...
  if (hDib == NULL    ||
    nSampsPerRow <= 0 || pcsMsg == NULL)
  {
    if (pcsMsg !=NULL)
       pcsMsg="Invalid input data";
    return FALSE;
  }

  int r=0, p=0, q=0, b=0, n=0,
      nUnused=0, nBytesWide=0, nUsed=0, nLastBits=0, nLastNibs=0, nCTEntries=0,
      nRow=0, nByte=0, nPixel=0;
  BYTE bytCTEnt=0;
  LPBITMAPINFOHEADER pbBmHdr= (LPBITMAPINFOHEADER)hDib; //The bit count tells you the format of the bitmap: //Decide how many entries will be in the color table (if any)

  switch (pbBmHdr->biBitCount)
  {
     case 1:
        nCTEntries = 2;   //Monochrome
        break;

     case 4:
        nCTEntries = 16;  //16-color
        break;

     case 8:
        nCTEntries = 256; //256-color
        break;

     case 16:
     case 24:
     case 32:
        nCTEntries = 0;   //No color table needed
        break;

     default:
        pcsMsg = "Invalid bitmap bit count";
        return FALSE; //Unsupported format
  }

  //Point to the color table and pixels
  DWORD     dwCTab = (DWORD)pbBmHdr + pbBmHdr->biSize;
  LPRGBQUAD pCTab  = (LPRGBQUAD)(dwCTab);
  LPSTR     lpBits = (LPSTR)pbBmHdr +
                     (WORD)pbBmHdr->biSize +
                     (WORD)(nCTEntries * sizeof(RGBQUAD));

  //Different formats for the image bits
  LPBYTE   lpPixels = (LPBYTE)  lpBits;
  RGBQUAD* pRgbQs   = (RGBQUAD*)lpBits;
  WORD*    wPixels  = (WORD*)   lpBits;

  //Set up the jsamps according to the bitmap's format.
  //Note that rows are processed bottom to top, because
  //that's how bitmaps are created.
  switch (pbBmHdr->biBitCount)
  {
     case 1:
        nUsed      = (pbBmHdr->biWidth + 7) / 8;
        nUnused    = (((nUsed + 3) / 4) * 4) - nUsed;
        nBytesWide = nUsed + nUnused;
        nLastBits  = 8 - ((nUsed * 8) - pbBmHdr->biWidth);

        for (r=0; r < pbBmHdr->biHeight; r++)
        {
           for (p=0,q=0; p < nUsed; p++)
           {
              nRow=(pbBmHdr->biHeight-r-1) * nBytesWide;
              nByte =  nRow + p;

              int nBUsed = (p <(nUsed -1)) ? 8 : nLastBits;
     
     for(b=0; b < nBUsed;b++)
              {
                 bytCTEnt = lpPixels[nByte] << b;
                 bytCTEnt = bytCTEnt >> 7;

                 jsmpPixels[r][q+0] = pCTab[bytCTEnt].rgbRed;
                 jsmpPixels[r][q+1] = pCTab[bytCTEnt].rgbGreen;
                 jsmpPixels[r][q+2] = pCTab[bytCTEnt].rgbBlue;

                 q += 3;
              }
           }
        }
        break;

     case 4:
        nUsed      = (pbBmHdr->biWidth + 1) / 2;
        nUnused    = (((nUsed + 3) / 4) * 4) - nUsed;
        nBytesWide = nUsed + nUnused;
        nLastNibs  = 2 - ((nUsed * 2) - pbBmHdr->biWidth);

        for (r=0; r < pbBmHdr->biHeight;r++)
        {
           for (p=0,q=0; p < nUsed;p++)
           {
              nRow=(pbBmHdr->biHeight-r-1) * nBytesWide;
              nByte = nRow + p;

              int nNibbles = p;
     for(n=0; n < nNibbles;n++)
              {
                 bytCTEnt=lpPixels[nByte] << (n*4);
     bytCTEnt=bytCTEnt >> (4-(n*4));

                 jsmpPixels[r][q+0] = pCTab[bytCTEnt].rgbRed;
                 jsmpPixels[r][q+1] = pCTab[bytCTEnt].rgbGreen;
                 jsmpPixels[r][q+2] = pCTab[bytCTEnt].rgbBlue;

                 q += 3;
              }
           }
        }
        break;

     default:
     case 8: //Each byte is a pointer to a pixel color
        nUnused = (((pbBmHdr->biWidth + 3) / 4) * 4) -
                  pbBmHdr->biWidth;

        for (r=0;r < pbBmHdr->biHeight; r++)
        {
           for (p=0,q=0; p < pbBmHdr->biWidth; p++,q+=3)
           {
              nRow   = (pbBmHdr->biHeight-r-1) * (pbBmHdr->biWidth + nUnused);
              nPixel =  nRow + p;

              jsmpPixels[r][q+0] = pCTab[lpPixels[nPixel]].rgbRed;
              jsmpPixels[r][q+1] = pCTab[lpPixels[nPixel]].rgbGreen;
              jsmpPixels[r][q+2] = pCTab[lpPixels[nPixel]].rgbBlue;
           }
        }
        break;

     case 16: //Hi-color (16 bits per pixel)
        for (r=0;r < pbBmHdr->biHeight; r++)
        {
           for (p=0,q=0; p < pbBmHdr->biWidth; p++,q+=3)
           {
              nRow    = (pbBmHdr->biHeight-r-1) * pbBmHdr->biWidth;
              nPixel  = nRow + p;

              RGBQUAD quad = QuadFromWord(wPixels[nPixel]);

              jsmpPixels[r][q+0] = quad.rgbRed;
              jsmpPixels[r][q+1] = quad.rgbGreen;
              jsmpPixels[r][q+2] = quad.rgbBlue;
           }
        }
        break;

     case 24:
        nBytesWide =  (pbBmHdr->biWidth*3);
        nUnused    =  (((nBytesWide + 3) / 4) * 4) -
                      nBytesWide;
        nBytesWide += nUnused;

        for (r=0;r < pbBmHdr->biHeight;r++)
        {
           for (p=0,q=0;p < (nBytesWide-nUnused); p+=3,q+=3)
           {
              nRow = (pbBmHdr->biHeight-r-1) * nBytesWide;
              nPixel  = nRow + p;

              jsmpPixels[r][q+0] = lpPixels[nPixel+2]; //Red
              jsmpPixels[r][q+1] = lpPixels[nPixel+1]; //Green
              jsmpPixels[r][q+2] = lpPixels[nPixel+0]; //Blue
           }
        }
        break;

     case 32:
        for (r=0; r < pbBmHdr->biHeight; r++)
        {
           for (p=0,q=0; p < pbBmHdr->biWidth; p++,q+=3)
           {
              nRow    = (pbBmHdr->biHeight-r-1) *
                         pbBmHdr->biWidth;
              nPixel  = nRow + p;

              jsmpPixels[r][q+0] = pRgbQs[nPixel].rgbRed;
              jsmpPixels[r][q+1] = pRgbQs[nPixel].rgbGreen;
              jsmpPixels[r][q+2] = pRgbQs[nPixel].rgbBlue;
           }
        }
        break;
  }   //end switch

return TRUE;
}

////////////////////////////////////////
//This function turns a 16-bit pixel
//into an RGBQUAD value.
////////////////////////////////////////
RGBQUAD QuadFromWord(WORD b16)
{
  BYTE bytVals[] =
  {
    0,  16, 24, 32,  40, 48, 56, 64,
    72, 80, 88, 96, 104,112,120,128,
    136,144,152,160,168,176,184,192,
    200,208,216,224,232,240,248,255
  };

  WORD wR = b16;
  WORD wG = b16;
  WORD wB = b16;

  wR <<= 1; wR >>= 11;
  wG <<= 6; wG >>= 11;
  wB <<= 11; wB >>= 11;

  RGBQUAD rgb;

  rgb.rgbReserved = 0;
  rgb.rgbBlue     = bytVals[wB];
  rgb.rgbGreen    = bytVals[wG];
  rgb.rgbRed      = bytVals[wR];

  return rgb;
}


void main()
{
PBITMAPINFOHEADER pbih;
BITMAP bmp;
   PBITMAPINFO pbmi;
   WORD    cClrBits;
 LPBYTE lpBits;

HWND hPPT;
HDC hdc;
HDC memDC;
HBITMAP  m_Bitmap;


int cx, cy;

printf("dll 파일이 불려 집니다. ");
hPPT=FindWindow("screenClass",NULL);  //파워포인트 전체화면 차일드 윈도우
if (hPPT == NULL) {
 printf("파워포인트가 실행중이지 않습니다 ");
} else {
  hdc=GetDC(hPPT);

cx = GetSystemMetrics(SM_CXSCREEN);
cy = GetSystemMetrics(SM_CYSCREEN);

memDC = CreateCompatibleDC(hdc);
m_Bitmap = CreateCompatibleBitmap(hdc, cx, cy);
SelectObject ( memDC, m_Bitmap );

 StretchBlt(  memDC,
                    0, 0,
                    cx, cy,                    // qt 해상도 width, height
                    hdc,
                    0, 0,
                    cx, cy,                   // cx -> width, cy -> height
                    SRCCOPY);
}

   // Retrieve the bitmap color format, width, and height.
   if (!GetObject(m_Bitmap, sizeof(BITMAP), (LPSTR)&bmp))

   // Convert the color format to a count of bits.
   cClrBits = (WORD)(bmp.bmPlanes * bmp.bmBitsPixel);
   if (cClrBits == 1)
       cClrBits = 1;
   else if (cClrBits <= 4)
       cClrBits = 4;
   else if (cClrBits <= 8)
       cClrBits = 8;
   else if (cClrBits <= 16)
       cClrBits = 16;
   else if (cClrBits <= 24)
       cClrBits = 24;
   else cClrBits = 32;

   // Allocate memory for the BITMAPINFO structure. (This structure
   // contains a BITMAPINFOHEADER structure and an array of RGBQUAD
   // data structures.)

    if (cClrBits != 24)
        pbmi = (PBITMAPINFO) GlobalAlloc(LPTR,
                   sizeof(BITMAPINFOHEADER) +
                   sizeof(RGBQUAD) * (1<< cClrBits));

    // There is no RGBQUAD array for the 24-bit-per-pixel format.

    else
        pbmi = (PBITMAPINFO) GlobalAlloc(LPTR,
                   sizeof(BITMAPINFOHEADER));

   // Initialize the fields in the BITMAPINFO structure.

   pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
   pbmi->bmiHeader.biWidth = bmp.bmWidth;
   pbmi->bmiHeader.biHeight = bmp.bmHeight;
   pbmi->bmiHeader.biPlanes = bmp.bmPlanes;
   pbmi->bmiHeader.biBitCount = bmp.bmBitsPixel;
   if (cClrBits < 24)
       pbmi->bmiHeader.biClrUsed = (1<<cClrBits);

   // If the bitmap is not compressed, set the BI_RGB flag.
   pbmi->bmiHeader.biCompression = BI_RGB;

   // Compute the number of bytes in the array of color
   // indices and store the result in biSizeImage.
   // For Windows NT, the width must be DWORD aligned unless
   // the bitmap is RLE compressed. This example shows this.
   // For Windows 95/98/Me, the width must be WORD aligned unless the
   // bitmap is RLE compressed.
   pbmi->bmiHeader.biSizeImage = ((pbmi->bmiHeader.biWidth * cClrBits +31) & ~31) /8
                                 * pbmi->bmiHeader.biHeight;
   // Set biClrImportant to 0, indicating that all of the
   // device colors are important.
   pbmi->bmiHeader.biClrImportant = 0;

HANDLE  hDib = DDBToDIB(m_Bitmap, BI_RGB);
JpegFromDib(hDib, 75, "test.jpg", " ");
GlobalFree(hDib);

// CreateBMPFile(hPPT, "test.bmp",  pbmi, m_Bitmap, memDC) ;
}

HANDLE DDBToDIB(HBITMAP bitmap, DWORD dwCompression)
{
       // 리턴되는 HANDLE에는
       // "비트맵 정보 -> 팔레트 정보 -> 비트맵 데이터"의 순서로
       // 데이터가 저장된다.
       // 256색 이상의 비트맵은 팔레트 정보가 없다.

       BITMAP                  bm;
       BITMAPINFOHEADER        bi;
       LPBITMAPINFOHEADER      lpbi;
       DWORD                   dwLen;
       HANDLE                  hDIB;
       HANDLE                  handle;
       HDC                     hDC;
       HPALETTE                hPal;

       // The function has no arg for bitfields
       if( dwCompression == BI_BITFIELDS ) return NULL;

       // 팔레트가 없는 경우 디폴트 팔레트를 사용
     
       if (hPal == NULL)
               hPal = (HPALETTE)GetStockObject(DEFAULT_PALETTE);
 

       // 비트맵 정보를 얻어온다.
      GetObject(bitmap, sizeof(BITMAP), (LPSTR)&bm);

       // Initialize the bitmapinfoheader
       bi.biSize               = sizeof(BITMAPINFOHEADER);
       bi.biWidth              = bm.bmWidth;
       bi.biHeight             = bm.bmHeight;
       bi.biPlanes             = 1;
       bi.biBitCount           = bm.bmPlanes * bm.bmBitsPixel;
       bi.biCompression        = dwCompression;
       bi.biSizeImage          = 0;
       bi.biXPelsPerMeter      = 0;
       bi.biYPelsPerMeter      = 0;
       bi.biClrUsed            = 0;
       bi.biClrImportant       = 0;

       // 256색 이하일 경우에만 색상의 수가 의미가 있다.
       // 즉, 256색 이하일 경우에만 팔레트가 주어진다.
       int nColors = 0;
       if(bi.biBitCount <= 8) nColors = (1 << bi.biBitCount);
       
       // 헤더의 크기
       dwLen = bi.biSize + nColors * sizeof(RGBQUAD);

       // We need a device context to get the DIB from
       hDC = ::GetDC(NULL); // Screem DC를 얻음
       // Screen DC에 팔레트를 설정
       hPal = SelectPalette(hDC, hPal, FALSE);
       RealizePalette(hDC);

       // BITMAPINFOHEADER와 팔레트를 위한 메모리 할당
       hDIB = GlobalAlloc(GMEM_FIXED, dwLen);

       if (!hDIB){ // 메모리 할당 실패
               SelectPalette(hDC, hPal, FALSE); // 팔레트 복귀
               ::ReleaseDC(NULL, hDC);
               return NULL;
       }

       lpbi = (LPBITMAPINFOHEADER)hDIB;
       *lpbi = bi;

       // 구조체에 정보를 얻어오기 위해 데이터를 위한 버퍼를 NULL로 준다.
       // 특히 이미지 크기를 나타내는 biSizeImage를 얻어온다.
       GetDIBits(
               hDC,                             // DC 핸들
               bitmap,                          // 비트맵 핸들
               0L,                              // start scan line
               (DWORD)bi.biHeight,              // 줄 수
               (LPBYTE)NULL,                    // 비트맵 데이터를 위한 버퍼
               (LPBITMAPINFO)lpbi,              // 비트맵 정보 구조체에 대한 포인터
               (DWORD)DIB_RGB_COLORS            // 색상 정보
       );

       bi = *lpbi;

       // If the driver did not fill in the biSizeImage field, then compute it
       // Each scan line of the image is aligned on a DWORD (32bit) boundary
       if (bi.biSizeImage == 0){
               bi.biSizeImage = ((((bi.biWidth * bi.biBitCount) + 31) & ~31) / 8)
                       * bi.biHeight;

               // If a compression scheme is used the result may infact be larger
               // Increase the size to account for this.
               if (dwCompression != BI_RGB)
                       bi.biSizeImage = (bi.biSizeImage * 3) / 2;
       }

       // 비트맵 데이터를 저장하기 위해 메모리를 재할당 한다.
       dwLen += bi.biSizeImage;
       if (handle = GlobalReAlloc(hDIB, dwLen, GMEM_MOVEABLE))
               hDIB = handle;
       else{
               GlobalFree(hDIB);

               // Reselect the original palette
               SelectPalette(hDC,hPal,FALSE);
               ::ReleaseDC(NULL,hDC);
               return NULL;
       }

       // 실제 비트맵의 데이터를 얻어온다.
       lpbi = (LPBITMAPINFOHEADER)hDIB;
       BOOL bGotBits = GetDIBits(
               hDC,                             // DC 핸들
               bitmap,                          // 비트맵 핸들
               0L,                              // start scan line
               (DWORD)bi.biHeight,              // 줄 수
               (LPBYTE)lpbi + (bi.biSize + nColors * sizeof(RGBQUAD)),
                                                // 비트맵 데이터를 위한 버퍼
               (LPBITMAPINFO)lpbi,              // 비트맵 정보 구조체에 대한 포인터
               (DWORD)DIB_RGB_COLORS            // 색상 정보
       );

       if( !bGotBits )
       {
               GlobalFree(hDIB);
               
               SelectPalette(hDC,hPal,FALSE);
               ::ReleaseDC(NULL,hDC);
               return NULL;
       }

       // 팔레트를 복귀
       SelectPalette(hDC,hPal,FALSE);
       ::ReleaseDC(NULL,hDC);

       return hDIB;
       // hDIB에 대해서는 호출한 쪽에서 메모리를 해제해 주어야 한다.
       // GlobalFree(/* HANDLE */ hDIB);
}



void CreateBMPFile(HWND hwnd, LPTSTR pszFile, PBITMAPINFO pbi,
                 HBITMAP hBMP, HDC hDC)
{
   HANDLE hf;                 // file handle
   BITMAPFILEHEADER hdr;       // bitmap file-header
   PBITMAPINFOHEADER pbih;     // bitmap info-header
   LPBYTE lpBits;              // memory pointer
   DWORD dwTotal;              // total count of bytes
   DWORD cb;                   // incremental count of bytes
   BYTE *hp;                   // byte pointer
   DWORD dwTmp;
HANDLE                  hDIB;


   pbih = (PBITMAPINFOHEADER) pbi;
   lpBits = (LPBYTE) GlobalAlloc(GMEM_FIXED, pbih->biSizeImage);

   if (!lpBits)
        errhandler("GlobalAlloc", hwnd);

   // Retrieve the color table (RGBQUAD array) and the bits
   // (array of palette indices) from the DIB.
   if (!GetDIBits(hDC, hBMP, 0, (WORD) pbih->biHeight, lpBits, pbi,
       DIB_RGB_COLORS))
   {
       errhandler("GetDIBits", hwnd);
   }

   // Create the .BMP file.
   hf = CreateFile(pszFile,
                  GENERIC_READ | GENERIC_WRITE,
                  (DWORD) 0,
                   NULL,
                  CREATE_ALWAYS,
                  FILE_ATTRIBUTE_NORMAL,
                  (HANDLE) NULL);
   if (hf == INVALID_HANDLE_VALUE)
       errhandler("CreateFile", hwnd);
   hdr.bfType = 0x4d42;        // 0x42 = "B" 0x4d = "M"
   // Compute the size of the entire file.
   hdr.bfSize = (DWORD) (sizeof(BITMAPFILEHEADER) +
                pbih->biSize + pbih->biClrUsed
                * sizeof(RGBQUAD) + pbih->biSizeImage);
   hdr.bfReserved1 = 0;
   hdr.bfReserved2 = 0;

   // Compute the offset to the array of color indices.
   hdr.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) +
                   pbih->biSize + pbih->biClrUsed
                   * sizeof (RGBQUAD);

   // Copy the BITMAPFILEHEADER into the .BMP file.
   if (!WriteFile(hf, (LPVOID) &hdr, sizeof(BITMAPFILEHEADER),
       (LPDWORD) &dwTmp,  NULL))
   {
      errhandler("WriteFile", hwnd);
   }

   // Copy the BITMAPINFOHEADER and RGBQUAD array into the file.
   if (!WriteFile(hf, (LPVOID) pbih, sizeof(BITMAPINFOHEADER)
                 + pbih->biClrUsed * sizeof (RGBQUAD),
                 (LPDWORD) &dwTmp, ( NULL)) )
       errhandler("WriteFile", hwnd);

   // Copy the array of color indices into the .BMP file.
   dwTotal = cb = pbih->biSizeImage;
   hp = lpBits;
   if (!WriteFile(hf, (LPSTR) hp, (int) cb, (LPDWORD) &dwTmp,NULL))
          errhandler("WriteFile", hwnd);

   // Close the .BMP file.
   if (!CloseHandle(hf))
          errhandler("CloseHandle", hwnd);

   // Free memory.
   GlobalFree((HGLOBAL)lpBits);
}

// 윈도우로 짜면 윈도우 화면에 에러를 보여줄 예정
void errhandler(LPTSTR pszFile, HWND hwnd)
{
 printf("pszFile");
}

'프로그래밍 > MFC' 카테고리의 다른 글

MFC 디렉토리 생성  (0) 2013.08.14
쓰레드 생성 (펌글)  (0) 2013.08.14
쓰레드 생성.  (0) 2013.08.14
유니코드  (0) 2013.08.14
CEvent 클래스  (0) 2013.08.14












리눅스 커널을 보던 중 Container_of 라는 매크로라는 녀석을 자주 보게 된다.

container_of - cast a member of a structure out to the containing structure

  
SYNOPSIS
container_of  (ptr, type, member);

ARGUMENTS

ptr
the pointer to the member.

type
the type of the container struct this is embedded in.

member
the name of the member within the struct.

하는 역활은 한 구조체의 멤버만 알고 있을 경우 그 구조체의 시작 주소를 구할 때 사용한다. 예를 들어 다음과 같은 경우에 사용된다.

struct tagTEMP
{
   int X;
   int Y;

  struct list *next;
};

void calc(struct list *node )
{
   struct tagTEMP *pTEMP;

  pTEMP = container_of(next, struct tagTEMP, next );
}

'프로그래밍 > 리눅스 드라이버' 카테고리의 다른 글

쓰레드 동기화(synchronization)  (0) 2013.08.14
make 간단 설명  (0) 2013.08.14
GCC 옵션 간단하게 정리하기  (0) 2013.08.14
모듈 프로그래밍 #2  (0) 2013.08.14
모듈 프로그래밍 #1  (0) 2013.08.14

동기화가 뭔지에 대해선 설명하지 않겠다. 리눅스에서 쓰레드 동기화를 위해 사용하는 mutex 와 semaphore 에 대해서 간략한 사용방법을 설명한다.

1. 뮤텍스(mutex)

리눅스에서 사용하는 mutex는 pthread에서 사용하는 mutex를 사용한다. 이 pthread mutex는 프로세스간 동기화에도 사용할 수는 있으나 리눅스에서는 잘 사용하지 않고 쓰레드간 동기화에 사용한다. 이 mutex 관련 함수는 다음과 같다.

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr );
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

 

pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr )

mutex 를 생성하고 초기화 한다.

pthread_mutexattr_t : mutex 생성시 속성 값으로 { FAST, RECURSIVE, ERROR CHECKING } 이 있으며 보통 NULL 을 준다.

또한 pthread_mutex_init 함수를 사용하지 않고 상수들을 이용하여 정적으로 초기화 할 수 있다.

pthread_mutex_t   mutex_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t   mutex_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP ;
pthread_mutex_t   mutex_lock = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;

 

 

pthread_mutex_lock(pthread_mutex_t *mutex)

lock을 실행한다. 이미 다른곳에서 lock이 된 경우 , unlock 될 때까지 블록킹 된다.

 

pthread_mutex_trylock(pthread_mutex_t *mutex)

lock을 실행한다. 이미 다른곳에서 lock이 된경우, EBUSY를 리턴 한다.

 

 

pthread_mutex_unlock(pthread_mutex_t *mutex)

unlock을 실행한다.

 

pthread_mutex_destroy(pthread_mutex_t *mutex)

mutex를 삭제한다.

 

 

2. 세마포어(semaphore)

mutex와 유사하지만, mutex는 하나의 접근만 허용하지만 semaphore는 여러개의 접근도 허용하게 해준다. 또한 semaphore는 프로세스 , 쓰레드 에서 사용 될 수 있으며, SYSTEM V, POSIX 로 구분 된다.

 

2.1 SYSTEM V

SYSTEM V 는 전통적 semaphore 방식이다.

int semget(key_t key, int nsems, int semflag);
int semctl(int semid, int semnum, int cmd, union semun arg);
int semop(int semid, struct sembuf *sops, size_t nsops);

 

int semget(key_t key, int nseems, int semflag)

semaphore를 생성한다.

key_t : semaphore 식별 ID 로 IPC_PRIVATE (0 ) 또는 임의의 수, ftok() 함수에서 리턴하는 값으로 사용할수 있다.

nseems : semaphore 집합 갯수

semflag : IPC_CREATE 일 경우 key id 가 없으면 생성하도록 한다. IPC_EXCL 일 경우 key id가 존재 시 실패를 리턴한다.(이 경우 기존 미리 생성된 semaphore를 사용못한다)

 

semctl(int semid, int semnum, int cmd, union semun arg )

semaphore를 제어한다.

semid: semaphore 식별 ID

semnum : semaphore 집합내의 위치

cmd : 제어 명령

GETVAL : semaphore 값을 구한다.
GETPID : semaphore에 가장 최근에 접근했던 프로세서 ID 구한다.
GETNCNT : semaphore값이 증가하기를 기다리는 프로세스의 갯수
GETZCNT : semaphore값이 0이 되기를 기다리는 프로세스의 갯수
GETALL : semaphore 집합의 모든 semaphore 값을 구한다.
SETVAL : semaphore 값을 설정한다.
SETALL : semaphore 집합의 모든 semaphore 값을 설정
IPC_STAT : 정보를 구한다.
IPC_SET : semaphore 소유권과 접근허가를 설정한다.
IPC_RMID : semaphore 집합을 삭제한다.

arg: semaphore의 값을 설정하거라 값을 얻을 때 사용하는 변수

union semun{
   int                  val;
   struct   semid_ds   *buf;
   unsigned short int  *arrary;
}

 

semop(int semid, struct sembuf *sops, size_t nsops)

semaphore값을 변경하는 함수이다.

semid: semaphore 식별 ID

sops : semaphore 설정값 변경을 위한 값

struct sembuf {
    short sem_num;   semaphore  집합 번호
    short sem_op;     semaphore 증감값
    short sem_flg;     옵션(IPC_NOWAIT : 호출 시 실행 못했을 때 실패 리턴. SEM_UNDO : 프로세서 종료시 semaphore 설정을 원래 상태로 복귀 하는 옵션. 보통 이 옵션 사용) 
}

nsops : 변경하려는 갯수

 

2.2 POSIX semaphore

int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t* sem);
int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem, int *sval);
int sem_destroy(sem_t *sem);

sem_init(sem_t *sem, int pshared, unsigned int value)

semaphore 값을 value로 초기화 한다.

pshared : 0 이면 현 프로세스 에서 사용, 그외에는 여러 프로세스 에서 사용



sem_wait(sem_t *sem)

semaphore 값을 1 줄이고 , 0 일 경우 대기한다.



sem_trywait(sem_t *sem)

sem_wait와 동일한 기능을 하며 0일 경우 EAGAIN을 리턴한다.



sem_post(sem_t *sem)

semaphore 값을 1 증가 시킨다.



sem_getvalue(sem_t *sem, int *sval)

semaphore 값을 sval에 저장한다.



sem_destroy(sem_t *sem )

semaphore 삭제하고 해제한다.

+ Recent posts