이 글은 Code Project에 있는 Timers Tutorial을 정리한 것입니다.

우선, 윈도우는 윈도우 CE를 제외하면 리얼타임OS가 아닌 관계로 10ms와 같은 매우 작은 시간에 대한 정확한 처리를 요구하는 건 한계가 있답니다. (쥔장도 자세한 이유는 잘... 흠...) QueryPerformanceFrequency나 QueryPerformanceCounter와 같은 경우는 특정 시간 경과 후 이벤트를 처리하는 것이 주요 쓰임이 아니라 시간과 시간사이의 시간차를 계산하는 것이 주요 쓰임인 관계로 이 글에서는 언급되어 있지 않습니다. 반대로 말하면 시간과 시간사이의 차이를 정확히 측정하려면 QueryPerformanceFrequency를 사용하여야 겠죠?

1. Standard Win32 Timer (표준 윈32 타이머)
 Win32 UI프로그래밍 처음 배우면 사용하는 WM_TIMER 메시지 관련된 API를 사용하는 것입니다. 시간 정확도가 떨어지지만 몇분에 한번과 같이 느슨하게 처리할 때 사용하면 좋습니다. 처리방식은 Win32의 윈도우 메시지 큐를 사용하며 callback 함수를 이용하는 경우는 WM_TIMER 메시지를 큐에서 끄내는 대신 함수를 콜하는 방식이라 메시지 방식과 큰 차이를 보이지 않습니다. 중요한 점은 WM_TIMER 메시지를 처리하는 동안 UI의 반응성이 나뻐질 수 있다는 것입니다.
  • SetTimer(...)
  • KillTimer(...)

2. Multimedia Timers (멀티미디어 타이머) 
 많이 사용되는 이유는 호환성 때문이며 최근에는 비권장되는 방식입니다. 표준 윈32 타이머보다 정확한 처리를 하며 자신의 쓰레드에서 동작합니다. 이 방식은 짧은 진행시간을 지정하였을 때는 메시지 큐의 보호를 받지 못하므로 조금 더 위험하다는데 자세한 이유가 나와 있지는 않습니다. (지송합니다. 쥔장이 현재 구지 쓸 이유가 없기 때문에 자세한 이유는 찾아보지 않았습니다. 원문에는 관련 블로그 포스트가 링크되어 있으므로 관심 있는 분은 한 번 찾아보기 바랍니다.)
  • timeGetDevCaps(...)
  • timeBeginPeriod(...)
  • timeSetEvent(...)
  • TimeProc(...)
  • timeKillEvent(...)
  • timeEndPeriod(...)

3. Waitable Timers
 적은 CPU 점유율을 가지면 메시지 큐를 가지지 않는 장점이 있지만 호출하는 쓰레드를 블록시키며 alertable상태로 만들어야 합니다. APC(Asynchronous Procedure Call)로 동작합니다.
  • CreateWaitableTimer(...)
  • SetWaitableTimer(...)
  • CancelWaitableTimer(...)

Alerable 관련 함수

  • SleepEx(...)
  • WaitForSingleObjectEx(...)
  • WaitForMultipleObjectsEx(...)
  • MsgWaitForMultipleObjectsEx(...)
  • SignalObjectAndWait(...)

4. Queue Timers
 현재 최고 성능의 타이머 있데 아쉽게도 윈도우2000이후 버전만을 지원합니다. 이 API는 윈도우 쓰레드풀에서 작동하며 굉장히 정확하게 작동합니다. 저자가 강력하게 추천하는 API입니다.
  • CreateTimerQueueTimer(...)
  • WaitOrTimerCallback(...)
  • DeleteTimerQueueTimer(...)


API 선택가이드
1. GUI에서 사용하며, 높은 정확도를 요구하지 않는 경우 표준 윈32 타이머가 좋은 선택입니다.
2. 높은 호환성과 높은 정확도를 원하면 멀티미디어 타이머가 좋은 선택입니다.
3. 윈도우 98/NT4.0 이후 버전만을 대상으로 하며 적은 오버헤드를 가지며 호출쓰레드가 블록되는 것이 괜찮다면 Waitable Timer가 좋은 선택입니다.
4. 윈도우 2K 이후 버전만을 대상으로 하며 적은 호버헤드, 높은 정확성, 논블록킹 타이머를 원한다면 Queue Timer가 좋은 선택입니다. 아마도 MS는 현재 이 Timer를 밀고 있는 듯 싶군요... 흠...

'library > WIN32API' 카테고리의 다른 글

자식 컨트롤 겹칠 때  (0) 2013.11.26
DllMain에서 동기화 문제 주의  (0) 2010.12.16
MBCS 프로젝트 유니코드 전환 가이드  (0) 2010.11.24
프로세스간 동기화 테스트  (0) 2010.11.24
dll LoadLibrary/FreeLibrary  (0) 2010.10.01
블로그 이미지

란마12

,

strcpy ... 이런 함수 쓰면 절대 안된다. strncpy를 써야지...
wsprintf ... 이런 함수도 쓰면 안된다. _snprintf를 써야지... (리눅스 같으면 snprintf)
모든 문자열 함수에 경계체크 하는 타입과 하지 않는 타입이 있어서, 하지 않는 타입을 사용하면 문자열이 깨지기도 프로그램이 죽기도 하고 경우에 따라 해킹을 당할 수도 있다. 하도 보안 보안 하다보니 모 이런건 이제 기본이 되었다.

근데...  경계 체크를 하는 함수들도.. 경계 체크하는 방식이 조금씩 차이가 있어서...
lstrcpyn 사용하는 식으로 _snprintf를 쓰다 보면 문제가 생길 때가 있다.

이 이슈는... 예전부터 머리속에 새기고 필요할 때마다 인터넷을 뒤져보던 주제이지만,
사실 일도 많고 귀찮다 보니.. 꼼꼼하게 따지지 않고 그냥 적당~히 넘어가는 것이 사실이다.
에이 설마~ 하던 문제가 오늘 터졌다.. ㅋㅋ
그래서 이번엔 내가 직접 함 정리해보려 한다.

※ 아래의 샘플과 테스트 들은 모두 MBCS 환경을 가정한다.
    유니코드일 때도 달라질 일은 없다.
   - 그냥 바이트수와 글자수를 헷갈리지 말고,
   - T-Type 함수와 T-TYPE 데이터를 사용한다.
      (strXXX 대신 _tcsXXX를 쓴다. CHAR 대신 TCHAR를 사용한다. 등등)


샘플코드다.
  1. #ifndef _countof   
  2. #define    _countof(X)    sizeof(X)/sizeof(X[0])   
  3. #endif   
  4.   
  5. int main(int argc, char* argv[])   
  6. {   
  7.     CHAR    szBuf[4] = {0,};   
  8.       
  9.     // 1. strncpy   
  10.     memset (szBuf, 'X'sizeof(szBuf));   
  11.     strncpy(szBuf, "AAAAAAAAA", _countof(szBuf) -1);   
  12.     printf("strncpy : %c%c%c%c\r\n", szBuf[0], szBuf[1], szBuf[2], szBuf[3]);   
  13.   
  14.     // 2. lstrcpyn   
  15.     memset (szBuf, 'X'sizeof(szBuf));   
  16.     lstrcpyn(szBuf, "AAAAAAAAA", _countof(szBuf) -1);   
  17.     printf("lstrcpyn : %c%c%c%c\r\n", szBuf[0], szBuf[1], szBuf[2], szBuf[3]);   
  18.   
  19.     // 3. _snprintf   
  20.     memset (szBuf, 'X'sizeof(szBuf));   
  21.     _snprintf(szBuf, _countof(szBuf) -1, "%s""AAAAAAAAA");   
  22.     printf("_snprintf : %c%c%c%c\r\n", szBuf[0], szBuf[1], szBuf[2], szBuf[3]);   
  23.   
  24.     // 4. strncat   
  25.     memset (szBuf, 'X'sizeof(szBuf));   
  26.     szBuf[0] = NULL;   
  27.     strncat(szBuf, "AAAAAAAAA", _countof(szBuf) -1);   
  28.     printf("strncat : %c%c%c%c\r\n", szBuf[0], szBuf[1], szBuf[2], szBuf[3]);   
  29.   
  30.     return 0;   
  31. }   


4글자 짜리 버퍼에 네가지 문자열 함수로 문자열을 복사해넣되, 버퍼사이즈는 전부 3으로 준다.
그리고 버퍼 4바이트를 한바이트씩 프린트해 본다. (스트링으로 프린트하는게 아님)
어떤 일이 벌어질까?

실행 결과는 다음과 같다. (A는 복사된 문자열, X는 버퍼 초기화된 값이 남아있는 것이다.)
strncpy : AAAX
lstrcpyn : AA X
_snprintf : AAAX
strncat : AAA
Press any key to continue

결과를 하나씩 분석해 보자.

1. strncpy, _snprintf
이 두가지 함수에서 지정된 버퍼사이즈는 곧 복사할 최대 글자수를 의미한다. 버퍼사이즈를 넘어서는 길이의 문자열을 복사하려고 시도할 경우 지정된 글자수만큼만 복사된다.
말하자면... Null로 스트링을 Terminate 시켜주는 따위의 일은 하지 않는다는 것이다. 따라서 이 문자열 함수를 쓸때 글자수는 버퍼사이즈 - 1 만큼을 지정하고, 수동으로 스트링 종결 처리를 해주어야 한다.
다음의 두가지 중 한가지 패턴을 지켜주어야 한다.

// 방법 1 : 일단 전체 버퍼를 초기화시켜 놓고 최대 "버퍼 사이즈 - 1" 글자 만큼만 복사하는 방법
CHAR  szBuf[1024] = {0,}; // 혹은 memcpy(szBuf, NULL, sizeof(szBuf)); 해준다.
strncpy(szBuf, lpSrcStr, _countof(szBuf) - 1);

// 방법 2 : 문자열을 복사한 후 마지막 글자를 NULL로 덮어써서 수동으로 문자열을 Terminate 시켜준다.
CHAR  szBuf[1024] = {0,};
strncpy(szBuf, lpSrcStr, _countof(szBuf));
szBuf[_countof(szBuf) - 1] = NULL;

만약 스트링 종결처리를 안해주었을 땐...버퍼사이즈를 넘어서는 문자열이 입력되었을 때 스트링이 종결되지 않고, 뒤에 쓰레기 값이 붙게 된다. (상기 실행결과를 살펴보라.)

물론.. 입력 문자열이 버퍼를 넘어서지 않을 때는 문제될 게 없다. 하지만, 항상 좋은 입력만 들어올 거라고 누가 장담하겠는가? 심지어 GUI에서 입력문자열 제한을 하는 경우에도 문자열 핸들링시 버퍼체크는 반드시 해주어야 한다.


2. lstrcpyn
가장 쓰기 편한 함수이다. 이 함수에서 지정된 버퍼사이즈에는 말 그대로 버퍼사이즈를 주면 된다.
원본 문자열이 버퍼 사이즈를 초과할 경우, 자동으로 "버퍼사이즈 - 1" 글자 만큼만 복사한 후 나머지 한글자를 NULL로 Terminate 시켜준다. 상기 실행결과를 보면, 버퍼사이즈를 3글자를 주었는데, 결과는 2글자만 복사되고 3번째에는 NULL이 들어가서 문자열이 종결된 것을 알 수 있다.


3. strncat
이 함수는... 좀 특이하다. "n"은 버퍼사이즈가 아니라 복사할 최대 글자수를 의미한다. Src문자열의 길이가 지정된 최대 글자수를 넘어설 경우, 지정된 글자수만큼을 복사한 후 그 뒤에 NULL을 append해준다... 말하자면... 최대 글자수를 N으로 지정했을 경우, 실제로는 N+1 바이트가 복사될 수도 있다는 의미이다. 상기 실행결과를 보면 3글자를 복사했는데, 4번째 글자가 NULL로 덮어씌워졌음을 알 수 있다. 따라서 그냥 생각없이 버퍼 사이즈만큼을 지정해주게 되면... 1바이트 오버플로우가 발생할 수 있다.
게다가 문자열 Concat 함수이기 때문에 다른 함수들 처럼 버퍼사이즈를 생각없이 써주는 게 아니라... 버퍼 중 현재 사용된 사이즈를 제외하고, 실제로 더 쓸 수 있는 바이트 수를 계산하여 다음과 같이 해주어야 한다.

    CHAR    szBigBuf[10] = {'a','b','c','d'};
    CHAR*    lpTest = "TESTSTRING";
    strncat (szBigBuf, lpTest, _countof(szBigBuf) - strlen(szBigBuf) - 1);
         // 덧붙여질 NULL을 고려하여 마지막에 -1을 해준다.
    printf ("%s\r\n", szBigBuf);


... 하여간 복잡한 함수다.


C에서 문자열을 다룬다는 것은... 정말 쉽지 않은 주제인 것 같다.
처음엔 멋도 모르고 쓰다가... 포인터를 이해하게 되고... 그다음엔 버퍼 체크의 개념을 알게 되고, (그땐 내가 문자열을 정복한 줄 알았지 ^^) 그 다음엔 유니코드를 알게 되고... 유니코드를 알고 나면 거꾸로 MBCS 다루기가 얼마나 어려운지를 알게 된다...

http://kuaaan.tistory.com/56

'library > CRT' 카테고리의 다른 글

errno  (0) 2011.01.08
유니코드를 위한 함수  (0) 2011.01.08
시간과 날짜와 관련된 라이브러리 함수들  (0) 2010.12.15
코딩시 Unicode 관련 고려사항  (0) 2010.11.23
디버그 시 malloc 메모리 누수 탐지  (0) 2010.07.16
블로그 이미지

란마12

,

이미 만들어져 있는 프로젝트를 UNICODE로 전환한다는 것은... 어려운 작업까지는 아니지만 상당한 노가다와 시간을 필요로 하는 일임에는 틀림 없다. 약간의 요령과 정형화된 절차가 있다면 조금이나마 시간과 노력을 절약할 수 있다.

다음은 MBCS로 쓰여진 코드를 UNICODE로 변환하는 순서를 설명한다.
(여기서 말하는 UNICODE란 Generic T-TYPE이다. W-Type 아님... ^^)


1. UNICODE 매크로 선언
Visual Studio (6.0) 실행 -> Project -> C++ 탭에서 Preprocessor definitions: 항목에 _MBCS를 삭제하고 _UNICODE와 UNICODE를 추가

사용자 삽입 이미지


2. UNICODE가 불가한 부분이 있는지 검토
코드 전체를 검토하여 UNICODE로 전환할 수 없는 부분이 있는지 체크한다. 예를 들면 유니코드가 아닌 외부모듈을 사용하는 부분이라던지... 통신 프로토콜 문제로 전환이 불가한 부분, GetProcAddress 와 같이 T-TYPE을 지원하지 않는 API를 호출하는 부분 등이 되겠다. 이런 부분은 억지로 유니코드화 하는 것보다 차라리 ANSI 코드를 내버려 두고 결과 스트링을 UNICODE로 한번 더 변환해서 쓰는게 여러 모로 낫다.
다음의 API를 사용한다.
int WideCharToMultiByte( UINT CodePage, // code page
                     DWORD
dwFlags, // performance and mapping flags
                          LPCWSTR lpWideCharStr, // wide-character string          
                     int cchWideChar, // number of chars in string.
                     LPSTR lpMultiByteStr, // buffer for new string          
                     int cbMultiByte, // size of buffer              
                     LPCSTR lpDefaultChar, // default for unmappable chars
                     LPBOOL lpUsedDefaultChar // set when default char used
);
int MultiByteToWideChar( UINT CodePage, // code page
                          DWORD dwFlags, // character-type options         
                          LPCSTR lpMultiByteStr, // string to map
                          int cbMultiByte, // number of bytes in string
                          LPWSTR lpWideCharStr, // wide-character buffer
                          int cchWideChar // size of buffer
);
제대로 Generic하게 Conversion하려면, UNICODE 전환 불가 부분에 대해 다음과 같이 해줘야 한다.
(... blah~ blah~...)
#ifndef _UNICODE
        lstrcpyn(szBuffer1, szBuffer, _countof(szBuffer1));
#else
        MultiByteToWideChar(CP_ACP, FALSE, szBuffer, nLen, szBuffer1, _countof(szBuffer1)-1);
        szBuffer1[_countof(szBuffer1) - 1] = NULL;
#endif
(... blah~ blah~...)


3. Quoted String 전환
다음과 같이 Quoted String을 전환한다.
"TEXT DATA" --> _T("TEXT DATA")
'C' --> _T('C')

           한꺼번에 전환하려면 다음과 같이 한다. 
   1) Visual Studio에서 ctrl + H 를 눌러 변환 창을 띄운다.
   2) "Regular String"에 체크를 한다.
   3) Find What 에는 Quoted String을 의미하는 "\:q" 를 입력한다.
       (혹은 우측 [▶]를 클릭해 "Quoted String"을 선택하면 입력된다.)
   4) Replace With: 에는 _T(\0) 를 입력한다. (\0 는 발견된 스트링을 의미한다.)
   5) 찾아지는 Quoted String을 확인하면서 Replace 를 시작한다.
       "Replace All"을 해버리고 나서 에러나는 부분을 수정해나가는 방법도 괜찮다.
       (보통 #include 등에서 에러가 난다.)
   6) 소스코드 내의 모든 파일에 대해 1~5를 반복한다. 이 때 위에서 확인된 변환불가 부분은 제외한다.
   7) 에러없이 빌드가 되는지 확인한다.

※ #include "HeaderFile.h" 같은 부분이 #include _T("HeaderFile.h") 로 잘못 전환되지 않도록 조심. ^^;


사용자 삽입 이미지



4. Data Type과 문자열 함수를 T-Type 버젼으로 전환
Data Type과 문자열 함수를 T-Type 버젼으로 전환한다.
이때 API는 100% 대체가 가능한 API로 변환해야 하므로 주의하여 MSDN을 참고하여 진행해야 한다. 예를 들어 strncpy는 _tcsncpy 로 전환해야지 lstrcpyn 으로 전환해서는 안된다. (동작이 미묘하게 다르다. 자세한 내용은 여기 참조) 

      1) Data Type을 전환한다. 
    몇가지 예를 들면 다음과 같다.       
    다음의 항목에 대해서는 울트라에디트 등 텍스트 에디터의 Replace in Files 기능으로 한꺼번에 작업해도 된다. ("완전한 단어" 옵션에 체크하면 실수를 줄일 수 있다.)
"LPSTR" -> "LPTSTR"
"LPCSTR" -> "LPCTSTR"
"PSTR" -> "PTSTR"
"PCSTR" -> "PCTSTR"
    다음의 타입들은 VisualStudio의 Replace기능으로 하나하나 확인하며 파일별로 전환해야 한다.
"CHAR" -> "TCHAR"
"char" -> "TCHAR"

※ 소켓 통신이나 파일 I/O 등에서 CHAR Array를 버퍼로 사용하는 경우는 TCHAR로 전환해선 안된다. (이럴 땐 CHAR Array보다 BYTE array를 쓰는 게 맞다.)
※ 혹시 "unsigned char" 와 같이 쓰여진 코드를 전환하면 이상하게 되므로.. 조심. ㅡ.ㅡ

     2) 문자열 핸들링 함수들을 전환한다.   
문자열 핸들링 함수들이 무지 많아서 다 적을 수는 없고... 대표적인 것 몇가지만 적으면 다음과 같다. 다음의 API들은 Replace in Files로 한꺼번에 작업해도 된다.
"strcpy" -> "_tcscpy"
"strncpy" -> "_tcsncpy"
"strcat" -> "_tcscat"
"strncat" -> "_tcsncat"
"strcmp" -> "_tcscmp"
"strncmp" -> "_tcsncmp"
"strstr" -> "_tcsstr"
"strchr" -> "_tcsstr"
(기타 등등 strXXX 류의 함수들은 대부분 _tcsXXX 로 전환하면 된다.)

"wsprintf" -> "_stprintf"
"_snprintf" -> "_sntprintf"
"printf" -> "_tprintf"
(기타 등등.. 무지 많다.)

※ memset, memcpy 등은 문자열 함수가 아닌 메모리 함수이므로 건드리면 안된다.
※ lstrcpyn 계열의 함수들은 원래 T-Type을 지원하므로 건드릴 필요 없다.
※ 위의 함수들은 인자의 종류와 순서가 완전히 동일하므로 함수 이름만 Replace하면 유니코드로 전환이 된다. 만약 strcpy와 같이 버퍼체크를 안하는 함수를 버퍼체크 하는 버젼으로 변경하고 싶다면... 먼저 _tcscpy로 변환하여 유니코드로 컨버젼을 끝낸 다음에 _tcscpy_s (혹은 _tcsncpy) 로 다시한번 전환하는 것이 낫다.

   3) 문자열 핸들링 함수의 버퍼 사이즈에 sizeof가 사용된 곳이 없는지 체크하고, 있다면 _countof로 변환
문자열 함수에서 버퍼사이즈는 버퍼의 바이트수가 아닌 버퍼의 글자수를 지정해야 하며, memcpy 등 메모리 함수는 글자수가 아닌 바이트수를 지정해야 한다. MBCS일 때는 1글자가 1바이트이므로 sizeof가 문제가 안되지만, 유니코드일 때는 지켜주어야 한다.
VisualStudio 6.0의 경우 _countof를 지원하지 않으므로 다음과 같이 Define해서 사용하면 된다.
#ifndef _countof
#define _countof(X) sizeof(X)/sizeof(X[0])
#endif
_tcsncpy (szBuffer, lpSource, sizeof(szBuffer));  // Wrong!!
_tcsncpy (szBuffer, lpSource, _countof(szBuffer));  // OK!!

memcpy (lpBuffer, lpSource, sizeof(szBuffer)); // OK!
memset (lpBuffer, NULL, _countof(szBuffer)); // Wrong!! _countof가 아닌 sizeof 를 사용~!!



5. 기능테스트를 진행하면서 오류나는 부분을 체크하고 수정
모.. 사람마다 생각이 틀리겠지만... 코드 전환을 꼼꼼히 해봐도 어차피 컨버젼하고 나면 결함투성이고 에러를 다 잡아야 한다. 내생각엔 차라리 코드 전환을 대충(?) 해놓고, 테스트를 꼼꼼하게 하는게 낫지 않을까 싶다. ㅎㅎ

http://kuaaan.tistory.com/59

'library > WIN32API' 카테고리의 다른 글

DllMain에서 동기화 문제 주의  (0) 2010.12.16
Win32 과연 어떤 Timer함수를 사용할 것인가?  (0) 2010.12.14
프로세스간 동기화 테스트  (0) 2010.11.24
dll LoadLibrary/FreeLibrary  (0) 2010.10.01
IOCP  (0) 2010.07.08
블로그 이미지

란마12

,

뮤텍스, 이벤트, 세마포어등등으로 프로세스간 동기화가 가능하다
다음 예제는 뮤텍스를 이용하여 먼저 실행된 프로세스가 종료되기를 3초간 대기 후 결과를 메세지박스로 보여준다.

  HANDLE m_hMutex = CreateMutex(NULL, TRUE, "MTXTEST");
  if (GetLastError() == ERROR_ALREADY_EXISTS){
    DWORD dwRet = WaitForSingleObject(m_hMutex, 3000);
    //error, 뮤텍스를 최초 생성한 프로세스가 해제(신호상태로 만듦)할 수 있다.
    //BOOL bRet = ReleaseMutex(m_hMutex); 
    CloseHandle(m_hMutex);        
    if (dwRet == WAIT_FAILED){
      DWORD dwErr = GetLastError();
      MessageBox(NULL, "mtx err", "", MB_OK);
      return;
    }

    if (dwRet == WAIT_TIMEOUT){
      MessageBox(NULL, "mtx timeout", "", MB_OK);
      return;
    }

    MessageBox(NULL, "mtx terminated", "", MB_OK);
  }else{
    MessageBox(NULL, "mtx first running", "", MB_OK);
    ReleaseMutex(m_hMutex);
    CloseHandle(m_hMutex);
  }

블로그 이미지

란마12

,
윈도에서 코딩시
유니코드 적용해야 할때
#Define _UNICOE 을 하면
기존에 잘 컴파일 되던 플젝이 에러 투성이로 변하는 경우가 발생하져..ㅜㅜ

이런경우 당황하지 하지 마시고 모든 에러요인을 찾아다니면서 삽질을 하셔야 할껍니다.

아래와 같이 하시면 대략의 삽질은 면하지 않을까요..?? ^^
( 물론 직접 소스를 찾아가 수정을 가해야하는 부분이 발생하지만 아래와 같이 하시면 많은 부분의 수정요인이 사라지리라 생각합니다. )

1. 윈도 API를 사용한다.
sprintf 등 보다는 wsprintf등 과 같은 윈도에서 제공하는 함수나 MFC를 사용한다.
아무래도 UNICODE 설정만으로 한번에 변경하기 위해선 윈도에서 제공하는것을 써야할듯 싶습니다..^^

2. char * aa = NULL; 와 같은 char 사용시 TCHAR 을 사용한다.
ex> TCHAR *aa = NULL;

3. aa = "나텍스트"; 와 같이 텍스트 값을 사용할때는 _T("") 매크로를 사용한다.
ex> TCHAR aa[50] = { 0 , } , *a1 = NULL;
a1 = _T("나텍스트");
wsprintf( aa , _T("%s") , a1 );

3. 메모리 함수 memset , memcpy 등을 사용시 사용되는 스트링의 크기 ( 글자수가 아닌 ) 는 항상 ( sizeof(TCHAR) * ( 글자수 ) ) 로 한다.
ex> TCHAR aa[100];
memset( (BYTE*)aa , 0x00 , ( sizeof(TCHAR) * (100 ) ) );

4. 스트링 펑션을 쓸때는 _tcs... 으로 나가는 매크로를 사용한다.
ex> TCHAR *aa = "나도텍스트" , a1[50] , a2[50];
memset( (BYTE*)a1 , 0x00 , ( sizeof(TCHAR) * 50 ) );
memset( (BYTE*)a2 , 0x00 , ( sizeof(TCHAR) * 50 ) );

if ( 49 >=  _tcslen( aa ) )
{
_tcscpy( a1 , aa );
memcpy( (BYTE*)a2 , (BYTE*)aa , ( sizeof(TCHAR) * _tcslen(aa) ) );
}


** 아래는 4에서 말한 윈도용 매크로 함수들입니다.  참고하시기 바랍니당

1.       Formatted I/O

MACRO

ANSI

UNICODE

설명

_tprintf

printf

wprintf

 

_ftprintf

fprintf

fwprintf

 

_stprintf

sprint

swprintf

 

_sntprintf

_snprintf

_snwprintf

 

_vtprintf

vprintf

vwprintf

 

_vftprintf

vfprintf

vfwprintf

 

_vstprintf

vsprintf

vswprintf

 

_vsntprintf

_vsnprintf

_vsnwprintf

 

_tscanf

scanf

wscanf

 

_ftscanf

fscanf

fwscanf

 

_stscanf

sscanf

swscanf

 

 

2.       Formatted I/O

MACRO

ANSI

UNICODE

설명

_fgettc

fgetc

fgetwc

 

_fgetts

fgets

fgetwc

 

_fputtc

fputc

fputwc

 

_fputts

fputs

fputws

 

_gettc

getc

getwc

 

_gettchar

getchar

getwchar

 

_getts

gets

_getws

 

_puttc

putc

putwc

 

_puttchar

putchar

putwchar

 

_putts

puts

_putws

 

_ungettc

ungetc

ungetwc

 

 

3.       Stdio function

MACRO

ANSI

UNICODE

설명

_tfopen

 

_wfopen

 

_tfreopen

 

_wfreopen

 

 

4.       String conversion function

MACRO

ANSI

UNICODE

설명

_tcstod

strtod

wcstod

 

_tcstol

strtol

wcstol

 

_tcstoul

strtoul

wcstoul

 

_itot

_itoa

_itow

 

_ltot

_ltoa

_ltow

 

_ultot

_ultoa

_ultow

 

_ttoi

atoi

_wtoi

 

_ttol

atol

_wtol

 

 

5.       String function

MACRO

ANSI

UNICODE

설명

_tcscat

strcat

wcscat

 

_tcschr

strchr

wcschr

 

_tcscmp

strcmp

wcscmp

 

_tcscpy

strcpy

wcscpy

 

_tcscspn

strcspn

wcscspn

 

_tcslen

strlen

wcslen

 

_tcsclen

strlen

wcslen

 

_tcsncat

strncat

wcsncat

 

_tcsnccat

strncat

wcsncat

 

_tcsnccmp

strncmp

wcsncmp

 

_tcsncmp

strncmp

wcsncmp

 

_tcsncpy

strncpy

wcsncpy

 

_tcsnccpy

strncpy

wcsncpy

 

_tcspbrk

strpbrk

wcspbrk

 

_tcsrchr

strrchr

wcsrchr

 

_tcsspn

strspn

wcsspn

 

_tcsstr

strstr

wcsstr

 

_tcstok

strtok

wcstok

 

_tcsdup

_strdup

_wcsdup

 

_tcsicmp

_stricmp

_wcsicmp

 

_tcsncicmp

_strnicmp

_wcsnicmp

 

_tcsnicmp

_strnicmp

_wcsnicmp

 

_tcsnset

_strnset

_wcsnset

 

_tcsncset

_strnset

_wcsnset

 

_tcsrev

_strrev

_wcsrev

 

_tcsset

_strset

_wcsset

 

_tcslwr

_strlwr

_wcslwr

 

_tcsupr

_strupr

_wcsupr

 

 

6.       ctype function

MACRO

ANSI

UNICODE

설명

_istalpha

isalpha

iswalpha

 

_istupper

isupper

iswupper

 

_istlower

islower

iswlower

 

_istdigit

isdigit

iswdigit

 

_istxdigit

isxdigit

iswxdigit

 

_istspace

isspace

iswspace

 

_istpunct

ispunct

iswpunct

 

_istalnum

isalnum

iswalnum

 

_istprint

isprint

iswprint

 

_istgraph

isgraph

iswgraph

 

_istcntrl

iscntrl

iswcntrl

 

_istascii

isascii

iswascii

 

_totupper

toupper

towupper

 

_totlower

tolower

towlower

 


http://demo.initech.com/?document_srl=4077



Standard CRT function

StrSafe Function

strcat

StringCchCat, StringCchCatEx, StringCbCat, StringCbCatEx

strcmp

(no equivalent function)

strcpy

StringCchCopy, StringCchCopyEx, StringCbCopy, StringCbCopyEx

strlen

StringCchLength, StringCbLength

Standard CRT function

StrSafe Function

sprintf

StringCchPrintf, StringCchPrintfEx, StringCbPrintf, StringCbPrintfEx

vsprintf

StringCchVPrintf, StringCchVPrintfEx, StringCbVPrintf, StringCbVPrintfEx

 

 

Data Types

ANSI

Wide

TCHAR

char

wchar_t

_TCHAR

_finddata_t

_wfinddata_t

_tfinddata_t

__finddata64_t

__wfinddata64_t

_tfinddata64_t

_finddatai64_t

_wfinddatai64_t

_tfinddatai64_t

int

wint_t

_TINT

signed char

wchar_t

_TSCHAR

unsigned char

wchar_t

_TUCHAR

char

wchar_t

_TXCHAR

 

L

_T or _TEXT

LPSTR
(char *)

LPWSTR
(wchar_t *)

LPTSTR
(_TCHAR *)

LPCSTR
(const char *)

LPCWSTR
(const wchar_t *)

LPCTSTR
(const _TCHAR *)

LPOLESTR
(for OLE)

LPWSTR

LPTSTR

 

 

 

TCHAR String Functions

ANSI

Wide

TCHAR

_access

_waccess

_taccess

_atoi64

_wtoi64

_tstoi64

_atoi64

_wtoi64

_ttoi64

_cgets

_cgetws

cgetts

_chdir

_wchdir

_tchdir

_chmod

_wchmod

_tchmod

_cprintf

_cwprintf

_tcprintf

_cputs

_cputws

_cputts

_creat

_wcreat

_tcreat

_cscanf

_cwscanf

_tcscanf

_ctime64

_wctime64

_tctime64

_execl

_wexecl

_texecl

_execle

_wexecle

_texecle

_execlp

_wexeclp

_texeclp

_execlpe

_wexeclpe

_texeclpe

_execv

_wexecv

_texecv

_execve

_wexecve

_texecve

_execvp

_wexecvp

_texecvp

_execvpe

_wexecvpe

_texecvpe

_fdopen

_wfdopen

_tfdopen

_fgetchar

_fgetwchar

_fgettchar

_findfirst

_wfindfirst

_tfindfirst

_findnext64

_wfindnext64

_tfindnext64

_findnext

_wfindnext

_tfindnext

_findnexti64

_wfindnexti64

_tfindnexti64

_fputchar

_fputwchar

_fputtchar

_fsopen

_wfsopen

_tfsopen

_fullpath

_wfullpath

_tfullpath

_getch

_getwch

_gettch

_getche

_getwche

_gettche

_getcwd

_wgetcwd

_tgetcwd

_getdcwd

_wgetdcwd

_tgetdcwd

_ltoa

_ltow

_ltot

_makepath

_wmakepath

_tmakepath

_mkdir

_wmkdir

_tmkdir

_mktemp

_wmktemp

_tmktemp

_open

_wopen

_topen

_popen

_wpopen

_tpopen

_putch

_putwch

_puttch

_putenv

_wputenv

_tputenv

_rmdir

_wrmdir

_trmdir

_scprintf

_scwprintf

_sctprintf

_searchenv

_wsearchenv

_tsearchenv

_snprintf

_snwprintf

_sntprintf

_snscanf

_snwscanf

_sntscanf

_sopen

_wsopen

_tsopen

_spawnl

_wspawnl

_tspawnl

_spawnle

_wspawnle

_tspawnle

_spawnlp

_wspawnlp

_tspawnlp

_spawnlpe

_wspawnlpe

_tspawnlpe

_spawnv

_wspawnv

_tspawnv

_spawnve

_wspawnve

_tspawnve

_spawnvp

_wspawnvp

_tspawnvp

_spawnvpe

_wspawnvpe

_tspawnvpe

_splitpath

_wsplitpath

_tsplitpath

_stat64

_wstat64

_tstat64

_stat

_wstat

_tstat

_stati64

_wstati64

_tstati64

_strdate

_wstrdate

_tstrdate

_strdec

_wcsdec

_tcsdec

_strdup

_wcsdup

_tcsdup

_stricmp

_wcsicmp

_tcsicmp

_stricoll

_wcsicoll

_tcsicoll

_strinc

_wcsinc

_tcsinc

_strlwr

_wcslwr

_tcslwr

_strncnt

_wcsncnt

_tcsnbcnt

_strncnt

_wcsncnt

_tcsnccnt

_strncnt

_wcsncnt

_tcsnccnt

_strncoll

_wcsncoll

_tcsnccoll

_strnextc

_wcsnextc

_tcsnextc

_strnicmp

_wcsnicmp

_tcsncicmp

_strnicmp

_wcsnicmp

_tcsnicmp

_strnicoll

_wcsnicoll

_tcsncicoll

_strnicoll

_wcsnicoll

_tcsnicoll

_strninc

_wcsninc

_tcsninc

_strnset

_wcsnset

_tcsncset

_strnset

_wcsnset

_tcsnset

_strrev

_wcsrev

_tcsrev

_strset

_wcsset

_tcsset

_strspnp

_wcsspnp

_tcsspnp

_strtime

_wstrtime

_tstrtime

_strtoi64

_wcstoi64

_tcstoi64

_strtoui64

_wcstoui64

_tcstoui64

_strupr

_wcsupr

_tcsupr

_tempnam

_wtempnam

_ttempnam

_ui64toa

_ui64tow

_ui64tot

_ultoa

_ultow

_ultot

_ungetch

_ungetwch

_ungettch

_unlink

_wunlink

_tunlink

_utime64

_wutime64

_tutime64

_utime

_wutime

_tutime

_vscprintf

_vscwprintf

_vsctprintf

_vsnprintf

_vsnwprintf

_vsntprintf

asctime

_wasctime

_tasctime

atof

_wtof

_tstof

atoi

_wtoi

_tstoi

atoi

_wtoi

_ttoi

atol

_wtol

_tstol

atol

_wtol

_ttol

character compare

Maps to macro or inline function

_tccmp

character copy

Maps to macro or inline function

_tccpy

character length

Maps to macro or inline function

_tclen

ctime

_wctime

_tctime

fgetc

fgetwc

_fgettc

fgets

fgetws

_fgetts

fopen

_wfopen

_tfopen

fprintf

fwprintf

_ftprintf

fputc

fputwc

_fputtc

fputs

fputws

_fputts

freopen

_wfreopen

_tfreopen

fscanf

fwscanf

_ftscanf

getc

getwc

_gettc

getchar

getwchar

_gettchar

getenv

_wgetenv

_tgetenv

gets

getws

_getts

isalnum

iswalnum

_istalnum

isalpha

iswalpha

_istalpha

isascii

iswascii

_istascii

iscntrl

iswcntrl

_istcntrl

isdigit

iswdigit

_istdigit

isgraph

iswgraph

_istgraph

islead (Always FALSE)

(Always FALSE)

_istlead

isleadbyte (Always FALSE)

isleadbyte (Always FALSE)

_istleadbyte

islegal (Always TRUE)

(Always TRUE)

_istlegal

islower

iswlower

_istlower

isprint

iswprint

_istprint

ispunct

iswpunct

_istpunct

isspace

iswspace

_istspace

isupper

iswupper

_istupper

isxdigit

iswxdigit

_istxdigit

main

wmain

_tmain

perror

_wperror

_tperror

printf

wprintf

_tprintf

putc

putwc

_puttc

putchar

putwchar

_puttchar

puts

_putws

_putts

remove

_wremove

_tremove

rename

_wrename

_trename

scanf

wscanf

_tscanf

setlocale

_wsetlocale

_tsetlocale

sprintf

swprintf

_stprintf

sscanf

swscanf

_stscanf

strcat

wcscat

_tcscat

strchr

wcschr

_tcschr

strcmp

wcscmp

_tcscmp

strcoll

wcscoll

_tcscoll

strcpy

wcscpy

_tcscpy

strcspn

wcscspn

_tcscspn

strerror

_wcserror

_tcserror

strftime

wcsftime

_tcsftime

strlen

wcslen

_tcsclen

strlen

wcslen

_tcslen

strncat

wcsncat

_tcsncat

strncat

wcsncat

_tcsnccat

strncmp

wcsncmp

_tcsnccmp

strncmp

wcsncmp

_tcsncmp

strncpy

wcsncpy

_tcsnccpy

strncpy

wcsncpy

_tcsncpy

strpbrk

wcspbrk

_tcspbrk

strrchr

wcsrchr

_tcsrchr

strspn

wcsspn

_tcsspn

strstr

wcsstr

_tcsstr

strtod

wcstod

_tcstod

strtok

wcstok

_tcstok

strtol

wcstol

_tcstol

strtoul

wcstoul

_tcstoul

strxfrm

wcsxfrm

_tcsxfrm

system

_wsystem

_tsystem

tmpnam

_wtmpnam

_ttmpnam

tolower

towlower

_totlower

toupper

towupper

_totupper

ungetc

ungetwc

_ungettc

vfprintf

vfwprintf

_vftprintf

vprintf

vwprintf

_vtprintf

vsprintf

vswprintf

_vstprintf

WinMain

wWinMain

_tWinMain

 

'library > CRT' 카테고리의 다른 글

errno  (0) 2011.01.08
유니코드를 위한 함수  (0) 2011.01.08
시간과 날짜와 관련된 라이브러리 함수들  (0) 2010.12.15
문자열 함수들의 버퍼 체크 방법 차이  (0) 2010.11.24
디버그 시 malloc 메모리 누수 탐지  (0) 2010.07.16
블로그 이미지

란마12

,

같은 dll을 하나의 프로세스에서 두번 로딩하면 어떨까 하고 테스트해봤다.

- 두번째 로드된 dll핸들은 처음 로드된 dll핸들을 참조한다.(참조카운터만 1증가)
- 두번째 dll은 로드될 때 DllMain에서 ATTACH/DETACH관련 이벤트가 발생하지 않는다.
- 두 핸들은 같지만 로드된 모든 dll핸들을 FreeLibrary하기 전까지 free되지 않는다.
  (참조카운터가 0이 되면 free)
블로그 이미지

란마12

,

map insert 반환값

library/STL 2010. 9. 12. 19:51

#include <stdio.h>
#include <map>
#include <string>
 
 
int main(int argc, char *argv[])
{
 
    map<int, string>                        mmNames;
    map<int, string>::iterator              iter;
    pair<map<int, string>::iterator, bool>  ret;
 
 
    iter = mmNames.find(1);
    if(iter!=mmNames.end())
    {
        printf("\nmmNames contains 1");
    }
    else
    {
        printf("\nmmNames does not contains 1");
    }
 
 
    ret = mmNames.insert(map<int, string>::value_type(1, string("Lee")));
    if(ret.second == true)
    {
        printf("\ninsert success");
    }
    else
    {
        printf("\ninsert fail");
    }
 
    iter = mmNames.find(1);
    if(iter!=mmNames.end())
    {
        printf("\nmmNames contains 1");
    }
    else
    {
        printf("\nmmNames does not contains 1");
    }
 
    return(0);
}

'library > STL' 카테고리의 다른 글

STL관련 step into무시하기  (0) 2011.04.21
erase  (0) 2010.12.14
copy  (0) 2010.02.09
블로그 이미지

란마12

,

#ifdef _DEBUG
 #define _INC_MALLOC  // exclude standard memory alloc procedures
 #define _CRTDBG_MAP_ALLOC // include Microsoft memory leak detection procedures
 #include "crtdbg.h"
#endif

프로그램 진입점에서 최초에  

#ifdef _DEBUG
 _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF);
#endif


발견된 메모리 누수지점에 브레이크포인트 걸기

Detected memory leaks!
Dumping objects ->
{60} normal block at 0x00324818, 4 bytes long.
Data: <,   > 2C 00 00 00
Object dump complete.

위와같이 누수가 발견되었다면 CrtSetBreakAlloc함수를 다음과 같이 호출

#ifdef _DEBUG
 _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF);
 _CrtSetBreakAlloc(60); //또는 _crtBreakAlloc = 60;
#endif

visual studio를 사용한다면
1. F11로 디버깅을 시작
2. ALT+F9를 눌러서 중단점 창
3. 새로만들기>새데이터중단점에서 주소(0x00324818) 입력
4. 적당한 바이트(위의 경우는 4)를 입력

_CRTDBG_MAP_ALLOC를 선언하면 이렇게 파일명과 라인번호까지 알려준다던데 안되더라..

Detected memory leaks!
Dumping objects ->
D:\VisualC++\CodeGuru\MemoryLeak\MemoryLeak.cpp(67) : {60}
normal block at 0x00324818, 4 bytes long.
Data: <,   > 2C 00 00 00
Object dump complete.

원인이나 해결책 아시는 분은 댓글 부탁드립니다.

문제점: stl사용 시 실제로 누수되지 않았는데도 불구하고 무조건 누수발생 보고됨.

http://msdn.microsoft.com/en-us/library/x98tx3cf
http://msdn.microsoft.com/ko-kr/library/w2fhc9a3.aspx

'library > CRT' 카테고리의 다른 글

errno  (0) 2011.01.08
유니코드를 위한 함수  (0) 2011.01.08
시간과 날짜와 관련된 라이브러리 함수들  (0) 2010.12.15
문자열 함수들의 버퍼 체크 방법 차이  (0) 2010.11.24
코딩시 Unicode 관련 고려사항  (0) 2010.11.23
블로그 이미지

란마12

,

IOCP

library/WIN32API 2010. 7. 8. 02:25

* 이기탁님이 작성한 문서를 요약함

1. Overlapped IO

 1.1. 넌블러킹, 비동기적으로 IO처리
 1.2. 쓰레드가 Device Driver에게 IO요청 후 신경쓰지 않음
 1.3. Device Driver가 IO감시하면서 완료되면 알려 줌
  1) 이벤트 객체 멤버 이용
  2) 콜백함수
  3) 직접 조회
  4) IOCP
 1.4. IO blocking없음, FIFO로 IO처리하지 않음
 1.5. 소켓생성
      SOCKET sock=WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
 1.6. 송신
  int WSASend(SOCKET s,
        LPWSABUF lpBuffers,
              DWORD dwBufferCount,
              LPDWORD lpNumberOfBytesSent,
              DWORD dwFlags,
              LPWSAOVERLAPPED lpOverlapped,
              LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
  1) IO가 끝나기 전에 lpOverlapped가 메모리에서 사라지면 안됨.
  2) lpCompletionRoutine: 1.3.2)의 콜백함수
  3) 리턴값이 SOCKET_ERROR이면서 WSAGetLastError()==ERROR_IO_PENDING인 경우는
     IO진행중이라는 의미

2. IOCP

 2.1. 커널 오브젝트중 하나
 2.2. 재사용 가능한  쓰레드풀 유지
 2.3. CPU에 dispatch되는 쓰레드 조절
 2.4. 생성
  HANDLE CreateIoCompletionPort (
  HANDLE FileHandle,              // handle to file
  HANDLE ExistingCompletionPort,  // handle to I/O completion port
  ULONG_PTR CompletionKey,        // completion key
  DWORD NumberOfConcurrentThreads // number of threads to execute concurrently);
    두가지 일을 한다.
    1) IOCP커널 객체 생성
        hiocp = CreateCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 쓰레드숫자);
    2) Device와 IOCP객체 연결
        CreateCompletionPort(sock, hIocp, 컴플리션 키, 0 );
 2.5. 쓰레드풀 생성
 2.6. 동작에 필요한 자료구조
  2.6.1. Device List
   1) hDevice - dwCompletionKey
   2) hDevice와 관련된 IO가 완료되었을 때 dwCompletionKey를 던짐
   3) 생성: CreateIoCompletionPort가 호출될 때
   4) 제거: hDevice가 close될 때

  2.6.2. IO Completion Queue(IOCQ)
   1) dwBytesTransferred - dwCompletionKey - pOverlapped - dwError
   2) IO의 결과를 저장
   3) worker 쓰레드에서 하나하나 빼서 처리
   4) PostQueuedCompletionStatus함수를 이용해서 직접 넣을 수 있음.
      쓰레드간 큐나 쓰레드 종료 시 활용
   5) 생성: - IO 요청이 완료되었을 때 
            - PostQueuedCompletionStatus를 호출하였을 때
      제거: - Waiting Thread Queue로부터 Entry를 하나 제거할 때
              즉 IOCP Worker Thread를 하나 깨우고 이 쓰레드에서 레코드 하나를
              꺼낼 때

  2.6.3. Waiting Thread Queue(WTQ)
   1) dwThreadid
   2) IOCP Worker 쓰레드 풀
   3) GetQueuedCompletionStatus(GQCS)함수가 호출되면 Worker 쓰레드가 이 큐에
      쌓고 대기.
   4) 2.6.2의 IOCQ에 레코드가 들어오면 IOCP가 이 큐에서 쓰레드 하나를 깨우고
      GQCS함수를 리턴 시킴
      (CreateIoCompletionPort호출시 마지막 인자인 Concurrent Thread 개수만큼만)
   5) 실제로는 LIFO구조인데 Queue라는 이름이 붙었음
      LIFO구조를 이용해서 사용되는 쓰레드수를 최소화 함(CPU scheduling이 최소화됨)
   6) 제거: IOCQ가 비어있지 않고 Release Thread List(RTL)에 있는 쓰레드 수가
            Concurrent Thread수를 넘지 않을 때, IOCQ에서 레코드 entry가 하나
            제거되고, WTQ에 있던 dwThreadid가 RTL로 옮겨감.

  2.6.4. Released Thread List(RTL)
   1) 쓰레드 풀에서 꺼내온 쓰레드들
   2) 생성: - IOCP가 WTQ에서 쓰레드를 깨울 때
            - Paused Thread가 깨어날 때
   3) 제거: - 쓰레드가 다시 GetQueuedCompletionStatus 함수를 부를 때
            - 쓰레드가 스스로 Suspend(Sleep, WaitForSinglObject등으로 블록) 될 때

  2.6.5 Paused Thread List(PTL)
   1) 어떤 이유로 Suspend된 쓰레드들의 모임
   2) 생성: Released Thread 즉 돌고 있는 쓰레드가 스스로 멈출 때
      제거: Suspend된 쓰레드가 깨어날 때(dwThreadid는 RTL로 옮겨감)

  2.6.6. Concurrent Thread 수와 PTL, RTL
   1) Concurrent Thread 수: RTL에 들어갈 쓰레드 수
   2) Suspend된 쓰레드가 있다면 PTL에에 옮겨 놓고 WTQ에 대기중인 쓰레드를
      RTL로 가져와 처리.
   3) Suspend된 쓰레드가 깨어나면 RTL에 쓰레드가 Concurrent Thread 수보다
      초과될 수도 있다. 적어질 때까지 IOCP는 WTQ에서 쓰레드를 가져오지
      않는다. (효율적인 Thread Context Switching)

블로그 이미지

란마12

,

리소스 탐색기에서 다이얼로그 속성창에서 언어를 다른 언어로 바꿔본다.
블로그 이미지

란마12

,