--------파일의 시간 정보 얻어오기

윈도우에서는 대표적으로 다음과 같은 정보를 얻을 수 있다.

1) 만든 날짜
2) 수정한 날짜(마지막으로)
3) 액세스한 날짜(마지막으로)


이를 위해 다음 함수를 사용한다.

bool GetFileTime(
 HANDLE hFile,
 LPFILETIME lpCreationTime,
 LPFILETIME lpLastAccessTime,
 LPFILETIME lpLastWriteTime
);

1) 시간 관련 정보를 얻을 대상 파일의 핸들을 지정한다
2) 파일이 생성된 시간을 얻기 위해 FILETIME 구조체 변수의
주소값을 전달하며, NULL을 전달하는 것도 가능하다.
3) 파일의 마지막 접근 시간을 얻기 위해 ~~
4) 파일의 마지막 데이터 갱신 시간(덮어쓰기 포함)을 얻기 위해 ~~~

FILETIME 구조체의 멤버들을 자세히 알려고 하는 것보단
다음 사실이 중요하다.

"FILETIME 구조체는 시간 정보를 나타내는 8바이트 자료형(DOWRD * 2)이다.
그리고 이 구조체는 UTC 기반으로 시간을 표현한다.

GetFileTime 함수는 UTC 기반으로 시간 정보를 돌려준다.

UTC 란, Coordinated Universal Time 의 간략한 표현으로, 세계 시간의 기준을
만들기 위해 정의된 시간이다. 간단히 설명하면,
세계 각 나라의 시간은 현재 제각각이며, 초 단위 아래로 내려가면 오차도 심하다.
그래서 나노초 단위의 높은 정밀도를 기준으로 세계 시간을 구성할 필요성이 대두되어
1601년 1월 1일을 기준으로 100 나노초 단위 기준으로 지나간 시간을 계산하는 것이다.
쉽게 말해 시,분,초는 생략하고, 지금이 2011년 8월 5일이라면,
현재의 UTC는 [2010-08-05] - [1601-01-01] 을 100나노초 단위로 환산한 값이 UTC가 된다.
이러한 UTC는 지금도 오차없이 유지되고 있으며, 정밀도를 최대한으로 높이기 위하여
높은 정밀도의 Atomic Clocks 라는 시계가 사용된다고 한다.
따라서 시간 정보를 얻을 경우 대부분 UTC 기반으로 얻게 되며,
문제는 우리가 원하는 것은 우리가 이해할 수 있는 스케일의 시간 정보라는 것이다.
이를 위해 우리가 원하는 타입으로 시간 정보를 변경하는 함수가 필요하며,
이를 다음 예제를 통해 우리가 이해할 수 있는 형태의 스케일로 변환하는 과정을 보여준다.

TCHAR fileName[] = _T("RealScaleViewer.exe");

TCHAR fileCreateTimeInfo[100];
TCHAR fileAccessTimeInfo[100];
TCHAR fileWriteTimeInfo[100];

FILETIME ftCreate, ftAccess, ftWrite;

SYSTEMTIME stCreateUTC, stCreateLocal;
SYSTEMTIME stAccessUTC, stAccessLocal;
SYSTEMTIME stWriteUTC, stWriteLocal;

HANDLE hFile = CreateFile(

 fileName, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATRRIBUTE_NORMAL, 0
);

//파일 시간 정보 추출
if( !GetFileTime(hFile, &ftCreate, &ftAccess,  &ftWrite))
{
 _tprintf(_T("GetFileTime Function call Fault!!\n"));
 return false;
}

//시간 정보 변환
FileTimeToSystemTime(&ftCreate, &stCreateUTC);
SystemTimeToTzSpecificLocalTime(
 NULL, &stCreateUTC, &stCreateLocal
);

~~ (Create대신 Access와 Write로 바꿔서 대입)

~~

_stprintf(
 fileCreateTimeInfo, _T("%02d / %02d / %d   %02d: %02d"),
 stCreateLocal.wMonth, stCreateLocal.wDay, stCreateLocal.wYear,
 stCreateLocal.wHour, stCreateLocal.wMinute
);

~~ (Create대신 Access와 Write로 바꿔서 대입)



---------------------------------------------파일을 열어서 읽고 쓰고 닫는 예제
// 유니코드 기반 //

int _tmain(int argc, TCHAR* argv[])
{
 TCHAR fileName[] = _T("data.txt");
 TCHAR fileData[] = _T("Just Test String");
 
 HANDLE hFile = CreateFile (  //파일 열기
 
   fileName,  GENERIC_WRITE ,  FILE_SHARE_WRITE,
   0, CREATE_ALWAYS, FILE_ATTRIBUTES_NORMAL,  0
  );

 if( hFile == INVALID_HANDLE_VALUE)
 {
  _tprintf(_T("File Created Fault!!"));
  return -1;
 }

 DWORD numOfByteWritten = 0;
 WriteFile( //파일 저장
  hFile,  fileData,  sizeof(fileData), &numOfByteWritten,  NULL
  );
 
 _tprintf(_T("Written data size = %u \n", numOfByteWritten);
 CloseHandle(hFile);

 return 0;
}

-----------------------------------앞서 만든 파일에 저장된 데이터를 읽어 들이는 예제


TCHAR fileName[] = _T("data.txt");
TCHAR fileData[100];

HANDLE hFile = CreateFile( //파일 열기
 fileName,  GENERIC_READ,  FILE_SHARE_READ,
 0,  OPEN_EXISTING, FILE_ATTRIBUTES_NORMAL, 0 
);

DWORD numOfByteRead =0;
ReadFile(
 hFile, fileData, sizeof(fileData), &numOfByteRead, NULL
);
fileData[numOfByteRead/sizeof(TCHAR)] = 0;

_tprintf(_T("Read Data Size : %u \n"), numOfByteRead);
_tprintf(_T("Read String : %s \n"), fileData);

CloseHandle(hFile);


//파일 핸들 닫기 / 열기
HANDLE WINAPI CreateFile( //파일을 열때
  __in          LPCTSTR lpFileName,
  __in          DWORD dwDesiredAccess,
  __in          DWORD dwShareMode,
  __in          LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  __in          DWORD dwCreationDisposition,
  __in          DWORD dwFlagsAndAttributes,
  __in          HANDLE hTemplateFile
);

1) 개방할 파일 이름을 지정한다.
2) 읽기 쓰기 모드를 지정한다
3) 파일 공유 방식을 지정한다.
4) 보안 속성을 지정한다
5) 파일이 생성되는 방법을 지정한다
6) 파일의 특성 정보를 설정한다
7) 기존에 존재하는 파일과 동일한 특성을 가지는 새 파일을 만들 때 사용되는 전달인자다.
일반적으로 NULL 을 넣는다.

열린 파일을 종료할 때는 여느 커널 오브젝트의 핸들과 마찬가지로 CloseHandle() 을 호출한다.

파일을 개방할 때도 커널 오브젝트가 생성되고, 핸들이 반환된다.
이 함수의 호출을 통해 생성되는 커널 오브젝트에는 파일에 대한 정보로 가득할 것이다.


//파일 읽기 / 쓰기

파일에 데이터를 읽을 때에는 다음 함수를 사용한다.

BOOL WINAPI ReadFile(
  __in          HANDLE hFile,
  __out         LPVOID lpBuffer,
  __in          DWORD nNumberOfBytesToRead,
  __out         LPDWORD lpNumberOfBytesRead,
  __in          LPOVERLAPPED lpOverlapped
);

1) 데이터를 읽을 파일의 핸들을 지정한다
2) 읽어 들인 데이터를 저장할 버퍼(배열, 메모리)의 주소(포인터)를 지정한다.
3) 파일로부터 읽고자하는 데이터의 크기를 바이트 단위로 지정한다.
4) 실제 읽어 들인 데이터 크기를 얻기 위한 변수의 주소를 지정한다.
5) 나중에 설명한다.

반대로 파일에 데이터를 저장할 때에는 다음 함수를 이용한다.

BOOL WINAPI WriteFile( //데이터 저장을 위해
  __in          HANDLE hFile,
  __in          LPCVOID lpBuffer,
  __in          DWORD nNumberOfBytesToWrite,
  __out         LPDWORD lpNumberOfBytesWritten,
  __in          LPOVERLAPPED lpOverlapped
);


1) 데이터를 저장할 파일의 핸들을 지정한다
2) 데이터를 저장하고 있는 버퍼의 주소를 지정한다
3) 파일에 저장하고자 하는 데이터 크기를 바이트 단위로 지정한다.
4) 파일에 실제 저장된 데이터 크기를 얻기위해 변수의 주소를 지정한다.
5) 나중에 설명한다.


자식 프로세스에게 핸들 정보를 전달하기 위해 파일을 활용하는 방법은
잘 동작하긴 하나 촌스럽고 안정적이지 않은 방식이다.
이보다는 프로세스 생성 시 메인 함수의 매개변수를 활용하는 것이
훨씬 안정적이다.


이번에는 프로세스 환경변수를 활용하는 방법을 알아보겠다.
프로세스별로 별도의 메모리 공간에 문자열 데이터를 저장하고 관리할 수
있도록 되어 있으며, 문자열의 구조는 다음과 같고, 이를 환경 변수라 한다.

key = value

[key,value] 의 형태를 띠므로 둘 이상의 데이터를 관리하기가 좋으며,
부모 프로세스는 자식 프로세스의 환경변수를 등록할 수도 있고,
그냥 부모 프로세스의 환경 변수를 상속시킬 수 있다.

값을 참조하는 과정에서 key와 value를 따로 분리시킬 필요도 없는데,
이는 다음 함수를 통해 우리가 쉽게 값을 등록 및 참조할 수 있기 때문이다.
다음은 프로세스 환경변수를 등록할 때 사용하는 함수다.

BOOL SetEnvironmentVariable(
 LPCTSTR lpName,
 LPCTSTR lpValue
);

1) Key 에 해당하는 값을 지정하며, 이후 Key를 통해 value 값을 참조하게 된다.
2) value 에 해당하는 값을 지정한다.

다음은 위 함수를 통해서 등록한 환경변수를 참조할 때 사용하는 함수이다.

DWORD GetEnvironmentVarible(
 LPCTSTR lpName,
 LPTSTR lpBuffer,
 DWORD nSize
);

1) Key를 전달하며, Key에 해당하는 value를 얻게 된다.
2) value 값을 저장하기 위한 메모리의 주소를 지정한다
3) lpBuffer 가 가리키는 메모리의 크기를 지정한다

이 함수는 성공시에 lpBuffer 에 저장된 문자열의 길이를 반환한다.
다음 예제는 위의 두 함수의 사용법을 보여준다.
부모 프로세스가 자신의 환경변수를 등록하고, 자식 프로세스에게 상속시켜
자식 프로세스가 값을 확인하는 간단한 예제이다.
우선 환경변수를 설정하는 부모 프로세스부터 보자.

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 ps={0,};
 si.cb=sizeof(si);

 CreateProcess(
  NULL, _T("EnvChild"), NULL, NULL, FALSE,
  CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
  NULL, //이게 NULL이면 부모 프로세스에 등록되어 있는 환경변수를 등록하겠다 라는 의미다.
  NULL,
  &si,  &pi
 );

 CloseHandle( pi.hProcess);
 CloseHandle( pi.hThread);


 return 0;
}


------------정리---------------------------------------------------------------------------

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

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

3. 핸들의 상속
핻늘은 자식 프로세스를 생성하는 과정에서 상속할 수 있다.
핸들이 자식 프로세스에게 상속된다는 말은 부모 프로세스의 핸들 테이블 정보가
자식 프로세스의 핸들 테이블에 복사된다는 뜻이다.

4. 가짜 핸들(Pseudo 핸들)
GetCurrentProcess 함수 호출을 통해 얻은 핸들을 가짜 핸들이라 하며,
핸들 테이블에 등록된 핸들값이 아닌, 자기 자신의 프로세스를
가리키기 위한 약속된 상수이다. 핸들 테이블에 등록된 핸들을 얻기 위해서는
DuplicateHandle() 함수를 사용해야 한다.

5. 파이프
이름없는 파이프와, 이름있는 파이프가 있으며, 이름없는 파이프의 경우 아주
유용하므로 잘 알아둬야 한다.


/*
    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
 {
  /* 프로세스 이름, ID 정보 출력 */
  _tprintf(_T("%25s %5d \n"), pe32.szExeFile, pe32.th32ProcessID);

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

 CloseHandle( hProcessSnap );
 return 0;
}


앞에서 커널 오브젝트의 UsageCount는 공유하는 프로세스의 수만큼 증가한다고 설명했다.
그렇다면 커널 오브젝트를 참조하는 프로세스가 되기 위한 조건은 무엇일까?
핸들 테이블의 관점에서 봤을 때

"프로세스가 핸들을 얻게 되었다"

위의 말의 의미는, 핸들 테이블에 해당 핸들에 대한 정보가 갱신(추가) 되었음을 의미하는 것이다."

예를 들어 createMailslot 함수의 호출을 통해 메일슬롯을 생성햇다고 가정하면,
1) 메일 슬롯 리소스 생성
2) 커널 오브젝트 생성
3) 핸들 정보가 핸들 테이블에 갱신
4) CreateMailslot 함수를 빠져나오며 핸들값 반환

대부분의 경우 프로세스가 핸들을 얻게 되었다고 하면,
보통 4번째 단계에 초점을 맞춰서 생각을 한다. 그러나 이는 잘못된 것이다.
프로세스가 핸들을 얻었다고 말할 수 있는 부분은 3번째 단계 이후부터다.
즉 핸들 테이블에 A 핸들에 대한 정보가 등록되면, A 핸들을 얻은 것이다.

다음은 CreateMailslot 함수와 CreateProcess 함수에서 생성되는 자식 프로세스의
핸들을 상속하기 위한 샘플 코드들이다.

SECURITY_ATTRIBUTES sa;
sa.nLength=sizeof(sa);
sa.lpSecurityDescriptor=NULL;
sa.bInheritHandle=TRUE;

CreateMailslot(~,~,~,&sa);

-----------------

SECURITY_ATTRIBUTES sa;
sa.nLength=sizeof(sa);
sa.lpSecurityDescriptor=NULL;
sa.bInheritHandle=TRUE;

CreateProcess(~,~,&sa,~~~~);

이렇게 자식 프로세스의 핸들이 상속될 수 있도록 설정하면,
이후에 생성되는 자식 프로세스에게 상속될 것이다.

 

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

현재 실행 중에 있는 프로세스 자신의 해늘을 얻는 방법으로써
GetCurrentProcess 함수를 통해 프로세스 자신의 커널 오브젝트에 접근이 가능하다.
하지만 이 함수 호출을 통해 얻은 핸들을 가리켜 가짜 핸들(Pseudo Handle) 이라 한다.
왜냐면 이렇게 얻어진 핸들은 핸들 테이블에 등록되어 있지도 않으며,
단지 현재 실행 중인 프로세스를 참조하기 위한 용도로 정의해 놓은, 약속된 상수가 반환되는 것이기 때문이다.
(현재 -1이 반환되도록 정의되어 잇다) 따라서 자식 프로세스로 상속되지 않으며,
CloseHandle 함수의 인자로 전달할 필요도 없다.
현재 실행중인 프로세스가 GetCurrentProcess 함수 호출을 통해 얻은 가짜 핸들 이외에 핸들
테이블에 등록되어 있는 진짜 핸들을 얻어야 할 필요성이 있는가?
우선 진자 핸들을 얻는 방법에 대해 알아보자.
다음은 DuplicateHandle 이라는 함수이며, 핸들을 복사하는 기능을 지닌다.
만약 현재 실행중인 프로세스의 진짜 핸들을 얻고자 한다면 이 함수를 사용하자

BOOL WINAPI DuplicateHandle(
  __in          HANDLE hSourceProcessHandle,
  __in          HANDLE hSourceHandle,
  __in          HANDLE hTargetProcessHandle,
  __out         LPHANDLE lpTargetHandle,
  __in          DWORD dwDesiredAccess,
  __in          BOOL bInheritHandle,
  __in          DWORD dwOptions
);

1) 복제할 핸들을 소유하는 프로세스를 지정한다
2) 복제할 핸들을 지정한다
3) 복제된 핸들을 소유할 프로세스를 지정한다
4) 복제된 핸들값을 저장할 변수의 주소를 지정한다
5) 복제된 핸들의 접근권한을 지정한다. 방대한 내용이 잇으므로 msdn 참고할 것
6) 복제된 핸들의 상속 여부를 지정한다. TRUE 전달 시 새로운 자식 프로세스로 상속되며, FALSE 전달 시 상속되지 않는다
7) DUPLICATE_SAME_ACCESS 를 전달하면 원본 핸들과 동일한 접근권한을 가지게 된다.
그 외에 DUPLICATE_CLOSE_SOURCE 가 올 수 있는데 이 인자는 원본 핸들을 종료시킨다( CloseHandle과 같다).
이 둘은 비트 단위 OR 연산자를 통해 동시 전달이 가능하다.
이 함수는 4번째 까지의 전달인자가 가장 중요하다.

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

핸들 테이블은 핸들 정보를 저장하고 있는 테이블로서 프로세스별로 독립적이다.
쉽게 말해 각각의 프로세스가 자신만의 핸들 테이블을 하나씩 구성하고 관리한다.
프로세스가 CreateProcess 함수나 CreateMailslot 과 가은 함수 호출을 통해 리소스 생성을
요구한 결과로 핸들 정보를 얻게 될 경우, 프로세스 자신에게 속해 있는 핸들 테이블에 해당 정보가 등록된다.
여기서 가장 중요한 것은 핸들 테이블은 프로세스별로 독립적이라는 것이다.


핸들의 상속

CreateProcess 함수를 호출하면 새로운 자식 프로세스가 생성된다.
또한 자식 프로세스를 위한 핸들 테이블도 더불어 생성된다.
이때 부모 프로세스의 핸들 테이블과 자식 프로세스의 핸들 테이블은 어떤 관계가 있을까?
그것은 CreaetProcess 함수 호출 시 전달되는 인자(다섯번째)가 무엇이냐에 따라서 부모 프로세스 핸들 테이블에
등록되어 있는 핸들 정보는 새롭게 생성되는 자식 프로세스에게 상속될 수 있다.
이러한 특성은 여러 상황에서 유용하게 사용 될 수 있다.

 

핸들의 상속에 대한 이해

자식 프로세스는 부모 프로세스의 핸들 테이블에 등록되어 있는 핸들 정보를 상속받을 수 있다.
하지만 모든 핸들 정보를 상속받는 것은 아니다.
부모 프로세스 핸들 테이블에서 상속여부가 Y 인 것만 상속받을 수 있으며,
자식 프로세스 핸들 테이블에서 상속여부에 대한 정보도 변경 없이 그대로 상속된다.
따라서 자식이 또 다른 자식을 생성해도 이 핸들에 대한 정보는 계속해서 상속된다.
그렇다면 상속되는 핸들과 상속되지 않는 핸들에 대한 기준은 어디서 나뉠까?
이는 리소스를 생성하는 함수의 전달인자를 통해 프로그래머가 결정할 수 있다.

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

모든 자식 프로세스가 무조건 부모 프로세스의 핸들을 상속하는 것은 아니다.
다음은 CreateProcess 함수의 선언이다.

BOOL WINAPI CreateProcess(
  __in          LPCTSTR lpApplicationName,
  __in_out      LPTSTR lpCommandLine,
  __in          LPSECURITY_ATTRIBUTES lpProcessAttributes,
  __in          LPSECURITY_ATTRIBUTES lpThreadAttributes,
  __in          BOOL bInheritHandles,
  __in          DWORD dwCreationFlags,
  __in          LPVOID lpEnvironment,
  __in          LPCTSTR lpCurrentDirectory,
  __in          LPSTARTUPINFO lpStartupInfo,
  __out         LPPROCESS_INFORMATION lpProcessInformation
);

이 함수의 다섯번째 전달인자는 자식 프로세스에게 핸들 테이블에 등록되어 있는
핸들정보를 상속할 건지 말건지 결정하는 요소다.
TRUE 일 경우 핸들 테이블 정보는 자식에게 상속된다.
핸들의 상속 여부를 결정하는 것이 아니라, 부모 프로세스가 소유하고 있는 핸들 테이블
정보의 상속 여부를 결정하는 것이다.



상태에 대한 이해

커널 오브젝트는 두 가지의 상태를 지니며,
하나는 Signaled 상태(신호를 받은 상태) 이고, 하나는 Non-Signaled 상태이다.
특정 상황 하에서 커널 오브젝트의 상태는 Signaled 에서 Non으로 Non에서 Signal 로 변경된다.
즉 Non-Signaled 상태일 때 FALSE 값을 지니고, Signaled 면 TRUE 값을 가진다.


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

커널 오브젝트의 상태는 리소스에 특정 상황이 발생하였음을 알려주기 위해 존재하는 것이다.
특정상황은 리소스마다 다르기 때문에 커널 오브젝트의 상태가 변하는 시점은 커널 오브젝트의 종류에 따라 달라진다.


프로세스 커널 오브젝트는 프로세스가 생성될 때 만들어지며,
처음 생성될 때 커널 오브젝트의 상태는 Non-signaled 상태에 놓이다가, 프로세스가 종료될 때 signaled 상태로 변경된다.
이는 일종의 약속이다. 그러므로 우리는 Signaled 상태의 프로세스 커널 오브젝트를 보고
프로세스가 종료되었음을 알 수 있다.
정리하자면 프로세스 커널 오브젝트는 프로세스 실행 중에는 Non-signaled 상태에 놓이며,
그러다가 프로세스가 종료될 때 Windows 운영체제에 의해 자동적으로 Signaled 상태가 된다.
그러나 종료될 때 Signaled가 되었다고 했는데, 그 반대의 경우는 어떠할까?
물론 불가능하다. 종료된 그 프로세스를 다시 살릴수가 없기 때문이다.
즉 일단 Signaled 가 되면 절대 다시 Non-Signaled 상태로 변경되지 않는다.

 

 

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

커널 오브젝트가 상태를 지니도록 Windows 운영체제가 디자인 된 것은 프로그래머에게 다양한 기능을 제공하기 위함이다.
다양한 상황을 알리기 위함인데, 커널 오브젝트의 상태 정보가 우리에게 주는 이점이 있다. 이는 나중에 살펴보고, 우선
커널 오브젝트의 상태 정보를 확인하는데 사용되는 대표적인 함수 하나를 보자.
다음 함수는 핸들을 인자로 전달해서 커널 오브젝트의 상태를 확인하는데 사용되는 함수이다.

DWORD WaitForSingleObject(
 HANDLE hHandle;
 DWORD dwMilliseconds
);
// 실패시 WAIT_FAILED 가 리턴된다.

1) 상태 확인을 원하는 커널 오브젝트의 핸들을 인자로 젇날한다.
2) 이 함수는 인자로 전달된 hHandle 이 가리키는 커널 오브젝트가 Signaled 상태가 되엇을 때 반환한다.
그래서 함수의 이름이 Wait~ 으로 시작되며, 커널 오브젝트가 Signaled 상태가 될 때 까지 기다린다.
두번 째 인자는 커널 오브젝트가 Signaled 상태가 될 때까지 기다릴 수 있는 최대 시간을 밀리 세컨드 단위로 지정하는 용도로 사용된다.
즉 타임아웃을 설정하는 인자이며, INFINITE 를 인자로 전달시 Signaled 상태가 될 때까지 반환하지 않고 무한정 기다린다.

== 이 함수가 반환하는 상황은 다양하므로 함수 호출이 완료된 후 반환값을 꼭 확인해야 한다.

VALUE  의미
WAIT_OBJECT_0 커널 오브젝트가 Signaled 상태가 되었을 때 반환하는 값
WAIT_TIMEOUT 커널 오브젝트가 Signaled 상태가 되지 않고, 설정된 시간이 다 된 경우 반환되는 값
WAIT_ABANDONED 소유 관계와 관련하여 함수가 정상적이지 못한 오류 발생에 의해 반환하는 경우 반환되는 값


다음 함수는 상태를 확인하고자 하는 커널 오브젝트가 둘 이상이고, 핸들이 배열로 묶여 있다면 다음 함수를 활용하는 것이 편리하다

DWORD WINAPI WaitForMultipleObjects(
  __in          DWORD nCount,
  __in          const HANDLE* lpHandles,
  __in          BOOL bWaitAll,
  __in          DWORD dwMilliseconds
);

1) 배열에 저장되어 잇는 핸들 개수를 전달한다
2) 핸들을 저장하고 있는 배열의 주소 정보를 전달한다.
이 주소값을 시작으로 총 nCount 개의 핸들이 관찰 대상이 된다.
3) 관찰 대상이 모두  Signaled 상태가 되기를 기다리고자 하는지, 아니면 하나라도
Signaled 상태가 되면 반환할 것인지를 결정한다.

 


메일슬롯과 IPC에 대한 고찰

메일슬롯은 한쪽 방향으로만 메시지를 전달할 수 있으므로 채팅 프로그램 제작에는 한계가 있다.
따라서 두 프로세스가 서로 메시지를 받을 수 있는 채팅 프로그램을 구현하기 위해서는
두개의 메일슬롯을 생성해야만 한다.

이때 양쪽 방향으로 메시지를 주고 받기 위해 사용 가능한 IPC 사용 기법이 있다.
그것은 바로, 파이프를 이용한 IPC인데
파이프에는 Anonymous 파이프와 Named 파이프가 있다.
이 중 Named 파이프는 기본적으로 양방향 데이터 송 수신을 지원하며, 채팅 프로그램 구현에 적합하다

메일슬롯은 브로드 캐스팅 방식의 통신을 지원한다.
즉 하나의 sender는 한번의 메시지 전송으로 여러 receiver에게 동일한 메시지를 동시에 전송하는 것이 가능하다.
예를 들어 동일한 네트워크로 연결되어 있는 서로 다른 컴퓨터에서
실행중인 receiver 프로세스 A,B,C가 존재할 때,
이 Receiver 들은 각각 다음과 같이 동일한 메일슬롯 주소를 기반으로 메일슬롯 오브젝트를 생성하였다.

" \\.\mailslot\mailbox"

그리고 동일한 네트워크상에 존재하는 sender 프로세스 D가 데이터를 전송하기 위해 사용하는
주소의 구성은 다음과 같다
" \\*\mailslot\mailbox"

컴퓨터 이름이 와야 하는 부분에 * 표시가 있다. 이는 모든 컴퓨터를 지칭하는 것이며,
\\.\mailslot\mailbox 을 주소로 생성된 모든 메일슬롯에 동일한 메시지가 전달된다.


메일슬롯은 생성과 동시에 Usage Count 가 1인데, 참조하는 프로세스는 메일슬롯을 생성한
프로세스 하나이기 때문이다. 자식 프로세스의 경우에는 생성과 동시에
참조하는 프로세스가 둘이 되기 때문에 2가 되는 것이다.
메일슬롯뿐만 아니라, 프로세스와 쓰레드를 제외한 다른 모든 커널 오브젝트는 생성과 동시에
Usage Count는 1이 된다. 윈도우즈는 내부적으로 생성하는 대부분의 리소스들에 대해 커널 오브젝트를 생성한다.


--------------------mail receiver-----------------------

#include <stdio.h>
#include <tchar.h>
#include <windows.h>

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

int _tmain(int argc, TCHAR* 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"), stdout);
  return 1; //오류 종료시
 }

 //메세지 수신
 _fputts(_T("------------- Message -------------\n"),stdout);
 
 while(1)
 {
  if(  !ReadFile(hMailSlot,  messageBox,   sizeof(TCHAR) * 50, &bytesRead,  NULL))
  {
   _fputts(_T("unable to read!"), stdout);
   return 1;
  }

  if(   !_tcsncmp(messageBox,  _T("exit"),   4))
  {
   _fputts(_T("good bye man!"), stdout);
   break;
  }

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

 }


 CloseHandle(hMailSlot);

 return 0;
}

 


--------------------mail sender-----------------------

 

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <locale.h>
#include <tchar.h>

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

 

int _tmain(int argc, TCHAR* argv[])
{
 HANDLE hMailSlot;
 TCHAR message[50];
 DWORD bytesWritten;
 //첫번째,두번째, 다섯번째만 확실히 이해할 것.
 hMailSlot=CreateFile(SLOT_NAME,  GENERIC_WRITE,   FILE_SHARE_READ,   NULL,
  OPEN_EXISTING ,  FILE_ATTRIBUTE_NORMAL,  NULL);
 //파일의 개방모드를 지정하는데, 이때 전송자이므로 쓰기모드에 해당하는 GNERIC_WRITE를 쓴다.
 //다섯번째 인자는 파일의 생성방식을 결정짓는 용도로 사용되며, 즉 새로운 파일을 생성할지, 기존 파일을 열어서 접근할지 결정한다
 //여기서는 리시버가 만들어놓은 메일슬롯에 접근하는 것이 목적이므로 OPEN_EXISTING을 전달하며, 이는 기존에 만들어진 파일을 개방할 때 사용한다.


 if(hMailSlot == INVALID_HANDLE_VALUE)
 {
  _fputts(_T("error! 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! man! "), stdout);
   break;
  }
 
 
 }
 CloseHandle(hMailSlot);

 return 0;
}


IPC란 Inter - Process - Communication 의 약자로서
프로세스 사이의 통신 이라는 뜻을 가진다.
통신이란 기본적으로 데이터를 주고 받는 행위이며,
따라서 프로세스들이 서로 통신을 한다는 것은 둘 이상의
프로세스가 데이터를 주고 받는 행위라고 정의할 수 있다.


IPC의 종류

1. 메일슬롯 원리 (Mail Slot)
메일슬롯은 파이프와 더불어 대표적인 IPC 기법이다.
메일슬롯은 쉽게 우체통이라 생각하면 되며,
데이터를 주고 받기 위해 프로세스가 우체통을 마련하는 것이다
즉 Receiver 프로세스는 우체통을 하나 마련하고
Sender 프로세스가 데이터를 Receiver 우체통으로 보낸다.
그러면 Receiver가 데이터를 받을 수 있게 된다.

 

메일슬롯 구성을 위해 필요한 요소

1. Receiver 의 조건 사항
Receiver에 해당하는 프로세스는 우체통을 생성해야 하며,
이를 생성하는 함수는 CreateMailslot() 함수이다.

HANDLE WINAPI CreateMailslot(
  __in          LPCTSTR lpName,
  __in          DWORD nMaxMessageSize,
  __in          DWORD lReadTimeout,
  __in_opt      LPSECURITY_ATTRIBUTES lpSecurityAttributes
);

- lpName   생성하는 메일슬롯의 이름을 결정하는데 사용한다.
즉 주소를 지정하는 것이다. 기본 형식은 다음과 같다.

\\ Computername \ mailslot \ [path]name


- nMaxMessageSize  메일슬롯의 버퍼 크기를 지정하는데 사용한다,
즉 우체통의 크기를 지정하는데 사용하면 0일 경우 시스템이 허용하는 최대크기로 설정한다.

lReadTimeout  메일 슬롯을 통해 전송된 데이터를 읽기 위해 ReadFile함수가 사용되며,
메일슬롯에 데이터가 있으면 데이터를 읽으면서 ReadFile 함수를 빠져나오게 된다.

그러나 메일슬롯이 비어있다면 데이터가 채워질때까지 블로킹 상태에 놓이게 된다.
이 인자는 최대 블로킹 시간을 밀리세컨드 단위로 지정하며, 0을 인자로 전달하면
메일슬롯에 데이터가 잇든지 없든지 블로킹 상태없이 빠져 나와 다음 단계를 실행한다.

그리고 상수 MAILSLOT_WAIT_FOREVER 를 인자로 전달할 경우 ReadFile 함수는
읽어 들일 데이터가 존재하게 될 때까지 블로킹 상태에 놓이게 된다.

lpSecurityAttributes 핸들을 상속하기 위한 용도이다.

함수의 반환 타입은 커널 오브젝트의 핸들이며, 메일슬롯도
커널에 의해서 관리되는 리소스이므로, 커널 오브젝트가 더불어 생성되고,
이 커널 오브젝트의 핸들이 반환되는 것이다.

 

둘째 Sender의 조건사항

Sender는 Receiver 가 만들어 놓은 메일슬롯의 이름 즉 주소를 알아야 하며,
다음은 실제 예이다.

//통로 마련 작업, 해당 주소의 메일슬롯을 개방한다. 즉 데이터 스트림을 형성한다.
HANDLE hMailSlot;
hMailSlot = CreateFile ( "\\\\.\\.mailslot\\mailbox", ~~);

 

//데이터 전송
CHAR Message[50];
WriteFile(hMailSlot, Message, ~~);

 

---- 메일슬롯 분석

\\ Computername \ mailslot \ [path]name

실제 채워 넣어야 할 부분은 컴퓨터이름과 pathname 이다.
메일슬롯은 동일한 네트워크 도메인에 존재하는 호스트들
사이의 데이터 전달을 목적으로도 사용할 수 있다.
위 샘플에서 컴퓨터네임 부분에 . 을 넣은 것은 로컬 컴퓨터를 의미한다.

path 네임은 실질적인 메일슬롯 이름이며, 위 샘플은 mailbox 이다.
그래서 다음과 같은 형태의 메일 슬롯 이름이 구성되었다.

\\. \mailslot\mailbox

path name은 path 정보를 포함하여 계층 구조의 형태로 보다 체계화가 가능하다.
즉 다음처럼 구성할 수 있다.

\\.\mailslot\abc\def\ mailbox

메일슬롯에 데이터를 전송하기 위해 해당 메일슬롯의 연결을 의미하는
데이터 스트림이 필요한데, 이는 CreateMailslot 함수의 호출을 통해 생성되는 메일
슬롯과는 또다른 형태의 리소스로서 이 역시도 운영체제의 의해
커널 오브젝트와 핸들의 생성을 동반한다.

'Windows > System Programming' 카테고리의 다른 글

커널 오브젝트의 두 가지 상태  (0) 2011.08.12
메일슬롯과 IPC 에 대한 고찰  (0) 2011.08.12
메일슬롯 (MailSlot) 의 예  (0) 2011.08.12
커널 오브젝트와 Usage Count 란?  (0) 2011.08.12
다양한 문자셋  (0) 2011.07.16


 Usage Count 란?

 자식 프로세스의 종료코드는 커널 오브젝트에 저장된다.
 만약 자식 프로세스가 종료될 시 커널 오브젝트도 동시에 소멸되면
 부모 프로세스는 종료코드를 얻을 수 없게 된다.

 커널 오브젝트를 참조하는 대상이 하나도 없을 때 소멸하는 것이 가장 인상적이며
 이것이 윈도우스가 오브젝트 소멸시기를 결정하는 방식이다.

 즉 커널 오브젝트를 참조하는 프로세스가 하나라도 존재할 시
 커널 오브젝트는 소멸되지 않는다.
 윈도우 운영체제는 이것을 위해 Usage count(참조횟수) 를 관리하며,
 이것이 0이 될 시 커널 오브젝트를 소멸한다.
 또한 커널 오브젝트에 접근 가능한 핸들 개수가 증가 될때 마다 늘어난다.
 
 그러므로 부모 프로세스가 CreateProcess 함수 호출 과정에
 자식 프로세스의 핸들을 얻기 때문에 ( PROCESS_INFORMATION )
 자식 프로세스 생성이 완료되면 Usage Count 는 2가 된다.
 
 자식 프로세스는 GetCurrentProcess 함수를 통해
 언제든지 자신의 커널 오브젝트 참조를 위한 핸들을 얻을 수 있다.
 UsageCount 는 커널 오브젝트의 멤버로 존재한다.

 즉 CloseHandle() 함수는 핸들을 반환하면서 커널 오브젝트의 UsageCount를 하나 감소 시키는
 기능을 가지고 있으며, 또한 프로세스 및 쓰레드도 프로세스가 종료되는 시점에
 Usage Count가 하나 감소한다.

 BOOL WINAPI GetExitCodeProcess( //종료코드를 얻는다.
  __in          HANDLE hProcess,
  __out         LPDWORD lpExitCode
 );

 
 // TerminateProcess() //악성코드 제거를 위해서만 사용할 것.
 //입출력도중 사용시 굉장한 오류가 발생!! 사용자제할 것.

 BOOL WINAPI TerminateProcess(
  __in          HANDLE hProcess,
  __in          UINT uExitCode
 );


1. SBCS ( Single Byte Character Set )

문자를 표현하는데 있어서 1바이트만을 사용하는 방식으로, ASCII 코드가 대표적이다.


2. MBCS ( Multi Byte Character Set )

동일한 바이트 수로 문자를 표현하는게 아니라 , 다양한 바이트 수를 사용해서 문자를 표현하는 방식이다.
예를들면 어떤 문자는 1바이트로 표현하고 어떤 문자는 2바이트로 표현한다.
MBCS는 SBCS를 포함하는데, 대부분의 문자들을 2바이트로 처리하되, 아스키코드에서 정의하고 있는 문자를
표현할 때는 1바이트로 처리한다. 즉 MBCS 에서는 영어는 1바이트로, 한글은 2바이트로 처리한다.
굉장히 효율적인 방법이라 생각할 수 있지만, 구현하기에는 굉장히 까다롭다.


3. WBCS ( Wide Byte Character Set )

모든 문자를 2바이트로 표현하는 문자셋이다.
대표적으로 유니코드가 WBCS 에 해당하며, 유니코드 기반 문자열이라고도 한다.



WBCS 기반의 프로그램을 만들기 위해서는 몇 가지 중요한 점이 있다.

1.  char - > wchar_t
- 문자를 표현하는데 사용되는 자료형 char 를 대신해서 자료형 wchar_t 를 사용한다.
- char은 1바이트 메모리 공간만 할당되지만, wchar_t 는 2바이트 메모리 공간이 할당된다.
- 따라서 유니코드 기반의 문자표현이 가능하다.
- typedef unsigned short wchar_t

2. ABC  ->   L"ABC"
- wchar_t str[] = "ABC"  로 할 경우 오류가 생기는데, wchar_t 는 유니코드 문자형식이지만
- 오른쪽 "ABC" 는 MBCS 기반이기 때문이다. 그러므로 다음 형태로 바꿔줘야 한다.
- wchar_t str[] = L"ABC"
- 여기서 L 은 다음에 나오는 문자열을 유니코드 기반으로 표현하라는 의미이다. 널 문자도 2바이트로 처리되므로 총 8바이트!

3.  strlen  -> wcslen
- strlen 은 SBCS 기반이므로 wcslen 으로 (WBCS 기반) 으로 바꿔서 사용해야 한다.
- 사용법이나 전달인자의 개수 등은 완전히 동일하므로 어려울 것은 없다.


 SBCS 기반 함수  WBCS 기반 함수
 strlen  size_t [각주:1] wcslen
 strcpy  wcscpy
 strncpy  wcsncpy 
 strcat  wcscat 




4. 완전한 유니코드 기반 (1)

윈도우 2000 이상의 운영체제에서는 이제 기본적으로 유니코드를 지원하며, 내부적으로도 모든 문자열을 유니코드
기반으로 처리한다. 그러므로 유니코드 기반으로 프로그램을 작성하지 않는다면 성능에 영향을 미칠 수 있다.

wprintf( L"Hello World");

위 문장은 printf 함수의 유니코드 버전이며, 다음은 문자열 입출력 함수 중 WBCS 기반의 예이다.
 SBCS 기반 함수  WBCS 기반 함수 
 printf  int wprintf 
 scanf  int wscanf 
 fgets  wchar_t * fgetws 
 fputs  int fputws 





5. 완전한 유니코드 기반 (2)

main  ->  wmain(int argc, wchar_t* argv[])

본래의 main 함수의 argv 는 char* 형식 (SBCS) 이고 argv[]를 wchar_t* argv[] 로 바꾸더라도 main함수는 실행 시 전달되는 문자열을 MBCS 기반으로 구성하기 때문에 엉뚱한 출력결과가 보일 것이다.
그러므로 유니코드 기반의 main함수를 사용해야 하는데 그것이 바로 wmain 함수이다.



  1. typedef unsigned int size_t [본문으로]

+ Recent posts