본장은 실제 윈도우 커널에 대한 컨트롤에 필요한 부분들에 대한 설명을 합니다. 윈도우에서 메모리관리를 하는 방법과 파일에서 데이터를 읽고 쓰는 방법, 특정작업을 현재 어플리케이션의 수행과 관계없이 독자적으로 움직이게 하는 스레드,클립보드에 데이터를 복사하고 클립보드에서 데이터를 얻는 방법등입니다. 윈도우95로 넘어가면서 이 부분들이 매우 간편하고 편리하게 되어있습니다. 지금까지는 매우 복잡했던 메모리관리, 파일공유에서 문제점이 많았던 부분 다중 루프를 돌릴 경우 시스템전체가 작업을 중지하던 부분들이 윈도우95 이후부터는 해결이 되었습니다.
본장을 통해서 이런 작업들을 어떻게 하는가에 대해서 알게 될것입니다.
1.메모리 관리
윈도우에서 메모리관리는 어떻게 해야하는가? 정답을 말하자며 "잘해야 한다"입니다. 또하나의 정답을 이야기 하자면 "대강해도 된다" 라고 말할수 있습니다. 그이유는 16비트 OS에서 문제시 되었던 세그먼트 개념을 완전히 없애 버렸기 때문입니다. 윈도우95는 총4기가의 메모리를 사용할수 있습니다.물론 시스템에 4기가의 메모리가 있다면 말입니다. 이 메모리는 메인 메모리 뿐만 아니라 보조 기억장치인 HDD까지 포함한 메모리를 의미합니다. 메인 메모리가 32메가이고 디스크용량이 2기가 이며 현재 남아있는 디스크용량이 1기가이라면 윈도우95는 32메가와 1기가를 함께 메모리를 사용합니다. 윈도우95에서는 실제 4기가의 메모리를 사용할수 있다는 가상아래 2기가는 시스템에서 나머지 2기가는 어플리케이션에서 사용할수 있도록 되어 있으며 윈도우 NT에서는 이것을 분리하지 않고 4기가를 통합적으로 사용합니다. 그렇기 때문에 디스크 용량이 충분하다면 또한 메인 메모리가 많다면 사용할수 있는 만큼 사용할수 있기 때문에 메모리 관리에 큰 문제점이 없다는 말을 할 수가 있는 것입니다. 메모리 관리에 큰문제점이 없다는 의미와 메모리 관리를 하지 말라는 의미는 다릅니다. 처리할수 있는 메모리가 많다고 하여도 시스템에서 존재하고 있는 메모리가 적다면 이것을 적당하게 나누어서 소유해야 하며 또한 어플리케이션은 가장 최상의 속도로 움직여야 하기 때문입니다. 메인 메모리에서 데이터를 읽어 사용할경우에는 매우 빠르게 움직일수 있으나 디스크에서 데이터를 읽을 경우에는 속도가 떨어집니다. (디스크에서 데이터를 읽는 일이 수행되지 않았는데 가끔 드르륵하는 경우) 따라서 빠르게 읽어야 할데이터는 메인 메모리에 천천히 읽어도 되는 데이터는 디스크용량에서 또한 이것 저것 관계없다면 시스템이 알아서 처리하도록 하는 방법들이 필요한것입니다.
가상 메모리 (Virtual memory)
윈도우 시스템은 가상 메모리를 사용합니다. 가상 메모리란 디스크 용량을 메모리로 생각하고 사용한다는 의미입니다. 메인 메모리는 가상 활성화 된 프로그램에서 작업에 필요한 필수 메모리만 설정되며 순위가 낮은 메모리는 디스크에 페이지 블록으로 기록됩니다. 쉽게 보면 현재 수행되는 메모리들이 페이지 블록으로 디스크에 저장이 되고 필요한 페이지 들만 메인 메모리에 올려 집니다. 이렇게 올려지고 사용을 한후에 자주 사용하지 않는 부분이라면 다시 디스크의 페이지 블록으로 저장됩니다. 이런 처리는 시스템에서 자동으로 처리됩니다. 결국 시스템에서는 현재 최우선으로 필요한 페이지 블록이 무엇인가를 확인해야 하며 이때 필요한 페이지 블록을 메인 메모리에 올려 주도록 합니다.
시스템에서는 메인 메모리에 현재 사용되는 전체 메모리의 페이지 테이블만 가지고 있으며 페이지를 적재할수 있는 필수적인 메인 메모리를 확보해 놓습니다. 예를 들어서 메인 메모리에 적재할수 있는 페이지 블록이 5개라고 가정을 한다면 시스템 먼저 프로그램이 수행될 때 필요한 메모리를 페이지 테이블에서 가상 메모리 번지를 찾고 이에 해당하는 데이터를 디스크 용량에서 메모리로 로드합니다. 이렇게 프로그램이 수행되는 도중 또다른 데이터가 필요할 경우 시스템은 또다시 페이지 테이블에서 가상 메모리 번지를 찾고 이에 해당하는 데이터를 메모리에 로드합니다. 이런식으로 로드하였을 때 5개의 블록을 다사용했다면 그다음 어떻게 될까요? 가장 먼저 사용한 메모리 블록을 다시 디스크에 저장하게 됩니다. 위의 예에서 보면 최초에 사용한 메모리가 다시 디스크로 돌아가게 되는 것입니다. 이렇게 페이지 블록들이 디스크에서 메인 메모리로 이동이 되며 이렇게 이동이 되는 상황을 페이지 테이블에 기록하게 되는 것입니다. 보통 이런 페이지 테이블은 케쉬 메모리에 저장을 합니다. 그이유는 케쉬 메모리에서 데이터를 얻고자 하는 경우가 메인 메모리에서 데이터를 얻고자 하는 때보다 속도가 매우 빠르기 때문입니다. 페이지 테이블에서 가상 메모리 번지를 얻고자 하는 속도는 매우빨라야 하기 때문입니다.
가상 메모리를 관리는 일반적으로 시스템이 처리를 합니다. 즉 시스템이 여러 알고리듬을 이용해서 필수적인 메모리를 결정하고 결정된 메모리를 디스크에서에서 메인메모리로 이동을 합니다. 때로는 어플리케이션 프로그램에서도 이런 결정을 해야할 필요가 있습니다. 예를 들어서 특정 프로그램에서 "도둑이야!" 라는 음성데이터를 수시로 출력해야 할 필요성이 있을 때가 있다고 가정을 합시다. 음성데이터를 실제로 매우 많은 용량을 가지고 있습니다. 이것을 시스템에 관리를 맺긴다고 할 경우 음성 데이터는 디스크로 또는 메모리로 임의적인 이동을 하게 될것입니다. 디스크에 있을경우에는 데이터를 로드하고자 하는 시간이 걸리기 때문에 메모리에 있을경우보다 다소 늦게 음성이 출력될것이고 이것 때문에 이속도 때문에 도둑은 물건을 훔쳐서 달아날수도 있을것입니다. 때로는 아주 작은 메모리만 필로하는 경우도 있을수 있습니다. 이런 데이터가 페이지 기법에 의해서 디스크에서 메모리로 또는 메모리에서 디스크로 이동한다는 것은 매우 불필요합니다. 예를 들어서 "안녕하세요" 라는 문자열 데이터를 사용한다고 할 경우 이것은 10바이트의 데이터 밖에는 되지 않습니다. 이데이터를 출력하고자 할 때 디스크에 있어서 드르륵 하면서 시간이 걸린다면 이것도 좋은 프로그램은 아닙니다.
결론을 내린다면 프로그램을 제작할 때 데이터가 이동을 어떻게 결정할것인가를 정확하게 결정하는 것이 메모리 관리의 핵심입니다. 이데이터는 필수적으로 메모리에만 상주되어야하며 이데이터는 언제나 디스크에 있어서 필요시만 로드해야한다는 부분들을 결정하는 것이 어플리케이션 프로그램의 책임이라고 볼수 있습니다. 본책에서는 페이지블럭과 페이지 테이블에서 이동되는 방법 및 주소 번지 지정이 어떻게 되어있는가에 대한 설명을 하지는 않습니다. 이부분은 어플리케이션 프로그램을 작성할때는 거히 필요가 없기 때문입니다. 본책에서 설명하는 부분은 메모리를 어떻게 할당하고 어떻게 정의하는가에 대해 초점을 맞추고 있습니다.
표준 메모리 함수
표준 메모리 함수는 전에도 설명을 한것처럼 malloc와 calloc그리고 realloc함수입니다. 이런 함수를 이용하여 메모리를 할당하고 할당된 메모리를 해제 하고자 할 때는 free를 사용하면 됩니다. 예를 들어 char단위로 512의 메모리르 할당하고자 한다면 다음과 같이 할수 있습니다.
char *data;
data=malloc(512);
메모리를 할당하고 이할당된 메모리에 모든 값을 0으로 채우고자 한다면 calloc함수를 사용하여 할당할수 있습니다.
char *data;
data =calloc(512);
현재 설정된 data라는 메모리가 512이었는데 후에 512가 더필요하다고 할 경우 이때 사용하는 함수가 realloc입니다.
data= realloc(1024);
realloc함수를 사용하면 이전에 사용한 메모리 뒤로 512바이트가 더 증가됩니다. 표준 메모리 함수를 사용하여 메모리를 할당하면 이것은 모두 가상메모리로 처리 됩니다. 즉 메모리가 시스템에 의해서 디스크에서 메인 메모리로 또는 메인 메모리에서 디스크로 이동하는 것입니다.
윈도우 메모리 함수
윈도우에서 사용되는 메모리 함수는 플러그를 두어서 이플러그에 의해서 어떤 형태의 메모리를 설정할까를 결정합니다. 윈도우에서 사용하는 일반적인 메모리 함수는 GlobalAlloc입니다.
HGLOBAL GlobalAlloc(
UINT uFlags, // 할당 속성 플러그
DWORD dwBytes // 할당할 메모리
);
HGLOBAL이라는 형식은 쉽게 보면 void와 같이 어느 형에도 적응을 할수 있는 형입니다. GlobalAlloc에 의해서 넘겨지는 메모리는 보통 HANDEL 형으로 넘겨 받습니다. 그리고 이메모리를 필요한 포인터에 점유권을 넘겨줄수가 있습니다.
uFlags는 다양한 메모리 할당을 정의하는 플러그입니다. 이플러그값들은 다음과 같습니다.
GMEM_FIXED :메모리 이동이 없다. 고정된 형태로 메모리를 설정한다.
GMEM_MOVEABLE : 메모리가 이동이 된다.
GPTR : 이동이 없는 고정된 메모리 형태(GMEM_FIXED) 로 설정되면서 해당되는 메모리가 모두 0으로 설정된다.(GMEM_ZEROINIT ).
GHND : 메모리가 이동이 되면서 (GMEM_MOVEABLE) 해당되는 메모리가 모두 0으로 설정된다.(GMEM_ZEROINIT).
GMEM_ZEROINIT : 할당된 메모리가 0으로 설정된다.
GMEM_SHARE : 메모리가 공유될수 있다.
GMEM_FIXED 로 메모리를 설정하면 이메모리는 메인 메모리에 상주하게 됩니다. 즉 이동이 되지 않는 형태라는 의미입니다. 이렇게 설정을 하면 이메모리는 항시 사용을 할 때 빠른속도로 사용할수 있습니다. 또한 부가적으로 항시 사용하지 않는 메모리라면 이 옵션을 설정하지 않는 것이 좋습니다. GMEM_FIXED를 설정하였을경우에는 메모리를 꼭해제를 해주어야 합니다. 그렇지 않을 경우 공포의 파란 화면이 나타날수도 있기 때문입니다. GMEM_MOVEABLE 은 페이지 기법에 의해서 이동이 되면서 필요시에만 메모리에 로드되는 형태입니다. 필수적으로 항시 설정할 필요가 없는 데이터들은 이플러그를 사용합니다. 이플러그를 사용했다고 데이터가 유실된다는 의미가 아닙니다. 이플러그를 사용하면 데이터가 때로는 디스크에 때로는 메모리에 시스템에 의해서 이동이 된다는 의미입니다. 혹자는 GMEM_MOVEABLE를 사용하면 이동이 되기 때문에 데이터가 없어지지 않을까 하는 걱정을 하는경우도 보았는데 그런의미가 아닙니다. 페이지 기법에 의거해서 움직인다는 의미입니다. GMEM_ZEROINIT 는 메모리가 할당되면 그안에 내용들이 모두 0으로 설정된다는 것입니다. calloc와 비슷한 기능을 가진 플러그입니다. 메모리를 할당하면 이 메모리에는 전에 사용했던 쓰레기 값이 남아 있습니다. 이런 쓰레기 값을 깨끗하게 지우고자 할 경우 이 플러그를 사용하시면 됩니다. GMEM_SHARE 는 메모리를 다른 프로세서에서 공유할수 있다는 의미입니다.다른 플러그들은 GMEM_ZEROINIT를 포함하는가 안하는에 의해서 분리된 옵션들입니다. GlobalAlloc 함수는 메모리를 할당할뿐입니다. 예약한다는 의미를 보면 됩니다. 이것을 어떻게 사용하는가에 대한 문제는 프로그래머에 주어집니다. 예를 든다면 만득이 아버지가 콘도를 예약하였다 하여도 이것을 만득이가 사용할수도 있습니다. 즉 콘도를 사용할수 있다는 만득이 아버지의 허가만 있다면요. 마찬가지로 GlobalAlloc에 의해서 메모리가 할당되면 이메모리를 프로그램 내에서 다른 포인터 변수가 사용할 수가 있습니다. GlobalAlloc에 함수에 의해서 넘겨받은 핸들러의 허가만 있다면요. 이때 사용허가를 해주는 함수가 GlobalLock입니다.
LPVOID GlobalLock(
HGLOBAL hMem // 할당한 메모리 핸들
);
다음은 메모리를 할당하고 이것을 특정 변수에게 허가권을 넘겨준 형태입니다.
LPSTR datapoint;
HANDLE hdata;
hdata=GlobalAlloc(GHND,512);
datapoint=(LPSTR)GlobalLock(hdata);
위의 예는 hdata가 메모리를 512로 할당하고 이메모리의 사용권을 datapoint에 넘겨준 형태입니다. 이런식으로 메모리 할당과 사용권을 분리한 것은 메모리를 효율적으로 사용하자는 의미에서입니다. 위의 예에서 datapoint가 메모리를 사용하다가 다른 곳에서 이데이터를 사용하고자 할 경우 일반적인 방법에서는 다시 메모리르 할당하고 datapoint에 설정된 데이터를 복사하는 방법을 사용하는데, 위의 두함수를 이용하면 한 개의메모리로 사용이 가능합니다.
LPSTR datapoint;
LPSTR datapoint2;
HANDLE hdata;
hdata=GlobalAlloc(GHND,512);
datapoint=(LPSTR)GlobalLock(hdata);
datapoint가 데이터를 사용한다.
GlobalUnlock(hdata);//사용권을 해제한다.
datapoint2=GlobalLock(GHND,512);//datapoint2에게 허가권을 준다.
datapoint2가 데이터를 사용한다.
위의 예는 datapoint가 hdata가 예약한 메모리를 사용하고 또다시 datapoint2가 hdata가 예약한 메모리를 사용하는 예입니다. 처음에는 datapoint가 메모리 사용허가권을 받은뒤에 사용을 하다가 datapoint2가 다시 사용하고자 한다면 GlobalUnlock함수를 이용하여 datapoint가 사용하는 허가권을 해제한후에 datapoint2에 다시 허가권을 주는것입니다.
이런 식으로 두 개의 포인터가 한 개의 메모리를 사용을 할 수가 있습니다.
메모리를 해제 하고자 할 경우 GlobalFree함수를 사용하면 됩니다.
HGLOBAL GlobalFree(
HGLOBAL hMem // 메모리 핸들
);
만약 현재 다른 변수에 허가권이 넘겨져 있다면 즉 GlobalLock가 실행되었다면 GlobalFree하기 이전에 GlobalUnlock를 해주어야 합니다. 메모리르 해제 할경우에는 꼭 메모리 사용허가권을 우선적으로 해제를 해주어야 합니다. 메모리를 할당하였다가 현재 할당한 메모리보다 더 필요할 경우 즉 realloc와 같은 함수가 윈도우에서는 GlobalRealloc로 제공됩니다.
HGLOBAL GlobalReAlloc(
HGLOBAL hMem, // 메모리 사이즈
DWORD dwBytes, // 새로 설정할 크기
UINT uFlags // 할당 속성 플러그
);
위의 예에서 hdata에 1024의 메모리를 할당하고 한다면 다음과 같이 할수 있습니다.
hdata=GlobalReAlloc(hdata,GHND,1024);
GlobalReAlloc함수 또한 현재 GlobalLock가 되어 있다면 해제를 해주어야 합니다. 즉 GlobalUnlock함수를 실행한 후에 GlobalReAlloc를 사용해야 합니다.
메모리에 데이터를 설정하다가 잘못했을 경우 메모리 블록을 초과해서 다는 메모리에 피해를 주는경우가 있고 때로는 메모리 자체를 어떻게 할수 없을 경우도 있습니다. 즉 현재 설정된 메모리가 GlobalLock를 하였는데 GlobalUnlock를 하여도 해제가 되지 않고 GlobalFree를 할려고 했는데 가동이 되지 않을 경우 또는 때로
는 필요없어서 그냥 무시할경우도 있습니다. 즉 할당된 메모리를 가감하게 버리고자 할 경우 이때 사용하는 함수가 GlobalDiscard입니다.
HGLOBAL GlobalDiscard(
HGLOBAL hglbMem // 메모리 핸들
)
만일 hdata에 할당된 메모리를 버리고자 할 경우 다음과 같이 할수 있습니다.
GlobalDiscard(hdata);
현재 시스템의 메모리의 상태를 얻고자 할 경우 이때는 GlobalMemoryStatus 함수를 사용할수 있습니다.
VOID GlobalMemoryStatus(
LPMEMORYSTATUS lpBuffer //메모리 상태 구조체 포인터
);
MEMORYSTATUS 구조체는 다음과 같습니다.
typedef struct _MEMORYSTATUS { // mst
DWORD dwLength; // MEMORYSTATUS 구조체 크기
DWORD dwMemoryLoad; // 현재 사용되고 있는 메모리 퍼센트
DWORD dwTotalPhys; // 전체 물리적 메모리 크기
DWORD dwAvailPhys; // 남아 있는 물리적 메모리
DWORD dwTotalPageFile; // 전체 페이지 파일수
DWORD dwAvailPageFile; // 현재 사용할수 잇는 페이지 파일수
DWORD dwTotalVirtual; // 전체 가상 메모리
DWORD dwAvailVirtual; // 현재 사용가능한 가상메모리
} MEMORYSTATUS, *LPMEMORYSTATUS;
GlobalMemoryStatus를 사용하면 현재 메인 메모리 용량과 남아 있는 용량 또한 가상메모리 용량과 남아 있는 가상 메모리 용량을 확인할수 있습니다. 거대한 데이터를 로드하고자 할 경우 이함수를 먼저 이용하는 것이 좋습니다. 이 예는 다음과 같습니다.
MEMORYSTATUS ms;
GlobalMemoryStatus(&ms);
if(ms.dwAvailVirtual < msize)
{
//메모리가 부족함
}
else
{
//GlobalAlloc함수등을 이용하여 메모리를 할당한다.
}
윈도우 가상 메모리 함수
윈도우에서는 새로운 가상 메모리 함수를 제공합니다. 이함수는 Virtual 계열의 함수입니다.
이함수들은 거대한 메모리를 사용하고자 할 경우 사용되는 함수입니다. Virtual을 이용하여 할당한 메모리는 여러개의 페이질 블록을 링크하여 사용합니다. 결국 Virtual계열의 함수를 사용하며 메모리를 사용하면 이것은 언제나 디스크에 존재하고 프로그램수행중 필요한 포인터 블록만 메모리에 로드하게 됩니다. 이함수를 사용하여 할당된 메모리는 속도가 느리다는 단점이 있으나 거대한 메모리르 사용할때는 매우 편리하기도 합니다.
Virtual 로 메모리를 할당하고자 할 경우 다음과 같이 합니다.
LPVOID VirtualAlloc(
LPVOID lpAddress, // 예약된 메모리 번지 시작포인터
DWORD dwSize, // 메모리 크기
DWORD flAllocationType, // 메모리 할당 형태
DWORD flProtect // 보호 모드 형태
);
lpAddress는 처음 메모리를 설정하고자 할 때는 NULL로 사용해야합니다. lpAddress의 의미는 예약된 메모리의 특정 포인터를 의미하기 때문입니다. VirtualAlloc함수는 에 의해서 할당된 메모리에서 또다시 VirtualAlloc를 할수 있습니다. 이럴 경우 lpAddress에 예약된 메모리 번지의 시작 포인터를 넘겨줍니다.
flAllocationType는 다음과 같은 값으로 설정할수 있습니다.
MEM_COMMIT : 위탁플러그
MEM_RESERVE : 예약 플러그
MEM_COMMIT는 이미 설정된 메모리를 위탁할 경우 이며 MEM_RESERVE는 메모리를 새롭게 예약할경우에 사용됩니다. 새롭게 메모리를 할당하고자 한다면 MEM_RESERVE를 사용하면 됩니다.
flProtect 는 메모리의 보호모드와 데이터 스타일을 설정하는 플러그입니다. 이값은 다음과 같습니다.
PAGE_READONLY :읽기 전용으로 설정
PAGE_READWRITE:읽고 쓰기
PAGE_EXECUTE : 실행가능한 데이터
PAGE_EXECUTE_READ: 실행가능하며 읽기
PAGE_EXECUTE_READWRITE: 실행가능하며 읽기 쓰기
메모리를 할당함과 함께 메모리 사용권을 넘겨주고자 할 경우 VirtualLock함수를 사용권 해제는 VirtualUnlock함수를 메모리를 해제할경우에는 VirtualFree함수를 사용합니다. 이 함수들은 도움말을 참조하시면 됩니다. 메모리를 사용하는 방법은 Global과 같은 방법입니다. 다만 Virtual 계열의 메모리 함수는 메모리 안에서 또다른 메모리를 확보할수있으며 실행가능한 데이터를 메모리로 설정할수 있다는 것입니다. Visual C++에서 컴파일하여 EXE를 만들 때 다량의 메모리가 필요합니다. 이럴경우에 Global계열의 메모리를 사용하는 것이 불가능합니다. 이때는 Virtual메모리를 사용합니다.
윈도우 힙 메모리 함수
Heap 메모리란 프로그램이 수행될 때 일시적으로 필요한 데이터를 적재하는 메모리입니다. 이 메모리는 아주 작은 형태의 메모리입니다. 허나 이메모리는 이동되지 않습니다. 즉 메인 메모리에 적재됩니다. 그렇기 때문에 빠르게 접근할 수가 있습니다. 작은 데이터이면서 항시 사용해야 하고 그리고 빠르게 접근해야할 필요성이 있을 경우 Heap계열의 함수를 사용합니다. 표1은 Heap게열의 함수리스트입니다.
(표1) Heap계열 메모리 함수
함수명 | 내용 |
HeapAlloc | 힙 메모리를 확보한다. |
HeapLock | 메모리 사용을 허가 한다. |
HeapUnlock | 메모리 사용허가를 해제 한다. |
HeapFree | 메모리를 해제한다 |
HeapReAlloc | 메모리를 새로운 크기로 재할당한다. |
2.파일 입출력
C언어를 배울 때 _open,_write,_read,_close,_lseek등의 함수를 배웠습니다. 이함수들은 표준 입출력 함수입니다. 윈도우즈 상에서도 이함수는 완벽하게 동작합니다. 파일 입출력을 하고자 할 경우 이함수만 이용해도 어려움은 없습니다. 이함수에 외에 윈도우에서 사용할수 있는 새로운 함수들을 SDK에서즌 제공합니다.이함수들이 CreateFile,ReadFile,WriteFile등의 함수입니다. 이함수들은 파일에 데이터를 입출력하고자 할 경우 보호모드가 설정되게 할수 있는 기능이 있습니다. 즉 현재 특정 프로그램에서 CreateFile를 이용하여 파일을 오픈하였다면 이 파일을 다른 프로그램에서 접근할수 없게 할수도 있습니다. 표준 입출력함수에서 제공되지 않는 기능들입니다.
파일 열기와 만들기
표준 입출력에서는 파일을 열고자 할 경우 _open 파일을 만들고자 할 경우 _creat를 사용하였습니다. 윈도우에서는 이두개를 포함한 한 개의 함수 CreateFile를 이용하여 파일을 열고 만들 수 있습니다.
HANDLE CreateFile(
LPCTSTR lpFileName, //파일 명
DWORD dwDesiredAccess, // 읽기 쓰기 모드
DWORD dwShareMode, // 공유 모드
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 보안 속성
DWORD dwCreationDistribution, // 만들것인가 오픈할것인가?
DWORD dwFlagsAndAttributes, // 파일속성
HANDLE hTemplateFile // 속성을 저장할 템플레이트 파일
);
lpFileName은 설정할 파일 명입니다. 이파일명은 일반 파일외에도 여러 디바이스 파일 파이프등을 설정할수도 있습니다. 예를 든다면 RS232C 포트를 파일로 간주하고 CreateFile를 이용하여 오픈할수 있다는 것입니다. 이것은 원초적으로 보면 유닉스 체제에서의 개념으로 볼수 있습니다. 유닉스에서는 모든 하드웨어 디바이스 및 포트 또한 파이프라인 등을 파일로 처리합니다. 그렇기 때문에 파일을 개방하듯이 시스템 포트를 개방할수 있는 것입니다.
dwDesireAccess 는 읽기 쓰기에 대한 모드입니다. GENERIC_READ 이면 읽기 전용이며 GENERIC_WRITE 이면 쓰기 모드입니다. GENERIC_READ | GENERIC_WRITE를 함께 쓴다면 읽기도 가능하고 쓰기도 가능하다는 의미입니다.
파일이 아닌 디바이스 일경우에는 이것을 같이 쓸수가 없는 경우가 있습니다. 예를 들어 프린터가 설정되어 있지않는 LPT포트 같은 경우를 위의 함수로 오픈하고자 할 경우 두 개의 옵션을 함께 사용하여 열지 못하고 한 개씩 따로열어야 합니다. 즉 데이터를 디바이스에 보내고자 한다면 먼저 GENERIC_WRITE를 이용하고 다시 디바이스에서 데이터를 얻고자 한다면 GENERIC_READ를 사용해야 합니다.
dwShareMode 는 공유에 대한 속성을 설정하는 플러그입니다. FILE_SHARE_READ 이면 읽기를 공유할수 있으며 FILE_SHARE_WRITE 쓰기를 공유할수 있습니다. 두 개다 공유하고자 한다면 FILE_SHARE_READ | FILE_SHARE_WRITE를 함께 사용하면 됩니다. lpSecurityAttributes 보안에 관계된 속성을 정의하는 구조체입니다. dwCreationDistribution 는 파일을 생성하는 것을 정의하는 속성으로 파일을 새로 만들것인가 아니면 기존에 있는 파일을 오픈할것인가를 결정합니다. CREATE_NEW 이면 새로 만드는 것을 의미합니다.만일 현재 파일이 존해하면 위의 플러그를 사용하여 파일을 새로 만들고자 할경우에는 파일이 만들어지지 않습니다. 기존에 있는 파일을 지우고 만들고자 할경우에는 즉 파일이 존재하는 것을 무시하고 새로운 파일을 만들고자 할경우에는 CREATE_ALWAYS 플러그를 사용합니다. 기존에 있는 파일을 오픈하고자 할경우에는 OPEN_EXISTING 를 사용합니다. 만일 파일이 존재 하지 않을 경우에는 파일이 만들어지지 않으면서 CreateFile는 INVALID_HANDLE_VALUE 값을 리턴합니다. 존재 하지 않을경우에도 파일을 만들어서 개방하고자 한다면 OPEN_ALWAYS 플러그를 사용합니다. dwFlagsAndAttributes 는 파일 속성을 말합니다. 일반적인 파일을 오픈할경우에는 FILE_ATTRIBUTE_NORMAL를 사용하며 그외에 여러 플러그들이 있습니다. 이것은 도움말을 참조하시기 바랍니다.
CreateFile함수를 이용하여 "test.c"라는 파일을 열고자 한다면 다음과 같이 할수 있습니다.
HANDLE hFile;
hFile=CreateFile("test.c",GENERIC_READ |
GENERIC_WRITE,FILE_SHARE_READ |FILE_SHARE_WRITE ,
NULL,OPEN_EXISTING ,0,0);
파일 읽기 와 쓰기
파일에 데이터를 넣고자 할경우에 사용하는 함수는 WriteFile입니다.또한 데이터를 읽고자 할 때는 ReadFile함수를 사용합니다. 이함수들은 다음과 같습니다.
BOOL WriteFile(
HANDLE hFile, //파일 핸들
LPCVOID lpBuffer, // 기록할 데이터
DWORD nNumberOfBytesToWrite, //쓰고자 하는 바이트수
LPDWORD lpNumberOfBytesWritten, //실제 쓰여진 바이트
LPOVERLAPPED lpOverlapped //I/O overlapped 구조체 포인터
);
BOOL ReadFile(
HANDLE hFile, // 파일 핸들
LPVOID lpBuffer, //기록할 데이터
DWORD nNumberOfBytesToRead, // 읽고자 하는 바이트수
LPDWORD lpNumberOfBytesRead, // 실제 읽혀진 바이트
LPOVERLAPPED lpOverlapped // I/O overlaped 구조체 포인터
);
lpBuffer은 기록하고자 하는 데이터이며 nNumberOfBytesToWrite 와 nNumberOfBytesToRead는 쓰거나 읽고자 하는 바이트의 길입니다. 실제 기록하고 읽혀진 바이트는 lpNumberOfBytesRead 와 lpNumberOfBytesWrite에 설정됩니다. lpOverlapped는 파일이 아닌 디바이스를 열고자 할 경우 이 디바이스 파이프 통로입니다. 일반 파일을 에서는 이값을 NULL로 하여 사용합니다.
다음은 파일에서 데이터를 읽고 쓰는 예입니다.
ReadFile(hFile,pBuff,size,&size,NULL);
WriteFile(hFile,pBuff,size,&size,NULL);
파일 닫기와 기타 함수
파일을 다고자 할경우에는 CloseHandle를 사용합니다. 예를 들어서 위의 hFile 에 로드된 파일을 닫고자 한다면 다음과 같이 합니다.
CloseHandle(hFile);
그이외에 파일을 핸들링하는 함수는 표2와 같습니다.
함수 | 내용 |
MoveFile | 특정 파일을 다른 파일이름으로 변경하거나 이동시킨다. |
CopyFile | 특정 파일을 새로운 파일로 복사한다. |
CreateDirectory | 디렉토리를 만든다 |
DeleteFile | 특정 파일을 지운다 |
GetFileSize | 파일의 크기를 얻는다. |
FindFirstFile | 특정 파일을 찾는다. |
FindNextFile | FindFirstFile에 의해서 찾은 다음에 특정 파일을 찾는다. |
3.클립보드
클립보드는 특정 프로그램에서 데이터를 다른 프로그램으로 넘어갈수 있도록 해주는 윈도우의 기능입니다. 보통 "복사하기" 나 "Copy"항목을 이용하면 현재 설정된 데이터가 클립보드로 넘어갑니다. 클립보드로 넘어간 데이터를 다른 프로그램에서 "붙여넣기" 나 "Paste"를 이용하면 클립보드에 있는 데이터를 넘겨 받을수 있습니다. 클립보드에는 문자,그림 등의 형태를 저장할수 있으며 OLE를 이용하게 되면 다양한 형태의 데이터를 클립보드로 저장하고 저장된 데이터를 클립보드에서 얻을수 있습니다. 본항목에서는 클립보드에 데이터를 넣는 방법과 데이터를 읽는 방법에 대해서 설명합니다.
클립보드 상태 알기
클립보드를 열고자 할경우에는 OpenClipboard와 CloseClipboard 함수를 사용하면 됩니다.
OpenClipboard();//클립보드를 연다.
: 클립보드에 데이터를 넣던지 또는 데이터를 얻는다.
CloaseClipboard()//클립보드를 닫는다.
클립보드에는 다양한 포맷형태로 데이터가 저장되어 있습니다. 이런 클립보드에서 현재 원하는 형식의 데이터가 있는지를 확인할 때 사용하는 함수가IsClipboardFormatAvailable 입니다.
BOOL IsClipboardFormatAvailable(
UINT format //클립보드 데이터 포맷
);
format는 다음과 같은 값을 가집니다.
CF_BITMAP : HBITMAP 형태의 데이터
CF_DIB : 장치독립적인 비트맵 데이터 보통 bmp파일에서 로드된 데이터
CF_TEXT : 문자 데이터
CF_PALETTE : 팔레트 데이터
CF_RIFF : 멀티미디어에서 사용하는 오디오 음성등의 데이터
CF_WAVE : 웨이브 파일 포맷
CF_TIFF : 태그 이미지 파일 포맷
위의 플러그들외에서 다양한 플러그들이 있는데 그만큼 클립보드에 저장할 데이터는 광범위하다는 의미입니다. 예를 들어 현재 복사할 텍스트가 있는가를 확인하고자 한다면
BOOL check;
check=IsClipboardFormatAvailable(CF_TEXT);
하시면 됩니다.
만일 CF_TEXT 형의 데이터가 클립보드에 있다면 check는 TRUE값이 그렇지 않다면 FALSE값이 설정됩니다.
실제 클립보드에서 텍스트를 얻고자 한다면 다음과 같은 방법을 취할수 있습니다.
OpenClipboard();//클립보드를 연다.
if(IsClipboardFormatAvailable(CF_TEXT))//클립보드에 텍스트 데이터가 있으면
{
클립보드에서 데이터를 얻는다..
}
CloaseClipboard()//클립보드를 닫는다.
때로는 클립보드에 있는 데이터를 삭제하고자 할경우가 있습니다. 이때 사용하는 함수가 EmptyClipboard입니다.
만일 어떤 데이터를 클립보드에 넣고자 한다면 그리고 현재 있는 데이터를 삭제하고자 한다면 다음과 같이 할수 있습니다.
OpenClipboard();//클립보드를 연다.
EmptyClipboard();//클립보드에서 데이터를 지운다.
CloaseClipboard()//클립보드를 닫는다.
클립보드에서 데이터 얻기와 데이터 쓰기
클립보드에서 데이터를 얻고자 할 경우 GetClipboardData 함수를 사용합니다.
예를 들어서 클립보드에서 텍스트 데이터를 얻고자 한다면 다음과 같이 할수 있습니다.
HANDLE hClipboard;
hClipboard=GetClipboardData(CF_TEXT);
만약 텍스트 데이터가 없다면 hClipboard에는 NULL값이 전송될것입니다. 넘겨주는 형식이 메모리 핸들러 형식이기 때문에 이데이터를 사용허가권을 받기 위해서는 GlobalLock를 사용해야 합니다.
HANDLE hClipboard;
LPASTR pClipboard;
hClipboard=GetClipboardData(CF_TEXT);
pClipboard=GlobalLock(hClipboard);
//pClipboar를 사용한다.
위와 같이 하면 클립보드에 있는 데이터를 바로 사용하는 형식이 됩니다. 때로는 클립보드에 있는 데이터를 자신의 프로그램 내로 복사하고자 할경우가 있습니다. 이럴경우에는 클립보드에 저장된 데이터 크기만큼 메모리를 확보한후에 데이터를 복사하면 됩니다. 다음은 클립보드에서 데이터를 복사하는 예입니다.
//클립보드를 연다
OpenClipboard(hwnd);
//문자형식의 데이터를 클립보드에서 얻는다.
hClipboard=GetClipboardData(CF_TEXT);
//hClipboard가 NULL이면 클립보드를 닫는다.
if(!hClipboard)
{
CloseClipboard();
return 0;
}
//클립보드 데이터를 넣을 메모리 할당
szBuff=GlobalReAlloc(szBuff,GlobalSize(hClipboard),GMEM_MOVEABLE);
//메모리 사용 허가권을 받는다.
pBuff=(LPSTR)GlobalLock(szBuff);
//클립보드 메모리 사용허가권을 받는다.
pClipboard=GlobalLock(hClipboard);
//클립보드 데이터를 복사한다.
클립 보드에 데이터를 넣고자 한다면 SetClipboardData를 사용하여 간단하게 넣을 수 있습니다.
HANDLE SetClipboardData(
UINT uFormat, // 클립 보드 데이터 포맷
HANDLE hMem // 데이터 적재 메모리 핸들
);
위의 예에서 pBuff에 있는 데이터를 수정한후 다시 클립보드에 넘기고자 할 때 있대 pBuff를 넘겨주는 것이 아니라 szBuff를 넘겨주어야 합니다. 클립보드로 넘어가는 것은 메모리 사용 허가 포인터 변수가 아니라 메모리 핸들러 이기 때문입니다.
SetClipboardData(CF_TEXT,szBuff);
4.간단한 메모장 예제 ExFile
ExFile는 텍스트 데이터를 메모할수 있는 예제입니다. 파일 오픈 메뉴를 선택하면 파일을 선택할수 있으며 이렇게 선택된 파일을 읽어서 에디터 박스에 로드하고 또한 클립보드로 전송합니다. 본예제는 파일 입출력과 메모리 관리,클립보드를 사용하는 방법을 총괄적으로 만든 예제입니다.
(프로그램 소스)
//텍스트 읽고 쓰기
//ExFile.c
#include <windows.h>
#include "resource.h"
//기본 블럭 사이즈
#define DEFAULTSIZE 512
//텍스트 데이터를 핸들링하는 포인터
LPSTR pBuff=NULL;
//텍스트 데이터를 저장할 메모리 핸들러
HANDLE szBuff;
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
HINSTANCE hInst;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static char szAppName[] = "ExFile" ;
HWND hwnd ;
MSG msg ;
WNDCLASSEX wndclass ;
wndclass.cbSize = sizeof (wndclass) ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = "MENU" ;
wndclass.lpszClassName = szAppName ;
wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ;
RegisterClassEx (&wndclass) ;
hInst=hInstance;
hwnd = CreateWindow (szAppName,
"텍스트 읽고 쓰기:ExFile.c",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
//파일에서 데이터를 읽는 함수
BOOL ReadFileData(LPSTR file)
{
HANDLE hFile;
DWORD size;
//파일을 오픈한다.
hFile=CreateFile(file,GENERIC_READ,FILE_SHARE_READ ,NULL,OPEN_EXISTING,0,0);
//파일 오픈이 실패하면 리턴
if(hFile==INVALID_HANDLE_VALUE)
return FALSE;
//기존에 버퍼에 데이터가 있다면
if(pBuff)
{
//락킹 해제
GlobalUnlock(szBuff);
//메모리 해제
GlobalFree(szBuff);
pBuff=NULL;
}
//파일 크기를 얻는다.
size=GetFileSize(hFile,NULL);
//파일 크기만큼 메모리 할당
szBuff=GlobalAlloc(GHND,size);
//메모리 락킹
pBuff=(LPSTR)GlobalLock(szBuff);
//파일에서 데이터를 읽는다.
ReadFile(hFile,pBuff,size,&size,NULL);
//파일을 닫는다.
CloseHandle(hFile);
return TRUE;
}
//파일에 데이터 기록 함수
BOOL WriteFileData(LPSTR file)
{
HANDLE hFile;
DWORD size;
//파일을 오픈한다.
hFile=CreateFile(file,GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ |FILE_SHARE_WRITE ,NULL,OPEN_EXISTING ,0,0);
if(hFile==INVALID_HANDLE_VALUE)
return FALSE;
//pBuff가 NULL이면 기록할 데이터 없음
if(!pBuff)
return FALSE;
//pBuff크기를 알鱁고
size=strlen(pBuff);
//파일에 기록한다.
WriteFile(hFile,pBuff,size,&size,NULL);
//파일을 닫는다.
CloseHandle(hFile);
return TRUE;
}
//새로운 파일에 데이터를 기록하는함수
BOOL WriteNewFileData(LPSTR file)
{
HANDLE hFile;
DWORD size;
//파일을 만든다.
hFile=CreateFile(file,GENERIC_WRITE,FILE_SHARE_READ ,NULL,CREATE_NEW,0,0);
if(hFile==INVALID_HANDLE_VALUE)
return FALSE;
if(!pBuff)
return FALSE;
//파일을 기록한다.
size=strlen(pBuff);
WriteFile(hFile,pBuff,size,&size,NULL);
CloseHandle(hFile);
return TRUE;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc ;
PAINTSTRUCT ps ;
static HWND editwnd;
RECT rect;
static OPENFILENAME ofn;
static char file[200],oldfile[200];
static BOOL fileNewFlag=TRUE;
char temp[80];
DWORD size;
HANDLE hClipboard;
LPSTR pClipboard;
switch (iMsg)
{
case WM_CREATE :
GetClientRect(hwnd,&rect);
editwnd=CreateWindow("edit",NULL,
WS_CHILD |WS_VISIBLE| WS_HSCROLL|WS_VSCROLL|WS_BORDER| ES_LEFT|ES_MULTILINE| ES_AUTOHSCROLL | ES_AUTOVSCROLL,
rect.left,rect.top,rect.right,
rect.bottom,hwnd,(HMENU)1,hInst,NULL);
//파일 대화상자 구조체에 정보를 설정한다.
ofn.lStructSize=sizeof(OPENFILENAME);
ofn.hwndOwner=hwnd;
ofn.hInstance=NULL;
ofn.lpstrFilter="C파일 (*.c) \0 *.c";
ofn.lpstrCustomFilter=NULL;
ofn.nMaxCustFilter=0;
ofn.nMaxCustFilter=0;
ofn.nFilterIndex =0;
ofn.nMaxFile = _MAX_PATH;
ofn.nMaxFileTitle= _MAX_FNAME + _MAX_EXT;
ofn.lpstrInitialDir=NULL;
ofn.nFileOffset=0;
ofn.lCustData = 0L;
ofn.lpfnHook = NULL;
ofn.lpTemplateName=NULL;
ofn.lpstrDefExt="c";
return 0 ;
case WM_SIZE:
GetClientRect(hwnd,&rect);
MoveWindow(editwnd,rect.left,rect.top,rect.right,
rect.bottom,TRUE );
break;
case WM_COMMAND:
switch(LOWORD(wParam))
{
//새로운 파일 만들기
case ID_FILENEW:
if(pBuff)
{
GlobalUnlock(szBuff);
GlobalFree(szBuff);
pBuff=NULL;
}
szBuff=GlobalAlloc(GHND,DEFAULTSIZE);
pBuff=(LPSTR)GlobalLock(szBuff);
SetWindowText(editwnd,pBuff);
fileNewFlag=TRUE;
strcpy(file,"noname.c");
SetWindowText(hwnd,file);
return 0;
//기본 파일 열기
case ID_FILEOPEN:
//파일 대화상자 실행
ofn.hwndOwner=hwnd;
strcpy(oldfile,file);
wsprintf(file,"*.c");
wsprintf(temp,"파일열기");
ofn.lpstrFile=(PSTR)file;
ofn.lpstrFileTitle=(PSTR)temp;
ofn.Flags= OFN_HIDEREADONLY | OFN_CREATEPROMPT;
//파일 대화상자 실행
if(GetOpenFileName(&ofn))
{
//파일을 읽으면
if(ReadFileData(file))
{
//에디터 박스로 전송
SetWindowText(editwnd,pBuff);
//새로운 파일 플러그 FALSE
fileNewFlag=FALSE;
//메인 윈도우 타이틀 수정
SetWindowText(hwnd,file);
return 0;
}
}
strcpy(file,oldfile);
return 0;
//저장하기
case ID_FILESAVE:
size=GetWindowTextLength(editwnd);
GlobalUnlock(szBuff);
GlobalReAlloc(szBuff,size,GMEM_MOVEABLE);
pBuff=(LPSTR)GlobalLock(szBuff);
GetWindowText(editwnd,pBuff,size);
//새로운 파일이면
if(fileNewFlag)
{
ofn.hwndOwner=hwnd;
strcpy(oldfile,file);
wsprintf(file,"*.c");
wsprintf(temp,"파일열기");
ofn.lpstrFile=(PSTR)file;
ofn.lpstrFileTitle=(PSTR)temp;
ofn.Flags= OFN_OVERWRITEPROMPT;
//파일 대화상자 실행
if(GetSaveFileName(&ofn))
{
//데이터를 기록한다.
if(!WriteNewFileData(file))
{
wsprintf(temp,"%s에 데이터 기록에 실패",file);
MessageBox(hwnd,temp,"파일 저장 결과",MB_OK );
strcpy(file,oldfile);
return 0;
}
fileNewFlag=FALSE;
SetWindowText(hwnd,file);
return 0;
}
strcpy(file,oldfile);
return 0;
}
else
{//기존에 오픈한 파일이면 그냥 기존의 파일명으로 기록한다.
if(!WriteFileData(file))
{
wsprintf(temp,"%s에 데이터 기록에 실패",file);
MessageBox(hwnd,temp,"파일 저장 결과",MB_OK );
}
fileNewFlag=FALSE;
}
return 0;
case ID_FILESAVEAS:
ofn.hwndOwner=hwnd;
wsprintf(file,"*.c");
wsprintf(temp,"파일열기");
ofn.lpstrFile=(PSTR)file;
ofn.lpstrFileTitle=(PSTR)temp;
ofn.Flags= OFN_OVERWRITEPROMPT;
//파일 대화상자를 실행하고 새로운 파일로 기록한다.
if(GetSaveFileName(&ofn))
{
size=GetWindowTextLength(editwnd);
GlobalUnlock(szBuff);
GlobalReAlloc(szBuff,size,GMEM_MOVEABLE);
pBuff=(LPSTR)GlobalLock(szBuff);
GetWindowText(editwnd,pBuff,size);
if(!WriteNewFileData(file))
{
wsprintf(temp,"%s에 데이터 기록에 실패",file);
MessageBox(hwnd,temp,"파일 저장 결과",MB_OK );
}
SetWindowText(hwnd,file);
}
return 0;
case ID_GETDATA:
//클립보드를 연다
OpenClipboard(hwnd);
//문자형식의 데이터를 클립보드에서 얻는다.
hClipboard=GetClipboardData(CF_TEXT);
//hClipboard가 NULL이면 클립보드를 닫는다.
if(!hClipboard)
{
CloseClipboard();
return 0;
}
if(pBuff)
GlobalUnlock(szBuff);
//클립보드 데이터를 넣을 메모리 할당
szBuff=GlobalReAlloc(szBuff,GlobalSize(hClipboard),GMEM_MOVEABLE);
//메모리 사용 허가권을 받는다.
pBuff=(LPSTR)GlobalLock(szBuff);
//클립보드 메모리 사용허가권을 받는다.
pClipboard=GlobalLock(hClipboard);
//클립보드 데이터를 복사한다.
strcpy(pBuff,pClipboard);
SetWindowText(editwnd,pBuff);
GlobalUnlock(hClipboard);
CloseClipboard();
return 0;
case ID_SETDATA:
OpenClipboard(hwnd);
size=GetWindowTextLength(editwnd);
GlobalUnlock(szBuff);
GlobalReAlloc(szBuff,size,GMEM_MOVEABLE);
pBuff=(LPSTR)GlobalLock(szBuff);
GetWindowText(editwnd,pBuff,size);
EmptyClipboard();
SetClipboardData(CF_TEXT,szBuff);
CloseClipboard();
return 0;
}
break;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}
(프로그램 소스끝)
(그림 1)ExFile 출력 결과
5.스레드
스레드란 쉽게 보면 저혼자 따로 구동되는 모듈이라고 보시면 됩니다. 실제 윈도우에 있는 어플리케이션 프로그램들은 모두가 스레드 형태의 프로그램입니다. 통신을 하는 프로그램을 구동시키고 한쪽에서 워드를 상용한다고 하여도 통신을 하는 프로그램은 계속적으로 서로 데이터를 주고 받고 있으며 원드 프로그램은 입력되는 데이터를 메모리에 계속적으로 적재 하고 있습니다. 즉 멀티 테스킹이 된다는 의미입니다. 스레드는 내부적으로 멀티 테스킹을 가능하게 하는 하나의 독립 실행 모듈입니다.
스레드 함수 만들기와 실행
스레드 함수는 다음과 같은 형식으로 만들 수 있습니다.
VOID ThreadFouc(LPVOID lParam)
{
스레드 내용 코딩;
}
예를 든다면 ThreadEx 라는 함수로 스레드를 만들고자 한다면 다음과 같이 할수 있을것입니다.
VOID ThreadEx(LPVOID lParam)
{
스레드 내용
}
위와 같이 만든 스레드를 실행하고자 할 경우 _beginthread 함수를 사용합니다. _beginthread함수의 첫 번째 인자는 스레드 함수명이며 두 번째 인자는 lParam에 넘겨줄 데이터의 크기이며 3번째 인자는 lParam으로 넘겨줄 정보입니다.
만일 윈도우 핸드을 넘겨주고 스레드를 실행시킨다면 다음과 같이 할수 있습니다.
_beginthread(ThreadEx,sizeof(HWND),hwnd);
프로그램내에서 스레드가 필요한 이유는 보통 루프를 돌리는 긴작업이 수행될때입니다. 이런작업을 프로시저 내에서 처리하게 된다면 메시지를 놓칠 염려 때문에 스레드를 많이 이용합니다. 이때는 보통 스레드에 무한 루프를 돌리면서 작업을 실행하는경우가 많은데 이때는 스레드 밖에 하나의 플러그를 넣어두고 이플러그에 의해서 스레드를 종료 하는경우가 많습니다.
BOOL exitFlag=FLASE;
VOID ThreadEx(LPVOID lParam)
{
while(!exitflag)
{
작업 수행;
}
}
위와 같은 방법으로 스레드를 만들었을 경우 스레드를 실행시킬때는 exitflag를 FALSE로 나두고 _beginthread 함수를 실행시키면 됩니다.
exitFlag=FALSE;
_beginthread(ThreadEx,sizeof(HWND),hwnd);
만일 무한으로 도는 스레드를 종료하고 싶을 경우 이때는 간단하게 스레드를 종료 할수 있습니다. 즉 exifFlag=TRUE;로 설정하는것입니다.
//스레드 종료
exitFlag=TRUE;
while루프는 exitFlag가 TRUE가 되면 종료 되고 따라서 ThreadEx함수의 수행은 종료 됩니다.
이런 방법으로 스레드를 구동하는것인 가장 기본적인 형태입니다.
동기화 방법
한 개의 데이터를 가지고 두 개의 스레드가 돌아간다고 가정을 합시다. 이때 한 개의데이터에서 수정이 일어날 경우 즉 데이터의 수정 중간에는 다른 스레드에서는 데이터를 읽어서는 안됩니다. 이럴 경우 스레드 두 개가 동기화가 되어야 합니다.
예를 하나 들어 보겠습니다.
int data1,data2,data3;
int rockflag=FALSE;
VOID Thread1(LPVOID lParam)
{
while(!exitflag)
{
if(!rockflag)
{
rockflag=TRUE;
data1++;
data2++;
data3++;
rockflag=FALSE;
}
}
}
VOID Thread2(LPVOID lParam)
{
while(!exitflag)
{
if(!rockflag)
{
rockflag=TRUE;
data1--;
data2--;
data3--;
rockflag=FALSE;
}
}
}
Thread1은 data1,2,3를 증가하고 Thread2는 data1,2,3를 감소시킵니다. 이두개의 스레드를 가동시키면 data1,2,3의 값은 변화가 있지 않습니다. 그것은 data1,2,3를 수정하고 있는 순간에는 다른 스레드가 데이터를 수정할수 없기 때문입니다. 위의 문장에서 if(!rockflag) 의미는 현재 락이 걸려 있지 않으면 즉 현재 data1,2,3가 수정되고 있지 않다면 이란 의미이고 그다음에 rockflag는 TRUE로 설정하여 락을 걸고 데이터를 수정한다음 FALSE로 전환하기 때문입니다. if(!rockflag) 문장이 없다면 data1,2,3가 수정되는 중간에 다른 스레드가 data1,2,3를 수정할수 있기 때문입니다.이렇게 두 개의 스레드가 한 개의 데이터를 동기를 ꁹ추어서 돌아가야할 필요성이 있을경우가 많습니다. 예를 들어서 공장에서 3개의 팔을 가진 로봇이 움질일 때 첫 번째 팔은 원판에 나사를 넣고 두 번째 팔은 나사를 조이고 세 번째 팔은 나사에 납땜을 한다고 가정을 합니다. 이 3개의 팔은 각각 돌아가나 동기가 맞추어져야 합니다. 즉 첫 번째 팔이 나사를 다 조여야만 2번째 팔이 움직이고 이2번째 팔의작업이 끝나면 바로 3번째 팔이움직이는데 이것이 물흐르듯이 쭉 이어져야 합니다. 이럴 경우 각각의 스레드가 서로 동기를 맞추어야 하는데 이방식이 바로 위에서 간단하게 보여준 형식에 의거합니다. 이때 락킹을 하는 객체가 데이터이냐 또는 스레드이냐의 문제 또한 데이터를 수정할수 있는 권한의 수문제등을 설정하기 위해서 뮤텍스,세마포,크리티컬섹션 등이 나온것입니다. 이부분에 대해서 좀더 자세한 것을 알고자 하신다면 Visual C++ Programming Bible 5.x(영진 출판사 이상엽저) 내용을 참조하십시오. 본책에서는 스레드가 어떻게 돌아가는가와 이스레드를 이용한 동기화 방법만 설명합니다.
스레드 예제 ExThread
ExThread예제는 좌측 버튼을 누르면 스레드가 실행되면서 화면에 임의적인 색의 박스가 마구출력됩니다. 이상태에서 메뉴선택하는 등 다른 작업을 할수 있습니다. 스레드를 멈추고자 한다면 우측버튼을 누르시면 되는 예제입니다.
(프로그램 소스)
//스레드 예제
//ExThread.c
#include <windows.h>
#include <process.h>
#include <stdlib.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static char szAppName[] = "ExThread" ;
HWND hwnd ;
MSG msg ;
WNDCLASSEX wndclass ;
wndclass.cbSize = sizeof (wndclass) ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
wndclass.hIconSm = LoadIcon (NULL, IDI_APPLICATION) ;
RegisterClassEx (&wndclass) ;
hwnd = CreateWindow (szAppName,
"스레드예제:ExThread",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
BOOL exitFlag=FALSE;
VOID ThreadEx(LPVOID lParam)
{
HBRUSH hBrush ;
HWND hwnd;
HDC hdc ;
RECT rect;
int xLeft, xRight, yTop, yBottom, iRed, iGreen, iBlue ;
hwnd=(HWND)lParam;
GetClientRect(hwnd,&rect);
while (TRUE)
{
xLeft = rand () % rect.right ;
xRight = rand () % rect.right ;
yTop = rand () % rect.bottom ;
yBottom = rand () % rect.bottom;
iRed = rand () & 255 ;
iGreen = rand () & 255 ;
iBlue = rand () & 255 ;
hdc = GetDC (hwnd) ;
hBrush = CreateSolidBrush (RGB (iRed, iGreen, iBlue)) ;
SelectObject (hdc, hBrush) ;
Rectangle (hdc, min (xLeft, xRight), min (yTop, yBottom),
max (xLeft, xRight), max (yTop, yBottom)) ;
ReleaseDC (hwnd, hdc) ;
DeleteObject (hBrush) ;
if(exitFlag)
break;
}
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc ;
PAINTSTRUCT ps ;
switch (iMsg)
{
case WM_CREATE :
return 0 ;
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_LBUTTONDOWN:
exitFlag=FALSE;
_beginthread(ThreadEx,sizeof(HWND),hwnd);
return 0;
case WM_RBUTTONDOWN:
exitFlag=TRUE;
return 0;
case WM_DESTROY :
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, iMsg, wParam, lParam) ;
}
(프로그램 소스끝)
(그림 2) ExThread출력 결과
'기본 카테고리' 카테고리의 다른 글
야근과 쳘야의 오묘한 조화.... (0) | 2007.07.20 |
---|---|
일본CF 다시 Japan CF (0) | 2007.07.20 |
액티브 X 컴포넌트에 param 태그 쓰기 (0) | 2007.07.12 |
CString , BSTR ,char ,CComVariant 간의 자료형변환 (0) | 2007.07.12 |
Hosting the Windows Media Player Control in a Windows Application (0) | 2007.07.12 |