[윈도우즈 시스템 프로그래밍][7장] - 프로세스간 통신(IPC) (1)

1. 프로세스간 통신(IPC)의 의미

 

책에서는 이 장을 보기 전에 CreateFile , ReadFile , WriteFile 함수를 먼저 공부하는 것을 추천한다.

 

ANSI 표준 파일 입,출력 함수와 비교해서 참조 가능한 정도로만 이여도 좋다.

 

IPC Inter-Process Communication 으로써, 프로세스 사이의 통신 이라는 뜻이다.

 

통신이란 기본적으로 데이터를 주고받는 행위이다.

 

따라서, 프로세스 사이의 통신이란, 둘 이상의 프로세스가 데이터를 주고 받는 행위이다.

 

 

프로세스 사이에서 통신이 이뤄지기 위한 조건

 

프로세스 사이에서 데이터를 주고받는 방법을 생각해보자.

 

일단 상식선에서 우리가 누군가와 대화(통신) 하는 상황을 생각해보자. 아니면 물건을 건네주는 상황을 생각하자.

 

일단은 만나야한다. , 두 사람의 접선 장소가 필요하다.

 

만약 사정이 있으면 전화를 하거나, 메신져를 이용하고, 물건은 소포를 이용하면 된다.

 

이렇게 전화나 메신저,소포와 같은 것이 IPC 이다.

 

위의 것은 간단하다. 만날 수 있는 여건만 되면 전혀 문제되지 않는다.

 

그러나 그게 아닐 경우, 다른 방법을 찾아야 한다.

 

이것은 프로세스도 마찬가지이다.

 

공유하는 메모리 영역이 있으면 프로세스간 통신은 아주 쉽다.

 

하지만, 그런 여건이 안 된다면 전화나 메신저 같은 수단이 필요하다.

 

 

프로세스들이 서로 만날 수 없는 이유

 

결론적으로, 프로세스들은 서로 만나는게 불가능하다. 접선 장소를 만들 수 없기 때문이다.

 

프로세스는 자신에게 할당된 메모리 공강 이외에는 접근이 불가능 하다.

 

 

프로세스들이 서로 만나지 못하게 디자인한 이유

 

오늘날 모든 운영체제는  프로세스가 자신의 영역 이외의 영역에 접근하는 것을 금지한다.

 

만약, 현재 Mp3 Player MS Word 를 실행시켜서 노래를 들으며 문서 작업을 하고 있다.

 

그런데 이상하게 MS WORD의 글자가 계속 깨지는 것이다.

 

문제는 Mp3 Player MS WORD 의 메모리 공간을 망가뜨리고 있었다.

 

물론 우리는 이런 상황을 볼 수 없다. 위에서 말했듯 프로세스는 자신의 영역 이외에는 건드릴 수 없기 때문이다.

 

만약 아니라면, 위와 같은 경우가 충분히 가능해진다.

 

일부로 그렇게는 안하겠지만, 프로그래머는 그만큼 주의를 기울여야 한다.

 

그래서 프로세스가 다른 프로세스의 것을 못건드리는 것이 안전하다.

 

 

2. 메일슬롯 방식의 IPC

 

메일슬롯(Mail Slot) 원리

 

메일슬롯은 파이프와 더불어 대표적인 IPC 기법이다.

 

메일슬롯의 원리를 이해하기 위해 우체통을 연상하면 좋다.

 

데이터를 주고 받기 위해서 프로세스가 우체통을 마련하는 것.”

 

데이터를 전달하는 프로세스를 Sender 이라 하고, 받는 프로세스를 Receiver 라 하자.

 

Sender Receiver 에게 직접적으로 메일을 줄 수 없으니, 외부에 우체통을 하나 만든다.

 

이 우체통이 바로 메일슬롯이다.

 

그리고 Sender Receiver 의 메일슬롯에 데이터를 날린다. 그리고 난 후, Receiver 은 메일슬롯의 데이터를 받아온다.

 

 

메일슬롯(Mail Slot) 구성을 위해 필요한 요소

 

 

첫 째로, Receiver 가 준비해야 할 것들

 

Receiver 프로세스는 우체통을 생성해야 한다.

 

메일 슬롯을 생성하는 함수는

 

HANDLE CreateMailslot(

           LPCTSTR lpName,

           DWORD nMaxMessageSize,

           DWORD lReadTimeout,

           LPSECURITY_ATTRIBUTES lpSecurityAttributes

);

 

첫 번째 인자는 생성하는 메일슬롯의 이름이다.

 

두 번째 인자는 메일슬롯의 버퍼 크기이다. 쉽게 말하면 우체통의 크기라 생각하면 된다.

 

0 값을 주면 시스템이 허용하는 최대 크기로 지정한다.

 

세 번째 인자는 메일슬롯을 통해 전송된 데이터를 읽기 위해, 파일 입,출력 함수인 ReadFile이 사용된다.

 

읽을 데이터가 있다면, ReadFile 함수는 데이터를 읽고 빠져나오게 된다.

 

하지만 메일슬롯이 비워있으면 데이터가 채워질 때 까지 ReadFile 함수는 반환하지 않고 블로킹 상태에 놓인다.

 

lReadTimeout 은 최대 블로킹 시간을 밀리세컨드 단위로 지정한다.

 

따라서, 0을 주면 , 데이터가 있던지 없던지 빠져나오게 된다.

 

그리고 MAILSLOT_WAIT_FOREVER 을 주면, 이름그대로 계속 기다리게 된다.

 

네 번째 인자는 핸들을 상속하기 위한 용도로, 잠시 후 설명한다.

 

이 함수의 반환 타입은 커널 오브젝트의 핸들이다.

 

메일슬롯도 커널에 의해 관리되는 리소스이므로, 커널 오브젝트가 생성되고 이 핸들이 반환된다.

 

 

둘 째로, Sender 가 준비해야 할 것들

 

Sender Receiver 가 만들어 놓은 메일슬롯의 이름을 알아야 한다.

 

메일슬롯은 Windows 파일 시스템을 기반으로 하고 있다. 따라서, 메일슬롯을 기반으로 하는 함수를 만들 필요 없이, CreateFile, WritheFile 등을 사용하면 된다.

 

주소 체계의 기본 골격은 다음과 같다.

 

\\computername\mailslot\[path]name

 

여기서 채워넣어야 할 부분은 computername [path]name 부분이다.

 

Computername 에는 . 을 넣어두면 된다. 이것은 현재 사용하는 로컬 컴퓨터를 의미한다.

 

[path]name 은 실질적인 메일슬롯 이름이다.

 

\\.\mailslot\mailbox 이런식으로 하면 된다.

 

[path]name path 정보를 포함해서 보다 쳬계화 할 수 있다.

 

\\.\mailslot\abc\def\mailbox 이런식으로도 가능하다.

 

메일슬롯에 데이터를 전송하기 위해서는 해당 메일슬롯의 연결을 의미하는 데이터 스트림이 필요하다.

 

이 것은 CreateMailslot 함수의 호출로 생성되는 메일슬롯과는 또 다른 형태로, 운영체제에 의해 커널 오브젝트와 핸들의 생성을 동반한다.

 

 

메일슬롯의 예

 

일단 Receiver 코드를 보자.

 

/*

        MailReceiver.cpp

*/

 

#include <windows.h>

#include <tchar.h>

#include <stdio.h>

 

#define SLOT_NAME    _T("\\\\.\\mailslot\\mailbox")

 

int _tmain(int argc, LPTSTR argv[])

{

        HANDLE hMailSlot;  //mailslot 핸들

        TCHAR messageBox[50];

  DWORD bytesRead;  // number of bytes read

 

        /* mailslot 생성*/

        hMailSlot=CreateMailslot(SLOT_NAME, 0, MAILSLOT_WAIT_FOREVER, NULL);

        if(hMailSlot==INVALID_HANDLE_VALUE)

        {

               _fputts(_T("Unable to create mailslot!\n"), stdout);

               return 1;

        }

 

        /* Message 수신*/

        _fputts(_T("******** Message ********\n"), stdout);

        while(1)

        {

               if(!ReadFile(hMailSlot, messageBox, sizeof(TCHAR)*50, &bytesRead, NULL))

               {

                       _fputts(_T("Unable to read!"), stdout);

                       CloseHandle(hMailSlot);

                       return 1;

                }

 

               if(!_tcsncmp(messageBox, _T("exit"), 4))

               {

                       _fputts(_T("Good Bye!"), stdout);

                       break;

               }

 

               messageBox[bytesRead/sizeof(TCHAR)]=0; //NULL 문자 삽입

               _fputts(messageBox, stdout);

        }

 

        CloseHandle(hMailSlot);

        return 0;

}

 

위의 #define ~ 로 되어있는 경로가 메일슬롯의 주소이다.

 

다음은 ReadFile의 원형이다.

 

BOOL ReadFile(

           HANDLE hFile,

           LPVOID lpBuffer,

           DWORD nNumberOfBytesToRead,

           LPDWORD lpNumberOfBytesRead,

           LPOVERLAPPED lpOverlapped

);

 

hFile 은 메일슬롯의 핸들이다.

 

lpBuffer 은 데이터를 저장할 버퍼를 지정하는 용도이다.

 

nNumberOfBytesToread 는 읽을 데이터의 최대 크기이다.

 

lpNumberOfBytesRead 는 함수 호출이 완료된 후, 읽어들인 데이터 크기를 바이트로 바꾸기 위한 것이다.

 

lpOverlapped 는 일단 NULL 로 넣자.

 

이제 Sender 을 보겠다.

 

/*

        MailSender.cpp

*/

 

#include <windows.h>

#include <tchar.h>

#include <stdio.h>

 

#define SLOT_NAME _T("\\\\.\\mailslot\\mailbox")

 

int _tmain(int argc, LPTSTR argv[])

{

        HANDLE hMailSlot;  //mailslot 핸들

        TCHAR message[50];

    DWORD bytesWritten;  // number of bytes write

 

        hMailSlot=CreateFile(SLOT_NAME, GENERIC_WRITE, FILE_SHARE_READ, NULL,

               OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

        if(hMailSlot==INVALID_HANDLE_VALUE)

        {

               _fputts(_T("Unable to create mailslot!\n"), stdout);

               return 1;

        }

 

        while(1)

        {

               _fputts(_T("MY CMD>"), stdout);

               _fgetts(message, sizeof(message)/sizeof(TCHAR), stdin);

 

               if(!WriteFile(hMailSlot, message, _tcslen(message)*sizeof(TCHAR), &bytesWritten, NULL))

               {

                       _fputts(_T("Unable to write!"), stdout);

                       CloseHandle(hMailSlot);

                       return 1;

               }

 

               if(!_tcscmp(message, _T("exit")))

               {

                       _fputts(_T("Good Bye!"), stdout);

                       break;

               }

 

        }

 

        CloseHandle(hMailSlot);

        return 0;

}

 

CreateFile 함수는 첫번째, 두번째, 다섯번째 인자만 알고 있으면 된다.

 

첫 번째 인자는 파일 이름이다.

 

두 번째 인자는 개방 모드이다. (Write , Read 등등) 여기선 쓰기모드로 GENERIC_WIRTE를 인자로 주었다.

 

다섯 번째 인자는 파일의 생성방식을 결정짓는 용도이다.

 

새롭게 만들 것인지, 아니면 불러올 것인지 이다. 이 예제는 OPEN_EXISTING 을 전달한다.

 

다음은 WriteFile 함수이다. 각각은 ReadFile 의 상대적이다.

 

BOOL WriteFile(

           HANDLE hFile,

           LPCVOID lpBuffer,

           DWORD nNumberOfBytesToWrite,

           LPDWORD lpNumberOfBytesWritten,

           LPOVERLAPPED lpOverlapped

);

 

ReadFile 과 상대적이므로 각각 인자에 대한 설명은 생략한다.

 

실행 과정에서 주의점은 Receiver 을 먼저 띄운 다음 Sender 을 띄워야 한다.

 

이유는 생각해 보세요

 

 

메일슬롯의 고찰

 

메시지를 송,수신 하는 프로그램을 작성한다고 했을 때, 나도 그랬지만 대부분이 채팅 정도는 가능할 것이라 생각했다.

 

하지만 지금 한 예제는 단방향 메시지 전달이다.

 

이것이 바로 메일슬롯의 기본적 특성이다. 메일슬롯은 단방향 통신만 가능하다.

 

메일슬롯과 IPC에 대한 고찰

 

메일슬롯은 한 쪽 방향으로만 메시지를 전달할 수 있다.

 

따라서 양방향 프로그램을 만들려면 두 개의 메일슬롯을 생성해야만 한다.

 

이 것은 직접 해보기 바란다. 위의 예제에서 조금만 손쓰면 된다.

 

양쪽 방향으로 메시지를 주고 받고자 할 때는 IPC 기법도 있다.

 

뒤에서 파이프를 이용한 IPC에 대해 살펴볼 것인데, 파이프 중에서는 양방향 데이터 송,수신을 지원하는 파이프도 있다.

 

메일슬롯은 브로드캐스팅(Broadcasting) 방식의 통신을 지원한다.

 

, 하나의 Sender 은 한번의 메시지 전송으로 여러 Receiver 에게 메시지를 전송 할 수 있다.

 

\\*\mailslot\mailbox

 

이런식으로 하면 된다. * 은 모든 컴퓨터를 지칭한다.

 

위의 예제에서 . * 로 변경해주면 끝난다.

 

, 네트워크로 연결되어 있어야 테스트 가능하다.

 

참고,

 

메일슬롯은 생성과 동시에 Usage Count 1이다.

 

참조하는 프로세스는 메일슬롯을 생성한 프로세스 하나이기 때문이다.

 

자식 프로세스는 생성과 동시에 참조하는 프로세스가 둘이 되기 때문에 2가 되는 것이다.

 

프로세스와 쓰레드를 제외한 다른 모든 커널 오브젝트는 1이다.

 

 

3. Signaled vs Non-Signaled

 

지금까지는 메일슬롯 기반의 IPC 기법을 소개했다.

 

이제는 이야기의 흐름을 전환한다.

 

IPC 는 다음 장에서 이어진다.

 

이제 커널 오브젝트의 상태 에 대한 내용을 설명하고자 한다.

 

커널 오브젝트의 두 가지 상태

 

커널 오브젝트는 두 가지 상태를 지닌다. 이는 리소스에 특정 상황이 발생하였음을 알리기 위한 용도이다.

 

상태에 대한 이해

 

상태라는 용어를 사용하는 이유는 변하기 때문이다.

 

전기밥솥은 취사 버튼을 누르면 취사 상태가 되고, 밥이 다 되면 보온 상태로 변한다.

 

정리하면 대부분 사물은 두 가지 이상의 상태를 지닌다.

 

그리고 그 상태는 특정 상황하에 변경된다.

 

마찬가지로 커널 오브젝트도 Signaled 상태(신호를 받은 상태) , Non-Signaled 상태(신호를 받지 않은 상태) 이다.

 

질문을 하겠다.

 

여기서 말하는 커널 오브젝트의 상태 정보는 어떻게 표현되겠는가?

 

쉽게말하면, 어디에 저장하겠냐는 말이다.

 

그렇다! 커널 오브젝트를 구성하는 멤버변수 중 하나는 커널 오브젝트의 상태 정보를 담당한다.

 

Non-Signaled 라면 FALSE , Signaled 라면 TRUE 를 가지게 된다.

 

 

프로세스 커널 오브젝트의 상태에 대한 이해

 

커널 오브젝트의 상태는 리소스에 특정 상황이 발생하였음을 알려주기 위해 존재하는 것이다.

 

그런데 특정 상황은 리소스마다 다르다.

 

프로세스 커널 오브젝트는 프로세스가 생성될 때 만들어진다.

 

처음 커널 오브젝트가 생성되면 Non-Signaled 상태이다.

 

그러다가 프로세스가 종료되면 Signaled 상태로 변한다.

 

그렇다면, Signaled 에서 Non-Signaled 로 변하는 시점은 언제일까?

 

아마 종료된 프로세스를 다시 실행하면 Non-Signaled 상태가 될 것이다.

 

문제는 종료된 프로세스는 다시 실행을 재개하지 못하는 것이다.

 

, 일단 Signaled 가 되면 절대 Non-Signaled 로 올 수 없다.

 

 

커널 오브젝트의 두 가지 상태를 확인하는 용도의 함수

 

그렇다면, 커널 오브젝트의 상태 정보가 우리에게 주는 이점은 무엇일까?

 

이건 잠시 후 예제에서 보자.

 

여기서 우선 함수 하나를 소개하겠다.

 

DWORD WaitForSingleObject(

           HANDLE hHandle,

           DWORD dwMilliseconds

);

 

핸들을 인자로 전달해서 커널 오브젝트의 상태를 확인하는 함수이다.

 

hHandle 은 상태 확인을 원하는 커널 오브젝트의 핸들이다.

 

dwMilliseconds hHandle 이 가리키는 커널 오브젝트가 Signaled 상태가 될 때 까지 기다리는 함수이다.

 

dwMilliseconds 는 기다릴 수 있는 최대 시간이다.

 

일반적으로, 이런 것을 타임아웃을 설정하는 인자라고 표현한다.

 

만약에 상수 INFINITE 를 전달하면, Signaled 상태가 될 때 까지 무한정 기다리게 된다.

 

함수의 반환값을 보자.

 

WAIT_OBJECT_0 : 커널 오브젝트가 Signaled 상태

 

WAIT_TIMEOUT : Signaled 가 안되고, 시간이 다 됬을 때.

 

WAIT_ABANDONED : 함수가 정상적이지 못한 오류에 의해 반환하는 경우

 

함수 하나를 더 소개하겠다 WaitForSingleObject 의 사촌쯤 되는 함수이다.

 

DWORD WaitForMultipleObjects(

DWORD nCount,

           Const HANDLE* lpHandles,

           BOOL bWaitAll,

DWORD dwMilliseconds

);

이것은 이름 그대로 여러 개의 커널 오브젝트를 확인하는 것이다.

 

nCount 는 배열에 저장되어 있는 핸들 개수를 전달한다.

 

ipHandles 는 핸들을 저장하는 배열의 주소 정보를 전달한다.

 

bWaitAll 관찰 대상이 모두 Signaled 상태가 되기를 기다리는지(TRUE) , 아니면 하나라도 Signaled 가 되면 반환할 것인지(FALSE) 를 결정짓는다.

 

dwMilliseconds WaitForSingleObject 와 같은 의미이다.

 

 

커널 오브젝트의 상태 확인이 필요한 상황의 연출

 

구현해야 할 프로그램의 시나리오

 

두 개의 자식 프로세스를 생성해서 각각 1 부터 5까지 , 그리고 6 부터 10까지 덧셈을 시킨 후 결과를 반환하자.

 

그리고 부모 프로세스는 그 값을 더해 출력하자.

 

구현에 있어서 고민해 봐야 할 내용들

 

두 프로세스가 하는 일은 같다. 다만 계산 범위가 다를 뿐이다.

 

그렇다면, 자식 프로세스의 역할을 하는 프로그램은 어떠한 형태로 해야할까?

 

프로그램이 프로세스가 되는 순간 덧셈의 범위를 전달하는 형식이 가장 쉽다.

 

/*

        PartAdder.cpp

*/

 

#include <stdio.h>

#include <windows.h>

#include <tchar.h>

 

int _tmain(int argc, TCHAR* argv[])

{

        if(argc!=3)

               return -1;

 

        DWORD start=_ttoi(argv[1]);

        DWORD end=_ttoi(argv[2]);

 

        DWORD total=0;

 

        for(DWORD i=start; i<=end; i++)

               total+=i;

 

        return total;

}

 

이제 이정도는 바로 알아볼 수 있을 것이다.

 

, 여기서 합이 -1 이 되면 안된다. 위에서 오류가 나면 -1을 리턴하기 때문에, 합이 -1이 되면 구별할 수 없기 때문이다.

 

오류코드를 따로 만들어도 되나, 그건 알아서 하세요~

 

 

커널 오브젝트에 존재하는 종료코드(Exit Code)

 

이제 이 반환값을 부모 프로세스에 전달해야 한다.

 

프로세스가 종료되면서 전달하는 값을 종료코드라 하고, 이 종료코드는 종료되는 프로세스의 커널 오브젝트에 저장 된다고 이 전에 말했다.

 

따라서 이 코드를 활용해 데이터를 전달할 수 있다.

 

구현의 예와 문제점

 

/*

        NonStopAdderManager.cpp

*/

 

#include <stdio.h>

#include <windows.h>

#include <tchar.h>

 

int _tmain(int argc, TCHAR* argv[])

{

        STARTUPINFO si1={0,};

        STARTUPINFO si2={0,};

       

        PROCESS_INFORMATION pi1;

        PROCESS_INFORMATION pi2;

       

        DWORD return_val1;

        DWORD return_val2;

 

        TCHAR command1[]=_T("PartAdder.exe 1 5");

        TCHAR command2[]=_T("PartAdder.exe 6 10");

 

  DWORD sum=0;

 

        si1.cb=sizeof(si1);

        si2.cb=sizeof(si2);

 

 

        CreateProcess(NULL,

                             command1,

                                NULL,

                                NULL,

                                TRUE,

                                0,

                                NULL,

                                NULL,

                                &si1,

                                &pi1

        );  //CreateProcess 1

 

        CreateProcess(NULL,

                             command2,

                                NULL,

                                NULL,

                                TRUE,

                                0,

                                NULL,

                                NULL,

                                &si2,

                                &pi2

        );  //CreateProcess 2

 

        CloseHandle(pi1.hThread);

        CloseHandle(pi2.hThread);    

 

        GetExitCodeProcess(pi1.hProcess, &return_val1);

        GetExitCodeProcess(pi2.hProcess, &return_val2);

 

        if(return_val1==-1 || return_val2==-1)      

               return -1; //비정상적 종료

       

        sum+=return_val1;

        sum+=return_val2;

 

        _tprintf(_T("total : %d \n"), sum);

       

        CloseHandle(pi1.hProcess);

        CloseHandle(pi2.hProcess);

 

        return 0;

}

 

하지만 이것을 실행하면 518 이 출력된다.

 

1~10의 합은 55인데 왜 이상한 값이 나올까??

 

이 예제에서는 우리도 모르게 한 가지를 가정하고 있다.

 

GetExitCodeProcess 가 실행되기 전에 두 개의 자식 프로세스는 연산결과에 해당하는 종료코드를 반환하고 종료한다. 라는 것이다.

 

여기서 오류가 난 것을 보아, 종료 되기 전에 함수가 호출이 된 것이라 볼 수 있다.

 

WaitForSingleObject 함수의 유용성

 

이제 이 문제점을 해결해 보자. 아주 간단하다

 

자식 프로세스가 종료될 때 까지 기다리면 되는 것이다.

 

그렇다면 그게 가능할까 ?? WaitForSingleObject 함수면 가능하다.

 

원리는 다음과 같다.

 

프로세스의 커널 오브젝트는 생성과 동시에 Non-Signaled 상태가 된다.

 

따라서 WaitForSingleObject 를 호출하면 , 함수는 블로킹이 되다가 종료되면 빠져나오는 것이다.

 

/*

        AdderManager.cpp

*/

 

#include <stdio.h>

#include <windows.h>

#include <tchar.h>

 

int _tmain(int argc, TCHAR* argv[])

{

        STARTUPINFO si1={0,};

        STARTUPINFO si2={0,};

       

        PROCESS_INFORMATION pi1;

        PROCESS_INFORMATION pi2;

       

        DWORD return_val1;

        DWORD return_val2;

 

        TCHAR command1[]=_T("PartAdder.exe 1 5");

        TCHAR command2[]=_T("PartAdder.exe 6 10");

 

  DWORD sum=0;

 

        si1.cb=sizeof(si1);

        si2.cb=sizeof(si2);

 

        CreateProcess(NULL,

                             command1,

                                NULL,

                                NULL,

                                TRUE,

                                0,

                                NULL,

                                NULL,

                                &si1,

                                &pi1

        );  //CreateProcess 1

 

        CreateProcess(NULL,

                             command2,

                                NULL,

                                NULL,

                                TRUE,

                                0,

                                NULL,

                                NULL,

                                &si2,

                                &pi2

        );  //CreateProcess 2

 

        CloseHandle(pi1.hThread);

        CloseHandle(pi2.hThread);    

 

  /****** 문제 해결을 위한 코드*******/

        WaitForSingleObject(pi1.hProcess, INFINITE);

        WaitForSingleObject(pi2.hProcess, INFINITE);

       

 

        GetExitCodeProcess(pi1.hProcess, &return_val1);

        GetExitCodeProcess(pi2.hProcess, &return_val2);

 

        if(return_val1==-1 || return_val2==-1)      

               return -1; //비정상적 종료.

       

        sum+=return_val1;

        sum+=return_val2;

 

        _tprintf(_T("total : %d \n"), sum);

       

        CloseHandle(pi1.hProcess);

        CloseHandle(pi2.hProcess);

 

        return 0;

}

 

 

GetExitCodeProcess 함수 앞에 위치했다.

 

물론 두 번째 인자는 INFINIT , 종료 될 때 까지 블로킹 상태가 된다.

 

이렇게 하고 실행하면 55가 출력됨을 알 수 있다.

 

이제 하나의 과제를 주겠다.

 

WaitForSingleObject 대신 WaitForMultipleObjects 함수를 이용하여 위에 것을 해 보아라.

 

이 내용은 8장에서 이어진다.

 

7.docx

설정

트랙백

댓글