정리

[C++] CRT Debug Heap(Memory Leak 확인 방법)

저장소/VC++

MFC 프로젝트에서는 이런거 필요 없다능...
귀찮아도 해두는 편이 좋으니까 적어둔다능... -_-;

우선 MSDN 링크
[CRT 디버그 힙] 
http://msdn.microsoft.com/ko-kr/library/aa292300(VS.71).aspx
[디버그 힙] http://msdn.microsoft.com/ko-kr/library/aa292313(VS.71).aspx

링크가 그게 그거임 -_-;
정리한 내용보다 링크가 훨 나음;;
그래도 일단 정리~


C Runtime(CRT) 환경에서 Memory Leak을 확인하는 방법은 아래와 같다.
C Runtime Library인 "crtdbg.h"를 이용하여 디버깅 중 확인할 수 있다.

#include <crtdbg.h>

#define _CRTDBG_MAP_ALLOC

#ifdef _DEBUG
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif

근데 이 내용 MFC 책에서 봤는데... 제목이 뭐였더라;;;
아무튼 일단 저렇게 해두면 됨.
그 다음은 아래와 같다.

일단 진입부에서 아래와 같이 설정한다.
예를 들어 main() 함수가 진입 시작점일 경우

int main()
{
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
...
}

이렇게 해두면 Memory Leak 발생시 MFC에서 보던 것 처럼 나온다.

각 Flag에 대한 설명은 아래와 같다.

 

비트 필드

기본값

설명

_CRTDBG_ALLOC_MEM_DF

On

디버그 할당을 사용합니다. 이 비트를 해제하면 할당은 함께 연결되어 있지만 블록 형식은 _IGNORE_BLOCK입니다.

_CRTDBG_DELAY_FREE_MEM_DF

Off

부족한 메모리 조건을 시뮬레이션하는 것과 관련해서 메모리가 실제로 해제되는 것을 방지합니다. 이 비트를 설정하면, 해제된 블록이 디버그 힙의 연결 리스트에 보관되지만 _FREE_BLOCK으로 표시되고 특별한 바이트 값으로 채워 집니다.

_CRTDBG_CHECK_ALWAYS_DF

Off

모든 할당 및 할당 취소에서 _CrtCheckMemory를 호출하게 합니다. 실행 속도는 지연되지만 오류를 신속하게 찾아 냅니다.

_CRTDBG_CHECK_CRT_DF

Off

_CRT_BLOCK 형식으로 표시된 블록을 누수 탐지와 상태 구별 작업에 포함시킵니다. 비트를 해제하면 위와 같은 작업을 하는 동안 런타임 라이브러리가 내부적으로 사용하는 메모리를 무시합니다.

_CRTDBG_LEAK_CHECK_DF

Off

_CrtDumpMemoryLeaks를 호출하여 프로그램을 종료할 때 누수 검사를 수행합니다. 응용 프로그램이 할당한 모든 메모리를 해제하는 데 실패하면 오류 보고서가 생성됩니다.


여기까지는 준비 과정(?)일 뿐이고 실제로 메모리 릭이 발생했을 때 확인하고자 하는 곳에서 브레이크 포인트를 걸려면 아래와 같이 메모리 할당 번호를 지정하여 확인한다.

int main()
{
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
_crtBreakAlloc(메모리 할당 번호);
...
}

이렇게 하면 원하는 메모리 위치를 확인할 수 있다.
열심히 디버깅 하세~ 예~ 붸뷔~ 요~ 체키라웃~

[참고] MSDN, 네이버님, 여러 블로거님들

SQLite - 쿼리 실행

저장소/VC++

닥치고 샘플 ㄱㄱ~

// Insert에 사용될 데이터 구조체
typedef struct _my_value
{
// 이것 저것 있음

} MY_VALUE, *PMY_VALUE;

sqlite3* g_pDB = NULL;

int TestFunc_Open(const char* pszDBFilePath);
int TestFunc_Close();
int TestFunc_QueryExec1(const char* pszTableName,
    const char* pszQuery,
    sqlite3_callback xCallback);
int TestFunc_QueryExec2(const char* pszTableName, const char* pszQuery);
int TestFunc_QE_Insert(const char* pszTableName,
 int nColumnCount,
 std::list<PMY_VALUE>* plstValues);


int TestFunc_Open(const char* pszDBFilePath)
{
// 파라미터검증 생략

int nRet = SQLITE_OK;
nRet = sqlite3_open_v2(pszDBFilePath,
&g_pDB,
SQLITE_OPEN_READWRITE,
NULL);
if(SQLITE_OK != nRet)
{
// 이쯤에서 적절한 에러처리.
}

return 0;
}

int TestFunc_Close()
{
if(NULL != g_pDB)
{
 int nRet = SQLITE_OK;
nRet = sqlite3_close(g_pDB);
if(SQLITE_OK != nRet)
{
// 아이고~ 실패입니다요! 에러처리!
}
}

return 0;
}

/*
 sqlite3_exec 함수를 사용한 방식
 sqlite3_exec 함수는 내부에서 아래와 같은 과정으로 처리됨.
 (sqlite3_prepare -> sqlite3_step -> sqlite3_finalize)
 단일 명령 처리에는 사용하기 편하지만
 빠르고 연속적인 쿼리를 처리하기엔 오버헤드를 무시할 수 없음.
*/
int TestFunc_QueryExec1(const char* pszTableName,
    const char* pszQuery,
    sqlite3_callback xCallback)
{
// 파라미터 및 이것저것 검증 생략

int nRet = SQLITE_OK;
char* pszErrMsg = NULL;
nRet = sqlite3_exec(g_pDB, pszQuery, xCallback, NULL, &pszErrMsg);
if(SQLITE_OK != nRet)
{
// 실패 원인 확인 1. Code
int nErrCode = sqlite3_errcode(g_pDB);

// 실패 원인 확인 2. Message
const char* pszErrMessage = sqlite3_errmsg(g_pDB);

// 실패 원인 확인 3. 반환된 Error Message
// pszErrMsg <- 요거요거
// 반환된 Error Message는 알아볼거 알아보고 나중에
// sqlite3_free를 통해 메모리 해제를 해줘야 한다고 한다.
// (참고로 잘못된 대상으로 sqlite3_free를 사용하면 뻗어버린다!)
sqlite3_free((void*)pszErrMsg);
}

return 0;
}

/*
 sqlite3_prepare 함수를 사용한 방식
 sqlite3_exec 함수를 사용한 것보다 귀찮긴 하지만
 sqlite3_bind_xxx 함수와 sqlite3_reset 함수를 활용하여 쿼리의 재사용성을 높일 수 있다.
 작업의 끝은 sqlite3_finalize 함수 호출로 막을 내린다.
*/
int TestFunc_QueryExec2(const char* pszTableName, const char* pszQuery)
{
 // 파라미터 및 이것저것 검증 생략

int nRet = SQLITE_OK;
sqlite3_stmt* pStmt = NULL;

// 실행할 쿼리를 준비하고
nRet = sqlite3_prepare(g_pDB, pszQuery, -1, &pStmt, NULL);
if(SQLITE_OK == nRet)
{
// 준비된 쿼리를 실행한다.
nRet = sqlite3_step(pStmt);
while(SQLITE_ROW == nRet)
{
// 뭐 필요한 작업 있으면 여기서
nRet = sqlite3_step(pStmt);
}

nRet = sqlite3_finalize(pStmt);
}
else
{
// 에러처리
}

return 0;
}

int TestFunc_QE_Insert(const char* pszTableName,
 int nColumnCount,
 std::list<PMY_VALUE>* plstValues)
{
// 파라미터 및 이것저것 검증 생략

// 대량의 INSERT를 처리할 경우 BEGIN ~ COMMIT 을 쓰면 속도가 빠르다고 함.
// 임시파일 어쩌구 하던데 자세히 모르니까 나중에 다시 찾아보도록...
int nRet = SQLITE_OK;

nRet = sqlite3_exec(g_pDB, "BEGIN;", NULL, NULL, NULL);

char szBind[100] = "?";
for(int i = 0 ; i < nColumnCount ; i++)
{
strncat_s(szBind, 100, ", ?", _TRUNCATE);
}

char szQuery[512] = {0,};
strncpy_s(szQuery, 512, "INSERT INTO ", _TRUNCATE);
strncat_s(szQuery, 512, pszTableName, _TRUNCATE);
strncat_s(szQuery, 512, " VALUES(", _TRUNCATE);
strncat_s(szQuery, 512, szBind, _TRUNCATE);
strncat_s(sqQuery, 512, ");", _TRUNCATE);

sqlite3_stmt* pStmt = NULL;
nRet = sqlite3_prepare(g_pDB, szQuery, -1, &pStmt, NULL);
if(SQLITE_OK == nRet)
{
std::list<PMY_VALUE>::iterator iter;

for(iter = plstValues->begin() ; iter != plstValues->end() ; ++iter)
{
PMY_VALUE pValue = *iter;

nRet = sqlite3_reset(pStmt);

// Column 수에 맞게, 데이터 형식에 맞게 sqlite3_bind 함수를 사용하여
// 필요 값을 넣는다.
sqlite3_bind_int(pStmt, 1, pValue->xxx);
sqlite3_bind_double(pStmt, 2, pValue->aaa);
sqlite3_bind_text(pStmt, 3, pValue->bbb, -1, SQLITE_STATIC);

// 완성된 쿼리 실행
nRet = sqlite3_step(pStmt);
whlie(SQLITE_ROW == nRet)
{
nRet = sqlite_step(pStmt);
}
}

nRet = sqlite3_finalize(pStmt);
}

nRet = sqlite3_exec(g_pDB, "COMMIT;", NULL, NULL, NULL);

return 0;
}

SQLite - 쿼리 실행(어떤거 써야해?)

저장소/VC++

sqlite3_exec로 Query 실행이 가능하다.

허나!! 또 다른 것이 있었네? 이건 뭐냐?
원문 보니까 보니까 영 이해 안된다.(영어 실력 부족;;;) -_-

일단 네이버님께 물어물어 뒤져보니 대략 이렇다.

1. sqlite3_open
2. sqlite3_stmt
3. sqlite3_prepare
4. sqlite3_bind
5. sqlite3_step
6. sqlite3_reset
7. sqlite3_finalize
8. sqlite3_close

하아... 골아프군.
설명을 하자면 이렇다. (정확한지 모르겠으니 나중에 다시 정리하자)

sqlite3_open과 sqlite3_close는 아니까 패스.
sqlite3_stmt는 statement로 sqlite3_prepare와 sqlite3_bind_xxx에서 사용된다.
sqlite3_prepare를 실행하여 쿼리를 준비한다.
Query에 Data Type 별로 값을 정해주려면 sqlite3_bind_xxx를 통해서 값을 추가한다.
sqlite3_step으로 준비된 stmt(그냥 Query라고 생각하면 될 것 같다.)를 실행한다.
sqlite3_prepare를 통해 생성된 sqlite3_stmt를 다시 활용하려면 sqlite3_reset을 하면 된다.
Query 실행을 끝내려거든 sqlite3_finalize를 해주면 된다.

일단 찾아본 내용으로는 이러한 과정을 거치는데
요걸 좀 더 편하게 사용할 수 있게 해주는 것이 sqlite3_exec라고 한다.

결론!
그냥 쉽게 만들려면

1. sqlite3_open
2. sqlite3_exec
3. sqlite3_close

이렇게 해주면 되겠지만... 이렇게 만들어야 쓰겠어?
사용상 편의를 위해 저기 위에 있는 1~8번 과정을 거치도록 만드는 것이 좋을 것 같다.

에휴... 제대로 해석이 되거든 다시 정리해야겠다.
영어영어영어영어영어영어영어영어영어영어영어영어영어영어영어영어영어영어영어영어
잊지 않겠다. -_-+

[MFC] MFC에서 Thread를 사용할 때는 _beginthread 말고 AfxBeginThread를 사용해야 한다.

저장소/VC++

[출처] [펌글] MFC 에서 쓰레드 관련한 글 |작성자 구리구리


MFC 프로그래밍에서 거의 100% 들어맞는 원칙이 하나 있습니다.
비슷한 기능을 하는 함수가 C/C++ 런타임 라이브러리 API 버전이 있고 앞에 Afx가 붙은 MFC 버전이 있다면 무조건 Afx가 붙은 버전을 써야 한다는 겁니다.
예를 들어, LoadLibrary 같은 것도 MFC에선 AfxLoadLibrary를 써야 합니다.


이런 얘기를 꺼내는 건 다름이 아니라
많은 분들이 MFC에서 _beginthread나 CreateThread를 이용해서 스레드를 만든 다음
그 스레드에서 CWinApp, CView 등의 인스턴스에 액세스하면서 에러를 경험하시는 것 같아서 입니다.


(1) MFC 오브젝트는 '스레드 안전'하지 않습니다.
동일한 오브젝트를 두 스레드에서 동시에 사용하려고 하면 맛 갈 수 있습니다


(2) MFC CWinThread나 AfxBeginThread를 이용하지 않고, _beginthread나 CreateThread로 만든 스레드에서
MFC 오브젝트에 액세스하면 맛 갈 수 있습니다.


(3) MFC 스레드(보조 스레드)에서 다른 스레드로 생성된 CWnd 계열 오브젝트를 사용하면 생각대로 결과가 안 나올 수 있습니다.


MFC 오브젝트는 실제 HWND 같은 핸들을 감싸는 C++ 랩퍼 오브젝트인데, 각각의 랩퍼 오브젝트에 대응되는 실제 HWND 핸들이 무엇인지 알려주는 정보가 스레드마다 개별적으로 사용되는 스레드 로컬 스토리지에 저장되기 때문입니다.
즉 같은 CWnd 오브젝트(같은 메모리 주소)에 액세스하더라도 그 오브젝트가 사용하는 핸들(HWND)가 다를 수 있다는 겁니다.


그러니 보조 스레드에서 뭔가 UI와 관련된 조작을 할 때 직접 하려고 하지 말고
마치 2개의 프로세스가 통신하는 것처럼 ::PostMessage 같은 방법을 이용하는 것이 좋습니다.


또한, 예를 들어, 다른 스레드로 CWnd 오브젝트를 넘길 때 오브젝트를 직접 넘기지 말고 HWND로 넘긴 다음 받은 스레드에서 FromHandle 메소드를 써서 다시 CWnd를 사용하는 식으로 해야 합니다.


이상의 내용은, 왠만한 Visual C++ 프로그래밍 책에는 다 나올 겁니다.


http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/_core_multithreading_with_c.2b2b_.and_mfc.asp
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/_core_Multithreading.3a_.Programming_Tips.asp


이 두 개의 글을 대충 정리한 것입니다.


출처 : 데브피아 vc++ 팁 강좌란 노영선(roh0sun)님 글입니다~~ 

[Win32] Thread 종료 확인 - GetExitCodeThread

저장소/VC++

thread의 상태(종료 여부)를 반환한다.
Retrieves the termination status of the specified thread.

BOOL WINAPI GetExitCodeThread(
   __in HANDLE hThread,
   __out LPDWORD lpExitCode
);


Parameters
hThread
  A Ahndle to the thread.
  The handle must have the THREAD_QUERY_INFORMATION access right. For more information, see Thread Security and Access Rights.

lpExitCode
  A pointer to a variable to receive the thread termination status. If the specified thread has not terminated and the function succeeds, the termination status returned is STILL_ACTIVE.

Return Value
If the function succeeds, the return value is nonzero.
If the function fails, the return value is zero. To get extended error information, call GetLastError.
 

Remarks

If the thread has terminated and the function succeeds, the termination status returned may be one of the following:

  • The exit value specified in the ExitThread or TerminateThread function.
  • The return value from the thread function.
  • The exit value of the thread's process.

Warning  If a thread happens to return STILL_ACTIVE (259) as an error code, applications that test for this value could end up in an infinite loop.

Requirements

Client

Requires Windows Vista, Windows XP, Windows 2000 Professional, Windows NT Workstation, Windows Me, Windows 98, or Windows 95.

Server

Requires Windows Server 2008, Windows Server 2003, Windows 2000 Server, or Windows NT Server.

Header

Declared in Winbase.h; include Windows.h.

Library

Use Kernel32.lib.

DLL

Requires Kernel32.dll.






[Win32] 환경변수 설정 값 가져오기 - GetEnvironmentVariable

저장소/VC++

Retrieves the contents of the specified variable from the environment block of the calling process.

DWORD WINAPI GetEnvironmentVariable(
   __in    LPCTSTR lpName,
   __out  LPTSTR lpBuffer,
   __in    DWORD nSize
);

Parameters
   lpName
      The name of the environment variable.
   lpBuffer
      A pointer to a buffer that receives the contents of the specified environment variable as a null-terminated string. An environment variable has a maximum size limit of 32,767 characters, including the null-terminating character.




환경변수 레지스트리 위치

시스템 환경 변수

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\control\session Manager\Enviroment

사용자 환경 변수

HKEY_CURRENT_USER\Environment

 

바로 적용시키는 코드

 ::SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, 0,(LPARAM)TEXT("Environment"));

- 이하 아래 내용은 프로그램 내에서만 유효한 변경이다.

 

프로그램 내에서 환경변수 가져오기

 int nSize = ::GetEnvironmentVariable("TEMP", NULL, NULL);

 TCHAR* buffer = new TCHAR[nSize +1];

 ::GetEnvironmentVariable("TEMP",buffer, nSize);

// 만약 버퍼의 크기가 변수의 크기보다 작다면 0이 리턴 되므로 버퍼의 크기를 nSize만큼 얻어와야 한다.

 

프로그램 내에서 환경변수 설정하기

 ::SetEnvironmentVariable("TEMP", "C:\\tmp");

 

프로그램 내에서 환경변수 지우기

::SetEnvironmentVariable("TEMP", NULL);

 

예제)

 // * Map 처음 문자를 환경변수에 세팅
 char buf[50] = {0,};
 ::GetPrivateProfileString("CONFIG", "PRE_CHAR", "S", buf, 50, szCfgFile);
 SetEnvironmentVariable("PRE_CHAR", buf);

 

 char buf[30] = {0,};
 // 환경변수에서 Map 처음문자 가져오기
 GetEnvironmentVariable("PRE_CHAR", buf, 30);




[Error/Warning] error PRJ0002 : xxxxxxxx

저장소/VC++

VS2008에서 프로젝트 빌드를 수행할 때
 
"error PRJ0002 : xxxxxx"

라는 에러가 발생할 경우 우선 프로젝트 폴더 내에 rc파일이 존재하는지
정상적으로 작성되어있는지 확인할 필요가 있다.

사례를 검색해보면  VC6과 충돌이 발생해서 그러니 VC6을 지우고 VS2008을 다시 설치해라 윈도우를 다시 설치해라 어쩌라 그러던데 그걸 언제 하고있냐;;;

결과적으로 내 자리에선 rc파일이 없어서 발생했던 문제였는데
rc파일이 없을 경우 이런 문제도 발생하는구나 확인할 수 있었던 경험이랄까?

아무튼 이런 문제가 생기면 일단 rc파일부터 확인하고
rc.exe가 제대로 구동되는지도 확인해보자.

근데 rc파일 문제가 아니면 정말 시스템 다시 설정해야 하는건가? -_-;;

[Win32] 시스템의 Locale 관련 함수들

저장소/VC++

[GetSystemDefaultLocaleName]
시스템의 기본 로케일 이름 반환 함수
(The application should call this function in preference to GetSystemDefaultLCID if designed to run only on Windows Vista and later.)

int GetSystemDefaultLocaleName(
  __out  LPWSTR lpLocaleName,
  __in   int cchLocaleName
);


Parameters

lpLocaleName [out]

Pointer to a buffer in which this function retrieves the locale name.

cchLocaleName [in]

Size, in characters, of the output buffer indicated by lpLocaleName. The maximum possible character length of a locale name (including a terminating null) is the value of LOCALE_NAME_MAX_LENGTH. This is the recommended size.

Return Value

Returns a value greater than 0 that indicates the length of the locale name, including the terminating null character, if successful.

This function returns 0 if it does not succeed. To get extended error information, the application can call GetLastError, which can return one of the following error codes:

  • ERROR_INSUFFICIENT_BUFFER. A supplied buffer size was not large enough, or it was incorrectly set to NULL.

Remarks

This function can retrieve data from custom locales. Data is not guaranteed to be the same from computer to computer or between runs of an application. If your application must persist or transmit data, see Using Persistent Locale Data.

Examples

An example showing the use of this function can be found in NLS: Name-based APIs Sample.

Requirements

Minimum supported client Windows Vista
Minimum supported server Windows Server 2008
Header Winnls.h (include Windows.h)
Library Kernel32.lib
DLL Kernel32.dll

See Also

National Language Support
National Language Support Functions
Mapping Locale Data
DownlevelLCIDToLocaleName
GetLocaleInfoEx
GetUserDefaultLocaleName

[GetSystemDefaultLangID]
시스템의 기본 언어 식별자 반환 함수

[GetUserDefaultLangID]
현 사용자의 기본 언어 식별자 반환 함수

[GetSystemDefaultLCID]
시스템의 기본 로케일 식별자 반환 함수

[GetUserDefaultLCID]
현 사용자의 기본 로케일 식별자 반환 함수
[제어판]-[국가 및 언어 옵션]-[표준 및 형식] 항목에 선택된 국가 정보를 반환한다.

[ConvertDefaultLocale]
기본 로케일을 변경하는 함수

[SetThreadLocale]
이 함수를 호출한 쓰레드의 현재 로케일을 변경하는 함수

[Win32] IPC - #3 Pipe(파이프)

저장소/VC++
[출처] 윈도우즈 API 정복, 김상형 지음

Win32 파이프는 두 프로세스간에 정보를 주고 받을 수 있는 통로(Information Conduit)를 말하는데 다양한 IPC 방법 중 하나이며 주로 연속적인 바이트 스트림을 교환할 때 많이 사용된다.
Win32 API는 이름없는 파이프와 이름있는 파이프 두 가지 종류의 파이프를 지원한다.

1. 이름없는 파이프(Anonymous Pipe) - 잘 안쓰이니 대충 넘겨도 됨.
 주로 부모 자식간의 단방향 통신에만 사용, 네트워크상의 다른 컴퓨터끼리는 사용할 수 없는 로컬 전용의 파이프이다. 이름있는 파이프에 비해 오버헤드가 적으니 기능상의 제약이 많다. 함수는 아래와 같다.

BOOL CreatePipe(PHANDLE hReadPipe, PHANDLE hWritePipe, LPSECURITY_ATTRIBUTES lpPipeAttributes, DWORD nSize);

이 함수는 파이프를 생성하고 파이프 양쪽 끝인 읽기 핸들과 쓰기 핸들을 첫 번째 인수(hReadPipe)와 두 번째 인수(hWritePipe)로 전달된 참조인수로 리턴한다. 두 개의 HANDLE형 변수를 선언하고 변수의 포인터를 이 함수로 전달하면 된다. 세 번째 인수는 파이프의 보안 속성을 지정하는데 이름없는 파이프는 주로 상속을 통해 자식 프로세스로 전달되므로 보안 속성의 bInheritHandle멤버를 TRUE로 설정해야 한다. 네 번째 인수 nSize는 파이프의 버퍼 크기이되 0으로 지정하면 디폴트 크기로 버퍼를 만든다.

부모 프로세스가 자식 프로세스에게 데이터를 보낼 때 취해야 하는 절차이다. 반대로 데이터를 받아야 할 때는 핸들만 바꿔주면 된다.

ㄱ. 일기 핸들 전달을 위해 표준 입력을 재지향하는데 핸들 전달 후 복구를 위해 표준 입력 핸들을 저장해 놓는다.
ㄴ. 상속 가능한 파이프를 생성한 후 표준 입력을 파이프의 읽기 핸들로 재지향한다. 이때 쓰기 핸들은 상속되어서는 안 되므로 상속되지 않는 복사본을 만들고 원래의 상속 가능한 쓰기 핸들은 파괴한다.
ㄷ. 차일드 프로세스를 실행한다. 부모 프로세스의 표준 입력 핸들이 차일드로 상속되며 이때 표준 입력이 파이프의 읽기 핸들로 재지향되어 있으므로 차일드는 결과적으로 파이프의 읽기 핸들을 상속받게 된다.
ㄹ. 부모는 읽기 핸들을 차일드에게 전달했으므로 자신이 가지고 있는 읽기 핸들은 닫는다. 그리고 보관해 놓았던 표준 입력 핸들을 원래대로 복구한다.
ㅁ. 쓰기 핸들의 복사본으로 데이터를 보내면 차일드는 표준 입력 핸들로부터 데이터를 읽을 수 있다.
ㅂ. 통신이 끝난 후 쓰기 핸들을 닫는다. 양쪽 끝의 핸들이 모두 파괴되면 파이프도 같이 파괴된다.


2. 이름있는 파이프(Named Pipe) - 확실히 알아둘 것




함수는 아래와 같다.
HANDLE CreateNamedPipe(LPCTSTR lpName, DWORD dwOpenMode, DWORD dwPipeMode, DWORD nMaxInstances, DWORD nOutBufferSize, DWORD nInBufferSize, DWORD nDefaultTimeOut, LPSECURITY_ATTRIBUTES lpSecurityAttributes);

[파라미터 설명]

lpName
만들고자 하는 파이프의 이름을 지정한다. 다른 파이프와 중복되지 않아야 한다.
형식은 아래와 같다.
"\\서버명\pipe\파이프 이름"
로컬 컴퓨터인 경우는 마침표(.)를 대신 찍으면 된다.
"\\.\pipe\파이프 이름"
CreateNamedPipe는 원격지 컴퓨터의 파이프를 생성하는 기능은 가지고 있지 않다. 그러나 CreateFile, CallNamedPipe 등 파이프를 여는 함수는 원격지의 파이프를 열 수 도 있으므로 이 때는 파이프 이름에 서버명을 같이 지정해야 한다.

파이프 이름은 최대 256문자까지 가능하며 대소문자는 구분하지 않는다.

dwOpenMode
동기화 입출력
비동기화 입출력

완전 쓰기 모드(Write through)는 바이트 유형의 원격지 컴퓨터끼리 통신할 때만 유효하다. FILE_FLAG_WRITE_THROUGH 플래그를 지정하므로써 파이프를 완전 쓰기 모드로 동작시킬 수 있다.
WriteFile 함수로 출력을 내보내면 원격지 컴퓨터의 버퍼에 데이터가 완전히 전달되기 전에는 리턴하지 않는다. 즉 데이터를 버퍼에 넣는 것 뿐만 아니라 이 데이터가 네트워크를 통해 반대편 컴퓨터의 버퍼로 완전히 전달될 때까지 대기한다.
이 모드는 쓰기 동작을 완벽하게 동기화해야 할 때 유용하다.
파이프를 생성할 때만 지정할 수 있어서 서버, 클라이언트가 각각 다른 모드로 지정하는 것도 가능하다.


dwPipeMode
파이프의 유형, 읽기 모드, 대기 모드를 지정한다.
파이프 유형은 파이프로 데이터가 출력되는 방법을 지정, 바이트 유형과 메시지 유형 두 가지가 있다.

바이트 유형

메시지 유형

읽기 모드

대기 모드

dwMaxInstances
같은 파이프 이름으로 생성할 수 있는 최대의 인스턴스 갯수를 지정한다. 즉 하나의 서버가 동시에 통신할 수 있는 클라이언트의 갯수이다.
1 ~ PIPE_UNLIMITED_INSTANCES(256)까지 지정할 수 있다.

nOutBufferSize, nInBufferSize
파이프의 입력용, 출력용 버퍼의 크기를 지정한다. 단, 이 크기는 어디까지나 권장치일 뿐이며 실제로 시스템은 효율을 높이기 위해 이 인수로 지정한 크기와는 다르게 버퍼를 할당하기도 한다.

nDefaultTimeOut
디폴트 제한 시간은 1/1000초 단위로 지정한다. 이 값은 WaitNamedPipe 함수가 여유 파이프를 대기할 때 제한 시간으로 사용된다. 모든 인스턴스가 동일한 제한 시간으로 파이프를 생성해야 한다.

lpSecurityAttributes
파이프도 커널 오브젝트이므로 보안 속성을 지정할 수 있다. NULL로 지정하면 디폴트 보안 속성이 적용된다. 대개의 경우 NULL로 지정하면 별 말썽이 없다.










[Win32] Service - #4 서비스 제어

저장소/VC++
[출처] 윈도우즈 API 정복, 김상형 지음


UI 디자인은 아래와 같다.



#define SVCNAME                        _T("MemStat")
#define SERVICE_CONTROL_NEWFILE        128

BOOL CControlDlg::OnInitDialog()
{
    /*
         중략...
    */

    hScm = ::OpenSCManager(NULL, NULL, GENERIC_READ);

    if(NULL == hScm)
    {
        MessageBox(_T("SCM을 열 수 없습니다."), _T("알림"), MB_OK);
        EndDialog(0);
    }

    hSvc = ::OpenService(hScm, SVCNAME, SERVICE_ALL_ACCESS);

    if(NULL == hSvc)
    {
        MessageBox(_T("MemStatService 서비스가 설치되어 있지 않습니다."), _T("알림"), MB_OK);

        ::CloseServiceHandle(hScm);
        EndDialog(0);
    }
    else
    {
        ::CloseServiceHandle(hSvc);
    }

    QueryService();
}

void CControlDlg::QueryService()
{
    hSvc = ::OpenService(hScm, SVCNAME, SERVICE_INTERROGATE);

    if(NULL == hSvc)
    {
        return;
    }

    do
    {
        ::ControlService(hSvc, SERVICE_CONTROL_INTERROGATE, &ss);
    }
    while(    (SERVICE_STOPPED != ss.dwCurrentState) &&
            (SERVICE_RUNNING != ss.dwCurrentState) &&
            (SERVICE_PAUSED != ss.dwCurrentState));

    GetDlgItem(IDC_BTN_START)->EnableWindow(FALSE);
    GetDlgItem(IDC_BTN_STOP)->EnableWindow(FALSE);
    GetDlgItem(IDC_BTN_PAUSE)->EnableWindow(FALSE);
    GetDlgItem(IDC_BTN_CONTINUE)->EnableWindow(FALSE);
    GetDlgItem(IDC_BTN_NEW_LOG)->EnableWindow(FALSE);

    switch(ss.dwCurrentState)
    {
    case SERVICE_STOPPED:
        {
            GetDlgItem(IDC_BTN_START)->EnableWindow(TRUE);
            GetDlgItem(IDC_STATIC)->SetWindowText(_T("현재 상태 : 중지"));
        }
        break;
    case SERVICE_RUNNING:
        {
            GetDlgItem(IDC_BTN_STOP)->EnableWindow(TRUE);
            GetDlgItem(IDC_BTN_PAUSE)->EnableWindow(TRUE);
            GetDlgItem(IDC_BTN_NEW_LOG)->EnableWindow(TRUE);
            GetDlgItem(IDC_STATIC)->SetWindowText(_T("현재 상태 : 실행중"));
        }
        break;
    case SERVICE_PAUSED:
        {
            GetDlgItem(IDC_BTN_STOP)->EnableWindow(TRUE);
            GetDlgItem(IDC_BTN_CONTINUE)->EnableWindow(TRUE);
            GetDlgItem(IDC_STATIC)->SetWindowText(_T("현재 상태 : 일시중지"));
        }
        break;
    }

    ::CloseServiceHandle(hSvc);
}

void CControlDlg::MemControl(DWORD dwControl)
{
    hSvc = ::OpenService(hScm, SVCNAME, GENERIC_EXECUTE);

    ::ControlService(hSvc, dwControl, &ss);

    ::CloseServiceHandle(hSvc);
    QueryService();
}

void CControlDlg::OnBnClickedBtnStart()
{
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.

    hSvc = ::OpenService(hScm, SVCNAME, SERVICE_START | SERVICE_QUERY_STATUS);

    // 서비스를 시작시키고 완전히 시작할 때까지 대기한다.
    SetCursor(LoadCursor(NULL, IDC_WAIT));
    {
        if(TRUE == ::StartService(hSvc, 0, NULL))
        {
            ::QueryServiceStatus(hSvc, &ss);

            while(SERVICE_RUNNING != ss.dwCurrentState)
            {
                Sleep(ss.dwWaitHint);
                ::QueryServiceStatus(hSvc, &ss);
            }
        }
    }
    SetCursor(LoadCursor(NULL, IDC_ARROW));

    ::CloseServiceHandle(hSvc);
    QueryService();
}

void CControlDlg::OnBnClickedBtnStop()
{
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.

    MemControl(SERVICE_CONTROL_STOP);
}

void CControlDlg::OnBnClickedBtnPause()
{
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.

    MemControl(SERVICE_CONTROL_PAUSE);
}

void CControlDlg::OnBnClickedBtnContinue()
{
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.

    MemControl(SERVICE_CONTROL_CONTINUE);
}

void CControlDlg::OnBnClickedBtnNewLog()
{
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.

    MemControl(SERVICE_CONTROL_NEWFILE);
}




'저장소 > VC++' 카테고리의 다른 글

[Win32] 시스템의 Locale 관련 함수들  (0) 2009.07.07
[Win32] IPC - #3 Pipe(파이프)  (0) 2009.05.14
[Win32] Service - #3 서비스 설치  (0) 2009.05.13
[Win32] Service - #2 서비스 생성  (0) 2009.05.13
[Win32] Service - #1 정의  (0) 2009.05.13