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

1. 핸들 테이블과 오브젝트 핸들의 상속

 

이제 오브젝트 핸들(커널 오브젝트 핸들) 의 상속과 핸들 테이블에 대한 개념을 소개한다.

 

도입 배경(Introduction to Jeffery Richter)

 

미국 유명한 저자 Jeffrey Richter 은 그의 저서 Programming Application for Microsoft Windows 에서 이런 말을 한다.

 

I believe that a competent Windows programmer must understand how a process’s handle table is managed.

 

유능한 윈도우 프로그래머는 프로세스 핸들 테이블이 어떻게 관리되는지 이해하고 있어야 한다는 뜻이다.

 

그의 저서에는 핸들 테이블이 어떻게 관리되는지를 설명한다.

 

그러나, Windows가 핸들 테이블을 관리하는 실질적 방법과 다소 차이가 있다.

 

그 이유는,

 

첫 째로, MS 에서는 Windows 운영체제를 공개하지 않고 있다. 이렇게 Windows 소스코드를 공개하지 않는 한, 프로세스 핸들 테이블이 어떻게 관리되는지는 정확히 알 수 없다.

 

공개되는 자료도 있긴 하지만, 최근의 정보는 담지 않는다.

 

둘 째로, Windows 운영체제의 종류 및 버전마다 핸들 테이블이 관리되는 방법에 차이가 있다.

 

따라서, 정확히 설명을 하려면 버전별로 나눠 설명해야 한다.

 

여기서 설명하는 방법도 실질적으로 Windows가 핸들 테이블을 관리하는 것과 조금 차이가 있다.

 

 

프로세스의 커널 오브젝트 핸들 테이블

 

핸들과 커널 오브젝트의 리뷰(Review)

 

7장의 MailReceiver.cpp 의 동작 원리를 다른 각도에서 살펴보고자 한다.

 

MailReceiver CreateMailslot 의 호출로 메일슬롯을 생성한다.

 

그런데, 이 리소스는 운영체제에 의해 관리되므로 메일슬롯 정보를 담고 있는 커널 오브젝트가 생성되고, 최종적으로는 커널 오브젝트의 핸들이 반환된다.

 

이 핸들은 메일슬롯의 커널 오브젝트를 가리키는 용도이다.

 

핸들은 메일 슬롯 정보를 저장하는 커널 오브젝트의 메모리 번지를 가리키는 용도이다.

 

그런데 핸들을 가지고 어떻게 메모리에 있는 메일 슬롯에 접근할까?

 

물론 윈도우가 다 해주긴 하지만, 어떤 방식인지 이해하는 것이 매우 중요하다.

 

 

프로세스의 핸들 테이블 도입

 

이것을 위해 핸들 테이블이라는 것이 도입된다.

 

핸들 테이블은 핸들 정보를 저장하고 있는 테이블로서 프로세스별로 독립적이다.

 

각각의 프로세스가 자신만의 핸들 테이블을 하나씩 가지고 있는 것을 의미한다.

 

CreateProcess 함수나 CreateMailslot 과 같은 함수 호출을 통해, 리소스 생성을 요구한 결과로 핸들 정보를 얻게 될 경우, 프로세스 자신에게 속해 있는 핸들 테이블에 해당 정보가 등록된다.

 

 

핸들의 상속

 

CreateProcess 함수를 호출하면 새로운 자식 프로세스가 생성된다.

 

물론 자식 프로세스를 위한 핸들 테이블도 생성된다.

 

그렇다면, 부모 프로세스의 핸들 테이블과 자식 프로세스의 핸들 테이블은 무슨 관계일까?

 

CreateProcess 5번째 인자가 무엇이냐에 따라 자식 핸들 테이블은 부모를 상속할 수 있다.

 

이것의 사용은 잠시후 보도록 하겠다.

 

 

핸들의 상속에 대한 이해

 

자식 프로세스는 부모 프로세스의 핸들 테이블의 핸들 정보를 상속받을 수 있다.

 

하지만 모든 핸들 정보를 받는 것이 아니다.

 

실제 핸들 테이블에는 상속 여부를 결정짓는 컬럼(Column) Y , N 이 존재한다.

 

또한 상속 받을 때, 상속 여부에 대한 정보 변경 없이 그대로 상속된다.

 

따라서, 자식 프로세스가 자식 프로세스를 만들 때 계속 상속 된다.

 

그럼, 상속되는 핸들과 안되는 핸들의 기준은 무엇일까?

 

이것은 리소스를 생성하는 함수의 전달인자로, 프로그래머가 결정하는 요소이다.

 

핸들의 상속을 위한 전달인자.

 

CreateProcess 5번째 인자인 BOOL bInheritHandles 가 상속 여부를 결정하는 요소이다.

 

참고,

핸들 테이블 , 오브젝트 핸들 테이블 , 커널 오브젝트 핸들 테이블 , 프로세스 핸들 테이블 , 프로세스 커널 오브젝트 핸들 테이블은 같은 표현들이다.

 

프로그래머들이 프로세스와 관련 있음을 부각시키기 위해 핸들 테이블 앞에 프로세스 란 단어도 붙이고 등등.. 하다보니 저런 많은 표현들이 나온 것이다.

 

 

핸들의 상속과 커널 오브젝트의 Usage Count

 

커널 오브젝트를 참조하는 프로세스 개수만큼 커널 오브젝트의 Usage Count가 증가한다.

 

그럼, 커널 오브젝트를 참조하는 프로세스가 되기 위한 조건은 무엇일까?

 

핸들 테이블의 관점에서 생각해 보자.

 

프로세스가 핸들을 얻게 되었다는 말은 핸들 테이블에 해당 핸들에 대한 정보가 갱신(추가) 되었음을 의미하는 것이다

 

CreateMailslot 함수의 포출로 메일슬롯을 생성한다고 가정하자. 그렇다면 이런 순서일 것이다.

 

1. 메일 슬롯 리소스 생성

2. 커널 오브젝트 생성

3. 핸들 정보가 핸들 테이블에 갱신

4. CreateMailslot 함수를 빠져 나오면서 핸들값 반환

 

보통 4번에 초점을 맞추게 되는데, 실질적으로 핸들 정보를 받는 것은 3번이다.

 

 

상속이 되기 위한 핸들의 조건

 

핸들의 상속 여부는 리소스가 생성되는 순간에 결정된다.

 

그것도 프로그래머에 의해 결정이 된다.

 

CreateProcess CreateMailslot 함수를 기반으로 설명하겠다.

 

CreateMailslot 함수의 네 번째 인자인 LPSECURITY_ATTRIBUTES lpSecurityAttributes 를 보자.

 

이 값에 NULL 이 되면 메일 슬롯의 핸들은 상속되지 않는다.

 

이 구조체를 적절히 초기화 해서 변수의 주소값을 전달하면 핸들은 상속 된다.

 

typedef struct _SECURITY_ATTRIBUTES {

    DWORD nLength;

    LPVOID lpSecurityDescriptor;

    BOOL bInheritHandle;

} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES;

 

번째 멤버는 구체 변수 크기를 바이트 단위로 설정해 준다. 반드시 구조체 변수 크기로 초기화한다.

 

번째 멤버는 NULL 초기화한다. 이것은 핸들의 상속 관점에서는 무의미하다.

 

번째 멤버는 TRUE 설정한다. 이것이 상속 여부를 결정짓는 것이므로, 반드시 TRUE 한다.

 

 

따라서 종합하면,

 

SECURITY_ATTRIBUTES sa;

sa.nLength = sizeof(sa);

sa.lpSecurityDescriptor = NULL;

sa.bInheritHandle = true;

 

이런 형태가 것이다.

 

이것은 메일슬롯 아니라 대부분 리소스 생성방식에 동일하다.

 

 

예제를 통해 확인하는 핸들 정보의 상속

 

 

메일슬롯 예제의 고찰

 

 

상속을 받지 않았을 , 핸들값이 같다고 해서 같은 핸들이 아니다.

 

각각의 프로세스는 하나씩의 핸들 테이블이 서로 독립적이기 때문에,

 

핸들값이 같다고 해서 같은 핸들이라고 수는 없다.

 

예제를 통해 보자.

 

먼저 7장에 나온 MailSender 수정한 것이다.

 

/*

        MailSender2_1.cpp

*/

 

#include <windows.h>

#include <tchar.h>

#include <stdio.h>

 

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

 

int _tmain(int argc, LPTSTR argv[])

{

        HANDLE hMailSlot;

        TCHAR message[50];

  DWORD bytesWritten;  // number of bytes write

       

        SECURITY_ATTRIBUTES sa;

        sa.nLength=sizeof(sa);

        sa.lpSecurityDescriptor=NULL;

        sa.bInheritHandle=TRUE;

 

        hMailSlot=CreateFile(SLOT_NAME, GENERIC_WRITE, FILE_SHARE_READ, &sa,

               OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

        if(hMailSlot==INVALID_HANDLE_VALUE)

        {

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

               return 1;

        }

 

        _tprintf(_T("Inheritable Handle : %d \n"), hMailSlot);

        FILE* file=_tfopen(_T("InheritableHandle.txt"), _T("wt"));

        _ftprintf(file, _T("%d"), hMailSlot);

        fclose(file);

 

        STARTUPINFO si={0,};

        PROCESS_INFORMATION pi;      

        si.cb=sizeof(si);

 

        TCHAR command[]=_T("MailSender2_2.exe");

 

        CreateProcess(NULL,

                             command,

                                NULL,

                                NULL,

                                TRUE,  // 자식 프로세스에게 핸들을 상속!

                                CREATE_NEW_CONSOLE,

                                NULL,

                                NULL,

                                &si,

                                &pi

        );  //CreateProcess

 

        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;

}

 

 

소스에서 중요한 사실은 5번째 인자가 TRUE 라는 것이다.

 

, 핸들 테이블을 상속하겠다는 의미이다.

 

소스에서 보면, 핸들 정보를 파일에 저장하는 것일까라는 의문이 든다.

 

예제에서는 자식 프로세스에게 메일슬롯 관련 핸들을 상속하고 있다.

 

따라서 자식 프로세스의 핸들 테이블에는 이와 관련된 정보가 등록 된다.

 

그러나, 등록된 핸들 정보를 자식 프로세스는 확인할 수가 없다.

 

핸들 테이블은 운영체제에 관리된다. 따라서, 우리가 프로그램상에서 핸들 테이블 정보를 조회해 없다.

 

이제, 예제에서 생성하는 자식 프로세스의 소스를 보자.

 

기능은 파일을 열어 상속된 핸들값을 확인하고, 이를 이용해 부모 프로세스와 마찬가지로 메일슬롯 데이터를 전송하도록 되어있다.

 

참고,

파일을 통해 데이터를 주고 받는 방식은 위험성이 있다.

 

자식 프로세스가 접근하기 전에 파일이 삭제 되거나, 악성 코드 등에서 말이다.

 

이보다는 프로세스 생성 매개변수로 전달하는 것이 훨씬 안전한 방식이다.

 

 

/*

        MailSender2_2.cpp

*/

 

#include <windows.h>

#include <tchar.h>

#include <stdio.h>

 

int _tmain(int argc, LPTSTR argv[])

{

        HANDLE hMailSlot;

        TCHAR message[50];

    DWORD bytesWritten;  // number of bytes write

 

        /************* 핸들을 얻는 코드 *****************/

 

        FILE* file=_tfopen(_T("InheritableHandle.txt"), _T("rt"));

        _ftscanf(file, _T("%d"), &hMailSlot);

        fclose(file);

 

        _tprintf(_T("Inheritable Handle : %d \n"), hMailSlot);

 

        /**********************************************/

 

        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);

                       _gettchar();

                       CloseHandle(hMailSlot);

                       return 1;

               }

 

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

               {

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

                       break;

               }

        }

 

        CloseHandle(hMailSlot);

        return 0;

}

 

 

 

Pseudo 핸들과 핸들의 중복(Duplicate)

 

현재 실행중에 있는 프로세스 자신의 핸들을 얻는 방법으로, GetCurrentProcess 함수가 있다.

 

실제로 함수를 통해 얻은 핸들을 이용해서, 프로세스 자신의 커널 오브젝트에 접근이 가능하다.

 

하지만, 이렇게 얻은 핸들은 가짜 핸들(Pseudo) 라고 한다.

 

왜냐면, 이런 핸들은 핸들 테이블에 등록되지 않은 핸들이고, 그냥 현재 실행 중인 프로세스를 참조하기 위한 용도로 정의해 놓은, 약속된 상수가 반환하는 것이기 때문이다

 

따라서, 자식 프로세스로 상속되지 않으며, CloseHandle 인자로 전달해도 아무 일도 없다.

 

일단 진짜 핸들을 얻는 방법을 생각해 보자.

 

DuplicateHandle 이라는 함수를 소개하겠다. 이름처럼 핸들 복사하는 함수이다.

 

DuplicateHandle(

    HANDLE hSourceProcessHandle,

    HANDLE hSourceHandle,

    HANDLE hTargetProcessHandle,

    LPHANDLE lpTargetHandle,

    DWORD dwDesiredAccess,

    BOOL bInheritHandle,

    DWORD dwOptions

    );

 

번째 인자는 복제할 핸들을 소유하는 프로세스를 지정한다.

 

번째 인자는 복제할 핸들을 지정한다.

 

번째 인자는 복제된 핸들을 소유할 프로세스를 지정한다.

 

번째 인자는 복제된 핸들값을 저장할 변수의 주소를 지정한다.

 

다섯 번째 인자는 접근 권할을 지정한다.일단 0 전달한다. 자세한건 MSDN 참고

 

여섯 번째 인자는 복제된 핸들의 상속 여부를 지정한다.

 

DuplicateaHandle 함수로 하는 핸들 복사는 비교적 쉽게 이뤄진다.

 

중요한 것은, 핸들 테이블에 등록되어야 진정한 복사이고, 복사한다고 해서 핸들값이 똑같다는 것은 아니다.

 

번째, 번째 인자에 같은 프로세스를 지정해 주어도 된다.

 

그러면 핸들은 복사되서 같은 프로세스에 하나 생기는 것이다.

 

그리고 함수를 사용 , Usage count 증가한다.

 

따라서, 복사한 핸들도 CloseHandle 함수 호출로 반환해야만 한다.

 

 

그리고, GetCurrentProcess 함수로 얻은 핸들을 복사(가짜 핸들) 한다면, 진짜 핸들이 생성되어 핸들 테이블에 등록된다.

 

 

부모 프로세스의 핸들을 자식 프로세스에게 전달하기

 

보통 부모 프로세스의 핸들을 자식에게 전달하는게 보통이지만, 상황에 따라 반대인 경우도 있다.

 

/*

    DuplicateHandleOne.cpp

*/

#include <stdio.h>

#include <stdlib.h>

#include <windows.h>

 

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

{

        HANDLE hProcess;

        TCHAR cmdString[1024];

 

        DuplicateHandle(

               GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(),

               &hProcess, 0, FALSE, DUPLICATE_SAME_ACCESS

        );

 

        _stprintf(cmdString, _T("%s %u"), _T("ChildProcess.exe"), (unsigned)hProcess);

 

        STARTUPINFO si={0,};

        PROCESS_INFORMATION pi={0,};

    si.cb = sizeof(si);

 

        BOOL isSuccess = CreateProcess(

               NULL, cmdString, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);

                      

        if(isSuccess == FALSE)

        {

               _tprintf( _T("CreateProcess failed \n") );

               return -1;

    }

       

        CloseHandle(pi.hProcess);

        CloseHandle(pi.hThread);

 

        _tprintf(_T("[Parent Process]\n"));

        _tprintf( _T("ooooooooooooooooooooooopps! \n"));

        return 0;

}

 

 

 

여기서는 DuplicateHandle 함수가 이다.

 

GetCurrentProcess 함수를 통해 얻는 가짜 핸들을 진짜 핸들로 구성해서 핸들 테이블에 등록한다.

 

그럼 이번에는 예제의 자식 프로세스 코드이다.

 

/*

    DuplicateHandleChildProcess.cpp

*/

#include <stdio.h>

#include <stdlib.h>

#include <windows.h>

 

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

{

        HANDLE hParent = (HANDLE)_ttoi(argv[1]);

        DWORD isSuccess = WaitForSingleObject(hParent, INFINITE);

 

        _tprintf(_T("[Child Process] \n"));

 

        if(isSuccess == WAIT_FAILED)

        {

               _tprintf( _T("WAIT_FAILED returned!") );

               Sleep(10000);

               return -1;

        }

        else

        {

               _tprintf(_T("General Lee said, \"Don't inform the enemy my death\""));

               Sleep(10000);

               return 0;

        }

}

 

것은 WaitForSingleObject 함수를 호출해서 부모 프로세스의 핸들을 인자로 전달한다.

 

부모 프로세스가 종료될 까지 기다리는 것이다.

 

 

2. 파이프 방식의 IPC

 

이제부터 장의 주제에 걸맞게, 프로세스간 통신기법 가장 대표적인 파이프에 대해 설명한다.

 

 

메일슬롯에 대한 회고와 파이프의 이해

 

Windows 파이프 메커니즘에는 가지 종류가 있는데 , 하나는 이름없는 파이프, 하나는 이름있는 파이프 이다.

 

메일슬롯은 서로 관련없는 프로세스들(네트워크로 연결되어 통신하는 프로세스들이나, 부모 자식간 연관 관계가 전혀 없는 프로세스들)사이에서 통신할 유용한 기법이다.

 

 

반면에 이름없는 파이프는 지극히 관계가 있는 프로세스들 사이에서 통신하는 경우에 유용하다.

 

이제 파이프와 메일슬롯을 비교하자.

 

공통점은 이름이 있고, 주소 정보가 있다.

 

주소가 있다는 뜻은 메일슬롯처럼 서로 관계가 없는 프로세스들 사이에서도 주소 정보를 공유함으로써 데이터를 주고 받을 있다는 뜻이 아닐까? 이것이 바로 핵심포인트이다.

 

실제로 이름있는 파이프는 메일슬롯과 마찬가지로, 서로 관계가 없는 프로세스들 사이에서도 데이터를 주고 받을 있다. 차이점은 이름있는 파이프는 양방향 통신이라는 점이다.

 

따라서, 간단한 채팅 프로그램을 만들고자 한다면, 이름있는 파이프가 좋을 것이다.

 

반면 메일슬롯은 브로드캐스트 방식의 데이터 전송이 가능하다.

 

정리하자!

 

메일슬롯 : 브로드캐스트 방식의 단방향 통신, 메일슬롯에 할당된 주소를 기반으로 통신한다. 따라서 관계없는 프로세스들 사이에서도 통신이 가능하다.

 

이름없는 파이프 : 단방향 통신방식을 취하며, 파이프를 통해 생성된 핸들을 기반으로 통신하기 때문에, 프로세스 사이 관계가 있어야 한다 ( 이거 쓰는지는 모르겠다..)

 

이름있는 파이프 : 메일슬롯과 유사하다. 차이가 있다면, 브로드캐스트 방식을 지원 않는 대신, 양방향 통신을 지원한다.

 

 

이름없는 파이프(Anonymous Pipe)

 

앞으로 이름없는 파이프라고 하면 비가 옥상에 고인 물을 배수하기 위해 땅으로 연결되어 있는 배수 파이프를 생각하기 바란다.

 

파이프로는 데이터를 방향으로 전송할 있다.

 

CreatePipe(

    PHANDLE hReadPipe,

    PHANDLE hWritePipe,

    LPSECURITY_ATTRIBUTES lpPipeAttributes,

    DWORD nSize

    );

 

끝에서는 데이터가 들어가고 다른 한쪽에서는 데이터가 나오는 것이 파이프의 원리 이다.

 

따라서 파이프 생성 각각의 끝에 접근하기 위한 개의 핸들을 얻게 되는데 이것이 번째와 번째 인자이다.

 

번째 인자는 보안 관련 정보를 전달할 사용된다.

 

번째 인자는 파이프의 버퍼 사이즈를 지정하는 용도이다.

 

파이프의 길이를 지정한다고 생각하면 된다.

 

0 전달하면 디폴트 사이즈가 된다.

 

예제는 이름없는 파이프의 특성을 설명하기 위한 예제이다.

 

/*

        anonymous_pipe.cpp

*/

#include <windows.h>

#include <tchar.h>

#include <stdio.h>

 

int _tmain(int argc, LPTSTR argv[])

{

        HANDLE hReadPipe, hWritePipe; //pipe handle

 

        TCHAR sendString[] = _T("anonymous pipe");

        TCHAR recvString[100];

 

        DWORD bytesWritten;

        DWORD bytesRead;         

 

        /* pipe 생성 */

        CreatePipe(&hReadPipe, &hWritePipe, NULL, 0);

 

        /* pipe 다른 한쪽 끝을 이용한 데이터 송신*/

        WriteFile(hWritePipe, sendString, lstrlen(sendString)*sizeof(TCHAR), &bytesWritten, NULL);

        _tprintf( _T("string send: %s \n"), sendString);

 

 

        /* pipe 다른 한쪽 끝을 이용한 데이터 수신*/

        ReadFile(hReadPipe, recvString, bytesWritten, &bytesRead, NULL);

        recvString[bytesRead/sizeof(TCHAR)] = 0;

        _tprintf( _T("string recv: %s \n"), recvString);

 

 

        CloseHandle(hReadPipe);

        CloseHandle(hWritePipe);

 

        return 0;

}

 

예제에서는 입력용과 출력용 핸들을 하나의 프로세스 내에서 사용하였다.

 

예제를 조금 변형해서 자식 프로세스를 생성하여 입력용 혹은 출력용 핸들을 상속한다면, 프로세스간 메시지 전송이 가능해진다.

 

 

참고,

 

여기서 이해가 안된다면, 그냥 다음장으로 넘어가도 된다.16 전까지만 이해하면 된다.

 

 

이름있는 파이프(Named Pipe)

 

이름있는 파이프의 핵심은 양방향 통신이다.

 

Server client 있다 하고,

 

PIPE CreateNamedPipe 의해 생성된다.

 

Client CreateFile 함수 호출에 의해 파이프가 연결 된다.

 

Server client PIPE 통하여 연결 된다.

 

파이프는 ConnectNamedPipe 의해 연결 대기 상태로 전환 한다.

 

위의 것을 보면.

 

CreateNamedPipe 함수를 통해서 파이프를 생성한다.

 

하지만, 아직 파이프는 역할을 하지 못한다.

 

파이프가 역할을 하기 위해서는 ConnectNamedPipe 함수가 호출되어야 하는데, 것에 의해 파이프는 연결 요청을 기다리는 상태로 변한다.

 

이제 서버의 역할이 끝났다.

 

클라이언트에서 연결 요청을 해야 것이다.

 

클라이언트는 서버가 만들어 놓은 파이프에 연결을 위해 연결 요청을 위한 리소스를 생성하고, 연결을 시도해야 하는데, CreateFile 모든 것을 처리한다.

 

CreateFile 함수는 파일 생성 함수이지만, 파이프로 연결 하는 요청을 하는 경우에도 사용된다.

 

CreateNamedPipe(

    LPCWSTR lpName,

    DWORD dwOpenMode,

    DWORD dwPipeMode,

    DWORD nMaxInstances,

    DWORD nOutBufferSize,

    DWORD nInBufferSize,

    DWORD nDefaultTimeOut,

    LPSECURITY_ATTRIBUTES lpSecurityAttributes

    );

 

번째 인자는 파이프의 이름이다. Ex) \\.\pipe\pipename

 

번째 인자는 다음 하나이다.

 

PIPE_ACCESS_DUPLEX : 읽기, 쓰기가 모두 가능함

PIPE_ACCESS_INBOUND : 읽기만 가능하다. (서버 입장)

PIPE_ACCESS_OUTBOUNT : 쓰기만 가능하다. (서버 입장)

 

번째 인자는 데이터 전송 타입,수신 타입, 블로킹 모드 이렇게 3가지를 설정한다.

 

번째 인자는 생성 가능한 파이프의 최대 개수를 지정한다.

 

여기서 지정하는 개수만큼 파이프 클라이언트의 연결 요청을 수용할 있다.

 

값의 범위는 1 에서 PIPE_UNLIMITED_INSTANCES 까지 이다.

 

PIPE_UNLIMITED_INSTANCES 전달되면 생성 가능한 최대 개수만큼 생성을 허용한다.

 

다섯 번째 인자는 이름 있는 파이프의 출력 버퍼 사이즈를 지정한다. 0 일시 디폴트

 

여섯 번째 인자는 버퍼 사이즈를 지정 한다. 이것 역시 0이면 디폴트

 

일곱 번째 인자는 WaitNamedPipe 함수에 적용할 기본 만료 시간을 밀리세컨드로 지정한다.

 

여덟 번째 인자는 보안 속성이다.

 

 

번째 인자에 대한 보충설명을 하겠다. 이것은 |(OR) 연산으로 전달된다.

 

데이터 전송방식 : PIPE_TYPE_BYTE(바이트),PIPE_TYPE_MESSAGE(메시지)

 

데이터 전송 바이너리로 전송할 것인지, 메시지로 전송할 것인지 정하는 것이다.

 

데이터 수신방식 : PIPE_READMODE_BYTE(바이트),PIPE_READMODE_MESSAGE(메시지)

 

데이터 전송방식과 같다.

 

함수 리턴방식 : PIPE_WAIT(블로킹) , PIPE_NOWAIT(-블로킹)

 

무조건 PIPE_WAIT 전달된다고 기억하자.

 

무조건 PIPE_WAIT 이다.

 

문자열을 주고받는 것이 목적이라면, 다음 조합이 최선이다.

 

PIPE_TYPE_MESSAGE | PIE_READMODE_MESSAGE | PIPE_WAIT

 

 

ConnectNamedPipe(

    HANDLE hNamedPipe,

    LPOVERLAPPED lpOverlapped

    );

 

함수는 생서한 파이프를 연결 요청 대기 상태로 변경시키는 함수이다.

 

번째 인자는 CreateNamedPipe 함수 호출을 통해 생성한 파이프의 핸들이다.

 

번째 인자는 중첩 I/O 위한 전달 인자이다. 일단은 NULL 전달하자.

 

 

이제 나올 예제에 대해 설명하겠다.

 

우선 클라이언트가 서버에 접속하고, 클라이언트는 파일 이름 하나를 메시지 형태로 전달한다.

 

마지막으로 서버는 이에 해당하는 파일을 열어서 파일의 내용을 클라이언트에 전달한다.

 

/*

        namedpipe_server.cpp

*/

#include <stdio.h>

#include <stdlib.h>

#include <windows.h>

 

#define BUF_SIZE 1024

 

int CommToClient(HANDLE);

 

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

{

        LPTSTR pipeName = _T("\\\\.\\pipe\\simple_pipe");

 

        HANDLE hPipe;

       

        while(1)

        {

        hPipe = CreateNamedPipe (

               pipeName,            // 파이프의 이름

               PIPE_ACCESS_DUPLEX,       // 읽기 쓰기 모드 지정

               PIPE_TYPE_MESSAGE |

               PIPE_READMODE_MESSAGE | PIPE_WAIT,

               PIPE_UNLIMITED_INSTANCES, // 최대 인스턴스 개수

               BUF_SIZE,                  // 출력 버퍼 사이즈

               BUF_SIZE,                  // 입력 버퍼 사이즈

               20000, // 클라이언트 타임아웃

               NULL                    // 디폴트 보안 속성

               );

 

        if (hPipe == INVALID_HANDLE_VALUE)

        {

               _tprintf( _T("CreatePipe failed"));

               return -1;

        }

 

        BOOL isSuccess;

        isSuccess = ConnectNamedPipe(hPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);

 

        if (isSuccess)

               CommToClient(hPipe);

        else

               CloseHandle(hPipe);

        }

        return 1;

}

 

int CommToClient(HANDLE hPipe)

{

        TCHAR fileName[MAX_PATH];

        TCHAR dataBuf[BUF_SIZE];

 

        BOOL isSuccess;

        DWORD fileNameSize;

 

        isSuccess = ReadFile (

               hPipe,        // 파이프 핸들

               fileName,    // read 버퍼 지정

               MAX_PATH * sizeof(TCHAR), // read 버퍼 사이즈

               &fileNameSize,  // 수신한 데이터 크기

               NULL);      

 

        if (!isSuccess || fileNameSize == 0)

        {

               _tprintf( _T("Pipe read message error! \n") );

               return -1;

        }

 

        FILE * filePtr = _tfopen(fileName, _T("r") );

 

        if(filePtr == NULL)

        {

               _tprintf( _T("File open fault! \n") );

               return -1;

        }

 

        DWORD bytesWritten = 0;

        DWORD bytesRead = 0;

 

        while( !feof(filePtr) )

        {

               bytesRead = fread(dataBuf, 1, BUF_SIZE, filePtr);

 

               WriteFile (

                       hPipe,                        // 파이프 핸들

                       dataBuf,                      // 전송할 데이터 버퍼

                       bytesRead,             // 전송할 데이터 크기

                       &bytesWritten, // 전송된 데이터 크기

                       NULL); 

 

       if (bytesRead != bytesWritten)

               {

                   _tprintf( _T("Pipe write message error! \n") );

                   break;

               }

        }

 

        FlushFileBuffers(hPipe);

        DisconnectNamedPipe(hPipe);

        CloseHandle(hPipe);

        return 1;

}

 

 

우선 가장 줄기를 보자. While 루프가 어느 영역을 반복하는지 확인하면, 것을 있다.

 

여러 클라이언트와 통신 가능! 하지만 순간에 클라이언트와만 가능!”

 

여기서 이상한 점이 있다.

 

CreateNamedPipe 함수는 while 루프를 돌면서 계속 호출된다.

 

그러면 때마다 생성할 있는 파이프의 최대 개수를 지정해야 하는 것일까??

 

이는 함수의 번째 호출과 이후의 호출에서 역할 차이가 있기 때문이다.

 

파이프의 최대 개수는 처음 호출될 지정된다.

 

이후로는 단지 파이프 생성만을 목적으로 한다. 그래도 번째 호출 값과 같아야 한다.

 

 

ConnectNamedPipe 클라이언트의 연결 요청을 수락하는 작업이다. 만약 요청이 없으면 때까지 블로킹 상태가 된다.

 

CommToClient 함수를 보자.

 

클라이언트와 연결이 성립되면, 파이프 핸들을 전달하면서 함수가 호출된다.

 

여기서 것은 수신하는 함수, 전송하는 함수, 파이프의 연결을 종료하는 함수(FlushFileBuffers,DisconnectNamedPipe) 이다.

 

FlushFileBuffers 출력 버퍼를 비우는 역할이다. 의미는 목적지로의 전송을 의미한다.

 

기본적으로 windows 파일 시스템을 포함한 대부분의 ,출력 상황에서도 버퍼링을 한다.

 

따라서 이미 전송되었다고 생각하는 데이터들이 시스템 메모리에 남아 있을 있다.

 

DisconnectNamedPipe 클라이언트가 에러 메시지를 받을 있도록 도와준다.

 

함수로 인해, 파이프 연결이 끊기고 나면, 클라이언트는 파이프 접근시 에러가 발생한다.

 

 

이제는 클라이언트를 제시하겠다.

 

예제는 동시에 두개 이상의 클라이언트 연결을 허용할 없는 구조이다.

 

이후에 멀티쓰레드 관련된 내용을 공부하면 가능해질 것이다.

 

/*

        namedpipe_client.cpp

*/

#include <stdio.h>

#include <stdlib.h>

#include <windows.h>

 

#define BUF_SIZE 1024

 

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

{

    HANDLE hPipe;

    TCHAR readDataBuf[BUF_SIZE + 1];

    LPTSTR pipeName = _T("\\\\.\\pipe\\simple_pipe");

   

    while(1)

    {

                hPipe = CreateFile(

                                       pipeName,             // 파이프 이름

                                       GENERIC_READ | GENERIC_WRITE, // 읽기 모드와 쓰기 동시 지정

                                       0,

                                       NULL,

                                       OPEN_EXISTING,

                                       0,

                                       NULL

                );

               

                if (hPipe != INVALID_HANDLE_VALUE)

                    break;

                    

                if (GetLastError() != ERROR_PIPE_BUSY)

                {

                    _tprintf( _T("Could not open pipe \n") );       

                    return 0;

                }

               

                if (!WaitNamedPipe(pipeName, 20000))

                {

                    _tprintf( _T("Could not open pipe \n") );

                    return 0;

                }

    }

   

    DWORD pipeMode = PIPE_READMODE_MESSAGE|PIPE_WAIT; // 메시지 기반으로 모드 변경

    BOOL isSuccess = SetNamedPipeHandleState (

                       hPipe,      // 파이프 핸들

                       &pipeMode,  // 변경할 모드 정보

                       NULL,     // 설정 x

                       NULL);    // 설정 x

   

    if (!isSuccess)

    {

        _tprintf( _T("SetNamedPipeHandleState failed") );

        return 0;

    }

   

    LPCTSTR fileName = _T("news.txt");

    DWORD bytesWritten = 0;

   

    isSuccess = WriteFile (

                    hPipe,                // 파이프 핸들

                    fileName,             // 전송할 메시지

                    (lstrlen(fileName)+1) * sizeof(TCHAR), // 메시지 길이

                    &bytesWritten,             // 전송된 바이트

                    NULL);

   

    if (!isSuccess)

    {

      _tprintf( _T("WriteFile failed") );

      return 0;

    }

   

    DWORD bytesRead = 0;

    while(1)

    {

        isSuccess = ReadFile(

                        hPipe,                                          // 파이프 핸들

                        readDataBuf,                            // 데이터 수신할 버퍼

                        BUF_SIZE * sizeof(TCHAR),  // 버퍼 사이즈

                        &bytesRead,                                     // 수신한 바이트

                        NULL);     

        if (! isSuccess && GetLastError() != ERROR_MORE_DATA)

            break;

       

        readDataBuf[bytesRead] = 0;

        _tprintf( _T("%s \n"), readDataBuf );

    }

   

    CloseHandle(hPipe);

    return 0;

}

 

 

위의 코드에서 이름을 지정해 주는 부분에서 \\.\pipe\pipename 이라고 되어 있는데, 것은 대의 컴퓨터에서 테스트 것을 고려한 것이다.

 

아닐 경우에는 반드시 . 대신 서버이름을 넣어주어야 한다.

 

에러코드 ERROR_PIPE_BUSY 연결 요청이 Pending 상태(여유분의 파이프가 없어서, 요청이 수락 되려면 잠시 기다려야 하는 상황) 이다.ERROR_PIPE_BUSY 반환되었으면, 잠시 다시 시도해 봐야 한다.

 

 

WaitNamedPipe 함수를 보겠다. 함수는 연결 가능한 상태가 까지 블로킹 상태가 되는 함수이다.

 

BOOL WaitNamedPipe(

    LPCWSTR lpNamedPipeName,

    DWORD nTimeOut

    );

 

번째 인자는 상태 확인의 대상이 되는 파이프 이름이다.

 

번째 인자는 타임아웃 시간을 지정한다.

 

시간이 지나서 함수를 나올 경우 FALSE 반환된다.. 시간을 NMPWAIT_WAIT_FOREVER 경우, 연결 가능한 상태가 까지 기다리며, NMPWAIT_USE_DEFAULT_WAIT 경우, 디폴트로 정의되어 있는 시간만큼 기다리게 되는데, 디폴트 시간이라는 것은 서버의 CreateNamedPipe 함수의 7번째 인자에 결정된다. 여기서는 20 이다.

 

SetNamedPipeHandleState 함수는 파이프와의 연결 속성을 변경시키는 것이다.

 

CreateFIle 함수를 사용하다 보니, 설정에 필요한 정보를 완전히 반영할 없다.

 

그래서 함수를 통해 추가 작업을 해준 것이다.

 

BOOL SetNamedPipeHandleState(

    HANDLE hNamedPipe,

    LPDWORD lpMode,

    LPDWORD lpMaxCollectionCount,

    LPDWORD lpCollectDataTimeout

    );

 

번째 인자는 파이프와의 연결 속성을 변경시키기 위한 핸들 이다.

 

번째 인자는 읽기 모드와 함수 리턴방식에 대한 값을 OR 연산하여 전달한다.

 

번째 인자는 서버로 데이터를 보내기에 앞서 버퍼링 있는 최대 바이트 크기를 지정한다. 이상의 버퍼링은 불가능하다. 만약 클라이언트와 서버가 같은 PC 반드시 NULL 준다.

 

번째 인자는 서버로 데이터를 보내기에 앞서 버퍼링을 허용하는 최대 시간을 지정하는 것이다. 번째 인자가 NULL 이면 NULL 전달 있고, 마찬가지로 서버와 클라이언트가 같은 PC 반드시 NULL 이다.

 

예제 아래 부분의 ReadFile 함수 쪽을 보자.

 

ReadFile 밑의 if 문은 서버가 전송한 문자열의 길이가 ReadFile 함수에서 지정한 버퍼의 길이를 초과 경우, 한번에 읽어들일 없기 때문에, FALSE 반환하고, GetLastError 함수 호출 결과는 ERROR_MORE_DATA 된다. 경우, 나머지 부분을 읽기 위해 while 다시 이어간다.

 

서버를 먼저 실행시키고, 클라이언트를 실행시킨다.

 

news.txt 내용 넣으면 그대로 출력이 된다.

 

 

3. 프로세스 환경변수

 

MailSender2_1 , MailSender2_2 에서 파일을 활용한 방법은 좋지 못하다고 말했다.

 

Main 함수의 매개변수를 활용하는 것이 더욱 안정적이다.

 

여기서는 다른 방법을 소개한다. 프로세스 환경변수를 활용하는 방법이다.

 

프로세스별 별도의 메모리 공간데 문자열 데이터를 지정하고 관리할 있다.

 

문자열 구조는 key = value 같으며, 것이 환경변수이다.

 

[key, value] 형태로, 이상의 데이터를 관리하기 좋다.

 

부모 프로세스는 자식 프로세스 생성 자식 프로세스의 환경변수를 등록할 있고, 그냥 부모의 것을 상속할 수도 있다. ( objective c NSMutableDictionary 생각났다)

 

BOOL SetEnvironmentVariable(

    LPCTSTR lpName,

    LPCTSTR lpValue

    );

 

번째 인자는 key 해당하는 값이다. 이후 key 통해 value 참조한다.

 

번째 인자는 value 해당하는 값을 지정한다.

 

 

다음은 함수를 통해 등록한 환경변수를 참조하는 함수이다.

 

DWORD GetEnvironmentVariable(

 LPCTSTR lpName,

 LPTSTR lpBuffer,

 DWORD nSize

    );

 

번째 인자는 key 전달해 해당하는 value 얻는다.

 

번째 인자는 value 값을 저장하기 위한 메모리의 주소를 지정한다.

 

번째 인자는 lpBuffer 가리키는 메모리의 크기를 지정한다.

 

함수는 성공시 lpBuffer 저장된 문자열의 길이를 리턴한다.

 

이제 나올 예제는 부모 프로세스가 자신의 환경변수를 등록하고, 자식에게 상속시켜 자식 프로세스가 값을 확인하는 예제이다.

 

/*

        EnvParent.cpp

*/

 

#include <stdio.h>

#include <stdlib.h>

#include <windows.h>

 

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

{

        SetEnvironmentVariable(_T("Good"), _T("morning"));

        SetEnvironmentVariable(_T("Hey"), _T("Ho!"));

        SetEnvironmentVariable(_T("Big"), _T("Boy"));

       

        STARTUPINFO si={0,};

        PROCESS_INFORMATION pi={0,};

        si.cb = sizeof(si);

 

        CreateProcess (

               NULL, _T("EnvChild"), NULL, NULL, FALSE,    

               CREATE_NEW_CONSOLE|CREATE_UNICODE_ENVIRONMENT,

               NULL,    // 부모 프로세스의 환경 변수 등록

               NULL, &si, &pi   

        );

 

        CloseHandle( pi.hProcess );

        CloseHandle( pi.hThread );

        return 0;

}

 

 

CreateProcess 함수의 주석 달려있는 7 번째 인자를 보면 NULL 넣어준다.

 

여기서 NULL 넣어주면, 부모 프로세스의 환경변수를 등록하는 것이다.

 

 

/*

        EnvChild.cpp

*/

#include <stdio.h>

#include <stdlib.h>

#include <windows.h>

 

#define BUFSIZE 1024

 

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

{

        TCHAR value[BUFSIZE];

       

        if(GetEnvironmentVariable(_T("Good"), value, BUFSIZE) > 0)

               _tprintf(_T("[%s = %s] \n"), _T("Good"), value);

       

        if(GetEnvironmentVariable(_T("Hey"), value, BUFSIZE) > 0)

               _tprintf(_T("[%s = %s] \n"), _T("Hey"), value);

       

        if(GetEnvironmentVariable(_T("Big"), value, BUFSIZE) > 0)

               _tprintf(_T("[%s = %s] \n"), _T("Big"), value);

       

        Sleep(10000);

 

        return 0;

}

 

 

그냥 보면 이해가 것이다.

 

 

4. 명령 프롬프트 프로젝트 기능 추가

 

이제 추가할 명령어는 가지이다. 하나는 현재 실행중인 프로세스 정보를 출력하는 것이고, 하나는 이름으로 프로세스를 종료시키는 명령어이다.

 

 

CreateToolhelp32Snapshot

 

현재 실행 중인 프로세스 정보를 얻기 위해서 알아야 함수가 가지 있다.

 

예제만 봐도 활용 가능할 정도이다.

 

/*

    ListProcessInfo.cpp

*/

 

#include <stdio.h>

#include <tchar.h>

#include <windows.h>

#include <tlhelp32.h> /* 프로세스 정보 추출관련 함수 선언 */

 

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

{

        /**** Snapshot! 그대로 사진을 찍는 함수************************

         **** 프로세스 상태 사진을 찍기 위해 TH32CS_SNAPPROCESS 인자 전달 */

        HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0 );

        if( hProcessSnap == INVALID_HANDLE_VALUE )

        {

               _tprintf( _T("CreateToolhelp32Snapshot error! \n") );

               return -1;

        }

 

        /** 함수 CreateToolHelp32Snapshot 통해 정보는 이미 얻었다 **

         ** 다음은 추출한 프로세스의 정보를 순차 접근하는 과정이다**/

 

        PROCESSENTRY32 pe32;   /* 프로세스 정보를 얻기 위한 구조체 */

 

        /* PROCESSENTRY32 변수는 사용전에 크기 정보를 초기화 해야 한다*/

        pe32.dwSize = sizeof( PROCESSENTRY32 );

 

        /** 번째 프로세스 정보 얻을 때는 Process32First 함수 사용***

         ** 이후 프로세스 정보 얻을 때는 Process32Next 함수 사용****/

        if( !Process32First( hProcessSnap, &pe32 ) )

        {

               _tprintf( _T("Process32First error! \n") );

               CloseHandle( hProcessSnap );

               return -1;

        }

 

        HANDLE hProcess;

        do

        {

               /* 프로세스 정보 출력

               _tprintf(_T("%25s %5d \n"), pe32.szExeFile, pe32.th32ProcessID);

 

        } while( Process32Next( hProcessSnap, &pe32 ) );

 

        CloseHandle( hProcessSnap );

        return 0;

}

 

 

CreateToolhelp32Snapshot 함수의 번째 인자는 TH32CS_SNAPPROCESS 인데, 것은 현재 실행 중인 프로세스의 사진을 찍으라는 의미다(사진을 찍는다는 것은 계속 변하는 프로세스 정보를 함수 호출 시점 기준으로 추출한다는 뜻이다) 함수는 프로세스 아니라 프로세스가 사용하는 , 모듈 그리고 쓰레드 사진도 찍을 있다.

 

번째 인자는 찍고자하는 프로세스의 ID 인데, 0 넣으면 단체사진 이다.

 

이제 프로세스 정보를 추출해야 한다.

 

, PROCESSENTRY32 함수를 사용한다.

 

 

여러분 차례

 

이제, 예제를 참조해서 프로세스 정보를 출력하는 명령어와 프로세스 이름으로 해당 프로세스를 종료하는 명령어를 추가하자.

 

번째 명령어를 lp(List Process) , 번째 명령어는 kp(Kill Process) 하겠다.

 

프로세스의 종료를 위해서는 TerminateProcess 함수를 사용하면 된다.

 

그런데, 함수의 번째 인자는 프로세스의 핸들을 요구하는데, CreateToolhelp32Snapshot 통하여 핸들을 얻을 없다. 프로세스 ID 정보만 얻을 있다.

 

, 프로세스 이름 -> 프로세스 ID -> 프로세스 핸들 변환하는 과정을 거쳐야 한다.

 

“OpenProcess 함수 대해 조사하라.

 

필자가 구현한 답안

 

HANDLE OpenProcess(

    DWORD dwDesiredAccess,

    BOOL bInheritHandle,

    DWORD dwProcessId

    );

 

것은 프로세스를 사용된다. 프로세스 ID 통해 핸들을 얻겠다는 뜻이다.

 

번째 인자로 ID 전달하면, 것에 해당하는 프로세스 핸들이 반환된다.

 

번째, 번째 인자는 핸들의 성격을 결정짓는다.

 

함수는 물론 Usage Count 증가시키므로, CloseHandle 함수를 주어야 한다.

 

/*

    CommandPrmpt_Four.cpp

*/

 

#include <stdio.h>

#include <tchar.h>

#include <locale.h>

#include <windows.h>

#include <tlhelp32.h>

 

#define STR_LEN    256

#define CMD_TOKEN_NUM  10

 

TCHAR ERROR_CMD[]

= _T("'%s'¨¬(¥A) öCaO ùo O¥A A¤I¾¡¿¤¡ÍI ú¨¡¥O¥I¥U. \n");

 

TCHAR cmdString[STR_LEN];

TCHAR cmdTokenList[CMD_TOKEN_NUM][STR_LEN];

TCHAR seps[]   = _T(" ,\t\n");

 

int CmdReadTokenize(void);

int CmdProcessing(int);

TCHAR * StrLower(TCHAR *);

 

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

{

        _tsetlocale(LC_ALL, _T("Korean"));

 

        if(argc > 2) 

        {

               for(int i= 1; i<argc; i++)

                       _tcscpy(cmdTokenList[i-1], argv[i]);

 

               CmdProcessing(argc-1);

        }

 

        DWORD isExit = NULL;

        while(1)

        {

               int tokenNum = CmdReadTokenize();

               if(tokenNum == 0)                     continue;

 

               isExit = CmdProcessing(tokenNum);

               if(isExit == TRUE)

               {

                       _fputts(_T("명령어 처리를 종료합니다. \n"), stdout );

                       break;

               }

        }

 

        return 0;

}

 

int CmdReadTokenize(void)

{

        TCHAR * token;

 

        _fputts( _T("Best command prompt>> "), stdout );

        _getts(cmdString);

 

        token = _tcstok(cmdString, seps);

 

        int tokenNum = 0;

 

        while(token != NULL)

        {

               _tcscpy ( cmdTokenList[tokenNum++], StrLower(token) );

               token = _tcstok(NULL, seps);

        }

 

        return tokenNum;

}

 

void ListProcessInfo(void)

{

        HANDLE hProcessSnap = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );

        if( hProcessSnap == INVALID_HANDLE_VALUE )

        {

               _tprintf( _T("CreateToolhelp32Snapshot error! \n") );

               return;

        }

 

        PROCESSENTRY32 pe32;  

        pe32.dwSize = sizeof( PROCESSENTRY32 );

        if( !Process32First( hProcessSnap, &pe32 ) )

        {

               _tprintf( _T("Process32First error! \n") );

               CloseHandle( hProcessSnap );

               return;

        }

 

        do

        {

               _tprintf(_T("%25s %5d \n"), pe32.szExeFile, pe32.th32ProcessID);

 

        } while( Process32Next( hProcessSnap, &pe32 ) );

 

        CloseHandle( hProcessSnap );

}

 

void KillProcess(void)

{

        HANDLE hProcessSnap = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );

        if( hProcessSnap == INVALID_HANDLE_VALUE )

        {

               _tprintf( _T("CreateToolhelp32Snapshot (of processes)") );

               return;

        }

 

        PROCESSENTRY32 pe32;

        pe32.dwSize = sizeof( PROCESSENTRY32 );

 

        if( !Process32First( hProcessSnap, &pe32 ) )

        {

               _tprintf( _T("Process32First") );

               CloseHandle( hProcessSnap );

               return;

        }

 

        HANDLE hProcess;

        BOOL isKill = FALSE;

        do

        {

               if(_tcscmp(pe32.szExeFile, cmdTokenList[1]) == 0)

               {

                       hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID );

 

                       if(hProcess != NULL)

                       {

                              TerminateProcess(hProcess, -1);

                              isKill = TRUE;

                       }

 

                       CloseHandle(hProcess);

                       break;

               }

 

        } while( Process32Next( hProcessSnap, &pe32 ) );

 

        CloseHandle( hProcessSnap );

 

        if(isKill == FALSE)

               _tprintf(_T("Kill process fail, Try again! \n"));

}

 

int CmdProcessing(int tokenNum)

{

        BOOL isRun;

        STARTUPINFO si={0,};

        PROCESS_INFORMATION pi;

 

        TCHAR cmdStringWithOptions[STR_LEN]={0,};

        TCHAR optString[STR_LEN]={0,};

 

        si.cb=sizeof(si);

        if( !_tcscmp(cmdTokenList[0],_T("exit")) )

        { 

               return TRUE;

        }

        else if ( !_tcscmp(cmdTokenList[0],_T("start")) )

        {

               if(tokenNum >1)

               {

                       for(int i=1; i<tokenNum; i++)

                              _stprintf(optString, _T("%s %s"), optString, cmdTokenList[i]);

 

                       _stprintf(cmdStringWithOptions, _T("%s %s"), _T("CmdProject.exe"), optString);

               }

               else

                       _stprintf(cmdStringWithOptions, _T("%s"), _T("CmdProject.exe"));

 

               isRun = CreateProcess(NULL, cmdStringWithOptions, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);

 

               CloseHandle(pi.hProcess);

               CloseHandle(pi.hThread);

        }

        else if ( !_tcscmp(cmdTokenList[0], _T("echo")) )

        {

               for(int i=1; i<tokenNum; i++)

                       _stprintf(optString, _T("%s %s"), optString, cmdTokenList[i]);

 

               _tprintf( _T("echo message:%s \n"), optString);

        }

        else if( !_tcscmp(cmdTokenList[0], _T("lp")) )

        {

               ListProcessInfo();

        }

        else if( !_tcscmp(cmdTokenList[0], _T("kp")) )

        {

               if(tokenNum <2)

               {

                       _tprintf(_T("usage: kp <process name> \n"));

                       return 0;

               }

 

               KillProcess();

        }

        else

        {

               _tcscpy(cmdStringWithOptions, cmdTokenList[0]);

 

               for(int i=1; i<tokenNum; i++)

                       _stprintf(cmdStringWithOptions, _T("%s %s"), cmdStringWithOptions, cmdTokenList[i]);

 

               isRun = CreateProcess(NULL, cmdStringWithOptions, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);

 

               CloseHandle(pi.hProcess);

               CloseHandle(pi.hThread);

 

               if(isRun == FALSE)

                       _tprintf(ERROR_CMD, cmdTokenList[0]);

        }

 

        return 0;

}

 

 

TCHAR * StrLower(TCHAR *pStr)

{

        TCHAR *ret = pStr;

 

        while(*pStr)

        {

               if(_istupper(*pStr))

                       *pStr = _totlower(*pStr);

 

               pStr++;

        }

 

        return ret;

}

 

 

정리

 

핸들 테이블

 

 커널 오브젝트와 핸들 사이에 존재한다. 이로서 핸들을 참조하여 특정 커널 오브젝트를 가리킬 있다.

 

핸들과 핸들 테이블

 

핸들 테이블은 프로세스별 독립적이다. 그리고 숫자가 의미를 지니기 위해, 해당 숫자가 핸들 테이블에 등록되어야 한다. 등록되는 순간부터 핸들이라 하고, 핸들로 커널 오브젝트에 접근한다.

 

핸들의 상속

 

핸들은 자식 프로세스를 생성하는 과정에서 상속할 있다. 상속하면 핸들 테이블 정보가 복사된다.

 

가짜 핸들(Pseudo 핸들)

 

GetCurrentProcess 함수를 통해 얻은 핸들을 말한다. 핸들 테이블에 등록된 값이 아닌, 자신의 프로세스를 가리키기 위한 약속된 상수이다.

 

핸들 테이블에 등록된 핸들을 얻기 위해서는 DuplicateHandle 사용해야 된다.

 

파이프

 

이름없는 파이프와 이름있는 파이프를 소개하였다. 각각의 특성은 기억해야 한다!

 

특히 이름없는 파이프를.

 

8.docx

설정

트랙백

댓글