레이블이 MFC인 게시물을 표시합니다. 모든 게시물 표시
레이블이 MFC인 게시물을 표시합니다. 모든 게시물 표시

2021년 1월 26일 화요일

[MFC] 코드 실행 순서

응용프로그램이 시작되기 전에 프로그램이 사용할 메모리나 기타 실행에 필요한 기본적인 요건들을 운영체제가 할들. 그러한 작업이 완료되면 운영체제는 __WinMainCRTStartup() 코드를 실행한다. 이 함수에서는 전역 객체를 생성하고 실제 WinMain()을 호출한다.

 

여기서 말하는 전역 객체는 모든 프로젝트 생성시 하나 존재하는 theApp 객체이다. 이 객체는 CWinApp 클래스를 상속받은 CSDI_SequenceApp 객체이다.

 

WinMain()은 다시 AfxWinMain() 함수를 호출하게 되는데, 이 함수는 내부적으로 AfxInInit()함수를 호출하여 MFC 프레임 워크를 초기화하고, 우리 눈에 보이는 코드들이 실행에 들어간다.

 

CSDI_SequenceApp()

- 눈에 보이지 않는 코드들에 의해서 응용 프로그램 객체는 생성될 것이며 이 시점에서 응용 프로그램 객체의 생성자가 호출된다.


CSDI_ASequenceApp::InitApplication()

- MFC에서는 더이상 사용되지 않는 멤버 함수이다.


CSDI_SequelceApp::InitInstance()

- 응용 프로그램이 초기화 되는 부분이며, 이와 관련한 코드들이 집결되는 곳이다.

ex) 응용프로그램의 설정 정보 로딩(윈도우 크기, 옵션, 스타일 등)

스플래시 윈도우 초기화 (일반적으로 출력은 CMainFrame의 OnCreate() 함수)

응용 프로그램의 중복 실행 방지

트라이얼(Trial) 버전의 기간 검사 루틴

운영체제 버전 확인 및 프로그램 실행 여부 결정

프로그램 사용자 인증 (특정 사용자만 프로그램을 실행하도록 하고 싶은 경우)

프로그램 실행 시 인자로 전달되는 매개변수 처리

 

중요 맴버 함수 :
AddDocTemplate() - 도큐먼트 템플릿 객체를 응용 프로그램 객체에 등록한다
ParseCommandLine() - 매개 변수에 따라서 기본적인 명령을 수행하기에 앞서 내용을 분석
ProcessShellCommand() - 매개 변수로 전달된 프로그램 실행 옵션이나 열어야 하는 문서를 지정한 경우, 파일의 열기 혹은 빈 문서의 생성과 같은 작업을 실행 (ex 워드 파일을 더블클릭하면 자동으로 워드가 실행되어 파일을 로딩)

 

CSDI_SequenceDoc::CSDI_SequenceDoc()

- 도큐먼트 객체의 생성자를 호출하면 응용 프로그램 객체에 이어 두 번째로 생성된다. 중요한 것은 현재까지 사용자 인터페이스와 관련한 객체, 즉 윈도우 객체에는 아무 것도 생성되지 않았다.


CMAinFrame::CmainFrame()

- 응용 프로그램 인터페이스의 틀이 되는 프레임 윈도우 객체를 생성한다. 객체가 생성되었을 뿐, 아직 아무 것도 만들어지거나 나타나는 것은 없다.


CMainFrame::LoadFrame()

- CFrameWnd 객체가 생성된 후 이 응용 프로그램 윈도우와 관련한 메뉴, 가속기, 아이콘과 같은 리소스들을 로딩하고 윈도우를 생성한다. 아직 화면에는 어무 것도 보이지 않는다.


CMainFrame::PreCreateWindow()

- 윈도우가 생성되기 직전에 호출되는 함수이다.

이 함수의 역할 중 가장 중요한 것은 생성되는 윈도우의 스타일 정의 혹은 변경이다. 기본적으로 생성되는 윈도우는 아주 흔하게 볼 수 있는 모든 요소를 가지고 있따. 이러한 사항들을 변경하고자 한다면 인자로 전달된 CREATESTRUCT 구조체의 값을 변경하면 된다.

 

CMainFrame::PreSubclassWindow()

- 서브 클래싱 직전에 호출되는 함수. 서브클래싱이랑 윈도우 프로시저 함수를 따로 두는 것을 의미한다.


CMAinFrame::OnCreate() - Call

- WM_CREATE 메시지를 받았을 때 호출되는 메시지 핸들러이다. LoadFrame() 함수가 호출되면서 윈도우가 생성되고, 그 때 WM_CREATE 메시지는 자동으로 발생한다. 이 함수에서 하는 일은 CMainFrame 객체 내에 있는 몇몇 차일드 윈도우(툴바나 상태 표시줄 등)를 생성하는 일을 한다. 이 함수 내에서 상위 클래스의 OnCreate() 함수를 명시적으로 호출함으로써, 다른 함수들이 또 다시 호출된다.
1) CMainFrame::OnCreateClient() - Call

상위 클래스의 명시적 호출로 인해 호출되는 첫 번째 함수인데, 함수의 주된 역할은 클라이언트 뷰를 생성하는 일이다.
2) CSDI_SequenceView::CSDI_SequenceView()

클라이언트 뷰의 생성자가 호출된다.
3) CSDI_Sequence::Create() - Call

클라이언트의 윈도우를 생성한다. 그리고 이 함수의 호출결과로 아래 네 개의 함수들이 추가로 호출된다.
CSDI_Sequence::PreCreateWindow()

앞서 프레임 윈도우 생성시의 함수와 같은 역할이다.
CSDI_Sequence::PreSubclassWindow()

앞서 프레임 윈도우 생성시의 함수와 같은 역할이다.
CSDI_Sequence::OnCreate() - Called

클라이언트 뷰에 WM_CREATE 메시지가 전달되고, 그 메시지 헨들러가 호출된 것이다. 결국 각각의 윈도우는 자기만의 메시지 핸들러를 두게 되며, 자신에게 해당되는 메시지를 받아 처리한다.
CSDI_Sequence::OnCreate() - Return
CSDI_Sequence::OnShowWindow() - Call
윈도우가 화면에 나타나거나 사라질 때 호출된다. 좀 더 정확히는 WM_SHOWWINDOW메시지에 대한 핸들러이다.

CSDI_Sequence::OnShowWindow() - Return


CSDI_Sequence::Create() - Return

- 클라이언트 뷰 윈도우의 생성이 완료되었다.


CMainFrame::OnCreateClient() - Return

- 프레임 윈도우는 클라이언트 뷰를 생성하는 작업을 끝냈다.


CMAinFrame - m_wndToolBar.CreateEx
CMainFrame - m_wndStatusBar.Create
CMainFrame::OnCreate() - Return

 

CSDI_Sequence::OnNewDocument() - Call

- 현재 프로그램이 실행될 때 엑셀이나 워드처럼 응용 프로그램과 연결된 데이터 파일을 오픈한 경우가 아니므로 빈 문서, 즉 새 문서를 연다. 관련된 것이 없다고 하더라도 SDI구조를 가졌기 때문에 내부적으로 문서를 만든다.

CSDI_Sequence::OnNewDocument() - Return


CSDI_Sequence::OnInitialUpdate() - Call

- 클라이언트 뷰의 생성이 완료되면 응용 프로그램의 프레임 윈도우는 자신의 클라이언트 뷰에 WM_INITIALUPDATE 메시지를 보내게 되고, 그 메시지를 받은 클라이언트 뷰는 이 메시지 핸들러를 호출한다.이 함수는 매우 중요하다. 이 메시지 핸들러는 새로운 문서가 열릴 때마다 호출된다. 그러나 응용 프로그램과 연결된 문서가 존재하지 않고 파일을 열 일이 없다면, 딱 한번만 수행될 것이다.
CSDI_Sequence::OnInitialUpdate() - Return

 

* 초기화 완료 ==============================

CMainFrame::OnActivateApp() - Call
CMainFrame::OnActivateApp() - Return
CMainFrame::OnActivate() - Call
CMainFrame::OnActivate() - Return

초기화가 완료된 후, 응용 프로그램은 활성화되어 모니터 화면의 맨 위로 나타나게 될 것이다. 두 함수는 화면에 응용 프로그램이 나타나는 것 까지로 보면 된다.


CMainFrame::OnShowWindow() - Call
CMainFrame::OnShowWindow() - Return

응용 프로그램이 활성화되고 프레임 윈도우가 화면에 나타나게 된다. 작업 표시줄에 자신의 항목도 하나 생겼을 것이고 모니터 화면에서 맨 위에 보여질 것이다.


### CSDI_SequenceApp::Run() - Call ###

메시지 루프를 시작하는 함수이다. 결국 이 함수는 내부적으로 ::PeekMessage() 함수를 호출하여 자신에게 메시지가 있는지 검사를 한다. 만일 처리할 메시지가 없다면 사용자가 입력을 받거나 혹은 마우스 이벤트가 발생한다거나 하는 일이 없다는 것이다.

 

하나의 메시지를 처리하는 세 가지 방법이 있다. 첫 번째는 CMainFrame의 WindowProc() 함수에 코드를 추가하는 것이고, 두 번째는 CMainFrame의 OnCreate() 메시지 핸들러 아래에 코드를 추가하는 것이다. 마지막으로 CMainFrame의 PreTranslateMessage() 멤버 함수에 코드를 추가하는 방법이다.  이 함수는  TranslateMessage() 함수가 호출되기 전에 호출된다. 즉 메시지 큐에서 메시지가 꺼내지고 해석되기 전에 호출되는 함수이다.

 

* 응용 프로그램 종료 ==============================

CMainFrame::OnClose() - Call

- 닫기 혹은 Alt+F4를 입력하면 WM_CLOSE 메시지가 발생한다. OnClose() 함수는 WM_CLOSE 메시지에 대한 핸들러이다. 이 함수가 리턴하기 전에 내부적으로 아래의 함수가 이어서 호출된다.
CMainFrame::OnShowWindow() - Call
CMainFrame::OnShowWindow() - Return
CMainFrame::OnActivate() - Call
CMainFrame::OnActivate() - Return
CMainFrame::OnActivateApp() - Call
CMainFrame::OnActivateApp() - Return

자원의 반납이나 차일드 윈도우의 내용을 저장하는 것 정도의 코딩을 한다.


CMainFrame::DestroyWindow() - Call

OnClose() 함수는 실제로 윈도우를 파괴하기 위해서 이 함수를 호출하게 되는데 이 함수가 호출되고 나면 윈도우가 파괴되므로 그 전에 차일드 윈도우를 모두 파괴하여야 한다. 이를 알리기 위해서 내부적으로 WM_DESTROY와 WM_NCDESTROY 메시지가 연이어 전달된다. WM_DESTROY는 윈도우 전체가 파괴되는 신호라면 WM_NCDESTROY는 프레임의 테두리가 파괴되는 신호이다.


CMainFrame::OnDestroy() - Call
CMainFrame::OnDestroy() - Return
CSDI_Sequence::OnDestroy() - Call
CSDI_Sequence::OnDestroy() - Return
CSDI_Sequence::PostNcDestroy() - Call
CSDI_SequenceView::~CSDI_SequenceView()
CSDI_Sequence::PostNcDestroy() - Return
CMainFrame::PostNcDestroy() - Call
CMAinFrame::~CmainFrame()
CMainFrame::PostNcDestroy() - Return


CMainFrame::DestroyWindow() - Return

응용 프로그램의 최상위 부모 윈도우인 메인 프레임 윈도우가 완전히 파괴되었다.
CSDI_SequenceDoc::~CSDI_SequenceDoc()

화면상에 나타나는 사용자 인터페이스, 즉 윈도우 객체들이 모두 파괴된 후 관련된 도큐먼트 객체가 소멸된다.

 

CMainFrame::OnClose() - Return

- 클라이언트 뷰, 도큐먼트 객체 뿐만 아니라, 자신도 이미 파괴되었다. 이제 남은 것은 응용프로그램이 사용한 자원들을 반납하는 것 뿐이다.


CSDI_ASequenceApp::ExitInstance()

- 모든 객체들이 소멸한 가운데, 응용 프로그램의 최종 종료에 앞서서 마지막으로 호출되는 함수이다. 따라서 이 함수에는 응용프로그램의 설정을 저장한다거나, 중복 실행 방지를 위해 획득했던 시스템 리소스를 반납한다거나, 메모리를 해제하는 등 최종 마무리 작업을 해야 한다.


### CSDI_SequenceApp::Run() - Call ###
- Run()의 리턴은 메시지 루프의 종료를 의미하므로 프로그램이 최종 종료되는 것이다. 이 과정에서 우리 눈에 보이지 않는 코드가 실행되는데, AfcWinInit와 반대로 AfxWinTerm() 이라는 함수가 수행되어 초기화했던 MFC 라이브러리를 해제한다. 

2021년 1월 15일 금요일

헝가리안 표기법(Hungarian Notation)

10, 15년전 Microsoft의 개발자 중 헝가리 사람의 프로그래머가 쓰던 변수 명명법.

MS 내부에서 따라 쓰기 시작하던 것이 점차 전세계의 프로그래머들에게 널리 퍼져 이젠 프로그램 코딩시 변수 명명의 표준적인 관례가 되었다. 그러나 실제로 현장에서 일하다 보면 헝가리안 표기법을 제대로 지키는 개발자는 그리 많지 않다. 어느 정도 개발 경험을 가지고 있는 프로그래머는 물론 심지어 시중의 프로그래밍 서적에서 조차 저자마다 변수명을 개인에 따라 가지각색으로 짓고 있어서 처음 프로그램을 배우는 입문자들이 변수 명명에 대한 기준을 제대로 잡지 못하고 있는 실정이다.

그러나 변수 명명에 관한 표준화된 관례를 지켜주면 코드의 가독성을 높여줄 뿐만 아니라 예를 들어 카운터 변수를 count라고 지을지 cnt라고 지을지 고민하지 않아도 되는 편리함을 누릴 수 있다.

Prefix

Data Type

Description

Example

b

BOOL

any boolean type

BOOL bTrue;

c

char

character type

char cLetter;

i

int

integer for index

int iCars;

n

int

number, quantity

int nNum;

l

long

long type

long lDistance;

u

unsigned

unsigned type

unsigned uPercent

f

float

floating point

float fPercent;

d

double

double floating point

double dPercent;

w

WORD

unsigned word

WORD wCnt

dw

DWORD

unsigned double word

DWORD dwLength

p

*

any pointer

int *piAddr;

pfn

*

function pointer

int (*pifnFunc1)(int x, int y);

rg, a

array

stands for range

float rgfTemp[16];

sz

*

Zero-terminated string of characters

char szText[16];

s

static

a static variable

static short ssChoice;

t

struct

a user defined type

 

e

enum

variable which takes enumerated values

 

E

enum

Enumerated type

 

g_

Global

Global Variable

String *g_psBuffer;

m_

Member

class private member variable

int m_iMember;

k

constant formal parameter

...

void vFunc(const long klGalaxies)

r

reference formal parameter

...

void vFunc(long &rlGalaxies)

prg

...

dynamically allocated array

char *prgGrades;

v

Void

 

 

x/y

...

used as size

int xWitdth, yHeight;

h

handle

handle to something

hMenu

 

 

Format

x_xXxxxxxx

0123456789

0 : 변수의 위치를 지정한다. g(전역변수), m(멤버변수), 없음(지역변수)
1 : 0 위치에 g 나 m 을 지정한 경우 _ 을 기술한다.
2 : 자료형의 종류를 나타낸다.

3 ~ : 변수의 의미 있는 이름을 기술하며, 3 위치는 대문자를 사용한다. 변수 이름이 너무 긴 경우 자음만을 기술한다. 예) g_nCnt

 

Example of type specific variable naming

int g_nCnt                              :정수형 글로벌 카운터
unsigned char ucByte;            :한 바이트 데이타 
         char cChar;                  :한 문자 
unsigned char rgucByte[10];   :바이트 데이타 10개 
         char rgcChar[10];         :문자 데이터 10개
         char szChar[16 +1];      :문자 16개를 저장할 수 있는 문자열 공간

2016년 4월 28일 목요일

[MFC] Thread(쓰레드)에서 Dialog의 UpdateData()사용하는 방법

다이얼로그 사용시특정동작을 실행하면 특정동작이 돌아가는 동안 다이얼로그가 먹통이 됩니다. 이를 해결하기 위해 쓰레드를 사용하게되는데 이때 쓰레드 안에서 바로 UpdateData함수를 사용하면 에러가 발생합니다. 이를 해결하기 위해 윈도우에 메세지로 UpdateData를보내서 다이얼로그의 변경값을 실시간으로 변경하게 할 수 있습니다.

-- 소스 -- 
header 부분에 추가
#define THREAD_UPDATE ( WM_USER +1 )  //메세지 값으로 WM_USER +1 이값이 아니어도 상관은 없습니다.

*Thread가 돌아가며 사용하는 UpdateData함수의 모든 Dlg 클래스에 추가해야 한다.
해당 Dlg의 h부분에 추가
class ThreadDlg 
{
...
afx_msg LRESULT OnThreadUpdate(WPARAM w, LPARAM l); //USER_MSG_TEST 메시지를 받아 실행하게되는 함수 정의이다. 함수형과 이름은 바꿔도 상관없지만 변수부분은 그대로 유지하도록 한다.
...
}

ThreadDlg 의 cpp 부분에 추가
BEGIN_MESSAGE_MAP(CCMDLLDlg , CDialog)
...
 ON_MESSAGE (THREAD_UPDATE , OnThreadUpdate)  //여기에 메시지 ID 값과 함수 이름을 넣는다. 해당 ID로 메시지가 오면 해당 함수을 실행하게 된다.
...
END_MESSAGE_MAP()
 
메시지로 실행될 함수부분 추가
//헤더파일에 정의된 함수를 작성한다.
LRESULT ThreadDlg::OnThreadUpdate(WPARAM w, LPARAM l)
{
  UpdateData(TRUE);  
//이 부분에서 다이얼로그를 업데이트한다.
  return 0;
}

실재 쓰레드 안에서는 해당 다이얼로그의 포인터를 이용하여
m_dlg->UpdateData(TRUE);
하던 것을
m_dlg->PostMessage(THREAD_UPDATE ,0,0); //뒤의 두값이 메시지로 넘어가는 변수 w와 l이다. 
또는
m_dlg->SendMessage(THREAD_UPDATE ,0,0);
위의 PostMessage와 SendMessage는 실행시간에 약간의 차이가 있어 PostMessage를 사용할 경우 정상동작이 이루어 지지 않을 수 있다. PostMessage() 의 경우 Message 처리 결과를 기다리지 않고 복귀하고, SendMessage() 의 경우 처리를 완료한 후에 다음 진행이 이루어 진다.

위의 함수를 멤버함수로 만들어 간편하게 사용이 가능하다.
void ThreadDlg::myUpdateData(BOOL enable)
{
  SendMessage(THREAD_UPDATE ,enable,0);

}
LRESULT ThreadDlg::OnThreadUpdate(WPARAM w, LPARAM l)
{
  UpdateData(w);  
//이 부분에서 다이얼로그를 업데이트한다.
  return 0;
}



2010년 7월 29일 목요일

멤버 함수를 쓰레드 함수로 만들기

오늘은 간단한 C++ 프로그래밍 기법에 대해 하나 써볼까 합니다.

간단한 기교를 부려볼 겸 클래스의 멤버함수를 쓰레드 함수로 작성하는 방법을 배워 보도록 하겠습니다.
(간단히 쓰레드 사용법도 배우고 일석이조! 야호! )

아직까진 그런 적은 없지만, 쓰레드를 돌릴 때 간간히 멤버함수를 쓰레드 함수로 제작하고플 때가 있더군요.
혹, 그런 분들을 위해 알려 드리겠습니다.
 
 class TestClass {

     INT B;

     VOID TestFunction( INT A ) { B = A };                       //이 함수를 쓰레드로 돌리고자 합니다.
     VOID Start();                                                          //이 함수에서 TestFunction을 호출하지요.

};

 
 Start() 멤버 함수에서 TestFunction()을 쓰레드로 돌리고자 한다고 해보죠.

 VOID TestClass ::Start() {

       HANDLE hThread = ( HANDLE ) __beginthread( NULL, 0, &TestFunction, NULL, NULL, NULL );
       // ........ 무엇가의 작업을 하고.

       WaitForSingleObject( hThread, INFINITE );
       CloseHandle( hThread );
}

  
물론 안됩니다. 쓰레드로 돌아갈  함수는 정적으로 선언되어야 하며 정적 함수여야 하죠. 따라서,
 
 class TestClass {

     static VOID WINAPI TestFunction( INT A );                    // 이제 원래 의도대로 이 함수를 정적 함수로 제작합니다.
     VOID Start();

};

  역시 물론 안됩니다. 직접 닥쳐보시면 알겠지만, 우리가 돌리고자 했던 TestFunction 함수에서는 B라는 비정적 멤버를 참조하고 있습니다.

결국은 다른 방법을 찾아야 합니다.

 class TestClass {

     VOID TestFunction( INT A );                                             //다시 원래대로 돌리고
     static VOID WINAPI TestFunctionThread( LPVOID );            //대신 쓰레드가 돌아갈 정적 함수를 만듭니다.
     VOID Start();

};

VOID Start() {

       HANDLE hThread = ( HANDLE ) __beginthread( NULL, 0, &TestFunctionThread, this, NULL, NULL );
       // ........ 무엇가의 작업을 하고.

       WaitForSingleObject( hThread, INFINITE );
       CloseHandle( hThread );

}

VOID WINAPI TestFunctionThread( LPVOID p ) {
      (  (TestClass* ) p )->TestFunction( 3 );
}


자, 위 예를 통해 해결했습니다. 포인터. 즉 동적 호출을 통해서 정적 멤버 함수를 호출하였습니다.

TestFunction의 인자 값도 동적으로 주고 싶다면,
 
 struct Arg {
      TestClass* p;
       INT A;
};

VOID Start() {

        Arg arg = { this, 3 };

       HANDLE hThread = ( HANDLE ) __beginthread( NULL, 0, &TestFunctionThread, &arg, NULL, NULL );
       // ........ 무엇가의 작업을 하고.

       WaitForSingleObject( hThread, INFINITE );
       CloseHandle( hThread );

}

VOID WINAPI TestFunctionThread( LPVOID p ) {
      Arg* pArg = ( Arg* ) p;
      pArg->p->TestFunction( p->A );     
}
 



2010년 4월 15일 목요일

삼항연산자 사용

삼항연산자

 

삼항연산자는 연산을 위해서 3개의 항이 필요함을 의미하는 것으로
if 문을 축소시킨 형태가 있다. 예1) 과 예2)는 같은 결과를 나타내며, 같은 의미이다.

실제 속도에 있어서는 예1)이 미미하지만 더 빠르게 실행이 된다. 하지만,

코드가 알아보기 어려운 점이 좀 있다... 눈에  잘 안들어오는..

 

 

예1)

    var a = 10,  b = 11;
    var str = a > b ? "a가 b보다 크다" : "a가 b보다 작다";
    trace(str);

 

 

첫번째 프레임에 위 소스를 넣고 확인해보면 된다.

a 와 b 의 값을 바꾸어서 실행해보기도 한다.

 

a>b 가 만족하면 : 의 앞에것을 실행하고 만족하지 않으면 후자를 실행하라는 의미이다.

이것은 if, else 문과 같다.

 

예2)

var a = 10,  b = 11;

 

if(a > b){

    str = "a가 b보다 크다";

}else{

    str = "b가 a보다 크다";

}

    trace(str); 

2010년 3월 30일 화요일

MFC에서의 멀티쓰레드(Multithread)

MFC에서의 Multithread
 
OS는 구분하지 않지만 MFC는 사용자 편의를 위하여 두 가지 형태로 지원
 
1.     Worker thread
2.     User Interface thread
 
Worker thread
 
::AfxBeginThread() 함수를 이용
 
CWinThread* ::AfxBeginThread(
       AFX_THREADPROC pfnThreadProc,
       LPVOID pParam,
       int nPriority = THREAD_PRIORITY_NORMAL, // 기본적으로 주 Process와 동일
       UINT nStackSize = 0,
       DWORD dwCreateFlags = 0,                      // 0 또는 CREATE_SUSPENDED
       LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);
 
우선 Thread를 이용할 함수를 먼저 정의한다
 
UINT ThreadFunc(LPVOID pParam)
{
       int option = (int)pParam;
       …
}
 
만약 인자가 많은 경우에는
 
typedef struct tagPARAMS
{
       ...
} PARAMS;
 
와 같이 한 후에
 
PARAMS *pParams = new PARAMS;
CWinThread *pThread = ::AfxBeginThread(ThreadFunc, &pParams);
 
와 같이 하고
 
UINT ThreadFunc(LPVOID pParam)
{
       PARAMS *pThreadParams = (PARAMS *)pParam;
       ...
       delete pThreadParams;
 
       return 0;
}
 
와 같이 사용하면 된다.
 
Thread를 잠시 중지시키고 싶을 때는 주 Process에서
 
pThread->SuspendThread();
 
다시 돌리고 싶을 때는 주 Process에서
 
pThread->ResumeThread();
 
와 같이 하면 된다.(Thread 자신이 호출할 수는 없다.)
또는 경우에 따라서는
 
Sleep(2000);
 
과 같이 사용할 수도 있는데 이 경우는 제어권을 다른 Process에 넘겨 주게 된다.
 
Sleep(0);
 
와 같이 할 경우에는 우선 순위가 높거나 같은 Process에 넘겨 주고 우선 순위가 높거나
같은 Process가 없을 경우에는 아무 일도 생기지 않는다.
 
Thread를 종료시키고 싶을 때는 TerminateThread() 함수를 사용하면 되는데 이 경우 Thread 함수가
내부 정리를 하지 못할 수가 있기 때문에 다음과 같은 방법이 많이 사용된다.
 
static BOOL bContinue = TRUE;
CWinThread *pThread = ::AfxBeginThread(ThreadFunc, &bContinue);

 
UINT ThreadPrintNum(LPVOID pParam)
{
       BOOL *pbContinue = (BOOL *)pParam;
       while ( *pbContinue )
       {
             …
       }
       return 0;
}
 
와 같이 하고 bContinue 값을 FALSE로 하면 Thread 함수가 종료된다.
 
Thread가 완전히 종료된 것을 확신해야 하는 경우에는
 
if ( ::WaitForSingleObject(pThread->m_hThread, INFINITE) )
{
       // 이곳은쓰레드가확실히종료된상태임
}
 
와 같이 하면 된다. Thread가 죽어 버려서 먹통이 되는 경우까지 대비하려면
 
DWORD result;
result = ::WaitForSingleObject(pThread->m_hThread, 1000);   // 1초기다림
if ( result == WAIT_OBJECT_0 )
{
       // 이곳은쓰레드가확실히종료된상태임
}
else if ( result == WAIT_TIMEOUT )
{
       // 1초가지나도쓰레드가종료되지않은상태
}
 
이 방법을 사용해야 한다. 어떤 Thread가 현재 실행 중인지 알고 싶을 때는
 
if ( ::WaitForSingleObject(pThread->m_hThread, 0 ) == WAIT_TIMEOUT )
{
       // pThread 실행중
}
else
{
       // pThread가실행중이아님
}
 
와 같이 하면 된다.
 
User Interface Thread
 
User interface thread는 그 자체로 윈도우와 메시지 루프를 가지고 있다.
 
class CUIThread : public CWinThread
{
       DECLARE_DYNCREATE(CUIThread)
 
public:
       virtual BOOL InitInstance();
};
 
이 User interface thread는 독자의 윈도우도 가질 수 있다. 일반적으로 전용 Dialog를 띄워
Thread를 처리하는 경우가 많으므로 이 User Dialog를 CMyDialog라고 이름 지었다고 가정하면
 
IMPLEMENT_DYNCREATE(CUIThread, CWinThread)
 
BOOL CUIThread::InitInstance()
{
       m_pMainWnd = new CMyDialog;
       m_pMainWnd->ShowWindow(SW_SHOW);
       m_pMainWnd->UpdateWindow();
       return TRUE;
}
 
와 같이 CMyDialog를 Thread로 띄울 수 있다. 그 다음
 
CWinThread *pThread = ::AfxBeginThread(RUNTIME_CLASS(CUIThread));
 
와 같이 하면 MFC가 알아서 CUIThread를 생성해서 그 포인터를 pThread에 넘겨 준다.
 
아래 예제에는 CMyDialog를 띄우고 주 Process는 사용자의
입력을 기다린다. Dialog의 Design 및 생성은 별도로 이야기하지 않는다. 아래 예제를 사용하기 위해서는
CMyDialog를 만들고 ID를 IDD_MYDIALOG라고 가정하면 CMyDialog의 생성자에 다음과 같이 추가해야 제대로 동작한다.
 
CMyDialog::CMyDialog(CWnd* pParent /*=NULL*/)
       : CDialog(CMyDialog::IDD, pParent)
{
       Create(IDD_MYDIALOG, NULL);
}
 
이제 완전히 별도로 동작하는(Thread로 동작하는) 윈도우를 하나 가지는 것이다. 만약 이것을 Dialog가 아닌
FrameWnd라고 해도 거의 똑같다. 다만 위에서도 언급했듯이 Thread를 이용할 때는 Dialog가 더 일반적일 것이다.
Worker thread에 비해 훨씬 더 많은 기능을 하는 것을 알게 되었을 것이다.
 
나머지 것들은 위의 Worker Thread에 있는 내용과 동일하다.
 
만약 여러 개의 CUIThread 를 여러 개 만들려고 한다면
 
CWinThread *pThread[5];
for ( int i = 0; i < 5; i++ )
       m_pThread[i] = ::AfxBeginThread(RUNTIME_CLASS(CUIThread));
 
와 같이 하면 5개의 Thread가 생성된다.
 
Program Execution Priority(프로그램 실행 우선순위)
 
Thread는 0~31까지의 priority를 가질 수 있다.
 
프로그램의 priority는
 
BOOL SetPriorityClass(HANDLE hProcess, DWORD dwPriorityClass);
 
함수를 이용해서 조정할 수 있다. 첫 번째 인자 hProcess는 ::AfxGetInstanceHandle()로 얻으면 된다.
 
dwPriorityClass
Execution Priority
Description

DLE_PRIORITY_CLASS
CPU가 IDLE일 때만 사용 가능

ORMAL_PRIORITY_CLASS
보통

IGH_PRIORITY_CLASS
높은 우선 순위

EALTIME_PRIORITY_CLASS
최상위의 우선순위


Thread Execution Priority(쓰레드 실행 우선순위)
 
::AfxBeginThread() 함수의 nPriority를 통해서 설정하거나 CWinThread::SetThreadPriority 를 사용해 설정할 수 있다.
 
BOOL SetThreadPriority(HANDLE hThread, int nPriority);
 
nPriority
Execution Priority
Description

HREAD_PRIORITY_IDLE
REALTIME_PRIORITY_CLASS의 경우 16, 그 외에는 1

HREAD_PRIORITY_LOWEST
프로세스의 우선순위보다 2단계 낮은 우선순위를 가진다

HREAD_PRIORITY_BELOW_NORMAL
프로세스의 우선순위보다 1단계 낮은 우선순위를 가진다

HREAD_PRIORITY_NORMAL
프로세스의 우선순위가 같은 우선순위

HREAD_PRIORITY_ABOVE_NORMAL
프로세스의 우선순위보다 1단계 높은 우선순위를 가진다

HREAD_PRIORITY_HIGHEST
프로세스의 우선순위보다 2단계 높은 우선순위를 가진다

HREAD_PRIORITY_CRITICAL
REALTIME_PRIORITY_CLAS의 경우 31 그 외에는 16


프로그래머가 우선순위를 조정해도 Windows Scheduler가 상황에 맞게 조정하기 때문에 우선순위는
생각하고 조금 다를 수 있다.
 
Thread & Memory
 
각 Thread의 지역 변수는 모두 별도로 Stack을 만들고 Local Variable들을 관리하기 때문에 위의
 
CWinThread *pThread[5];
for ( int i = 0; i < 5; i++ )
       m_pThread[i] = ::AfxBeginThread(RUNTIME_CLASS(CUIThread));
 
와 같은 경우에도 각 Thread가 다른 Thread를 침범하는 일은 없다.
 
이 쯤에서 끝났나 싶겠지만… 아직 갈 길이 멀다.
Critical section, Mutex, Semaphore 같은 것들은 다음에…
 
Multithreading synchronization(멀티쓰레드의 동기화) => http://blog.naver.com/xtelite/50023359879
 
프로그램

Worker thread
 
#include "stdafx.h"
#include "console.h"
 
#include <iostream>
 
using namespace std;
 
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
 
// The one and only application object
CWinApp theApp;
 
using namespace std;
 
UINT ThreadPrintNum(LPVOID pParam);
 
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
       int nRetCode = 0;
 
       // initialize MFC and print and error on failure
       if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
       {
             _tprintf(_T("Fatal Error: MFC initialization failed\n"));
             return 1;
       }
 
       static BOOL bContinue = TRUE;
       CWinThread *pThread = ::AfxBeginThread(ThreadPrintNum, &bContinue);
 
       int count = 0;
       while ( count < 1000 )
       {
             count++;
       }
 
       Sleep(1000);
       pThread->SuspendThread();
       cout << "Thread suspended. Waiting for 2 seconds" << endl;
 
       Sleep(2000);
       cout << "Thread resumed" << endl;
       pThread->ResumeThread();
 
       cout << "Quit thread" << endl;
       bContinue = FALSE;
       Sleep(100);
 
       return nRetCode;
}
 
// 쓰레드함수
UINT ThreadPrintNum(LPVOID pParam)
{
       BOOL *pbContinue = (BOOL *)pParam;
       int count = 0;
       while ( *pbContinue )
       {
             count++;
       }
       cout << "Exit thread" << endl;
       return 0;
}
 
User interface thread
 
#include "stdafx.h"
#include "console.h"
 
#include "MyDialog.h"
 
#include <cstdlib>
 
using namespace std;
 
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
 
// The one and only application object
CWinApp theApp;
 
using namespace std;
 
class CUIThread : public CWinThread
{
       DECLARE_DYNCREATE(CUIThread)
 
public:
       virtual BOOL InitInstance();
};
 
IMPLEMENT_DYNCREATE(CUIThread, CWinThread)
 
BOOL CUIThread::InitInstance()
{
       m_pMainWnd = new CMyDialog;
       m_pMainWnd->ShowWindow(SW_SHOW);
       m_pMainWnd->UpdateWindow();
       return TRUE;
}
 
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
       int nRetCode = 0;
 
       // initialize MFC and print and error on failure
       if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
       {
             _tprintf(_T("Fatal Error: MFC initialization failed\n"));
             return 1;
       }
 
       CWinThread *pThread = ::AfxBeginThread(RUNTIME_CLASS(CUIThread));
      
       system("pause");
 
       return nRetCode;
}


출처 : "http://system.tistory.com/entry/멀티-쓰레딩"

2009년 9월 9일 수요일

윈도우 환경에서 시간측정하는법 - 초정밀 카운터

글은 마이크로소프트웨어 99 2월의 "이보다 더 정확할 순 없다! 윈도우 환경에서 시간측정하는법" 이란 기사에서 참고한 것입니다. ( 저작권 걸리나요? )

 

윈도우 상에서 시간을 측정하기 위한 방법에는 몇가지가 있는데, clock() 함수를 사용하거나 WM_TIMER 메시지를 사용한다거나, 일반 타이머보다 더 높은 정확도를 위해 멀티미디어 타이머를 사용할 수 있다.

WM_TIMER의 경우는 초당 클럭수가 18.3 이므로 1/18.3 , 55 ms 정도의 정확도를 가진다. 그리고 멀티미디어 타이머의 경우는 최소시간 간격이 1ms 이고, 10ms 이내의 이벤트 지연시간을 가지는 타이머 이벤트는 CPU 자원을 많이 소모하기 때문에 주의해야 한다.

앞서 설명한 멀티미디어 타이머는 최소 시간간격이 1ms, 그 이하의 시간을 측정하는데는 적합하지 않다. 더구나 CPU의 성능이 높아지면서 1ms는 무척 긴 시간이 돼 버렸다. 1ms 동안 많은 명령을 수행할 수 있기 때문에 네트웍 패킷의 전송시간이나 특정 루틴의 시간을 측정하기 위해서는 멀티미디어 타이머는 도움이 되지 않는다. 이러한 경우에 Win32 API에서 제공하는 QueryPerformanceFrequency QueryPerformanceCounter , 이 두 개의 함수가 유용하게 쓰인다.

 QueryPerformanceFrequency 1초 동안 카운터가 증가하는 값을 얻어내는데, 시스템이 이 두 함수들을 지원하지 않으면 QueryPerformanceFrequency 의 값이 0 이 되고 결과값도 0 이 돌아온다.

다음 코드는 VC++에서 카운터를 사용한 예이다.

//-----------------------------------------------------------

#include "stdafx.h"  // pre-compiled header file for VC++

#include "windows.h"

#include "stdio.h"

 

void main()

{

        LARGE_INTEGER freq, start, end;

        LARGE_INTEGER tmp;

 

        if(QueryPerformanceFrequency(&freq)) {

                QueryPerformanceCounter(&start);

                printf("Performance Frequency is %I64d\n", freq);

                QueryPerformanceCounter(&end);

                printf("Performance Resolution is %4.3f micro sec.\n",

                        1.0/freq.QuadPart * 1000000);

                printf("printf elapsed time is %4.3f micro sec.\n",

                        (double)(end.QuadPart-start.QuadPart)/freq.QuadPart*1000000);

 

                QueryPerformanceCounter(&start);

                        QueryPerformanceCounter(&tmp); // 1

                        QueryPerformanceCounter(&tmp); // 2

                        QueryPerformanceCounter(&tmp); // 3

                        QueryPerformanceCounter(&tmp); // 4

                        QueryPerformanceCounter(&tmp); // 5

                        QueryPerformanceCounter(&tmp); // 6

                        QueryPerformanceCounter(&tmp); // 7

                        QueryPerformanceCounter(&tmp); // 8

                        QueryPerformanceCounter(&tmp); // 9

                        QueryPerformanceCounter(&tmp); // 10

                QueryPerformanceCounter(&end);

 

                printf("QueryPerformance delay time is %4.3f micro sec.\n",

                        (double)(end.QuadPart-start.QuadPart)/freq.QuadPart*100000); //  /10

        } else

                printf("This computer don't support QueryPerformance function sets !!\n");

}

//--------------------------------------------------------

 Pentium II-266 , Windows 2000 professional 에서의 결과는 다음과 같다.

Performance Frequency is 3579545

Performance Resolution is 0.279 micro sec.

printf elapsed time is 801.499 micro sec.

QueryPerformance delay time is 3.771 micro sec.

 

위 코드에서 LARGE_INTEGER 에 대한 MSDN의 설명은 다음과 같다.

The LARGE_INTEGER structure is used to represent a 64-bit signed integer value.

typedef union _LARGE_INTEGER {

    struct {

        DWORD LowPart;

        LONG  HighPart;

    };

    LONGLONG QuadPart;

} LARGE_INTEGER;

 

Members

LowPart

Specifies the low-order 32 bits.

HighPart

Specifies the high-order 32 bits.

QuadPart

        Specifies a 64-bit signed integer.

====================================================================

앞에서 설명한 API 보다 좀 더 정확한 방법으로 수행 시간을 측정하기 바란다면 한 가지 방법이 있다. 이 방법은 Win32 API를 이용한 것이 아니라 어셈블리를 이용한 방법으로 인텔 펜티엄 이상에서만 수행시킬 수 있는 방법이다. 펜티엄에서 추가된 명령중에 RDTSC(Read from Time Stamp Counter) 라는 것이 있다. 펜티엄은 내부적으로 TSC(Time Stamp Counter)라는 64비트 카운터를 가지고 있는데, 이 카운터의 값은 클럭 사이클마다 증가한다. RDTSC 명령은 내부 TSC 카운터의 값을 EDX EAX 레지스터에 복사하는 명령이다. 이 명령은 6에서 11 클럭을 소요한다.

다음의 rdtscEx 명령은 36 클럭을 소요하며 측정구간을 클럭단위로 측정할 수 있는 강력한 시간 측정 방법이다. 하지만 이 방법은 클럭 수만 측정할 뿐 시간을 알 수는 없다. 정확한 시간을 알려면 시스템의 CPU 클럭을 알아야 하며 측정한 클럭값을 CPU 클럭으로 나누어야 시간이 나온다.

 RDTSC 명령을 수행할 때 CPU가 수행속도 향상을 위해서 CPU 명령순서가 바뀔 수 있기 때문에 CPUID 명령을 전에 수행해 명령순서를 맞춰야 하는 경우도 있다.

//------------------------------------------------------

#include "stdafx.h"  // pre-compiled header file for VC++

#include <windows.h>

#include <stdio.h>

 

#define rdtsc(x) \

{ __asm __emit 0fh __asm __emit 031h __asm mov x, eax}

#define rdtscEx(low, high) \

{ __asm __emit 0fh __asm __emit 031h __asm mov low, eax _asm mov high, edx}

#define cpuid {__asm __emit 0fh __asm __emit 0a2h}

 

int main(void)

{

    LARGE_INTEGER start, end, tmp;

    __int64 freq, diff;

 

    if (QueryPerformanceFrequency((LARGE_INTEGER*)&freq)) {

        rdtscEx(start.LowPart, start.HighPart);

        printf("Performance Frequency is %I64d\n", freq);

        rdtscEx(end.LowPart, end.HighPart);

        printf("Preformance Resolution is %4.3f micro sec.\n",

                        1.0/freq * 1000000);

        diff = *(__int64*)&end - *(__int64*)&start;

        printf("printf elapsed clock cycle is %I64d\n", diff);

        printf("printf elapsed time is %4.3f micro sec.\n",

                        (double)diff/266);  // 클럭에 따라 이 부분이 바뀌어야한다.

 

        rdtscEx(start.LowPart, start.HighPart);

                rdtscEx(tmp.LowPart, tmp.HighPart); // 1

                rdtscEx(tmp.LowPart, tmp.HighPart);

                rdtscEx(tmp.LowPart, tmp.HighPart);

                rdtscEx(tmp.LowPart, tmp.HighPart);

                rdtscEx(tmp.LowPart, tmp.HighPart);

                rdtscEx(tmp.LowPart, tmp.HighPart);

                rdtscEx(tmp.LowPart, tmp.HighPart);

                rdtscEx(tmp.LowPart, tmp.HighPart);

                rdtscEx(tmp.LowPart, tmp.HighPart);

                rdtscEx(tmp.LowPart, tmp.HighPart); // 10

        rdtscEx(end.LowPart, end.HighPart);

        printf("rdtscEx elapsed clock clycle is %I64d\n",

                        (*(__int64*)&end - *(__int64*)&start)/10);

    } else

        printf("This computer don't support QueryPerformance function sets!!\n");

}

//----------------------------------------------------

결과는 다음과 같다.

Performance Frequency is 3579545

Preformance Resolution is 0.279 micro sec.

printf elapsed clock cycle is 182771

printf elapsed time is 687.109 micro sec.

rdtscEx elapsed clock clycle is 36

 

지금까지 측정한 시간들은 절대적인 값이라 볼 수는 없다. 실제로 위에서 소개한 두 코드를 가지고 여러번 실행해 보면 그때마다 값이 조금씩 다르게 나오는 것을 볼 수 있는데 그건 윈도우가 멀티 태스킹 환경이기 때문에 다른 영향을 받을 수 있기 때문이다. 그리고 windows 98 windows 2000 에서도 값이 다르게 나올 거라 생각한다. (직접 해보진 못했지만..)

그냥 상대적인 비교만으로 족할 듯 하다..

- the end of this article -