출처: http://www.nicklib.com/bbs/board.php?bo_table=bbs_lecture&wr_id=53&sfl=&stx=&sst=wr_datetime&sod=desc&sop=and&page=2


4장. 뮤텍스(Mutex)
 
뮤텍스는 Mutual Exclusion의 줄임말입니다. 굳이 해석하면 "상호배제"가 되겠네요.
해석의 의미는 모호하지만 뮤텍스 객체를 두 쓰레드가 동시에 사용할 수 없다는 것입니다.
앞장의 크리티컬 섹션을 이해하셨다면 이미 뮤텍스의 방법 역시 이해하신 겁니다.
다들 개념이 비슷비슷하거든요. 굳이 다시 설명한다면 하나뿐인 화장실에 한 사람이 들어가 문을 잠그고 볼일을 보는 동안 다른 사람은 그 사람이 문을 열어 줄 때까지 기다려야 한다. 입니다.
 
윈도우즈와 유닉스에서의 뮤텍스 사용법이 좀 다른데 우선 유닉스를 보겠습니다.
 
 
1. 유닉스/리눅스에서의 뮤텍스
 
 
유닉스에서 뮤텍스 관련함수는 다음과 같은 것들이 있습니다.
 
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_destroy()함수를 호출합니다.
뮤텍스를 사용할 때는 pthread_mutex_lock()과 pthread_mutex_unlock()함수로 락을 걸었다 풀었다 하면 됩니다. 뮤텍스를 검사할때는 pthread_mutex_trylock()함수를 사용하면 됩니다.
 
뮤텍스를 생성하고 초기화하는 방법은 정적인 방법과 동적인 방법 두가지가 있습니다.
 
정적인 방법은 다음과 같이 해서 생성 및 초기화를 합니다.
 
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 
만약 프로그램 실행 중 동적으로 뮤텍스를 생성하려면 pthread_mutex_init()함수를 사용합니다. 다시 말해서 뮤텍스를 사용한다고 해서 반듯이 pthread_mutex_init()함수를 사용해야 하는 것은 아니라는 것이죠.
pthread_mutex_init()함수의 두번째 아큐먼트인 mutexattr은 뮤텍스의 속성을 지정한다고 되어 있는데... 자세한 설명은 어디를 봐도 찾기가 힘드네요. 일반적으로 NULL을 사용합니다. 기본 뮤텍스를 만든다고 하는데... 다른 값을 사용한 예를 보질 못해서... 그냥 우리도 NULL만을 사용하기로 하죠.
 
모든 함수는 함수의 역할을 성공하면 0을 리턴합니다. 그외의 값은 에러입니다.
pthread_mutex_lock()함수는 해당 뮤텍스가 이미 lock되어 있는 경우 블로킹됩니다.
pthread_mutex_trylock()함수는 해당 뮤텍스를 사용할 수 있을 때는 락을 걸고 0을 리턴합니다. 이미 락이 되어 있는 경우는 EBUSY값을 리턴하고 빠져 나옵니다.  
 
#include <pthread.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> int cnt=0; pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; void *Thread1(void *arg) { int i, tmp; for(i=0; i<1000; i++){ pthread_mutex_lock(&mutex); tmp=cnt; usleep(1000); cnt=tmp+1; pthread_mutex_unlock(&mutex); } printf("Thread1 End\n"); return NULL; } void *Thread2(void *arg) { int i, tmp; for(i=0; i<1000; i++){ pthread_mutex_lock(&mutex); tmp=cnt; usleep(1000); cnt=tmp+1; pthread_mutex_unlock(&mutex); } printf("Thread2 End\n"); return NULL; } int main(int argc, char *argv[]) { pthread_t thread1; pthread_t thread2; pthread_create(&thread1, NULL, Thread1, NULL); pthread_create(&thread2, NULL, Thread2, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); printf("%d\n", cnt); return 0; }
 
앞장에서 보였던 샘플과 동일하니깐... 이해하기는 쉽겠죠? mutex관련 코드를 주석처리하면 결과 값이 1000이 나오게 되구요. mutex관련 코드를 살리면 2000이 최종 결과로 나오게 됩니다.
사용법도 크리티컬 섹션과 뭐 다른 것도 없죠?
 
 
2. 윈도우즈에서의 뮤텍스
 
윈도우즈에서 제공하는 뮤텍스 함수는 유닉스의 것과 사용 방법이 좀 다릅니다.
유닉스에서의 뮤텍스는 하나의 프로세스 안에서의 여러 쓰레드간의 동기화를 담하는 것에 반해 윈도우즈에서는 서로 다른 프로세스 간의 동기화도 가능합니다.
 
우선 뮤텍스를 이야기 하기 전에 윈도우즈에서 커널 모드의 동기화 객체를 대기하는 함수를 알아보도록 하겠습니다.
 
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
DWORD WaitForMultipleObjects(DWORD nCount, CONST HANDLE *lpHandles, BOOL fWatAll, DWORD dwMilliseconds);
 
동기화를 위해 객체의 사용을 대기하는 함수는 두가지가 있습니다. WaitForSingleObject()함수는 하나의 커널 객체를 기다리게 되고 WaitForMultipleObjects()함수는 이름에서 알 수 있듯이 여러 커널 객체를 기다릴 수 있습니다.
여기서 말하는 커널 객체는 뮤텍스, 세마포어, 쓰레드, 프로세스, 이벤트, 파이프 등이 이에 해당됩니다.
커널 객체는 두가지 상태를 가집니다. 하나는 신호상태(Signaled)이고 또 다른 하나는 비신호상태(Nonsignaled)입니다.
여기서 신호상태란 신호등에 파란불이 들어온 것과 같은 상태로 해당 객체를 사용할 수 있는 상태를 이야기 합니다. 비신호상태란 신호등에 빨간불이 들어온 것과 같이 해당 객체를 다른 쓰레드가 사용하고 있으니 기다리라는 상태입니다.
위에서 이야기한 두개의 함수는 지정한 객체가 신호상태일 때까지 기다리는 대기 함수입니다. 정확히 이야기 하면 신호상태의 객체를 기다려서 신호상태의 객체를 받으면 바로 그 객체를 비신호상태로 바꾸는 함수입니다.
 
그럼 함수 하나씩 이야기 해보죠.
먼저 WaitForSingleObject()함수입니다. 하나의 커널 객체가 신호상태가 될때까지 기다리는 함수로 다음과 같은 아큐먼트를 사용합니다.
- hHandle: 대상이 되는 커널 객체의 핸들입니다.
- dwMilliseconds: 최대 언제까지 기다릴지를 밀리초로 지정합니다. 이런걸 보고 타임아웃이라고 하죠? 즉, 신호상태가 될때까지 대기하긴 하는데 교착상태(Dead Lock)를 피하기 위해 최대 기다릴 시간을 지정해 주는 것이죠.
즉, WaitForSingleObject()함수가 대기를 마치고 리턴 되는 경우는 두가지 경우가 있겠죠? 하나는 해당 객체가 신호상태가 된 것이고 또 다른 하나는 타임 아웃 시간이 경과한 경우입니다.
 
리턴되는 값으로는 다음의 세가지 값 중에 하나입니다.
- WAIT_OBJECT_0 : hHandle객체가 신호상태로 되어 리턴함
- WAIT_TIMEOUT : 지정한 타임 아웃 시간이 모두 경과하여 리턴함
- WAIT_ABANDONED : hHandle객체가 신호상태로 되긴 했는데 hHandle객체를 잡고 있는 쓰레드가 해당 객체를 포기했을 경우에 리턴함
 
이번에는 WaitForMultipleObjects()함수입니다. 하나의 객체가 아니라 여러객체를 다룬 다는 점만 빼고는 WaitForSingleObject()와 같습니다. 여러 객체를 지정해야 함으로 사용방법이 좀 복잡합니다. 아큐먼트는 다음과 같습니다.
 
- nCount : 감시하고자 하는 커널 객체의 수를 지정합니다.
- lpHandles: 감시하고자 하는 커널 객체를 배열로 전달합니다. 이 배열의 크기가 nCount가 됩니다.
- fWaitAll : 감시 대상의 모든 객체가 모두 다 신호상태가 될때까지 대기하려면 TRUE를 하나라도 신호상태가 될때까지만 대기하려면 FALSE를 지정합니다.
- dwMilliseconds : 최대 대기 시간을 밀리초로 지정합니다.
 
WaitForMultipleObjects()함수의 리턴값은 다음과 같습니다.
fWaitAll이 TRUE일때와 FALSE일때 리턴되는 값이 다릅니다.
우선 TRUE일때는
- WAIT_OBJECT_0 : 감시대상의 모든 커널객체가 신호상태가 되었을 때 리턴함
FALSE일 때는
- WAIT_OBJECT_0 + 신호상태가된객체의인덱스번호 : 감시대상 커널객체 중 하나라도 신호상태가 되면 리턴합니다. 이 리턴값을 이용해서 lpHandles[리턴값-WAIT_OBJECT_0]으로 실제 신호상태가 된 객체를 알 수 있습니다.
- WAIT_ABANDONED_0 + 신호상태가된객체의인덱스번호 : 감시대상 커널객체 중 하나라도 신호상태가 되면 리턴합니다. 그런데 이 신호가 정상적으로 신호상태가 된게 아니라 다른 쓰레드로 부터 해당 객체가 포기되었다는 것을 나타냅니다. 이 리턴값을 이용해서 lpHandles[리턴값-WAIT_OBJECT_0]으로 실제 신호상태가 된 객체를 알 수 있습니다.
- WAIT_TIMEOUT : 지정한 타임 아웃 시간이 모두 경과하여 리턴함
- WAIT_FAILED : 대기에 실패했을 경우 리턴함
 
타임아웃값에 INFINITE라는 상수를 사용하게 되면 무한 대기 상태가 됩니다. 즉, 타임아웃을 지정하지 않는 것과 같습니다. 또한 해당 커널 객체를 포기하였다는 말은 정상적으로 객체의 소유권을 풀지 않고 쓰레드나 프로세스가 종료되었다는 의미입니다.
 
이 대기 함수들은 지금 이야기 하는 뮤텍스뿐만이 아니라 다음에 이야기 할 세마포어에서도 사용되어야 하므로 잘 기억해 두세요. 그리고 지금까지 윈도우즈 샘플 코드를 작성하면서 쓰레드의 종료를 대기하기 위해 메인 쓰레드에서 Sleep()을 사용했는데 대기함수를 공부했으므로 앞으로는 Sleep()함수 대신 대기함수를 사용하도록 하겠습니다.
 
그럼 이제 본격적으로 뮤텍스로 돌아와 보겠습니다.
윈도우즈의 뮤텍스와 관련된 함수는 다음과 것들이 있습니다.
 
HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName);
HANDLE OpenMutex(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName);
BOOL ReleaseMutex(HANDLE hMutex);
 
CreateMutex()함수는 뮤텍스를 생성합니다.
이미 생성된 이름을 갖는 뮤텍스의 핸들을 얻기 위해서는 OpenMutex()를 사용합니다.
뮤텍스의 사용이 끝나서 해당 뮤텍스를 놓아 줄때는 ReleaseMutex()함수를 사용합니다.
위에 원형은 밝히지 않았지만 생성한 뮤텍스를 파괴시킬때는 모든 커널객체가 그렇듯 CloseHandle()함수를 사용합니다.
참고로 신호상태인 뮤택스를 얻기 위해서는 위에서 설명한 대기함수를 사용해야 한다는 것은 짐작하셨겠죠?
 
이때 CreateMutex()는 이미 다른 프로세스에서 사용하고자 하는 뮤텍스를 생성하였다면 CreateMutex()함수를 호출하지 않고 OpenMutex()를 사용해도 되지만 CreateMutex()함수를 다시 한번 더 호출해도 상관이 없습니다.
 
CreateMutex()함수의 아큐먼트는 다음과 같습니다.
- lpMutexAttributes : 뮤텍스의 보안 속성을 지정하는 것으로서 주로 상속관계를 지정하기 위해 사용됩니다. 일반적으로는 NULL을 입력합니다.
- bInitialOwner : 뮤텍스를 생성하면서 사용권한을 갖을 것인지를 결정합니다. 즉, TRUE를 입력하면 생성하자 마자 뮤텍스의 사용권한(비신호상태의 뮤텍스 생성)을 갖습니다. FALSE를 입력할 경우 뮤텍스만 생성하기만(신호상태의 뮤텍스 생성) 하고 실제 사용권한을 얻을때는 대기함수를 사용해야 합니다.
- lpName : 뮤텍스에 이름을 문자열로 지어 줍니다. 이름이 지정된 뮤텍스를 만듦으로서 이 이름을 아는 다른 프로세스와의 동기화를 할 수 있습니다. 하나의 프로세스에서만 동기화 방식으로 뮤텍스를 사용한다면 NULL을 입력하여 이름을 지어 주지 않을 수도 있습니다. 참고 이름을 지어준 뮤텍스를 명명된 뮤텍스(named mutex)라고 합니다.
 
CreateMutex()함수는 생성한 뮤텍스의 핸들을 리턴합니다. 만약 생성하려는 뮤텍스의 이름으로 이미 뮤텍스가 생성되어 있는 경우는 해당 뮤텍스 핸들을 리턴하고 GetLastError()로는 ERROR_ALREADY_EXISTS값을 얻을 수 있습니다. 만약 생성되어 있는 뮤텍스에 접근할 수 있는 권한이 없는 경우(ERROR_ACCESS_DENIED) NULL을 리턴합니다. 구체적인 원인을 알기 위해서는 GetLastError()함수를 호출하여 알 수 있습니다. 만약 EROR_ACCESS_DENIED일 경우는 OpenMutex()함수를 사용하여야 합니다.
 
OpenMutex()함수의 아큐먼트는 다음과 같습니다.
- dwDesiredAccess : 접근속성
- bInheritHandle : 상속옵션
- lpName : 지정된 뮤텍스의 이름
 
OpenMutex()함수는 이름이 지정된 뮤텍스의 핸들의 핸들을 리턴합니다. 만약 지정된 이름의 뮤텍스가 없는 경우는 NULL을 리턴하고 GetLastEror()함수는 ERROR_FILE_NOT_FOUND값을 리턴합니다.
 
일반적으로 OpenMutex()함수는 잘 사용하지 않습니다. 왜냐하면 CreateMutex()함수로 새로운 뮤텍스를 생성할 수도 이미 생성된 뮤텍스의 핸들을 얻을 수도 있기 때문이죠.
위에서 이야기 한대로 뮤텍스에 보안 속성을 설정한 경우 OpenMutex()를 사용하기도 합니다.
 
ReleaseMutex()함수는 잡았던 뮤텍스(비신호상태의 뮤텍스)를 다시 놓아주는(신호상태로 만들어 주는) 함수입니다.
아큐먼트는 hMutex에 뮤텍스 핸들을 넣어 줍니다. 놓아주기에 성공하면 TRUE를 실패하면 FALSE를 리턴합니다.
 
휴, 드디어 모든 뮤텍스 함수에 대해서 설명을 마쳤네요. ㅋㅋㅋ
이제 예제만 보여드리면 되겠네요.
 
#include <stdio.h> #include <process.h> #include <windows.h> int cnt=0; HANDLE hMutex; DWORD WINAPI Thread1(void *arg) { int i, tmp; for(i=0; i<1000; i++){ WaitForSingleObject(hMutex, INFINITE); tmp=cnt; Sleep(1); cnt=tmp+1; ReleaseMutex(hMutex); } printf("Thread1 End\n"); return 0; } DWORD WINAPI Thread2(void *arg) { int i, tmp; for(i=0; i<1000; i++){ WaitForSingleObject(hMutex, INFINITE); tmp=cnt; Sleep(1); cnt=tmp+1; ReleaseMutex(hMutex); } printf("Thread2 End\n"); return 0; } int main(int argc, char *argv[]) { HANDLE hThread[2]; hMutex=CreateMutex(NULL, FALSE, NULL); hThread[0]=CreateThread(NULL, 0, Thread1, NULL, 0, NULL); hThread[1]=CreateThread(NULL, 0, Thread2, NULL, 0, NULL); WaitForMultipleObjects(2, hThread, TRUE, INFINITE); printf("%d\n", cnt); CloseHandle(hThread[0]); CloseHandle(hThread[1]); CloseHandle(hMutex); return 0; }
 
앞의 예제에서 사용했던 소스를 뮤텍스를 사용해서 작성하였으므로 굳이 설명드리지 않아도 되겠죠? thread함수도 _beginethread()에서 CreateThread()함수로 바뀌었네요. 사실 _beginethread()함수가 리턴함 값을 HANDLE로 casting해서 hThread배열에 담아도 결과는 같습니다.
 
뮤텍스는 여기까지 입니다. 별로 어렵지 않죠?
 
Creative Commons License
Creative Commons License 이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
Copyright 조희창(Nicholas Jo). Some rights reserved. http://www.nicklib.com
 
[이 게시물은 주인장님에 의해 2008-03-02 03:05:56 Advanced C/C++에서 이동
AND

출처: http://synch3d.com/wiki/moin/moin.cgi/_b5_bf_b1_e2_c8_ad

MoinMoin   동기화 UserPreferences
 
Help Info Print View Search Diffs Edit
 인덱스   도움말   찾기   대문    바뀐글 

크리티컬 섹션

void InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 
크리티컬 섹션을 초기화한다. 여기 들어가는 인자는 여러개의 스레드에 참조가 되야 하므로 주로 전역에서 쓰인다. 
 
void DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 
생성된 크리티컬 섹션을 삭제한다. 
 
CRITICAL_SECTION 구조체는 구체적으로 사용할 일이 없다. 그냥 주소를 넘겨주기만 하면 된다. 
 
 
void EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 
 
이 사이에서 공유 자원을 안전하게 액세스한다.  
 
void LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection); 

동기화 대기 함수

DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMiliseconds); 
hHandle는 동기화 객체를 나타내고 dwMiliseconds는 기다리는 시간을 정한다. 역시 INFINITE로 지정하면 무한대로 기다린다. 
반환값은 세가지 종류이다 
성공을 하였을때 WAIT_OBJECT_0 hHandle객체가 신호상태가 된경우 
WAIT_TIMER 설정된 시간을 경과하였을 경우 WAIT_ABANDONED 포기된 경우. 
 
DWORD WaitForMultipleObject(DWORD nCount, CONST HANDLE *lpHandles, BOOL fWaitAll, DWORD dwMiliseconds); 
위의 WaitForSingleObject함수가 하나의 객체에 동기화를 기다리는데 비해 이 함수는 복수개의 동기화 객체를 대기할 수 있다. 
동기화 객체의 핸들 배열을 만든후 lpHandles인수로 배열의 포인터를 전달해주면 nCount로 배열의 갯수를 넘겨준다. 
fWaitAll이 TRUE이면 모든 동기화 객체가 신호상태가 될 때까지 대기하면 FALSE이면 그중하나라도 신호상태가 되면 대기상태를 종료한다. 
리턴값의 의미가 조금 다르다. WAIT_TIMEOUT은 같고 bWaitAll이 TRUE이면 WAIT_OBJECT_0이 리턴되면 모든 동기화 객체가 신호상태이라는 
말이고 FALSE이면 lpHandles배열에서 신호상태가 된 동기화 객체의 인덱스를 넘겨준다. 이경우 
lpHandles[리턴값 - WAIT_OBJECT_0]의 방법으로 신호상태가 된 동기화객체의 핸들을 구할수 있다. 

뮤텍스

크리티컬 섹션에 비해서 느리다
크리티컬 섹션의 경우 구조체의 값을 통해 잠그기를 허용하는데 비해 뮤텍스는 객체를 생성하기 때문이다.
HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName); 
lpMutexAttributes는 보안속성으로 보통 NULL로 지정한다. 
bInitialOwner은 뮤텍스 생성과 동시에 소유할것인지 지정하는데 TRUE이면 이 스레드가 바로 뮤텍스를 소유하면서 다른 스레드는 소유할수 없다. 
lpName는 뮤텍스의 이름이다. NULL설정가능. 
반환값은 뮤텍스의 핸들이다. 
 
HANDLE OpenMutex(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName); 
뮤텍스를 연다. 프로세스 ID와 마찬가지로 뮤텍스의 이름은 전역적으로 유일하다. 
 
BOOL ReleaseMutex(HANDLE hMutex); 
해당 스레드의 뮤텍스 소유를 해제하여 다른 스레드가 가질수 있도록 해준다. 
 
HRESULT CloseHandle(HANDLE hHandle); 
모든 커널 객체와 마찬가지로 생성된 뮤텍스를 파괴할때 사용한다. 
반환값은 S_OK면 성공 그외의 값은 에러이다. 
포기된 뮤택스 
 
만약 뮤텍스를 소유하고 있는 스레드가 ExitThread나 TerminateThread로 비정상적으로 종료시켰을 경우 강제로 뮤텍스를  
신호상태로 만들어준다. 그러므로 대기중인 다른 스레드에서 뮤텍스를 가지게 되는데 WaitForSingleObject함수의 리턴값으로 
WAIT_ABANDONED값을 전달받음으로 이 뮤텍스가 정상적인 방법으로 신호상태가 된 것이 아니라 포기된 상태임을 알 수 있다. 
 
중복소유 
 
뮤텍스를 여러번 겹쳐서 사용했을 경우 데드락과 같은 상태에 빠질수도 있을것이다. 하지만 중복으로 소유하기위해 
Wait~Objet함수를 호출하여 기다릴때 뮤텍스를 여러번에 겹쳐서 소유하는게 아니라 한번의 뮤텍스를 소유하고 소유횟수 
(카운트)를 증가시킨다. 단, 다시 뮤텍스를 신호상태로 만들기 위해서는 ReleaseMutex를 카운트많큼 호출해주어야한다.  
중복 소유에 대해서는 크리티컬 섹션과 같다. 
 
공유자원의 해야 할 일 
WaitForSingleObject와 같은 대기상태에서 무한정으로 기다릴게 아니라 dwMiliseconds에 0을 넣어주고 반환값 WAIT_TIMEOUT을  
체크해주면 if문등을 통해 쉽게 공유자원외의 일을 할 수 있다. 

세마포어

세마포어와 뮤텍스는 유사한 동기화 객체이다. 뮤텍스는 하나의 공유자원을 보호하는데 비해 
세마포어는 일정 개수를 가지는 자원을 보호할수 있다. 여기서 자원이라함은 윈도우, 프로세서, 
스레드와 같은 소프트웨어적인거나 어떤 권한과 같은 무형적인것도 포함된다. 
 
HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName); 
lMaximumCount는 최대 사용 개수 lInitialCount에 초기값을 지정. 아주 특별한 경우외에는 이 두값이 같다. 세마포어는 뮤텍스와 같이 
이름을 가질 수 있고 이름을 알고 있는 프로세스는 언제든지 OpenSemaphore로 핸들을 구할 수 있다. 역시 파괴할때는 CloseHanle함수를 사용한다. 
 
HANDLE OpenSemaphore(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName); 
뮤텍스 부분과 같다. 
 
BOOL ReleaseSemaphore(HANDLE hSemaphore, LONG lReelaseCount, LPLONG lpPreviousCount); 
lReleaseCount로 사용한 자원의 개수를 알려줌. lpPreviousCount는 세마포어 이전 카운트를 리턴받기 위한 참조 인수이다. NULL가능하다. 

이벤트

위의 동기화객체들이 공유자원을 보호하기 위해 사용되는 데 비해 이벤트는 스레드의 작업순서나 시기를 조정하기 위해 사용된다.  
 
HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName); 
bManualReset은 이벤트가 수동 리셋(스레드가 비신호상태로 만들어줄 때까지 신호상태를 유지)인지 
자동 리셋(대기 상태가 종료되면 자동으로 비신호상태가 된다.)인지를 결정한다. TRUE이면 수동이다. 
bInitialState가 TRUE이면 자동으로 신호상태로 들어가 이벤트를 기다리는 스레드가 곧바로 실행할수 있게 한다. 
 
HANDLE OpenEvent(DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName); 
다른 부분과 같다. 
 
BOOL SetEvent(HANDLE hEvent); 
다른 동기화 객체와는 다르게 사용자 임으로 신호상태와 비신호상태를 설정할 수 있다. 위의 함수는 신호상태로 만들어 준다. 
 
BOOL ResetEvent(HANDLE hEvent); 
비신호 상태로 만든다. 
 
일반적으로 자동리셋을 사용하는데 이벤트를 발생시켜 대기 상태를 풀 때 자동으로 비신호 상태로 만드는 것이다. 
하지만 여러개의 스레드를 위해서 이벤트를 사용한다면 문제가 될수도 있다. 그러므로 수동리셋으로 이벤트를 생성후 
ResetEvent함수로 수동리셋을 한다.  

실험

Interlock CriticalSection? Mutex Not sync
Real Time 09:234 09:953 1:59:734 09:000
User Time 18:406 18:937 35:187 17:859
Kernel Time 00:015 00:375 1:28:608 00:000


PythonPowered EditText of this page (last modified 2006-12-06 10:41:34)
FindPage by browsing, searching, or an index
Or try one of these actions: DeletePage, DeleteUploadedFile, LikePages, SpellCheck, UploadFile
AND

춮처: http://cbuilder.borlandforum.com/impboard/impboard.dll?action=read&db=bcb_qna&no=17509

박정만 님이 쓰신 글 :
: 프로젝트 안에 *.C와 *.CPP가 같이 섞여있거든요.
:
: 근데, 컴파일하면 무수한 에러가 우두두 뜹니다.
:
: 그런데, *.C의 파일명을 *.CPP로 바꾸면 멀쩡하게 실행되거든요.
:
: extern "C"를 선언하면 된다고 들은 것 같은데, 그래도 안되네요.
:
: 구체적인 해결 방법을 부탁드립니다.
:

C와 C++ 코드는 링크시 서로 따로 관리됩니다.

함수 호출 방법등 많은 점이 다르기 때문이죠.

(자세한건 묻지마세요.. 잘 모르니깐..ㅡ,.ㅡa)

따라서 어떤 함수를 호출하는데 이건 C코드 중에서 찾아라.. 이건 C++에서 찾아라..

이런 것을 컴파일러에게 알려주어야 합니다. 그래야 링커가 제대로 찾아서 링크하겠져.

예를 들자면..

printf 함수는 C코드 함수입니다.

따라서 C코드에서 찾아라 하고 알려주어야 합니다.

안그러면 C++코드 중에 없다고 불평합니다.

그래서 헤더파일에 프로토타입을 쓸때 다음과 같이 쓰면 되지요.

extern "C" int printf....
extern "C" int sprintf....
extern "C" int vprintf....
extern "C" int fprintf....


근데 이거 귀찮지여.. 한두개면 몰라도 한 1000개쯤 되면 미칠 겁니다.

다음과 같이 쓰면 됩니다.

extern "C"
{
    int printf...
    int sprintf...
     ...
}

근데 이렇게 소스를 수정해 버리면 C++ 컴파일러에서는 문제가 없지만..

C 컴파일러가 extern "C" 를 알아 듣지 못하니 다시 C 컴파일러로 컴파일하자면 이번엔

C컴파일러가 불평을 합니다. 그래서 다음과 같이 씁니다.

#ifdef __cplusplus
extern "C"
{
#endif
    int printf...
    int sprintf...
     ...
#ifdef __cplusplus
}
#endif

__cplusplus는 모든 C++ 컴파일러가 내부적으로 가지고 있는 미리 정의된 define이져..

C 컴파일러는 없으니 #ifdef .. #endif 는 C 컴파일러로 컴파일할 때는 무시됩니다.

이제 문제가 없지여. C나 C++이나 다 쓸 수 있습니다.

보통 C++ 컴파일러들은 C 컴파일러를 같이 가지고 있습니다.

.C 는 C 컴파일러로 컴파일하고.. .CPP는 C++ 컴파일러로 컴파일하져.

따라서 저런 작업을 해주지 않으면.. 에러가 무수하게 쏟아 집니다.

그럼 .CPP 로 바꾸면 되는 이유는? 모두 C++코드로 관리되니까 링크에러가 날리없져..

문제는 .C 소스를 가지고 있지 않는 경우 즉 .lib나 .obj 파일만 가지고 링크하려면..

C++코드로 컴파일할 수 없으니.. C 코드 중에서 찾아라라고 위에서 말한 방법으로 헤더를

수정해주는 수밖에 없지여.

도움이 되셨길...

AND

출처: http://chungsm.tistory.com/27

Level of Difficulty     1  2   3

Calling Convention의 경우 대부분 함수호출 방식을 함께 정의한 Header를 제공함으로서 사용자가 API호출 규약을 따르도록 제공하고 있습니다.
프로그램적으로 강제할 수 있는 부분이 아니기 때문에 약속이 필요한 것입니다.

프로그램의 호환성등의 이유로 여러가지 호출방식이 사용되고 있는데 __stdcall, __cdecl, __fastcall, __thiscall등이 있으며 파라메터 전달과 처리 방법에 차이 가 있습니다.
(Win32 API는 WINAPI(__stdcall)형식으로 대부분 호출된다.)

위의 호출방식들이 stack에 파라메터를 저장하는 순서는 모두 마지막 파라메터(오른쪽) 부터 처음 파라메터(왼쪽)순으로 저장하게 됩니다. 파라메터의 접근은 모든 함수호출시에 스택포인터(ESP)의 기준(EBP)을 다시 설정하고 EBP를 기준으로 접근하게 됩니다.
stack address는 높은주소에서 낮은주소로 증가되므로 EBP + X값이 파라메터에 접근하는 값이 되는 것입니다.



__stdcall
(WINAPI)는 함수종료시 함수내부에 파라메터를 전달 받은 stack을 직접 정리(stack 포인터를 이전 상태로 올림)합니다.

184:      TestFunc(1, 2);
0040165D   push        2
0040165F   push        1

00401661   call        TestFunc (0040161b)


173:  DWORD __stdcall TestFunc(DWORD dw1, DWORD dw2)
174:  {

0040161B   push        ebp
0040161C   mov         ebp,esp

0040161E   push        ecx
0040161F   mov         dword ptr [ebp-4],0CCCCCCCCh
175:      DWORD dw = 0;
00401626   mov         dword ptr [dw],0
176:      dw = dw1 + dw2;
0040162D   mov         eax,dword ptr [dw1]
00401630   add         eax,dword ptr [dw2]
00401633   mov         dword ptr [dw],eax
177:      return dw;
00401636   mov         eax,dword ptr [dw]
178:  }
00401639   mov         esp,ebp
0040163B   pop         ebp

0040163C   ret         8

__stdcall로 함수를 호출했을 경우에 disassembly 코드를 보면 위와 같습니다.
가장 아래부분에 빨강색으로 강조된 "ret 8"을 보면 TestFunc()함수 자체적으로 종료시에 stack을 8(DWROD dw1 + DWORD dw2)만큼 정리해서 리턴하는 OPCODE를 호출하는 것을 확인할 수 있습니다.



 

__cdecl의 경우에는 함수를 호출하는 쪽에서 stack을 직접 정리하는 방식입니다.

184:      TestFunc(1, 2);
0040165B   push        2
0040165D   push        1
0040165F   call        TestFunc (0040161b)
00401664   add         esp,8


173:  DWORD __cdecl TestFunc(DWORD dw1, DWORD dw2)
174:  {

0040161B   push        ebp
0040161C   mov         ebp,esp

0040161E   push        ecx
0040161F   mov         dword ptr [ebp-4],0CCCCCCCCh
175:      DWORD dw = 0;
00401626   mov         dword ptr [dw],0
176:      dw = dw1 + dw2;
0040162D   mov         eax,dword ptr [dw1]
00401630   add         eax,dword ptr [dw2]
00401633   mov         dword ptr [dw],eax
177:      return dw;
00401636   mov         eax,dword ptr [dw]
178:  }
00401639   mov         esp,ebp
0040163B   pop         ebp

0040163C   ret

__cdecl의 경우 상위에 "0040165F   call   TestFunc (0040161b)"이후에 함수가 종료된후 해당함수를 호출한 쪽에서 "add   esp,8"를 직접 호출해서 stack을 정리하는 것을 알 수 있습니다.



__fastcall의 경우는 stack이 아닌 CPU register(ECX, EDX)를 이용해서 전달하는 방식으로 2개 이후부터는 stack을 사용합니다.
(64bit에서는 위의 모든 방식이 fastcall과 유사하게 register를 통해서 절달하는 방식으로 통일되었습니다. ^^)

184:      TestFunc(1, 2);
00401671   mov         edx,2
00401676   mov         ecx,1

0040167B   call        TestFunc (0040161b)


173:  DWORD __fastcall TestFunc(DWORD dw1, DWORD dw2)
174:  {

0040161B   push        ebp
0040161C   mov         ebp,esp

0040161E   sub         esp,0Ch
00401621   mov         dword ptr [ebp-0Ch],0CCCCCCCCh
00401628   mov         dword ptr [ebp-8],0CCCCCCCCh
0040162F   mov         dword ptr [ebp-4],0CCCCCCCCh
00401636   mov         dword ptr [ebp-0Ch],edx
00401639   mov         dword ptr [ebp-8],ecx
175:      DWORD dw = 0;
0040163C   mov         dword ptr [dw],0
176:      dw = dw1 + dw2;
00401643   mov         eax,dword ptr [dw1]
00401646   add         eax,dword ptr [dw2]
00401649   mov         dword ptr [dw],eax
177:      return dw;
0040164C   mov         eax,dword ptr [dw]
178:  }
0040164F   mov         esp,ebp
00401651   pop         ebp
00401652   ret


__fastcall의 경우 파라메터 전달을 위해서 CPU의 레지스터인 edx, ecx에 함수호출전 값을 대입하는 것을 알 수 있습니다.



__thiscall
의 경우는 c++의 class에서 사용되는 호출방식으로 별도로 함수에 직접 사용할 수 없으며 ECX레지스터에 클래스포인터(this)를 전달합니다. 파라메터 전달 방식과 처리는 __stdcall과 동일합니다.



이러한 호출규약이 틀릴경우에는 호출된 함수가 종료되는 시점에 전달받은 파라메터를 저장하고 있는 stack을 정리하면서 오류가 발생하게 됩니다.
예를 들면 WINAPI형식으로 구현된 함수를 CDECL형식으로 호출하면 함수내부에서 1차적으로 ESP(stack pointer)를 정리해서 리턴하도록 컴파일 되어 있는데 호출한 쪽에서도 stack을 정리하는 코드가 들어있어서 함수 종료 시 오류가 발생하게 되는 것입니다.
그 반대의 경우도 있겠죠.

"VOID WINAPI MyFunc()" 같이 정의된 헤더를 제공함으로서 WINAPI형식으로 호출 하도록 상호 약속을 하는 것입니다.
묵시적으로 외부에 제공되는 함수는 Win32API들 처럼 WINAPI(__stdcall)형식으로 제공하는게 대부분입니다.
즉 이경우 stack에 대한 정리는 API내부에서 처리하겠다는 의미로 생각 할 수 있겠습니다.

여기서 한가지 생각해 볼 것이 있는데 printf()같은 함수의 경우처럼 입력되는 파라메터가 가변적일 경우입니다.

printf("%s, %s, %s, %s, %s", szName, szAddress, szTemp, szTemp2, szTemp3);

위의 경우 파라메터로 5개의 문자열 포인터가 전달되었지만 이보다 더 많거나 혹은 하나만 전달될 경우도 있다는 것입니다.

이 경우 함수(printf())에서는 stack을 정리하는 코드가 미리 컴파일 되어서 생성 될 수 없겠죠.
stack을 통해서 파라메터가 얼마나 전달될지 모르는 경우이기 때문입니다.

이 경우에는 함수를 호출하는 쪽(파라메터를 입력한)에서 stack을 정리하도록 CDECL(__cdecl)형식으로 함수를 호출하고 사용해야 하는 것입니다.

Visual C++ 에서 기본적인(명시적인 정의가 없을때) 함수호출 방식이 __cdecl로 설정 되어 있습니다.
헤더에는 __stdcall로 선언하고 실제 구현부(.cpp)에는 아무런 선언을 안해놓으면 컴파일러는 __cdecl로 처리하게 되어서 아래와 같은 오류를 표시하게 됩니다.

error C2373: 'MyFunc' : redefinition; different type modifiers

함수명이 중복(호출방식은 다르므로)으로 재선언 되었다는 오류메세지입니다.
경력이 오래되시 개발자 중에서도 이 메세지에 당황하는 경우를 많이 보았습니다 -_-;
선언과 구현부는 되도록 동일하게 구현하는 습관을 가져야 겠죠..

AND

출처: http://wiki.rabidus.net/ow.asp?p=WindowsMessageFlow&revision=5

WindowsMessageFlow

FrontPage | RecentChanges | TitleIndex | UserPreferences | FindPage | Edit this page (last edited 2005-2-28)(diff)


Description

윈도우즈 프로그래밍에서 Message가 어떻게 발생되서 프로시저로 전달되는지에 대한 설명이다.

Window와 Message queue는 쓰레드 종속적이다.

윈도우즈에서 모든 객체의 소유권은 쓰레드가 가지고 있다. 윈도우를 생성한 쓰레드가 종료되면 윈도우도 따라 종료된다 따라서 윈도우에 메세지를 주는 메세지 큐는 해당 윈도우를 가지고 있는 쓰레드가 가지고 있다. 각 쓰레드는 한 쓰레드만 실행하는 것같은 환경에서 실행되어야 한다. 각 쓰레드는 다른 쓰레드에 영향을 받지 않는 메세지 큐를 가지고 있어야 하면 각 쓰레드는 키보드 포커스, 윈도우 활성화, 마우스 캡처등의 개념을 유지하는 모의 환경을 가지고 있어야 한다.

Windows의 기본적인 메세지 종류

윈도우즈는 기본적으로 메세지를 끊임없이 주고 받는 운영체제이다. 각종 이벤트(키보드, 마우스 입출력, 윈도우 사태변경등등)에 메세지가 발생하며 전달되고 처리된다. 메세지는 시스템 메세지 큐와 쓰레드 메세지 큐를 거치는 queued message와 해당 윈도우즈의 WNDCLASS에 등록된 프로시저에 전달되는 non-queued message로 구분된다.

함수로는 같은 쓰레드의 윈도우즈에 대해 호출되는 SendMessage?()가 non-queued message이며 PostXXXMessage?()류와 다른 쓰레드의 윈도우에 사용되는 SendMessage?()가 queued message이다.

메세지의 종류로는 queued message의 경우 키스트로크 (WM_KEYDOWN / WM_KEYUP등)과 키스트로크에 의한 문자(WM_CHAR), 마우스 이동(WM_MOUSEMOVE), 마우스 클릭(WM_LBUTTONDOWN), 타이머 메세지(WM_TIMER), 그리기 메세지 (WM_PAINT), 종료 메세지(WM_QUIT)가 있으며 non-queued message는 나머지 다른 메세지들이다. 종류로는 윈도우즈의 특정함수의 대한 메세지나 윈도우의 상태인 WM_CREATE, WM_SIZE, WM_SHOWWINDOW등이며 메뉴아이템의 선택결과인 WM_COMMAND메세지도 큐에 들어가지 않는다.

기본적인 메세지의 흐름도

base_message_flow.jpg

Windows는 쓰레드가 UI관련작업을 할 경우 THREADINFO를 생성하여 메세지큐를 쓰레드에 할당한다.

초기 쓰레드 생성시에는 UI에 필요한 리소스 생성작업을 하지 않는다. 하지만 쓰레드가 UI관련작업(메세지큐 체킹(GetMessage?()), 윈도우를 생성(CreateWindow?))을 할경우 THREADINFO라는 Undocumened 구조체를 할당한다. 만약 하나의 프로세스가 세개의 쓰레드를 생성하고 각 쓰레드가 CreateWindow?를 할경우 쓰레드별로 메세지큐가 생성된다.

다음 구조로 되어 있으며 아래 표와 그림을 참고하자.

threadinfo_struct.jpg

Posted-Message queue PostMessage?(), PostThreadMessage?()에 의해 Posted-Message queue에 메세지가 등록된다
Virtualized Input queue ...
Send-Message queue pointer 다른 쓰레드에서 SendMessageXXX?()함수에 의해 메세지가 등록된다.
Reply-Messsage queue pointer SendMessageXXX?()에 대한 다른쓰레드의 응답이나 ReplyMessage?()함수에 의해 메세지가 등록된다.
Wake Flags 메세지가 포스트 될때 어떤 메세지가 큐에 셋팅되어 있는지 알리는 flag이다. PostMessage?()의 경우 QS_POSTMESSAGE에 해당하는 비트셋을 셋팅한다. 만일 아무런 비트가 셋팅되어 있지 않다면 CPU는 스케줄링을 조절한다.

해당 윈도우에 메세지 Post하기

기본적으로 post를 하여 메세지를 보내는 방법은 비동기적이다. 즉 메세지를 post하고 바로 리턴하는 모델이다.

PostMessage(HWND, UIMT, WPARAM, LPARAM);

(1) 먼저 PostMessage?()에 전달하고자 하는 윈도우의 HWND와 MSG 그리고 파라미터를 셋팅하여 호출한다.

(2) 시스템은 다음 MSG구조체를 저장할 공간을 할당하고 파라미터로 넘어온 데이터를 시스템 메세지 큐에 저장한다.

typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
#ifdef _MAC
DWORD lPrivate;
#endif
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;

(3) 시스템은 hwnd에 해당하는 윈도우를 가진 쓰레드를 알아낸다

(4) 해당 쓰레드의 포스트 메시지 큐에 블록의 주소를 추가하고 THREADINFO의 Wake Flags의 QS_POSTMESSAGE의 비트를 셋팅한다. 후에 바로 리턴하며 해당 프로시저가 이 메세지를 처리할지는 알수없다.

(5) 해당 쓰레드에서 GetMessage?()나 PeekMessage?()를 호출하여 MSG구조체에 데이터를 채운후에 DispatchMessage?()를 통하여 해당 윈도우의 프로시저로 메세지를 보낸다.

DWORD PostThreadMessage(DWORD, UINT, WPARAM, LPARAM);

(1) 첫번째 파라미터인 쓰레드 아이디의 메세지큐에 HWND값은 NULL인체로 포스트된다. HWND가 NULL인경우는 특정윈도우에게 보내는 것이 아니라 해당 쓰레드가 가지고 있는 윈도우와 프로시저등에 메세지를 전달하는 것으로 반드시 윈도우를 생성하지 않더라도 쓰레드가 메세지 기반으로 작동중이면 이 함수를 이용해서 메세지를 전달 할 수 있다. 대표적인 예가 MFC의 CWindThread?이며 이 클래스는 UI가 있는 쓰레드와 어떤 특정 잡을 처리하는 Walker 쓰레드로 구분된다.

(2) 큐를 가지지 않는 Walker 쓰레드는 메세지를 받지 못한다.

DWORD PostQuitMessage(int);

(1) VOID PostThreadMessage(GetCurrentThreadId(), WM_QUIT, nExitCode, 0)과 같은 코드이다.

(2) 해당 쓰레드의 모든 윈도우는 종료 메세지를 받는다. HWND가 NULL임으로 GetMessage?루프에서 처리하는 것이 보통이다.

while(GetMessage(&msg, 0, 0, 0)) {
if (msg.hwnd == NULL) {
// 쓰레드에 보내진 메세지다.
}
else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

해당 윈도우에 메세지 Send하기

메세지를 Send하는 모델은 동기적인 방법이다. 따라서 함수를 호출한 후에 해당 처리 프로시저가 메세지를 처리하면 리턴되어 다음 실행을 하는 모델이다. 그래서 같은 쓰레드의 다른 윈도우의 프로시저에 메세지를 send하는것과 다른 프로시저의 쓰레드에 메세지를 send하는것은 동기화 문제를 가지고 있는데 윈도우즈는 다음과 같이 처리한다.

같은 쓰레드의 윈도우에 메세지 Send하기

같은 쓰레드의 윈도우에 SendMessage?()를 호출하면 Send message queue에 추가되는 것이 아니라 해당 윈도우의 프로시저를 직접 호출하여 프로시저가 처리를 다하면 리턴한다.

다른 쓰레드의 윈도우에 메세지 Send하기

BOOL SendMessage(HWND, UINT, WPARAM, LPARAM)

다른 쓰레드의 윈도우의 경우 다른 프로세스의 윈도우의 경우 직접적으로 SendMessage?()가 해당 윈도우의 프로시저를 호출할 수 없다. 그래서 이경우에는 다른 방법으로 동기화를 맞춘다.

(1) 일단 시스템 메세지 큐를 거쳐 해당 윈도우의 쓰레드의 Send message queue에 메세지가 추가되고 Wake Flag에 QS_SENDMESSAGE 비트를 셋팅한다. 중요한것은 SendMessage?()의 동기를 맞추기 위해서 시스템은 인터럽트를 사용하지 않으며 GetQueueStatus?()를 이용한 쓰레드의 메세지 큐에서 메세지를 꺼내는 알고리즘에 따라 작동한다. QS_SENDMESSAGE의 경우 제1순위임으로 Send message queue에 쌓여 있는 메세지를 해당 프로시저가 처리하게 된다.

(2) 이때 SendMessage?()를 호출한 쓰레드는 바로 리턴하지 않으며 SendMessage?()로 메세지를 전달한 대상 프로시저가 메세지를 처리한후에 자신의 쓰레드의reply message queue에 처리되었다는 메세지와 리턴값을 기다리며 실행중이다.

(3) 응답메세지가 상대 프로시저에 의해서 reply send queue에 들어오면 리턴값을 리턴하며 호출을 종료한다.

(4) 중요한것은 블럭킹이 걸려 있는 동안에도 시스템은 다른 쓰레드에서 보내어진 메세지는 send message queue에 쌓인다.

BOOL SendMessageTimeout(HWND, UINT, WPARAM, LPARAM, UINT flags, UINT timeout, PDWORD_PTR result)

기다릴 최대 시간과 기다리는 방법을 지정하여 SendMessage?를 할 수 있다.

BOOL SendMessageCallBack(HWND, UINT, WPARAM, LPARAM, SENDASYNCHPROC, ULONG_PYT)

쓰레드간의 메세지를 (interthread message) 가능하게 하는 함수로 SendMessageCallBack?을 호출하면 리턴되며 타켓 프로시저가 작업이 끝나고 reply message queue에 응답이 오면 등록된 콜백 함수를 호출한다. 콜백 함수의 프로토타입은 다음과 같다. VOID CALLBACK ResultCallBack(HWND, UINT, ULONG_PTR, LRESULT);

BOOL SendNotifyMessage(HWND, UINT, WPARAM, LPARAM)

메세지를 받는 쓰레드의 메세지 큐에 메세지를 추가하고 바로 리턴하지만 PostMessage?와는 2가지 경우에 따라 다르다.

 - 메세지 처리 우선순위가 높다.
 - 같은 쓰레드의 윈도우에게 보낼경우 SendMessage?()와 동일한 작업을 한다.

SendMessageXXX()를 통한 프로시저 호출에 대해 응답하기

SendMessageXXX?()의 함수등에서 호출당한 프로시저에서 BOOL ReplayMessage(LRESULT lResult);를 호출하면 바로 SendMessageXXX?()를 호출한 쓰레드로 작업완료를 알려주어 블럭킹을 풀리게 한다. 만약 동일한 쓰레드에서 호출된 SendMessageXXX?()의 경우 아무런 일도 일어나지 않는다.

SendMessageXXX()에서 보내진 메세지의 종류 알기

BOOL IsSendMessage();

같은 쓰레드에서 보내진 경우라면 TRUE, 다른 쓰레드라면 FALSE

BOOL IsSendMessageEx(PVOID)

파라미터의 리턴값으로 동일 쓰레드에서 호출한 SendMessageXXX?()인지 다른 쓰레드라면 어떤 종류의 SendMessageXXX?()인지를 알 수 있다.

모든 윈도우에 메세지 보내기

모든 윈도우에게 메세지를 보내는 방법은 long BroadcastSystemMessage(DWORD, LPDWORD, UINT, WPARAM, LPARAM)를 사용하여 수신자 종류를 지정해서 할수 있으며 SendMessageXXX?()함수에서 HWND_BROADCAST를 윈도우 핸들로 넘김으로서 가능하다.

하드웨어 입력을 통한 메세지 전달

쓰레드 큐에서 메세지를 꺼내는 알고리즘

기본적으로 메세지 큐를 가지고 있는 쓰레드에서 메세지 큐에 메세지가 없다면, 즉 THREADINFO구조체의 Wake Flag에 아무런 메세지 셋팅이 없다면 쓰레드는 스케줄링이 되지 않는다. 즉 메세지루프 내의 GetMessage?와 PeekMessage?함수는 UI관련된 작업을 수행하기전까지 Sleep상태에 빠진다.

만약 메세지가 큐에 들어온다면 GetMessage?와 PeekMessage?

DWORD GetQueueStatus(UINT flags)함수를 이용하여 정해진 알고리즘에 따라 메세지를 처리한다. 리턴값의 HIWORD는 현재 남아 있는 메세지의 타입의 플래그 값이다. 따라서 다음의 코드의 경우 WM_TIMER값이 있는지 확인하고 WM_PAINT값이 있는지도 리턴값을 통해 확인한다.
{{{BOOL IsPaintMsg = HIWORD(GetQueueStatus(QS_TIMER)) & QS_PAINT

리턴값의 LOWORD는 GetQueueStatus?, GetMessage?, PeekMessage?의 호출이후 진행되지 않았던 메세지의 타입을 가르킨다. 다음 표의 플래그는 어떤 메세지가 큐에 있는지를 나타내준다.

QS_KEY WM_KEYUP, WM_KEYDOWN, WM_SYSKEYUP, or WM_SYSKEYDOWN
QS_MOUSEMOVE WM_MOUSEMOVE
QS_MOUSEBUTTON WM_?BUTTON* (Where ? is L, M, or R, and * is DOWN, UP, or DBLCLK)
QS_MOUSE QS_MOUSEMOVE | QS_MOUSEBUTTON와 같다.
QS_INPUT QS_MOUSE | QS_KEY와 같다.
QS_PAINT WM_PAINT
QS_TIMER WM_TIMER
QS_HOTKEY WM_HOTKEY
QS_POSTMESSAGE 포스트된 메세지
QS_ALLPOSTMESSAGE 포스트된메세지
QS_ALLEVENTS QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY와 같다
QS_QUIT PostQuitMessage?에 의해 이 플래그 켜진다.
QS_SENDMESSAGE 다른쓰레드에서 Send된 메세지
QS_ALLINPUT QS_ALLEVENTS | QS_SENDMESSAGE와 같다

시스템은 웨이크 플래그의 성격에 따라 다르게 다룬다. 다음은 웨이크 플래그와 다루는 방법이다.

QS_MOUSEMOVE, QS_KEY, QS_MOUSEBUTTON, QS_HOTKEY의 입력관련 플래그 큐안에 메세지가 있는한 플래그는 유지되며 각 입력장치별로 메시자가 없다면 각 플래그별로 꺼진다
QS_PAINT 이 플래그는 해당 쓰레드의 윈도우가 Invalidate(무효)영역을 가질때 켜지며 ValidateRect?(), ValidateRegion?, BeginPaint?()등에 의해서 보여지는 윈도우가 모두 Validate가 되면 플래그는 꺼진다
QS_POSTMESSAGE 하나이상의 Post된 메세지가 있는 경우 켜지며 없을경우 사라진다
QS_TIMER 타이머가 설정될떄 켜지며 WM_TIMER가 꺼내어진후 플래그가 꺼지며 타이머는 설정된다.
QS_SENDMESSAGE 외부 쓰레드에서 온 Send메세지가 있다면 켜진다

GetMessage?나 PeekMessage?함수가 이런한 웨이크 플래그를 체킹하여 어떤 메세지가 실행해야 될지는 다음 알고리즘 처리 순서(우선순위)에 따르며 그 방법을 도식화한 그림이다.

(1) QS_SENDMESSAGE검사하여 적절한 윈도우 프로시저로 보내고 더이상의 Send된 메세지가 없다면 해당플래그는 꺼지고 GetMessage?리턴되지 않으며 다음 플래그를 검사한다.

(2) QS_POSTMESSAGE를 검사하여 있다면 MSG구조체에 데이터를 복사하고 더이상의 Post된 메세지가 없다면 플래그는 꺼지고 GetMessage?함수는 TRUE로 리턴된다.

(3) QS_QUIT를 검사하여 있다면 MSG구조체에 데이터를 복사하고 해당 플래그는 꺼지고 GetMessage?함수는 FALSE로 리턴되어 메세지 루프를 빠져나간다.

(4) QS_INPUT를 검사하여 관련 메세지가 있다면 MSG구조체를 채우고 키보드 메세지가 더 이상없다면 QS_KEY플래그가 꺼지고, 마우스 비턴 관련 메세지가 큐에 없다면 QS_MOUSEBUTTON이 꺼지고 마우스 이동관련 메세지가 없다면 QS_MOUSEMOVE가 꺼지고 GetMessage?함수는 TRUE로 리턴한다. 이때 WM_CHAR의 경우에는 다음과 같은 과정을 거쳐서 읽혀지게 된다.

1. 키보드를 누르면 WM_KEYDOWN / WM_KEYUP이나 WM_SYSKEYDOWN / WM_SYSKEYUP이 VIQ queue에 넣어지게 되고 QS_KEY플래그가 셋팅된다.
2. GetMessage가 WM_XXXKEYDOWN 메세지 정보를 가지고 리턴하면 DispatchWindow전에 TranslateMessage를 통하여 검사되고 가상키정보가 문자정보로 번역되어 Post큐에 WM_CHAR / WM_SYSCHAR로 입력된다.
3. 다음 GetMessage함수는 Post큐에서 WM_CHAR / WM_SYSCHAR를 읽게되고
4. 다음 GetMessage함수는 마지막 입력 이벤트 메세지인 WM_XXXKEYUP을 읽게 되어 프로시저에게 WM_XXXKEYDOWN, WM_CHAR / WM_SYSCHAR -> WM_XXXKEYUP을 전달하게 된다.

(5) QS_PAINT를 검사하여 켜져 있으면 MSG구조체를 데이터가 복사되고 해당 쓰레드의 윈도우 영역이 Validate상태가 되면 꺼지고 GetMessage?함수는 TRUE로 리턴된다.

(6) QS_TIMER를 검사하여 켜져 있으면 MSG구조체에 데이터가 복사되고 타이머는 리셋되며 QS_TIMER 플래그는 꺼진고 GetMessage?함수는 TRUE로 리턴된다.

msg_proc_algo.jpg

Windows 종료시에 발생하는 Message 순서

WM_QUIT, WM_CLOSE, WM_DESTORY메세지나, 마우스를 이용한 시스템메뉴의 종료버튼을 누르거나, 키보드의 ALT-F4에 의해서 생기는 종료에 대한 시퀀스다.

(1) 시스템 종료 이벤트 발생하여 WM_SYSCOMMAND메세지를 프로시저에 전달하고 프로시저는 DefWindowsProc()으로 전달한다.

(2) DefWindowsProc()은 응답으로 WM_CLOSE를 프로시저에 전달하고 프로시저는 DefWindowsProc()에 다시 전달한다.

(3) DefWindowsProc()WM_CLOSE에 대한 응답으로 DestroyWindow()호출하고 이 함수는 WM_DESTROY를 프로시저에 전달한다.

(4) 프로시저는 이 메세지가 발생할 경우 PostQuitMessage()를 호출하게 작성하며 함수가 호출될 경우 WM_QUIT가 전달되어 메세지 루프가 종료된다.

따라서 운도우즈에서 종료 확인을 검사할 경우 WM_CLOSE에서 하는 것이 좋다.

Walker쓰레드에 메세지큐와 커널오브젝트(이벤트)를 사용하여 잡처리루프 만들기

GetMessage?나 PeekMessage?의 경우 UI관련된 작업외에는 모두 Sleep상태에 둔다 하지만 메세지에 기반에 UI작업외에 다른 방법도 같이 사용하여 쓰레드를 컨트롤 할 수 있는데 이떄 사용하는 것이 다음의 함수이다. 이벤트 시그널과 메세지 풀링을 동시에 할수 있으며 GetMessage?와 PeekMessage?를 모두 사용 할 수 있다. 더 자세한것은 MSDN을 참고하자.

DWORD MsgWaitForMultipleObjectsEx(DWORD, PHANDLE, DWORD, DWORD, DWORD)

다음은 적절히 사용하는 코드이다.

BOOL fQuit = TRUE;

while(fQuit) {
DWORD dwResult = MsgWaitForMultipleObjectEx(1, &hEvent, INFINITE, QS_ALLEVENTS, MWMO_INPUTAVAILABLE);
switch(dwResult) {
case WAIT_OBJECT_0: // 이벤트가 시그널되었다.
break;
case WAIT_OBJECT_0 + 1: // 메시지가 큐에 있다.
{
MSG msg;
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
fQuie = FALSE;
}
else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
break;
}
}
}

메세지로 데이터 보내기

메세지의 파라미터로 특정 데이터의 주소를 넘겨주는 경우 다른 프로세스라 할지라도 반영이 되는 경우가 있다. 기본적으로 자기 프로세스의 공강에 공유된 메모리 매핑파일을 설정함으로서 가능한데 이때 아무런 설정없이 기본메세지로만 가능한 경우는 메세지의 종류가 알려진 경우이며(WM_SETTEXT, WM_GETTEXT) 다른 사용자 정의 메세지의 경우 시스템은 메모리 매핑파일을 위한인지 포인터를 전달하는건지 모르게 된다. 이런 경우에는 WM_COPYDATA메세지를 이용해서 가능해진다.

다음과 같이 사용하며, 반드시 SendMessage?를 사용해야 하며 사용시 다른 프로세스의 주소 공간에 복사본을 만드는 작업을 한다.

COPYDATASTRCUT cds;
//cds.dwData; // 이 멤버는 보내는 사람이 아무 값이 넣어 유용하게 사용하려는 뜻의 변수이다. 데이터의 형이나 정보등을 넣을수 있다.
cds.cbData = sizeof(send_data);
cds.lpData = send_data_ptr;
SendMessage(hwnd_receiver, WM_COPYDATA, (WPARAM) hwnd_sender, (LPARAM) &cds);

Reference

Documents

Website

Programming Application for window 5th

Win32 API Programming With Visual Basic

Books

AND

출처: http://discuss.joelonsoftware.com/default.asp?design.4.18520.7

Windows messaging system

I have a few questions nagging my brain about windows, and any answers would be appreciated:

1) Is it correct to state that each window has its own message loop, but that the process as a whole operates only as a single thread? How come no windows creation functions allow you to automatically spawn the window as a separate thread, which I am could be beneficial by allowing one window to do work while another is blocked.

2) When a window responds to a message, a function gets called. While this function is underway, new messages must have to queue up, and the window would not be able to respond to paint or timer messages. However, if the function calls a DoModal window, the parent seems to continue to respond to paint and timer messages and yet goes back to the same function after the DoModal is complete. How does this work? While DoModal is active can the parent still process messages? Doesnt this mean that the parent window can now effectively process two messages at a time, in a multi-threading type of way?

3) I am only aware of windows systems? Has the messaging subsystem idea been around since Win95? Is this how other OSes operate, just with their own set of messages?
ThreadMan
Saturday, October 23, 2004
 
 
There is only one active message loop. When you create a modal window, it has its own message loop, and the previous message loop is temporarily disabled.

Whatever window is currently active receives *all* messages for all windows.

The magic function is DispatchMessage, called from inside the message loop. It checks which window the message is for, and sends it to the appropriate window procedure.

This way you get the appearance of 'multithreading' i.e. all windows are processing as normal.

Questions like this usually solve themselves after doing some hands-on programming.
Alex Send private email
Sunday, October 24, 2004
 
 
> and yet goes back to the same function after the DoModal is complete.

The window procedure is "reentrant", which means it can be interrupted while in the middle of something, and then called from somewhere else, and it will perform correctly.
Alex Send private email
Sunday, October 24, 2004
 
 
"There is only one active message loop. When you create a modal window, it has its own message loop, and the previous message loop is temporarily disabled."

I think this is incorrect as the parent window of a modal can still process WM_PAINT messages. Otherwise moving the child window across the parent would not cause the parent to redraw itself. My guess is that WM_PAINT, WM_TIMER etc. are still active, why WM_CHAR, WM_MOUSEDOWN etc. are dropped.
ThreadMan
Sunday, October 24, 2004
 
 
All windows participate in the message queue, regardless of whether they're modal or non-modal.  There are wrinkles about whether an application is system modal or modal to an application.

All windowing systems use a similar mechanism but tend to be quite different in specific implementation.  Windows 1.0 had exactly the same message queue.
Simon Lucy Send private email
Sunday, October 24, 2004
 
 
> can still process WM_PAINT messages.

well you have to read the whole answer. Did you notice the part about DispatchMessage? Or do you skip over what you don't understand, and prefer to keep with whatever ideas you have of your own?
Alex Send private email
Sunday, October 24, 2004
 
 
Wow, the answers to this question have been all over the place, and mostly wrong (or glossing over important details).

Each thread in a Win32 app can have it's own message loop. All windows created by that thread share that thread's message loop. The prototypical form of this loop is:

  while( GetMessage( &msg, ... ) ) {
    TranslateMessage( &msg, ... );
    DispatchMessage( &msg, ... );
  }

(I'm too lazy to look up the exact parameters, so trust me on the ..., ok?)

Let's look at what each of these lines does. First, GetMessage. This is basically "wait until the OS says there's a message for this thread." Once a message appears on the queue, GetMessage returns, and you need to process it.

The first stage of processing is the TranslateMessage call, which takes raw hardware messages (WM_KEYDOWN, for example) and turns them into somewhat friendlier message (WM_CHAR).

The second stage is DispatchMessage. This figures out which window on the thread that the message was actually aimed at and calls that window's window proc. As a result, each window recieves it's own messages.

Inside the DoModal call, there's a chunk of code that spins the message loop as well. Since you're still on the main thread, all the windows on that thread still process their messages (paint, move, etc.) even when a modal dialog is displayed.

So, why don't parent windows get keyboard or mouse messages? Part of the windowing system is that only the "active" window gets these messages. Since the parent window is disabled, it cannot be activated, and therefore the message queueing automatically routes these to the currently active window, which is the modal dialog.
Chris Tavares Send private email
Monday, October 25, 2004
 
 
Oh, and yes, most other windowing systems act the same way in general, although the specifics of course vary widely from system to system.

X-Windows, for example, actually uses a network socket to transmit its messages, which is how you get the 'run on one machine, show window on another' behavior which is the best thing about X.
Chris Tavares Send private email
Monday, October 25, 2004
 
 

This topic is archived. No further replies will be accepted.

AND

출처: http://www.asmlove.co.kr/zBdC7/viewtopic.php?p=4052

다음의 매크로 치환은 토큰화 되어 my_func 매크로는 적법한 토큰인 my_f 로 치환됨을 보장합니다.

코드:
#define my_func_macro(m_prefix,m_name) m_prefix ## m_name
#define my_func(m_name) my_func_macro(my_,m_name)

void my_f(void)
{
 printf("hello\n");
}

int main(void)
{
 my_func(f)();
 return(0);
}



my_func_macro 를 my_func 로 치환한 것은 ## 구문의 이중확장 방지규칙을 벗어나기 위한것입니다.
예를 들자면 다음과 같은 경우
코드:
#define my_func_macro(m_prefix,m_name) m_prefix ## m_name
#define my_func(m_prefix,m_name) my_func_macro(m_prefix,m_name)

void my_my_f(void)
{
 printf("world\n");
}


다음의 호출은 적법하게 my_my_f로 치환되어 my_my_f 함수를 호출하게 되지만
코드:
my_func(my_,f)();
my_func(my_,my_func(my_,f))();
my_func(my_,my_func_macro(my_,f))();


다음의 호출은 ##의 2중확장 금지조건에 의해서 my_my_f 로 치환되지 못하게 됩니다.
코드:
my_func_macro(my_,my_func_macro(my_,f))();
my_func_macro(my_,my_func(my_,f))();


이때문에 my_func_macro 를 my_func 매크로로 감싸도록 하여 ## 이중확장 금지조건을 회피할수 있게 됩니다.



_________________
도구의 결함은 장인의 손으로 극복한다.

AND

I am loading multiple language resources and the strings load properly. I am having a problem getting the dialog identifiers. Here is the code:
Code:

LPCTSTR get_dialog(int nResID)
{
LPCTSTR ptcBlock;
HGLOBAL hg;
HRSRC hResourceInfoBlock;
LPCTSTR szTemplate = MAKEINTRESOURCE(nResID);
if( // use methods until we find one that works
(NULL != (hResourceInfoBlock = FindResourceEx( hInstGlobal, RT_DIALOG, szTemplate, GetUserDefaultLangID()))) ||
(NULL != (hResourceInfoBlock = FindResourceEx( hInstGlobal, RT_DIALOG, szTemplate, GetSystemDefaultLangID()))) ||
(NULL != (hResourceInfoBlock = FindResourceEx( hInstGlobal, RT_DIALOG, szTemplate, MAKELANGID( LANG_ENGLISH, SUBLANG_ENGLISH_US ))))
)
{
;
}
//
if( hResourceInfoBlock )
{
if(
(NULL != (hg = LoadResource( hInstGlobal, hResourceInfoBlock ))) &&
(NULL != (ptcBlock = (char*)LockResource(hg))) )
{
return ptcBlock;
}
}
return 0;
}
The result should be fed into CreateDialogParam as a dialog ID

Code:

lpDialog = get_dialog(MAKEINTRESOURCE(IDD_MAIN));
hDlg = CreateDialogParam(hInstance, lpDialog,
hWndParent, (DLGPROC)DlgProc, (LPARAM)this);
Please take a look and see what am I doing wrong.

Thanks
AND

출처: http://blog.naver.com/ipes4579/130009400614

북마크 사이트 10

1.MSDN(Microsoft Developer Network)

http://msdn.microsoft.com


2.CodeGuru

http://www.codeguru.com


3.CodeProject

http://www.codeproject.com


4.MVPS

http://www.mvps.org


5.데브피아

http://www.devpia.com


6.Sysinternals

http://www.sysinternals.com


7.Sourceforge

http://sourceforge.net


8.GNU

http://www.gnu.org


9.KLDP

http://kldp.org


10.KLTP

http://kltp.kldp.org


책 목록 10

1.Code Complete - Steve McConnell 저


2.Writing Solid Code - Steve Maquire 저


3. The C Programming Language -

Brian W.Kernighan, Dennis M.Ritchie 저


4.The C++ Programming Language - Bjame Stroustrup 저


5.Design Pattern - Gang of Four  저


6. TCP/IP IIIustrated Volume I, II, III - W.Richard Stevens 저


7. Debugging Application - John Robbins 저


8. Advanced Programming in UNIX Environment -

W. Richard Stevens 저


9. Operating System Concepts - Abraham Silberschatz 외


10. UNIX Network Programming - W. Richard Stevens 저



------

프로그래밍의 기본이 되는 책들, bible들이다.


'속전속결 프로그래밍 입문' 이란 책에서 펌.


AND

출처: http://www.sojins.net/tt/sojins/458

GetSystemInfo()함수를 사용.
SYSTEM_INFO의 wProcessorArchitecture 값으로 구분한다.

(1) x64 (AMD or Intel) : PROCESSOR_ARCHITECTURE_AMD64 9
(2) WOW64 : PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 10
(3) Intel Itanium Processor Family (IPF) : PROCESSOR_ARCHITECTURE_IA64 6
(4) x86 : PROCESSOR_ARCHITECTURE_INTEL 0
(5) Unknown processor : PROCESSOR_ARCHITECTURE_UNKNOWN 0xffff

[ inf string ]
x86 : (4)
amd64 : (1)
ia64 : (3)

+
DDK에서 OS버전별로 바이너리를 구분하여 빌드하는데, 이 값과 같다.
자세한 것은 MSDN에서 GetSystemInfo()를 검색해보면 예제소스도 있다는 사실.


 

이 글의 관련글(Trackback) 주소 :: http://www.sojins.net/tt/sojins/trackback/458
sojins| 2007/02/21 18:35 | PERMALINK | EDIT/DEL | REPLY
WOW64에서 실행될 경우 GetNativeSystemInfo() function을 사용한다.
WOW64인지 아닌지의 구분은 IsWow64Process() function으로 하면 된다.
AND