strcpy -> wcscpy -> _tcscpy (문자열을 복사)

strncpy -> wcsncpy -> _tcsncpy ( 사이즈 만큼 복사)

strlen -> wcslen -> _tcslen (문자열 길이 확인)

strcat -> wcscat -> _tcscat (두 문자열 이어 붙이기)

strncat -> wcsncat -> _tcsncat (사이즈 만큼 이어 붙이기)

strcmp -> wcscmp -> _tcscmp (문자열 비교) (반환 값(-1, 0, 1))

strncmp -> wcsncmp -> _tcsncmp ( 사이즈 만큼 문자열 비교)

stricmp -> wcsicmp -> _tcsicmp (대소문자를 구별하지 않고 문자열을 비교)

strnicmp -> wcsnicmp -> _tcsnicmp (사이즈 만큼 대소문자를 구별하지 않고 문자열을 비교)

strchr -> wcschr -> _tcschr (문자 찾기)

strrchr -> wcsrchr -> _tcsrchr (문자 찾기 (문자열 끝에서 부터 검색))

strstr -> wcsstr -> _tcsstr (문자열 찾기)

strpbrk -> wcspbrk -> _tcspbrk (문자 찾기 (두번째 인수를 찾고자 하는 문자들의 집합(문자열)으로 구성)

strtok -> wcstok -> _tcstok (문자열 자르기 (두번째 인수를 집합(문자열)으로 구성 가능)) => 해당 문자가 NULL로 치환 됨

strset -> wcsset -> _tcsset (문자 치환, 첫째 인수의 모든 문자를 두번째 인수 문자로 변경함) => "abc" -> "bbb"

strnset -> wcsnset -> _tscnset (사이즈 만큼 문자 치환)

strupr -> wcsupr -> _tcsupr (대문자로 치환)

strlwr -> wcslwr -> _tcslwr (소문자로 치환)

strrev -> wcsrev -> _tcsrev (문자열 역정렬) => "가나다라마" -> "마라다나가"










멀티 바이트 환경에서 사용하던 함수를 유니코드 환경에서 사용하려고 하면 사용이 안된다.

 

처음에 VS2008을 사용하면서 예전 책을 보면서 공부할 때 가장 많이 머리가 아프던 문제였다.

 

유니코드 환경에서 사용 할 수 있게 정의가 되어진 함수들은 같은 함수명을 써도 자동으로 처리가 되기 때문에

 

특별히 신경을 쓸 일은 없었다. 그러나 문자열처리 함수 같은경우는 아예 함수 이름이 바뀌었기 때문에 모른다면 사용 할 수가 없다.

 

그래서 문자열 처리 함수를 정리 할 겸 유니코드 환경에서 사용 할 수 있는 문자열 처리 함수를 정리해 보았다.

 

 

일단 기본적으로 멀티 바이트 환경에서 문자열 처리 함수의 접두어는 str 이었다 strlen, strcat, strstr 등등

 

그러나 유니코드 환경에서 접두어는 wcs이다. 이것만 기억해 둔다면 이미 문자열처리 함수의 절반은 안것이다.

 

 

1-1.strcpy -> wcscpy

 

:문자열을 복사하는 함수이다

 

원형 :

wchar_t *wcscpy(

   wchar_t *strDestination,           복사 당하는 대상

   const wchar_t *strSource           복사 하려는 소스

); 

ex)

TCHAR str[256];

wcscpy(str,L"안녕하세요");

TextOut(hdc,0,0,str,wcslen(str));

 

:결과 = 0,0에 "안녕하세요" 문자열 출력

 

 

1-2. strncpy -> wcsncpy

 

:입력한 사이즈 만큼 문자열을 복사하는 함수이다

 

원형 :

wchar_t *wcsncpy(

   wchar_t *strDest,                복사 당하는 대상

   const wchar_t *strSource,        박사 하려는 소스

   size_t count                     복사하려는 개수

); 

ex)

TCHAR str[256];

memset(str,0,sizeof(str));                  NULL값으로 배열을 초기화

wcsncpy(str,L"안녕하세요", 2);

TextOut(hdc,0,0,str,wcslen(str));

 

:결과 = 0,0에 "안녕" 문자열 출력

 

 

2. strlen -> wcslen

 

: 입력 받은 문자열의 길이를 반환해준다.

sizeof와 다른점은 sizeof는 배열의 전체크기를 반환해주고

wcslen은 중간에 NULL을 만나면 NULL까지의 길이만 반환한다.

 

원형 :

size_t wcslen(

   const wchar_t *str 

);

 

 

3-1.strcat -> wcscat

 

: 두 문자열을 이어준다

 

원형 :

wchar_t *wcscat(

   wchar_t *strDestination,

   const wchar_t *strSource 

); 

ex)

wcscpy(str,L"안녕하세요");

wcscat(str,L"오냐");

TextOut(hdc,0,0,str,wcslen(str));

 

:결과 = 0,0에 "안녕하세요오냐" 출력

 

 

3-2.strncat -> wcsncat

 

: 입력한 사이즈만큼 대상에 소스를 이어준다

 

원형 :

wchar_t *wcsncat(

   wchar_t *strDest,

   const wchar_t *strSource,

   size_t count 

); 

ex)

wcscpy(str,L"안녕하세요");

wcsncat(str,L"오냐",1);

TextOut(hdc,0,0,str,wcslen(str));

 

:결과 = 0,0에 "안녕하세요오" 출력

 

 

4-1.strcmp -> wcscmp

 

: 문자열을 비교한다.

 

원형 :

int wcscmp(

   const wchar_t *string1,

   const wchar_t *string2 

); 

string1과 string2가 같으면 0

string1>string2 이면 양수

string1<string2 이면 음수

 

ex)

wcscpy(str,L"안녕하세요");

wcscpy(str2,L"하이");

if(wcscmp(str,str2) == 0){

  TextOut(hdc,0,0,L"같다",2);

}else{

  TextOut(hdc,0,0,L"다르다",3);

}

:출력 = 0,0에 "다르다" 출력

 

 

4-2.strncmp -> wcsncmp

 

:지정한 사이즈만큼 대소문자를 구분하지 않고 문자열을 비교한다.

 

원형 :

int wcsncmp(

   const wchar_t *string1,

   const wchar_t *string2,

   size_t count 

);

 

string1과 string2가 같으면 0

string1>string2 이면 양수

string1<string2 이면 음수

 

ex)

wcscpy(str,L"안녕하세요");

wcscpy(str2,L"안하이");

if(wcsncmp(str,str2,1) == 0){

  TextOut(hdc,0,0,L"같다",2);

}else{

  TextOut(hdc,0,0,L"다르다",3);

}

:출력 = 0,0에 "같다" 출력

 

 

5-1. stricmp -> wcsicmp

 

:대소문자를 구별하지 않고 문자열을 비교

 

원형 :

int _wcsicmp(

   const wchar_t *string1,

   const wchar_t *string2 

); 

string1과 string2가 같으면 0

string1>string2 이면 양수

string1<string2 이면 음수

 

ex)

wcscpy(str,L"ABCDE");

wcscpy(str2,L"abcde");

if(wcsicmp(str,str2) == 0){

  TextOut(hdc,0,0,L"같다",2);

}else{

  TextOut(hdc,0,0,L"다르다",3);

}

:출력 = 0,0에 "같다" 출력

 

 

5-2. strnicmp -> wcsnicmp

 

:대소문자를 구별하지 않고 문자열을 비교

 

원형 :

int _wcsnicmp(

   const wchar_t *string1,

   const wchar_t *string2,

   size_t count 

);

 

string1과 string2가 같으면 0

string1>string2 이면 양수

string1<string2 이면 음수

 

ex)

wcscpy(str,L"ABCDE");

wcscpy(str2,L"abcdefgh");

if(wcsnicmp(str,str2,5) == 0){

  TextOut(hdc,0,0,L"같다",2);

}else{

  TextOut(hdc,0,0,L"다르다",3);

}

:출력 = 0,0에 "같다" 출력

 

 

6-1.strchr -> wcschr

 

:문자열에서 한 문자를 찾아 그 문자가 있는 주소를 반환하여 준다. (문자열의 처음부터 검사한다)

:만약에 찾지 못했을 경우에는 NULL값을 반환해준다

 

원형:

wchar_t *wcschr(

   wchar_t *str,

   wchar_t c 

); 

ex)

wcscpy(str2,L"에요안녕하세요하이");

   

TCHAR dd = L'요';

TCHAR * ddd;

   

ddd = wcschr(str2,dd);

 

TextOut(hdc,0,0,ddd,wcslen(ddd));

 

:출력 = 0,0에 "요안녕하세요하이" 출력

 

 

6-2.strrchr -> wcsrchr

 

:문자열에서 한 문자를 찾아 그 문자가 있는 주소를 반환하여 준다. (문자열의 맨 마지막부터 검색한다)

:만약에 찾지 못했을 경우에는 NULL값을 반환해준다

 

원형:

wchar_t *wcsrchr(

   wchar_t *str,

   wchar_t c 

); // C++ only

ex)

wcscpy(str2,L"에요안녕하세요하이");

   

TCHAR dd = L'요';

TCHAR * ddd;

   

ddd = wcsrchr(str2,dd);

 

TextOut(hdc,0,0,ddd,wcslen(ddd));

 

:출력 = 0,0에 "요하이" 출력

 

 

7.strstr -> wcsstr

 

:문자열에서 원하는 문자열을 찾아준다.

 

원형 :

wchar_t *wcsstr(

   wchar_t *str,

   const wchar_t *strSearch 

);

 

ex)

wcscpy(str2,L"에요안녕하세요하이");

   

TCHAR * ddd;

ddd = wcsstr(str2,L"안녕");

TextOut(hdc,0,0,ddd,wcslen(ddd)); 

:출력 = "안녕하세요하이" 출력

 

 

8.strpbrk -> wcspbrk

 

: 첫번째 인수로 받은 문자열에서 두번째 인수로 받은 문자열중 한문자라도 빨리 나오는 문자의 주소를 반환해준다.

 

원형 :

wchar_t *wcspbrk(

   wchar_t *str,

   const wchar_t *strCharSet 

); // C++ only 

ex)

wcscpy(str2,L"에요안녕하세요하이");

   

TCHAR * ddd; 

ddd = wcspbrk(str2,L"녕세");            '녕' 이나 '세' 이 두글자 중에 빨리 나오는 문자의 주소를 출력해준다

TextOut(hdc,0,0,ddd,wcslen(ddd)); 

:출력 = "녕하세요하이" 출력

 

 

9.strtok -> wcstok

 

: 첫번째 매개변수로 받은 문자열을 두번째 매개변수로 받은 토큰 문자로 나누어준다.

 

원형 :

wchar_t *wcstok(

   wchar_t *strToken,

   const wchar_t *strDelimit 

); 

ex)

wcscpy(str2,L"나의'가치'는 내가 결정하고, 당신의 '가치'는 당신이 결정한다");

TCHAR * ddd;

ddd = wcstok(str2,L" ,'");      //토큰은 여러개를 지정할 수 있다. 여기서는 공백(' ')과 콤마(',') 작은따옴표(')로 지정했다.

while(ddd != NULL){

 TextOut(hdc,0,i,ddd,wcslen(ddd));

 ddd = wcstok(NULL,L" ,'");     //검색을 계속 해야하기 때문에 NULL값을 매개변수로 넣어준다

 i+=20;

}

 

wcstok의 방식은 문자열에서 처음 토큰을 발견하면 그 주소를 저장하고 두번째 토큰을 찾아내면 그 토큰을 NULL로 만들고

 

처음 토큰 주소를 반환해준다. 또한 wcstok함수는 함수 중간 결과를 자체적으로 정적변수를 사용하고 있다. 그렇기 때문에

 

계속 검색을 하고 싶으면 첫번째 매개변수로 NULL을 넣어주면 문자열이 끝날때 까지 검색을 한다.

 

검색이 끝나면 NULL을 반환해 준다.

 

wcstok함수도 매개변수로 받은 데이터를 손상시키기 때문에 데이터가 손상되지 않게 하려면 다른곳에 복사한 후에 써야한다.

 

또한 함수 자체적으로 정적 변수를 사용하고있기 때문에 멀티 스레드 환경에서 동시에 호출될 염려가 있으므로

 

동시에 호출되지 않도록 보호해 주어야 한다.

 

:출력 =

나의

가치

내가

결정하고

당신의

가치

당신이

결정한다

 

 

10-1.strset -> wcsset

 

: 첫번째 매개변수로 받은 문자열을 NULL문자 전까지 모두 두번째 문자로 바꾼다

 

원형 :

wchar_t *_wcsset(

   wchar_t *str,

   wchar_t c 

); 

ex)

wcscpy(str,L"ABCDE");

wcsset(str,L'd');

TextOut(hdc,0,0,str,wcslen(str));

 

:출력 =  0,0에 "ddddd" 문자열이 출력된다

 

 

10-2.strnset -> wcsnset

 

: 첫번째 매개변수로 받은 문자열을 입력받은 사이즈 만큼 두번째 문자로 바꾼다

 

원형 :

wchar_t *_wcsnset(

   wchar_t *str,

   wchar_t c,

   size_t count 

);

ex)

wcscpy(str,L"ABCDE");

wcsnset(str,L'd',2);

TextOut(hdc,0,0,str,wcslen(str));

 

:출력 =  0,0에 "ddCBD" 문자열이 출력된다

 

 

11.

strupr -> wcspur

strlwr -> wcslwr

 

: 지정한 문자열을 대문자로 치환(wcspur) 하거나 소문자로 치환(wcslwr)한다.

: 단 대문자나 소문자만 치환이 되고 한글이나 숫자는 치환되지 않는다.

: 이 함수도 매개변수로 받은 문자열을 변형시키기 때문에 변형되어서는 안되는 문자열이면 복사를 해놓고 사용해야 한다.

 

wcscpy(str,L"1abcdeABCDE아");

wcsupr(str);

TextOut(hdc,0,0,str,wcslen(str)); 

wcslwr(str);

TextOut(hdc,0,20,str,wcslen(str)); 

:출력 =

0,0에 "1ABCDEABCDE아" 출력

0,20에 "1abcdeabcde아" 출력

 

12.strrev -> wcsrev

 

: 지정한 문자열을 역순으로 배치한다. 한글도 역순으로 배치된다.

: 이 함수도 매개변수로 받은 문자열을 변형시키기 때문에 변형되어서는 안되는 문자열이면 복사를 해놓고 사용해야 한다.

 

원형 :

wchar_t *_wcsrev(

   wchar_t *str 

);

ex)

wcscpy(str,L"1abcdeABCDE아");

wcscpy(str2,L"나의 '가치'는 내가 결정하고, 당신의 '가치'는 당신이 결정한다");

   

wcsrev(str);

wcsrev(str2);

TextOut(hdc,0,0,str2,wcslen(str2));

TextOut(hdc,0,20,str,wcslen(str));

:출력 =

0,0에 "다한정결 이신당 는'치가' 의신당 ,고하정결 가내 는'치가' 의나" 출력

0,20에 "아EDCBAedcba1" 출력





문자열 자르기



TCHAR   szSeps[] = _T(" ,'\"\t();");

TCHAR*  pszToken = NULL;

TCHAR* pszNextToken = NULL; // _tcstok_s → 3parameter _Context


// 인자 부분만을 취한다.

strTemp  = (LPCTSTR)strValue;

strValue = (LPCTSTR)strTemp.Mid(nFind[0] + 1);

// pszToken = _tcstok((TCHAR *)(LPCTSTR)strValue, szSeps);


// warning C4996: 'wcstok'

pszToken = _tcstok_s((TCHAR *)(LPCTSTR)strValue, szSeps, &pszNextToken); 


while(pszToken != NULL)

{

    astrArgument.Add((LPCTSTR)pszToken);

    //  pszToken = _tcstok(NULL, szSeps);

    pszToken = _tcstok_s(NULL, szSeps, &pszNextToken); // warning C4996: 'wcstok'

}

'Windows 개발' 카테고리의 다른 글

char, TCHAR 형변환  (0) 2014.11.25
파일숨기기  (0) 2014.11.25
메모장에 유니코드로 저장하기  (0) 2014.11.24
기본상식  (0) 2014.11.21
unicode 와 ansi, std::string 과 CString 상호 변경하기  (0) 2014.11.19
Posted by wakira
,


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>
#include <locale.h>
 
int wmain()
{
    setlocale(LC_ALL, ".OCP");
    FILE* fp = nullptr;
    if (_wfopen_s(&fp, L"test.txt", L"wt, ccs=UTF-16LE") != 0)                                                             
        return -1;
    fwprintf_s(fp, L"가나다라ABCD");
    fclose(fp);
    fp = nullptr;
 
    if (_wfopen_s(&fp, L"test.txt", L"rt, ccs=UTF-16LE") != 0)
        return -1;
    wchar_t ttt[1024] = {0};
    fwscanf_s(fp, L" %s ", ttt, 1024);
    fclose(fp);
    fp = nullptr;
 
    wprintf_s(L"%s\n", ttt);
 
    if (_wfopen_s(&fp, L"test.txt", L"rb") != 0)
        return -1;
 
    unsigned char buf[1024] = {0};
    size_t ret = fread_s(buf, 1024, 1, 1024, fp);
    wprintf_s(L"Size: %d bytes\n", (int)ret);
    for (size_t i = 0; i < ret; ++i)
    {
        wprintf_s(L"%02x ", buf[i]);
        if (i % 16 == 15)
            wprintf_s(L"\n");
    }
 
    fclose(fp);
    fp = nullptr;
 
    return 0;
}

필요시 , ccs=UTF-16LE뺴고 저장하면 안시로 저장잘됨

'Windows 개발' 카테고리의 다른 글

파일숨기기  (0) 2014.11.25
유니코드 문자열 함수들  (0) 2014.11.24
기본상식  (0) 2014.11.21
unicode 와 ansi, std::string 과 CString 상호 변경하기  (0) 2014.11.19
데이터 형 변환 string to TCHAR*  (0) 2014.11.11
Posted by wakira
,

기본상식

Windows 개발 2014. 11. 21. 03:20

[비트bit와 헤르쯔Hz] 컴퓨터에서 쓰는 단위들을 알기 위해서 가장 먼저 짚고 넘어가야 하는 게 이 두가지입니다. 보통 크기를 나타낼 땐 bit를, 속도를 표시할땐 Hz를 씁니다. bit는 이진수를 뜻하는 Binary Digit를 줄인 말입니다. 컴퓨터는 전기 신호를 넣느냐(on) 그렇지 않느냐(off)를 구분해서 모든 데이터를 1과 0 이진수로 표현합니다. 컴퓨터가 표현하는 2진수의 가장 작은 단위가 바로 1bit입니다. 알파벳 한 단어를 표현하려면 8bit가 필요합니다. 그래서 8bit를 묶어서 또 다른 기본 단위로 쓰는데 이것이 Byte입니다. 즉 8bit=1Byte가 되는 거죠. bit와 Byte가 모두 b로 시작하는 탓에 헷갈리는 경우가 많은데, 단위에서 bit는 소문자 'b'를 Byte는 대문자 'B'를 씁니다. 바이트는 킬로바이트(kilo byte), 메가바이트(mega byte), 기가바이트(giga byte) 순으로 커집니다. 10진수를 써서 단위를 표시할 때는 천배씩 커질 때마다 킬로, 메가 등이 앞에 붙습니다. 하지만 2진수를 기본으로 쓰는 컴퓨터의 데이터 크기는 각각 2의 10승씩 즉, 1,024배씩 커집니다. 컴퓨터의 속도를 나타낼 땐 헤르쯔(Hz)를 주로 씁니다. Hz는 전파(주파수)의 한 사이클을 뜻하는 것으로 컴퓨터 부품들은 Hz에 맞춰 일을 합니다. 컴퓨터가 일하는 것을 사공이 노를 젓는 것에 비유한다면 Hz는 노를 젓게 하는 신호라고 할 수 있습니다. 노 젓는 신호가 빠를수록 배의 속도가 빨라지듯이 Hz가 높을수록 컴퓨터 속도는 빨라집니다. 1초 동안에 한번의 주파수를 내면 1Hz입니다. 1초에 천 번의 주파수를 내면 킬로헤르쯔(KHz), 100만 번의 주파수를 내보내면 메가헤르쯔(MHz)가 됩니다. [CPU] CPU 속도 CPU의 속도는 클럭으로 표현하는데 클럭은 1초에 정보를 처리하는 횟수를 말하고, 앞서 설명한 Hz로 나타냅니다. 1GHz의 속도를 가진 CPU는 1초에 10억 번의 정보를 처리한다는 뜻입니다. 따라서 클럭이 높을수록 같은 시간에 많은 양의 정보를 처리해서 속도가 빠릅니다. 하지만 클럭이 높다고 무조건 CPU의 속도가 빠른 것은 아닙니다. CPU의 설계나 다른 부품들과 데이터를 주고받는 속도에 따라 효율이 크게 달라지기 때문입니다. 클럭이 낮은 코어 2듀오가 이보다 클럭이 높은 펜티엄4보다 높은 성능을 내는 것이 그 예입니다. 그래서 요새는 단순 클럭이 아니라 CPU가 같은 전력(Watt)을 가지고 얼마나 효율적으로 일하는지를 CPU 성능의 기준으로 삼기도 합니다. 물론 같은 종류의 CPU라면 클럭이 높은 게 빠르겠죠? Clock=CPU가 1초에 일을 처리하는 횟수=Hz FSB FSB(Front Side Bus)는 CPU가 메인보드의 노스브릿지와 데이터를 주고받은 통로를 가리킵니다. 노스브릿지는 메인보드에서 메모리를 관리하고, 그래픽카드와 데이터를 주고받는 등의 일을 하는 칩셋입니다. FSB의 클럭은 MHz로 표시하는데 클럭이 높을수록 더 많은 데이터를 CPU와 주고받을 수 있기 때문에 전체 시스템의 성능을 높입니다. 코어 2 듀오 E6300은 FSB 클럭이 233MHz입니다. 인텔 CPU는 quad pumping이라는 기술을 써서 같은 클럭에 4배의 데이터를 보내기 때문에 1,066MHz라고 말합니다. [하드디스크] 하드디스크 용량 하드디스크의 저장 용량은 Byte로 표시합니다. 데이터 크기 단위를 설명하면서 1KB는 1,024Byte라고 이야기 했습니다. 하드디스크 크기를 표시할 때에도 이렇게 해야 하는데 제조 회사들은 편의상 용량을 10진수로 계산해서 제품에 써 넣습니다. 1,000Byte=1KB, 1,000KB=1MB라는 식으로 말이죠. 실제로 컴퓨터에 200GB 하드디스크를 달면 이것을 186.3GB짜리로 알아챕니다. 하드디스크 제조사의 계산법을 알지 못하는 이는 용량을 속아서 샀다고 생각하기 십상이죠. 사실, 하드 디스크제조사의 용량 뻥튀기 계산법에 속은 거 맞습니다.하드디스크 속도 하드디스크 속도는 주로 RPM으로 표시합니다. RPM은 revolution per minute의 준말로 하드디스크의 플래터가 1분에 회전하는 횟수를 뜻합니다. 플래터는 하드디스크 안에 있는 데이터를 기록하는 둥근 원판입니다. 요즘 주로 쓰는 7,200rpm 하드디스크는 플래터가 1분에 7,400번 돕니다. 도는 속도가 빠를수록 원하는 자료를 빨리 찾을 수 있습니다. 하지만 플래터가 빨리 돌면 그만큼 하드디스크에서 나는 소리도 큽니다. 그래서 노트북에는 성능이 떨어지지만 조용한 5400rpm 이하의 하드디스크를 넣습니다. RPM = 하드디스크 플래터가 1분에 도는 횟수 하드디스크의 성능을 가늠하는 것으로 Seek Time도 있습니다. Seek Time은 하드디스크를 읽고 쓰는 역할을 하는 헤드가 원하는 데이터가 있는 위치를 찾는 데 걸리는 시간을 말합니다. 1,000분의 1초 인밀리초(ms, millisecond)로 표시합니다. ODD도 같은 단위를 씁니다. 밀리초(ms) = 천 분의 1초 (10의 -3승) [램] 램은 메모리 클럭과 대역폭으로 성능을 구별합니다. DDR2 800 또는 DDR2 PC-6400 등으로 쓰는 것을 보셨을 겁니다. DDR2 400, 800 등은 메모리 클럭에 따라 구분한 것입니다. 메모리 클럭은 메인보드가 램을 움직이기 위해 내는 주파수를 말하는 것으로 DDR2 800은 메인보드에서 800MHz의 클럭의 주파수를 받아 이 신호에 맞춰 데이터를 데이터를 주고받습니다. 클럭마다 64bit의 데이터를 주고 받을 수 있기 때문에 1초에 최대, 400MB(800MH x 8Byte(64bit))의 데이터를 처리할 수 있습니다. 이를 대역폭이라고 하고, 이것을 기준 삼아서 DDR2 800을 DDR2 PC-6400으로 표시하기도 하는 것입니다. DDR2 램을 대역폭에 따라 램을 구분하면 다음과 같습니다. 담고 있는 데이터에 접근하는 속도(Access Time)도 램의 속도를 가늠하는 잣대인데 나노초(ns, nano-second)를 단위로 씁니다. 1ns는 10억 분의 1초입니다. 수치가 낮을수록 데이터를 재빨리 처리합니다. 하드디스크의 데이터 접근속도를 나타내는 단위가 ms(천분의 1초)니 두개 사이의 속도의 차이를 느낄 수 있습니다.  나노초(㎱) = 10억 분의 1초(10에 -9승) 마이크로초(㎲) = 100만 분의 1초(10에 -6승) 밀리초(㎳) = 1천 분의 1초(10의 -3승) [인터넷 속도] bps bps는 bit per second의 준말로 1초에 몇 bit의 데이터를 주고 받는지를 나타내는 단위입니다. 우리는 앞서 bit와 Byte의 차이를 알아봤습니다. 데이터의 크기는 보통 Byte를 쓰는데 bps는 bit를 기초로 했기 때문에 헷갈리는 일이 자주 있습니다. 윈도나 P2P 사이트들은 인터넷 속도를 KB/s나 MB/s로 쓸 때가 많습니다. KB/s는 1초에 몇 KByte의 데이터를 주고받는지를 나타내는 단위입니다. MB/s는 1초에 몇 MByte를 받는지를 표시하는 단위입니다. 인터넷 사업자들은 당연히 숫자가 커서 빨라 보이는 Mbps를 씁니다. 사업자들이 100Mbps를 씁니다. 사업자들이 100Mbps라고 자랑하는 인터넷 서비스가 최고속도가 다 나온다고 했을 때 이를 MB/s로 따지면 12.5MB/s가 나오는 게 되죠. bit를 기초로 한 속도 단위와 Byte를 기초로 한 속도 단위가 헷갈리지 않게 보통 소문자 'b'와 대문자 'B'로 구별해서 씁니다. Byte일 땐 /s로 표시한다는 것도 기억하면 구분하기 편할 겁니다. PC통신 시절엔 네트워크 속도 단위로 cps도 많이 썼습니다. cps는 characters per second의 준말입니다. characters는 하나의 문자를 나타낼 수 있는 단위로 1characters는 하나의 문자를 나타낼 수 있는 단위로 1characters=8bit입니다. 즉 1characters는 1Byte와 같습니다. 1cps = 8bps 1kbps = 1,024bps 1KB/s = 8Kbps 1Gbps = 1,024Mbps [모니터] 인치 모니터는 크기를 인치(inch)로 잽니다. 1인치는 2.5399cm입니다. 모니터의 크기를 잴 때는 화면의 대각선 길이를 잽니다. 19인치는 화면 대각선의 길이가 약 48.26cm란 얘기죠. 요즘 잘나가는 22인치 와이드 LCD는 약 55.88cm가 나옵니다. 대각선의 길이를 재기 때문에 일반형과 와이드형은 같은 인치라도 차이가 있습니다. 19인치 와이드형 모니터를 같이 두고 보면 양 옆은 와이드형이 길지만 높이는 낮습니다. 1인치 = 2.5399cm 해상도 모니터의 해상도는 가로와 세로를 이루는 픽셀의 수로 표시합니다. 1,280X1,024 해상도라고 하면 옆으로 1,280개의 픽셀과 아래로 1,024개의 픽셀이 모여서 화면을 이루었다는 말입니다.

etc dpi 프린터나 스캐너의 해상도를 나타내는 단위로 dots per inch의 준말입니다. 가로 세로 1인치의 사각형 안에 몇 개의 점이 찍히는지를 뜻합니다. dpi가 높으면 그만큼 그림이 깨끗합니다. PPM Page per Minute. 프린터가 1분 동안에 뽑아내는 인쇄물의 장수를 말합니다. 데시벨(dB) 소리의 상대적인 크기를 나타내는 단위입니다. 정상적인 귀로 들을 수 있는 가장 작은 소리의 크기 0dB로 하고 10dB씩 증가할 때마다 소리의 세기는 10배씩 커집니다. 가정에서의 평균 생활소음은 약 40dB, 일상 대화는 약 60dB입니다. mAh 노트북 등의 배터리 용량을 나타내는 단위로 갖고 있는 전류의 양을 말합니다. 충전지에 2500mAhH라고 써 있다면 이것은 2500mA의 전류를 1시간 동안 쓸 수 있다는 뜻입니다.


Posted by wakira
,

unicode 와 ansi 를 섞어 쓰면서 std::string 과 std::wstring, CString 을 상호 변환하는 방법을 살펴 보자. unicode 를 쓰면서 MFC에서 STL 을 쓰기 위해선 이들을 서로 변경해야 할 경우가 많아 이 곳에 정리해 둔다. 



환경 : Visual Studio 2010 on Windows 7  Professional SP1, English, 64-bit. 


우선 기본적으로 MFC에서 unicode 과 ansi 를 상호변환하기 위해서는 CF2CT 형식을 따르며, 이 때 F 와 T 는 A와 T 중 하나의 값이 될 수 있다. T는 _T macro 에서 알 수 있듯이 multibyte 로의 변환을 의미하는 것이고, A 는 ANSI 를 의미한다. 따라서 ansi 에서 unicode 로 변경하기 위해선 CA2CT 를, unicode 에서 ansi로 변경하기 위해선 CT2CA 를 쓰면 된다. 또한 std::string은 class 이므로 CString 에서 std::string 을 만들 때는 std::string 의 생성자를 사용하면 된다. std::string --> CString 도 마찬가지. 


MFC로 project 를 만들 때 UNICODE 를 사용하게 되면 CString 은 CStringT 로 기본적으로 코드 상의 모든 CString 이 unicode 로 지정된다(물론 _UNICODE macro 에 의해). 이 경우 모든 literal 문자열은 _T macro로 지정해 주어야 한다, 다음처럼. 


_stimuliWnd.CreateEx(0,strClass,_T("stimuliWnd"),WS_POPUPWINDOW,0,0,width,height,NULL,NULL); 


std::string 은 ansi version 이고 st::wstring 은 wide-string, 즉 unicode version 이다. 또한, 


std::string::c_str() 함수는 std::string 의 객체를 const char* 로 접근할 수 있게 해주며, 

CString::operator LPCTSTR() 은 CString 객체를 const char* 로 접근할 수 있게 해준다. 


위의 ansi version 함수는 std::string 을 std::wstring 으로, CString::operator LPCTSTR() 을 CString::operator LPCWSTR() 으로 unicode version 으로 사용할 수 있다.


이제 기본적인 것들을 알았으니 CString <--> std::string 등의 변경 방법을 살펴 보자. 



CString cstring(_T("CString::CStringT"));  std::string str("std::string");  std::wstring wstr(_T("std::wstring"));  // std::string <-- CString str = std::string(CT2CA(cstring.operator LPCWSTR()));  // std::wstring <-- CString wstr = std::wstring(cstring.operator LPCWSTR());  // CString <-- std::string cstring = CString::CStringT(CA2CT(str.c_str()));  // CString <-- std::wstring cstring = CString::CStringT(wstr.c_str());  // std::string <-- std::wstring str = std::string(CT2CA(wstr.c_str()));  // std::wstring <-- std::string wstr = std::wstring(CA2CT(str.c_str())); 



어렵지 않다. 


CA2CT 는 ansi version const char* 를 multi-byte const char* 로 변경해 주는 것이고, 

CT2CA 는 unicode version const char* 를 ansi version const char* 로 변경해 주는 것이고, 

std::string 은 ansi version, std::wstring 은 unicode version 이고, 

CString 은 _UNICODE 가 설정되어 있으면 CStringT 의 unicode version 으로 자동으로 바뀌며, 

std::string 과 CString 은 모두 const char* 로 접근할 수 있게 해 주는 함수를 제공한다는 점, 


을 생각하면 된다. 그러면 다음과 같이 자유자재로 왔다갔다 할 수 있다. 



CString path;
// [path] 설정. 
std::vector<std::wstring> files; 
GetFileNameList(std::wstring(path.operator LPCWSTR()), _T("*.txt"), &files, NULL, NULL); 
std::vector<std::wstring>::const_iterator pos; 
for(pos = files.begin(); pos != files.end(); pos++){
	std::string file_name(CT2CA((path + _T("\\") + CString::CStringT(pos->c_str())).operator LPCWSTR())); 
	// ~~ 작업.
}



위에서 [path]는 CString 이므로 literal 문자 \ 는 _T("\\") 로 unicode 로 변경 후 더할 수 있다. 그 후, std::wstring 인 [pos]를 CString 으로 변경해서 덧붙인다. 그렇게 만들어진 


path + _T("\\") + CString::CStringT(pos->c_str())


위 변수는 CString 타입의 객체이다. 따라서 이것을 다시 std::string 으로 만들기 위해 CT2CA 와 operator LPCWSTR 을 사용한 것이다. 

'Windows 개발' 카테고리의 다른 글

메모장에 유니코드로 저장하기  (0) 2014.11.24
기본상식  (0) 2014.11.21
데이터 형 변환 string to TCHAR*  (0) 2014.11.11
OutputDebugString 응용  (0) 2014.11.11
tid 구하기  (0) 2014.10.19
Posted by wakira
,

코딩한 서버를 클라이언트와 붙이면서 데이터 형에서 문제를 많이 일으켰다.
첫째, string 을 tchar로 변환
둘째, 멀티바이트 사용 하는 코딩 과 유니코드 사용(내가 코딩한 것) 것 사이의 문제.
TCHAR을 사용한다고 했지만 서버와 클라 사이의 데이터 이동도 많고
결국에는 내 소스 역시 멀티바이트로(클라가 사람이 많으니 다수를 따라서..) 변환 했고
TCHAR로 처리 되었던 것은 string을 char로 변환해서 마무리 했다.

그 과정에서 찾은 데이터 변환 방법 들...
  
방법 1. 외국 블로그에서

How to convert std::string to TCHAR* 
출처 - http://ukanth.in/blog/?p=180

typedef std::basic_string<TCHAR> tstring;  
 
TCHAR* StringToTCHAR(string& s)
{
  tstring tstr;
  const char* all = s.c_str();
  int len = 1 + strlen(all);
  wchar_t* t = new wchar_t[len]; 
  if (NULL == t) throw std::bad_alloc();
  mbstowcs(t, all, len);
  return (TCHAR*)t;
}
 
std::string TCHARToString(const TCHAR* ptsz)
{
     int len = wcslen((wchar_t*)ptsz);
     char* psz = new char[2*len + 1];
     wcstombs(psz, (wchar_t*)ptsz, 2*len + 1);
     std::string s = psz;
     delete [] psz;
     return s;
}
추가 - mbstowcs 함수 : multi byte char 을 wide char로 변환
wcstombs 는 mbstowcs 의 반대.


사용  예)
// IdSet->getText() 는 string.
tstring tstr;
const char* all = IdSet->getText().c_str();
int len = strlen(all)+1;
wchar_t* t = new wchar_t[len];
if( t == NULL )
 throw std::bad_alloc();
mbstowcs( t, all, len );



방법2. String to char*, char* to TCHAR* (유니코드 사용 환경이였으므로 wchar*)

// IdSet->getText() is stl string

// String to char*
char* strID;
size_t tempSize = IdSet->getText().length();
strID = new char[tempSize];  
strcpy( strID, IdSet->getText().c_str() );

// char* to TCHAR*
TCHAR szUniID[256] = {0,};
int len = strlen(strID);
::MultiByteToWideChar( CP_ACP, 0, strID, -1, szUniID, len + 1 );
 
방법 3. string -> const char* -> char* (멀티바이트 사용)
//IdSet->getText() 은 string

char* id;

const char* strID = IdSet->getText().c_str();
id = const_cast<char*>(strID);

// 합쳐서 사용  
id = const_cast<char*>( IdSet->getText().c_str() );



추가 - const char*을 char*로 변환

방법 1. 직접 캐스팅
char* str1;
const char* str2;

str1 = (char*)(str2);

방법 2. 형변환 연산자 const_cast 사용
- const 객체에 대한 포인터를 const가 아닌 객체의 포인터로 변환 할때 사용하는 연산자
str1 = const_cast<char*>(str2);



std::string strOriginal;      // 원래 문자열
TCHAR strChange[30] = L"";    // 변환시킬 문자열을 담음
size_t origsize = 0, convertedChars = 0; // 원래 문자열 길이, 변환된 문자열 길이

origsize = strlen(strOriginal.c_str()) + 1;  // 변환시킬 문자열의 길이를 구함
mbstowcs_s(&convertedChars, strChange, origsize, strOriginal.c_str(), _TRUNCATE); // 변환

* mbstowcs() 함수를 사용하면 2005 이상의 버젼에서 경고를 띄운다. 그래서 mbstowcs_s() 함수를 사용하고,
mbstowcs_s() 이 함수가 문자열 오버플로우 같은걸 애초에 방지해준다고 검색하다가 본듯





결과로 내가만든건~~

#pragma once
#include <string>
#include <tchar.h>


TCHAR* StringToTCHAR(std::string& s)
{
size_t origsize = 0, convertedChars = 0; // 원래 문자열 길이, 변환된 문자열 길이
origsize = strlen(s.c_str()) + 1;  // 변환시킬 문자열의 길이를 구함
wchar_t* t = new wchar_t[origsize];
mbstowcs_s(&convertedChars, t, origsize, s.c_str(), _TRUNCATE);
return (TCHAR*)t;
}


'Windows 개발' 카테고리의 다른 글

기본상식  (0) 2014.11.21
unicode 와 ansi, std::string 과 CString 상호 변경하기  (0) 2014.11.19
OutputDebugString 응용  (0) 2014.11.11
tid 구하기  (0) 2014.10.19
dll 분석  (0) 2014.09.21
Posted by wakira
,

Log_sulac("sdfsdfasf %d",parm1); 이 가능하게 하려면 아래와 같이 수정하면 됨 (이건 VC6.0에서 사용하는 방법임)

void Log_Sulac(const char* format,...)
{
 char buffer[4096];
 va_list vaList;
 va_start(vaList, format);
 _vsnprintf(buffer, 4096, format, vaList);
 va_end(vaList);

 OutputDebugString(buffer);
}

아 그리고 만약 앞에 '[MEDIC]'같은 문자열을 넣고 싶을 경우 아래와 같이 바꾸면 되여
void Log_Sulac(const char* format,...)
{
 char buffer[4096];
 va_list vaList;
 va_start(vaList, format);
 _vsnprintf(buffer, 4096, format, vaList);
 va_end(vaList);

// OutputDebugString(buffer);
 CString str;
 str.Format("[MEDIC]%s",buffer);
 OutputDebugString(str);
}

밑에도됨..MFC나 C++에서 사용하면 될듯..ㅋㅋ웅왕굳!

LOG_SULAC(("Port %d, Iconnect %d, iDetect %d"), PARM1,PARM2,PARM3);

 

inline void LOG_SULAC(const charformat, ...)

{

CString str;

str.Format("[Medic] %s"format);

OutputDebugString(str);

}







/* Copyright (c) 2012 mbed.org, MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
 
#ifndef DEBUG_H
#define DEBUG_H
 
/** @file debug.h */
 
#ifndef NDEBUG
 
#include <stdarg.h>
#include <stdio.h>
 
/** Output a debug message
 * 
 * @param format printf-style format string, followed by variables
 */
static inline void debug(const char *format, ...) {
    va_list args;
    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
}
 
/** Conditionally output a debug message
 * 
 * @param condition output only if condition is true
 * @param format printf-style format string, followed by variables
 */
static inline void debug(bool condition, const char *format, ...) {
    if(condition) {
        va_list args;
        va_start(args, format);
        vfprintf(stderr, format, args);
        va_end(args);
    }
}
 
#else
 
static inline void debug(const char *format, ...) {}
static inline void debug(bool condition, const char *format, ...) {}
 
#endif
 
#endif



1. 디버그 뷰 설치 

2. 디버그 뷰 실행

3. 디버그 뷰 설정

많은 프로그램들이 디버그 뷰를 사용하다 보니 두서 없이 스트링이 많이 나올때가 있다.
기본적으로 개발이 끝나고 배포 할때는 디버그 스트링 값을 _OFF 상태로 설정해야 하는데,
다른 사용자들을 배려 하지 않는다고나 할까? ㅎ 뭐 어쨋튼,,,

많은 프로그램들이 디버그에 스트링을 뿌리다 보니 보기도 힘들고 하니,,,
필터링을 해야하는데,,,
첨에 많이 해맷다.

DebugView의 도움말에 잘 나와 있긴 하지만,, ,영어라서 ,,ㅎ 

단축키로는 CTRL + L 이고 아이콘은 깔데가기 거꾸로 있는 모습이다.

실행하면 필터링 옵션 창이 뜨고

include와 exclude가 있고 highlight가 있다.

include는 나타낼 스트링의 일부를 이야기 하는거고 기본값은 * 이다(모든 스트링이 나타남).

원하는 스트링만 나타낼때는 handsome;wonderful;sometime 뭐 이런 식으로 

구분자가 ; (세미콜론) 이다.

하이라이트 사용법은 일단 설정할 색을 고른 다음 filter1, filter2, filter3... 

그리고 색에 표현할 스트링을 적어 넣으면 된다.

그럼 그 단어가 포함된 라인은 설정된 색으로 나타난다.


4. 프로그램에서 사용하기 


stdafx.h 에 추가 

#define _ON  (0)
#define _OFF (1)

#define D0 _ON ? (VOID)0 : DebugView

inline void DebugView(TCHAR *pszErr, ...)
{
 static CString strErr;
 va_list ap;
 
 va_start(ap, pszErr);
 strErr.FormatV(pszErr, ap);
 va_end(ap);

 OutputDebugString(_T("[프로젝트명] ") + strErr + _T("\n"));
};


함수 내에서 사용하기

int a = 5;
CString str = _T("gg");
D0( _T("Test::create() a=%d str=%s"), a, str );

뭐 요런 식으로 쓰면,,,,

디버그 뷰에는 

[1776] [프로젝트명] Test::create() a=3 str=gg

요렇게 나타난다.

DLL에서도 디버그 모드는 돌아가지만,,,,

ActiveX는 디버그 모드에서 돌릴수가 없다.

디버그모드에서 개발할때보다 시간은 더 걸리지만 부득이하게 사용해야 할때가 있다..

그럴때 유용하게 사용하시길...

       


Posted by wakira
,

tid 구하기

Windows 개발 2014. 10. 19. 18:18

스레드 다루기 (기초편)
by 신영진(YoungJin Shin), codewiz at gmail.com, @codemaruhttp://www.jiniya.net

윈도우는 스레드를 관리하기 위해서 다양한 함수들을 제공해 준다. 그러한 함수들을 살펴보기에 앞서서 윈도우는 스레드를 위해서 어떠한 속성들을 저장하고 관리하는지를 먼저 살펴보도록 하자. <화면 1>에는 Process Hacker라는 유틸리티를 사용해서 특정 프로세스에서 생성한 스레드 목록을 살펴보고 있다. 이 화면에는 윈도우에서 저장하고 있는 스레드의 속성의 거의 대부분이 포함되어 있다. 각각의 항목이 어떤 의미를 나타내는지 먼저 살펴보도록 하자.


화면 1 스레드 속성들

화면에서 제일 먼저 보이는 것은 TID라는 것이다. 이는 Thread Identifier의 약자로 스레드 아이디를 의미한다. 윈도우에서 동작하는 모든 스레드는 이 아이디를 통해서 다른 스레드와 구분된다. 다음으로는 Start Address가 보인다. 이는 스레드의 시작 주소가 된다. 스레드는 이 주소에서 시작해서 이 함수가 ExitThread를 호출할 때까지 수행을 이어나간다. 다음으로는 Priority가 있다. 이는 우선순위를 나타내는 것으로 해당 스레드가 얼마나 높은 빈도로 스케줄링 될 것인지를 나타낸다. 아래에 있는 Started라고 표시된 항목은 스레드의 시작 시간을 나타낸다. State는 현재 스레드의 상태를 나타낸다. Kernel Time은 스레드가 수행한 커널 코드의 시간을 나타내며, User Time이란 스레드가 수행한 유저 코드의 시간을 나타낸다. Context Switches는 컨텍스트 전환 횟수를 Cycles는 이 스레드가 사용한 CPU 클럭을 나타낸다.

그러면 이제부터 스레드를 어떻게 생성하고 종료시키는지부터 개별 스레드의 이러한 속성들을 어떻게 프로그램 내에서 조작할 수 있는지에 대해서 알아보도록 하자.

스레드의 탄생
CreateThread와 CreateRemoteThread는 윈도우에서 스레드의 탄생을 책임지고 있는 함수다. 이 두 함수는 동일하게 스레드를 생성하는 역할을 한다. 단지 차이가 있다면 CreateThread는 호출한 프로세스에서 동작하는 스레드를 생성하는 역할을 하며, CreateRemoteThread는 파라미터로 넘어온 프로세스에서 수행되는 스레드를 생성하는 역할을 한다. 실제로 윈도우 내부에서 CreateThread는 GetCurrentThread를 사용해서 CreateRemoteThread를 호출하는 것으로 구현되기 때문에 CreateThread는 CreateRemoteThread의 서브셋이라고 보면 되겠다.

<리스트 1>에는 이 두 함수의 원형이 나와 있다. CreateThread의 함수 원형을 살펴보면 6개의 파라미터를 받는 복잡한 함수라는 것을 알 수 있다. 하지만 주로 사용하는 파라미터는 몇 개 되지 않기 때문에 차근차근 사용 방법을 살펴보면 크게 어렵지는 않다. 첫 번째 파라미터는 스레드의 보안 속성이다. 보통은 NULL을 전달하면 된다. 두 번째 파라미터로는 스레드 스택 크기를 지정한다. 0을 넘겨주면 기본 스택 크기로 스레드가 생성된다. 세 번째 파라미터인 lpStartAddress로는 실제 스레드가 수행될 함수 포인터를, 네 번째 파라미터인 lpParameter로는 lpStartAddress로 전달될 파라미터를 넘겨주면 된다. dwCreationFlags는 생성 플래그를 지정한다. 0을 지정하면 기본 상태로 생성한다. 스레드를 정지된 상태로 생성하고 싶으면 이 플래그에 CREATE_SUSPENDED를 지정하면 된다. 끝으로 lpThreadId에는 스레드 아이디를 전달 받을 포인터를 넘겨주면 된다. 스레드 아이디가 필요 없다면 NULL을 지정하면 된다.

리스트 1 CreateThread, CreateRemoteThread 함수 원형

  1. typedef DWORD (__stdcall *LPTHREAD_START_ROUTINE) (LPVOID lpThreadParameter);  
  2.   
  3. HANDLE WINAPI CreateThread(  
  4.     LPSECURITY_ATTRIBUTES lpThreadAttributes  
  5.     , SIZE_T dwStackSize  
  6.     , LPTHREAD_START_ROUTINE lpStartAddress  
  7.     , LPVOID lpParameter  
  8.     , DWORD dwCreationFlags  
  9.     , LPDWORD lpThreadId  
  10. );  
  11.   
  12. HANDLE WINAPI CreateRemoteThread(  
  13.     HANDLE hProcess  
  14.     , LPSECURITY_ATTRIBUTES lpThreadAttributes  
  15.     , SIZE_T dwStackSize  
  16.     , LPTHREAD_START_ROUTINE lpStartAddress  
  17.     , LPVOID lpParameter  
  18.     , DWORD dwCreationFlags  
  19.     , LPDWORD lpThreadId  
  20. );  

윈도우에서 동일한 실행 파일을 가진 프로세스를 여러 개 동시에 실행할 수 있는 것처럼 스레드 또한 동일한 lpStartAddress를 가지는 것을 동시에 여러 개 만들 수 있다. 이렇게 만들어진 스레드 인스턴스를 구분하기 위해서는 스레드 아이디라는 것이 사용된다. 스레드 내에서 현재 자신의 스레드 아이디를 구하기 위해서는 GetCurrentThreadId라는 함수를 사용하면 된다. 파라미터는 없으며, 리턴 값은 현재 실행되고 있는 스레드의 아이디를 반환해 준다. 마찬가지로 GetCurrentThread를 사용하면 현재 실행되는 스레드의 의사 핸들을 반환 받을 수 있다.

<리스트 2>에는 이러한 스레드 함수를 사용해서 실제로 스레드를 생성하는 예제가 나와 있다. 프로그램은 PrintThread 함수를 시작 주소로 하는 스레드를 세 개 생성해서 해당 스레드가 종료될 때까지 기다리는 역할을 한다. 세 개의 스레드가 모두 종료되면 프로그램도 같이 종료된다.

리스트 2 PrintThread 프로그램

  1. #include <windows.h>  
  2.   
  3. DWORD  
  4. CALLBACK  
  5. PrintThread(PVOID param)  
  6. {  
  7.     ULONG_PTR id = (ULONG_PTR) param;  
  8.   
  9.     for(int i=0; i<3; ++i)  
  10.     {  
  11.         printf("[TID = %8d] [PARAM = %d] Running...\n"  
  12.                 , GetCurrentThreadId()  
  13.                 , id);  
  14.   
  15.         Sleep(1000);  
  16.     }  
  17.   
  18.     return 0;  
  19. }  
  20.   
  21. int main()  
  22. {  
  23.     HANDLE threads[3];  
  24.   
  25.     for(int i=0; i<ARRAYSIZE(threads); ++i)  
  26.         threads[i] = CreateThread(NULL, 0, PrintThread, (PVOID) i, 0, NULL);  
  27.   
  28.     WaitForMultipleObjects(ARRAYSIZE(threads), threads, TRUE, INFINITE);  
  29.     printf("Complete\n");  
  30.     return 0;  
  31. }  

<화면 2>에는 PrintThread 프로그램의 실행 화면이 나와 있다. <화면 2>를 살펴보면 스레드가 생성 순서를 기준으로 0, 2, 1, 1, 2, 0, 0, 2, 1 순서대로 실행된 것을 볼 수 있다. 직접 입력해서 프로그램을 실행해보면 아마 화면의 순서와는 또 다른 실행 흐름을 볼 수 있을 것이다. 이렇게 스레드 프로그래밍은 운영체제 스케줄링 순서에 따라서 매번 실행 흐름이 바뀐다는 특징이 있다. 이런 비결정적인 특성 때문에 멀티 스레드 관련 버그들은 항상 재연이 쉽지 않고 재연이 된다 하더라도 그것을 관찰하려고 하면 버그가 사라지는 하이젠버그 같은 것들이 자주 만들어진다. 이런 특수한 성질이 있기 때문에 멀티스레드 프로그래밍에 있어서는 항상 머릿속으로 모든 실행 흐름을 염두에 두고 꼼꼼하게 따져보는 습관을 가지는 것이 좋다.


화면 2 PrintThread 실행 화면

스레드 종료
스레드 생성 부분을 살펴보았으니 이번에는 스레드를 종료 시키는 방법에 대해서 살펴보도록 하자. 스레드 종료에는 ExitThread, TerminateThread, FreeLibraryAndExitThread등의 함수가 사용된다. <리스트 3>에는 각 함수의 원형이 나와 있다. ExitThread는 가장 기본적인 함수로 해당 함수를 호출한 스레드를 종료하는 역할을 한다. 파라미터로는 스레드 종료 코드가 넘어간다. TerminateThread는 특정 스레드를 강제로 종료 시키는 역할을 한다. TerminateThread의 파라미터로는 종료시킬 스레드 핸들과 종료 코드가 넘어간다. 끝으로 FreeLibraryAndExitThread 함수는 스레드를 종료함과 동시에 특정 모듈을 FreeLibrary 시키는 역할을 한다. DLL 내에서 생성된 스레드에서 해당 스레드 종료와 동시에 모듈을 언로드 시키고 싶을 때 사용하기에 적합한 함수다.

리스트 3 스레드 종료 관련 함수 원형

  1. VOID WINAPI ExitThread(DWORD dwExitCode);  
  2. BOOL WINAPI TerminateThread(HANDLE hThread, DWORD dwExitCode);  
  3. VOID WINAPI FreeLibraryAndExitThread(HMODULE hModule, DWORD dwExitCode);  
  4. BOOL WINAPI GetExitCodeThread(HANDLE hThread, LPDWORD lpExitCode);  

윈도우 환경에서 프로그래밍을 해본 독자라면 사실 이렇게 복잡한 함수를 직접 호출하지 않고도 그냥 스레드 함수가 리턴 함으로써 스레드가 종료된다는 사실을 알고 있을 것이다. 앞서 작성해본 <리스트 2>의 프로그램도 그렇게 스레드를 종료했다. 또한 대부분의 책에서 그렇게 종료하는 것이 가장 좋은 방법이라고 강조하고 있다. 그렇다면 어떻게 스레드 함수가 리턴하는 것만으로도 스레드가 종료될 수 있을까? 그 마법의 비밀은 다름아닌 윈도우의 CreateThread 구현에 있다. CreateThread 함수는 사실 lpStartAddress를 시작으로 하는 스레드를 생성하지 않는다. 대신 <리스트 4>에 나타난 것과 같이 BaseThreadStart라고 명명된 래퍼 함수를 시작 주소로 하는 스레드를 생성한다. 해당 래퍼 함수는 <리스트 4>에 나와있는 것과 같이 개발자가 실제로 생성하려고 넘겨 주었던 lpStartAddress를 호출하고 해당 함수가 리턴하면 자동으로 ExitThread를 호출하도록 되어 있다. 이런 이유로 우리가 만든 스레드 시작 함수가 리턴 하는 것만으로도 자연스럽게 스레드가 종료될 수 있는 것이다.

리스트 4 BaseThreadStart 함수 의사 코드

  1. VOID  
  2. BaseThreadStart(  
  3.     LPTHREAD_START_ROUTINE lpStartAddress,  
  4.     LPVOID lpParameter  
  5. )  
  6. {  
  7.     ExitThread((lpStartAddress)(lpParameter));  
  8. }  

한 가지 더 살펴볼만한 내용은 TerminateThread 함수에 관한 오해다. 많은 책에서 TerminateThread를 사용하면 리소스 반환이 정상적으로 되지 않고 마치 큰 일이 생기는 것처럼 언급하고 있지만 이는 사실 잘못된 내용이다. TerminateThread를 호출한다고 해서 리소스 반환이 되지 않고 ExitThread를 호출한다고 해서 리소스 반환이 되는 것은 아니기 때문이다.

<리스트 5>와 같은 스레드를 살펴보자. 이 스레드는 스레드 함수 내에서 메모리를 할당한 다음 리턴한다. 이 스레드 함수가 정상적으로 종료된다면 결국 궁극에는 ExitThread에 의해서 종료된다. 그렇다면 ExitThread 함수는 할당된 buffer를 자동으로 소거해 줄까? 당연히 하지 않는다. 이 스레드의 경우에는 ExitThread로 종료하던, TerminateThread로 종료하던 스레드가 종료되면 메모리 릭이 발생한다.

리스트 5 메모리 할당 작업을 하는 스레드

  1. DWORD  
  2. CALLBACK  
  3. Thread1(PVOID)  
  4. {  
  5.     char *buffer = new char[10];  
  6.     return 0;  
  7. }  

윈도우는 프로그램에서 사용하는 리소스와 관련해서 스레드 별로 할당 내용을 추적하지 않는다. 따라서 ExitThread나 TerminateThread 모두 리소스를 소거해 주는 역할을 가지고 있지는 않다. TerminateThread와 ExitThread 사이의 차이는 단지 누가 언제 스레드를 종료 시키느냐의 차이 밖에는 없다. <리스트 6>를 살펴보면 그러한 차이를 알 수 있다. <리스트 6>의 스레드 루틴은 buffer를 할당한 다음 작업을 하고 buffer를 삭제한 다음 종료한다. 이 경우에 있어서 ExitThread에 의해서 종료된다는 것은 항상 자신이 생성한 buffer 리소스에 대해서 해제 작업이 완료된 다음에 종료된다는 것을 알 수 있다. 하지만 외부에서 TerminateThread로 종료 시키는 경우에는 스레드 실행 시점에 따라서 buffer가 해제된 다음일 수도 있고, buffer가 사용 중인 도중일 수도 있다. 따라서 이 경우에는 실행 시점에 따라서 리소스 릭이 발생할 수도 있고, 아닐 수도 있게 되는 것이다.

리스트 6 리소스 해제가 추가된 스레드 루틴

  1. DWORD  
  2. CALLBACK  
  3. Thread2(PVOID)  
  4. {  
  5.     char *buffer = new char[10];  
  6.   
  7.     // buffer 관련 작업  
  8.   
  9.     delete [] buffer;      
  10.     return 0;  
  11. }  

결국은 ExitThread나 TermianteThread나 운영체제 내부적으로 리소스 해제와 관련된 작업을 해주는 것은 없다. 스레드 내에서 생성된 리소스에 대한 책임은 해당 리소스를 생성한 스레드 루틴에 있다. TerminateThread라고 무턱대고 사용하지 말아야 하는 함수라고 생각하기보다는 이러한 내부적인 차이점을 정확하게 이해하고 상황에 맞는 함수를 선택해서 사용하는 습관을 가지는 것이 좋겠다.

끝으로 스레드의 종료 상태를 알아내는 방법에 대해서 살펴보자. 스레드의 종료 상태를 알아내기 위해서는 GetExitCodeThread 함수가 사용된다(<리스트 3> 참고). hThread로 종료 상태를 알고 싶은 스레드를 전달하면 lpExitCode로 종료 상태가 넘어온다. 종료되지 않고 스레드가 실행중인 상태라면 lpExitCode로 STILL_ACTIVE가 전달되며, 종료된 상태라면 스레드 종료 코드가 넘어온다.

실전에서는 GetExitCodeThread 함수보다는 WaitForSingleObject가 훨씬 더 많이 사용된다. 스레드 객체는 윈도우 운영체제에 의해서 종료 시에 시그널 상태로 설정된다. 따라서 WaitForSingleObject로 스레드 핸들을 전달해서 WAIT_OBJECT_0이 반환된다면 해당 스레드는 종료된 것이며, 다른 값이 반환된다면 여전히 실행 중인 상태로 판단할 수 있다.

스레드 일시 정지와 재개
스레드는 언제든지 SuspendThread 함수를 통해서 일시 정지 시킬 수 있고, 또 ResumeThread를 통해서 실행을 재개할 수 있다. 각 함수의 원형은 <리스트 7>에 나와 있다. 파라미터로 전달되는 hThread는 실행을 중지 하거나 재개시킬 스레드 핸들이며, 반환 값은 이전에 실행이 중지된 횟수를 반환한다. 중지된 횟수를 반환한다는 의미는 SuspendThread를 두 번 호출했다면 ResumeThread를 두 번 호출해야 스레드가 실제로 다시 수행된다는 것을 의미한다.

리스트 7 SuspendThread/ResumeThread 함수 원형

  1. DWORD WINAPI SuspendThread(HANDLE hThread);  
  2. DWORD WINAPI ResumeThread(HANDLE hThread);  

<리스트 8>에는 SuspendThread와 ResumeThread를 사용해서 스레드를 정지 시키고 다시 재개시키는 프로그램이 나와 있다. 프로그램의 코드를 바꿔가면서 SuspendThread와 ResumeThread의 반환 값이 어떤 식으로 동작하는지 살펴보도록 하자.

리스트 8 SuspendResumeThread 프로그램

  1. int main()  
  2. {  
  3.     HANDLE thread;  
  4.     ULONG count;  
  5.   
  6.     thread = CreateThread(NULL, 0, ThreadFunc, NULL, 0, NULL);  
  7.   
  8.     count = SuspendThread(thread);  
  9.     printf("suspend count = %d\n", count);  
  10.   
  11.     count = SuspendThread(thread);  
  12.     printf("suspend count = %d\n", count);  
  13.   
  14.     count = ResumeThread(thread);  
  15.     printf("suspend count = %d\n", count);  
  16.   
  17.     count = ResumeThread(thread);  
  18.     printf("suspend count = %d\n", count);  
  19.   
  20.     WaitForSingleObject(thread, INFINITE);  
  21.     return 0;  
  22. }  

스레드 컨텍스트
하나의 CPU 상에서도 스레드를 동시에 실행시키기 위해서는 스레드를 중지시킨 이후에 다시 실행을 재개 시키기 위해서는 어디서부터 실행을 재개할지 저장해 두어야 한다. 이렇게 다시 실행 시킬 위치를 저장해 두는 것을 컨텍스트라고 표현한다. x86/x64 환경에서는 이러한 컨텍스트로 CPU 레지스터를 저장해둔다. 해당 레지스터만 있으면 언제든지 다시 실행을 재개할 수 있기 때문이다.

윈도우에서는 특정 스레드의 컨텍스트를 구하기 위해서 GetThreadContext를 반대로 특정 스레드의 컨텍스트를 설정하기 위해서는 SetThreadContext라는 함수를 사용한다. <리스트 9>에는 해당 함수의 원형이 나와 있다. GetThreadContext 함수는 hThread로 지정된 스레드의 컨텍스트를 lpContext에 저장하는 역할을 한다. 이 함수를 사용할 때 한 가지 주의해야 할 점은 lpContext의 ContextFlags는 함수 호출 전에 미리 설정해 두어야 한다는 점이다. GetThreadContext가 lpContext의 ContextFlags에서 지정한 것과 관계된 레지스터 값만 반환하기 때문이다. SetThreadContext 함수는 hThread로 지정된 스레드의 컨텍스트를 lpContext로 대체시키는 역할을 한다. 이 두 함수 모두 현재 스레드나 실행 중인 스레드에 대해서 함수 호출을 할 경우에는 정상적인 컨텍스트를 구할 수 없다. 따라서 이 함수를 호출하기 위해서는 항상 대상 스레드를 일단 SuspendThread로 중지 시킨 다음에 작업해야 한다는 점을 명심하자.

리스트 9 스레드 컨텍스트 관련 함수들

  1. BOOL WINAPI GetThreadContext(HANDLE hThread, LPCONTEXT lpContext);  
  2. BOOL WINAPI SetThreadContext(HANDLE hThread, const CONTEXT *lpContext);  

<리스트 10>에는 GetThraedContext/SetThreadContext 함수를 사용해서 실행중인 스레드의 컨텍스트를 런타임에 변경하는 작업을 보여준다. SetThreadContext로 지정하기 전에 Eip를 Bypass로 바꾸었기 때문에 ResumeThread가 수행되면 스레드는 Bypass 함수부터 스레드 수행을 재개한다. <화면 3>에는 DebugView를 통해서 프로그램 실행 중에 디버그 출력을 캡쳐한 화면이 나와 있다. 화면에 나타난 것을 살펴보면 정상적이라면 실행되지 않았을 Bypass 함수가 실행된 것을 알 수 있다.

리스트 10 Bypass 프로그램

  1. #include <windows.h>  
  2.   
  3. ULONG  
  4. Bypass()  
  5. {  
  6.     OutputDebugStringA("Bypass\n");  
  7.     ExitThread(0);  
  8.     return 0;  
  9. }  
  10.   
  11. ULONG  
  12. CALLBACK  
  13. MyThread(PVOID)  
  14. {  
  15.     ULONG ntick = GetTickCount() + 1000;  
  16.   
  17.     for(int i=0; i<10; ++i)  
  18.     {  
  19.         OutputDebugStringA("Hello\n");  
  20.           
  21.         while(GetTickCount() < ntick)  
  22.             ;  
  23.   
  24.         ntick = GetTickCount() + 1000;  
  25.     }  
  26.   
  27.     return 0;  
  28. }  
  29.   
  30. int main()  
  31. {  
  32.     HANDLE thread;  
  33.       
  34.     thread = CreateThread(NULL, 0, MyThread, NULL, 0, NULL);  
  35.     Sleep(3000);  
  36.   
  37.     SuspendThread(thread);  
  38.   
  39.     CONTEXT ctx;  
  40.     ctx.ContextFlags = CONTEXT_FULL;  
  41.     GetThreadContext(thread, &ctx);  
  42.   
  43.     ctx.Eip = (ULONG)(ULONG_PTR) Bypass;  
  44.     SetThreadContext(thread, &ctx);  
  45.   
  46.     ResumeThread(thread);  
  47.     WaitForSingleObject(thread, INFINITE);  
  48.     return 0;  
  49. }  


화면 3 Bypass 프로그램 실행 화면

<리스트 10>의 프로그램에서는 Bypass 함수에서 ExitThread를 호출해서 스레드를 강제로 종료시킨다. 이렇게 하지 않고 Bypass 함수를 수행한 다음 원래 스레드 컨텍스트로 돌아가서 MyThread의 남은 코드를 마저 실행 시키도록 만들 방법은 없는지 생각해보자.

스레드 실행 시간
스레드 실행 시간을 구하는 데에는 GetThreadTimes 함수가 사용된다. 함수 원형은 아래 나와 있는 것과 같다. hThread로는 실행 시간을 구하고 싶은 스레드의 핸들을 전달해 주면, lpCreationTime에는 해당 스레드가 생성된 시간이, lpExitTime에는 해당 스레드가 종료된 시간이, lpKernelTime에는 해당 스레드가 사용한 커널 영역 코드의 시간이, lpUserTime은 해당 스레드가 사용한 유저 영역 코드의 시간이 넘어온다.

  1. BOOL WINAPI GetThreadTimes(  
  2.     HANDLE hThread  
  3.     , LPFILETIME lpCreationTime  
  4.     , LPFILETIME lpExitTime  
  5.     , LPFILETIME lpKernelTime  
  6.     , LPFILETIME lpUserTime  
  7. );  

이 함수를 사용하면 특정 스레드의 CPU 점유율을 계산할 수 있다. CPU 점유율이란 “사용한 CPU 시간 / 전체 CPU 시간”으로 계산할 수 있고, 사용한 CPU 시간은 앞서 살펴본 GetThreadTimes 함수의 lpKernelTime과 lpUserTime을 합한 값으로 계산할 수 있다. <리스트 11>에는 이러한 기본적인 원칙을 바탕으로 작성한 현재 스레드의 CPU 점유율을 출력해주는 CpuUsagePrinter 클래스 코드가 나와 있다. CpuUsagePrinter의 순간 점유율은 Reset 이후부터 Dump를 수행할 때까지의 점유율을 최근 1분은 최근 1분간 스레드의 CPU 점유율을 보여준다.

CpuUsagePrinter에서 출력되는 CPU 점유율은 싱글 코어를 기준으로 한 점유율을 말한다. 멀티코어라면 이 점유율이 코어 개수에 대한 비율만큼 줄어들어야 한다. 예를 들어 CpuUsagePrinter가 100%의 점유율을 출력했는데 듀얼코어라면 실제로 듀얼코어 상의 점유율은 50%가 된다는 의미다.

리스트 11 CpuUsagePrinter

  1. typedef LPCWSTR xcwstr;  
  2. typedef UINT64 xuint64;  
  3.   
  4. template <class T>  
  5. class CpuUsagePrinter  
  6. {  
  7. public:  
  8.     FILETIME fkt_, fut_, fft_;  
  9.     FILETIME skt_, sut_, sft_;  
  10.     FILETIME ekt_, eut_, eft_;  
  11.   
  12.     T &printer_;  
  13.     xcwstr tag_;  
  14.   
  15.     CpuUsagePrinter(xcwstr tag, T &printer)  
  16.     : printer_(printer), tag_(tag)  
  17.     {  
  18.         GetSystemTimeAsFileTime(&sft_);  
  19.   
  20.         FILETIME ct, et;  
  21.         GetThreadTimes(GetCurrentThread(), &ct, &et, &skt_, &sut_);  
  22.   
  23.         memcpy(&fft_, &sft_, sizeof(fft_));  
  24.         memcpy(&fkt_, &skt_, sizeof(fkt_));  
  25.         memcpy(&fut_, &sut_, sizeof(fut_));  
  26.     }  
  27.   
  28.     xuint64 ToUInt64(FILETIME &t)  
  29.     {  
  30.         ULARGE_INTEGER u;  
  31.         u.HighPart = t.dwHighDateTime;  
  32.         u.LowPart = t.dwLowDateTime;  
  33.         return u.QuadPart;  
  34.     }  
  35.   
  36.     xuint64 GetDiff64(FILETIME &s, FILETIME &e)  
  37.     {  
  38.         return ToUInt64(e) - ToUInt64(s);  
  39.     }  
  40.   
  41.     void Reset()  
  42.     {  
  43.         GetSystemTimeAsFileTime(&fft_);  
  44.   
  45.         FILETIME ct, et;  
  46.         GetThreadTimes(GetCurrentThread(), &ct, &et, &fkt_, &fut_);  
  47.     }  
  48.   
  49.     void Dump(xcwstr tag = NULL)  
  50.     {  
  51.         xuint64 lusage;  
  52.         xuint64 iusage;  
  53.   
  54.   
  55.         FILETIME ct, et;  
  56.         GetThreadTimes(GetCurrentThread(), &ct, &et, &ekt_, &eut_);  
  57.   
  58.         GetSystemTimeAsFileTime(&eft_);  
  59.   
  60.         xuint64 ltime = GetDiff64(sft_, eft_);  
  61.         if(ltime == 0)  
  62.             return;  
  63.   
  64.         xuint64 lcpu = GetDiff64(skt_, ekt_) + GetDiff64(sut_, eut_);  
  65.         if(lcpu < ltime)  
  66.             lusage = lcpu * 10000 / ltime;  
  67.         else  
  68.             lusage = 10000;  
  69.   
  70.         xuint64 itime = GetDiff64(fft_, eft_);  
  71.         if(itime == 0)  
  72.             return;  
  73.   
  74.         xuint64 icpu = GetDiff64(fkt_, ekt_) + GetDiff64(fut_, eut_);  
  75.         if(icpu < itime)  
  76.             iusage = icpu * 10000 / itime;  
  77.         else  
  78.             iusage = 10000;  
  79.   
  80.         printer_.Write("%ls 순간: %3llu.%02llu%%    최근 1분: %3llu.%02llu%%\n"  
  81.                         , tag ? tag : tag_  
  82.                         , iusage / 100  
  83.                         , iusage % 100  
  84.                         , lusage / 100  
  85.                         , lusage % 100);  
  86.   
  87.         if(ltime > 60 * 10000000)  
  88.         {  
  89.             memcpy(&skt_, &ekt_, sizeof(skt_));  
  90.             memcpy(&sut_, &eut_, sizeof(sut_));  
  91.             memcpy(&sft_, &eft_, sizeof(sft_));  
  92.         }  
  93.     }  
  94.   
  95.     ~CpuUsagePrinter()  
  96.     {  
  97.         Dump(tag_);  
  98.     }  
  99. };  

<리스트 12>에는 CpuUsagePrinter를 사용해서 간단한 스레드의 CPU 점유율을 측정하는 예제가 나와 있다. CalcThread를 실행하면 <화면 4>에 나와 있는 것과 같이 스레드 동작 과정에서 CPU 점유율이 지속적으로 표시된다. CalcThread 코드를 바꿔가면서 CPU 점유율이 어떻게 변화하는지 살펴보도록 하자.

리스트 12 CalcThread 프로그램

  1. #include <windows.h>  
  2.   
  3. class printer  
  4. {  
  5. public:  
  6.     void write(lpcstr fmt, ...)  
  7.     {  
  8.         va_list ap;  
  9.   
  10.         va_start(ap, fmt);  
  11.         vprintf(fmt, ap);  
  12.         va_end(ap);  
  13.     }  
  14. };  
  15.   
  16. DWORD  
  17. CALLBACK  
  18. CalcThread(PVOID param)  
  19. {  
  20.     CpuUsagePrinter<Printer> cpu(L"CalcThread", Printer());  
  21.     ULONG_PTR id = (ULONG_PTR) param;  
  22.   
  23.     for(int i=0; i<100; ++i)  
  24.     {  
  25.         cpu.Reset();  
  26.         int k = rand();  
  27.         int n = rand() * 10000;  
  28.         for(int i=0; i<n; ++i)  
  29.         {  
  30.             k = k * k;  
  31.   
  32.             if(i == n / 2)  
  33.                 Sleep(10);  
  34.         }  
  35.   
  36.         cpu.Dump();  
  37.         Sleep(100);  
  38.     }  
  39.   
  40.     return 0;  
  41. }  
  42.   
  43. int main()  
  44. {  
  45.     HANDLE thread;  
  46.     thread = CreateThread(NULL, 0, CalcThread, NULL, 0, NULL);  
  47.     WaitForSingleObject(thread, INFINITE);  
  48.     return 0;  
  49. }  



http://www.jiniya.net/wp/archives/7194
Read more: http://www.jiniya.net/wp/archives/7194#ixzz3Ga4NUBzm

Posted by wakira
,

dll 분석

Windows 개발 2014. 9. 21. 10:39

동적 라이브러리를 보기 전에 정적 라이브러리를 먼저 살펴 보겠습니다.


O 정적 라이브러리(.LIB)

  정적 라이브러리(lib)를 만드는 법은 간단합니다. Visual Studio 2008에서 프로젝트 구성 형식을 정적 라이브러리(.lib)로 만들고 실행을 하면 되기 때문입니다. 이용을 하려면 이 라이브러리(.lib) 해더 파일(.h)을 이 라이브러리를 이용할 프로젝트에 추가해주면, 이용 또한 쉽습니다.

  - 프로젝트에 추가 방법 [첫번째]
 1. [프로젝트 -  속성] 선택
 2. [링커 - 일반]탭을 선택 후, '추가 라이브러리 디렉터리'에 lib 파일의 경로를 추가.
 3. [링커 - 입력]탭을 선택 후, '추가 종속성' 란에 lib 파일을 입력하고, 확인을 누름.

  - 프로젝트에 추가 방법 [두번째]
 1. main.cpp(App 소스코드를 말함)에서 아래와 같이 입력합니다.

#include "XXX.h" // .h 파일의 경로를 입력한다. 또는 프로젝트에 그 폴더를 포함시킨다.
#pragma comment(lib, "XXX.lib") // lib경로를 입력해야한다. 속성창에서 하는 일과 같다.

void main()
{
   ......
}




O 동적 라이브러리(.DLL)
  동적 라이브러리 또한 정적 라이브러리와 같이 사용이 가능합니다. dll로 프로젝트를 만들고 실행을 하게 되면 dll과 lib 파일이 생성됩니다. 여기서 lib파일에는 dll에 구현된 함수를 찾을 수 있는 정보가 있습니다.(저두 본거라...) 그래서 정적 라이브러리와 같은 방법으로 사용이 가능합니다. 이런 방식을 '암시적 연결'이라고 합니다.
   암시적 연결 방법만 있는 것은 아닙니다. 명시적 방법 또한 있습니다. 이 방법은 dll 파일만 있어도 사용 할 수 있습니다. 하지만 C스타일의 함수여야 하는 듯 합니다. 아래를 보시면 아시겠지만 함수의 이름으로 주소를 얻어오기 때문에 C++에서 사용 할 수 있는 오버로딩이 되지 않습니다. 오버로딩이란 함수의 이름은 같지만 매개변수가 다를 경우 다른 함수로 간주 하는 것을 말합니다. 
  한번 코드로 정리 해보겠습니다. 첫번째는 헤더와 lib파일을 이용해서 dll을 사용하는 방법입니다. 

 - DLL 소스코드

//Swap.h
#ifndef __SWAP_H__
#define __SWAP_H__


#ifdef DLL_EXPORT
    #define DLLTYPE __declspec(dllexport)
#else
    #define DLLTYPE __declspec(dllimport)
#endif //DLL_EXPORT

extern "C" DLLTYPE void SwapInt(int* dest, int* src);
extern "C" DLLTYPE void SwapStr(char* dest, char* src, int size);

#endif //__SWAP_H__

//Swap.cpp
#define DLL_EXPORT 
#include "Swap.h"
void SwapInt(int* dest, int* src)
{
    int temp = *dest;
    *dest = *src;
    *src = temp;
}
void SwapStr(char* dest, char* src, int size)
{
    char* pcTemp = new char[size];
    for(int i = 0 ; i < size ; i++)
    {
        pcTemp[i] = dest[i];
        dest[i] = src[i];
        src[i] = pcTemp[i];
    }
    delete [] pcTemp;



 - App 소스코드1 (.h + .lib 이용하여 dll 함수 호출)

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

#include "..\SwapDll\Swap.h"
#pragma comment(lib, "..\\Debug\\SwapDll.lib")

int main()
{
    int a = 10, b = 20;

    printf("\t\ta : %d, b : %d\n", a, b);
    SwapInt(&a,&b); 
    printf("swap --> \ta : %d, b : %d\n", a, b);

    return 0;
}



- App 소스코드2 (WinAPI를 이용하여 dll 함수 호출)

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

typedef void (*Func)(int*, int*);
int main()
{
    int a = 10, b = 20;
    
    HINSTANCE hDll = LoadLibrary(_T("SwapDll.dll"));
    if(hDll == NULL)
    {
        printf("dll 로드 실패\n");
        return -1;
    }
    Func pFunc = (Func)GetProcAddress(hDll, "SwapInt");
    Func pFunc1 = (Func)GetProcAddress(hDll, "SwapStr");
    printf("\t\ta : %d, b : %d\n", a, b);
    pFunc(&a,&b); 
    printf("swap --> \ta : %d, b : %d\n", a, b);
 
    FreeLibrary(hDll);
    return 0;
}



  위의 소스코드는 코드 작성 이외의 다른 설정은 하지 않았습니다. dll의 구현은 함수 단위로 되어있습니다.
  dll을 클래스 단위로 구현한다면 어떻게 사용해야 할까요? 코드로 보겠습니다.

- DLL 소스코드(Class)

//SwapClass.h
#ifndef __SWAP_CLASS_H__
#define __SWAP_CLASS_H__

#ifdef DLL_EXPORT_CLASS
    #define DLLTYPE __declspec(dllexport)
#else
    #define DLLTYPE __declspec(dllimport)
#endif //DLL_EXPORT_CLASS

class DLLTYPE CSwapClass
{
public:
 CSwapClass();
 ~CSwapClass();
 void Int(int* dest, int* src);
 void String(char* dest, char* src, int size);
};

#endif //__SWAP_CLASS_H__

//SwapClass.cpp
#define DLL_EXPORT_CLASS
#include "SwapClass.h"

CSwapClass::CSwapClass()
{
}
CSwapClass::~CSwapClass()
{
}
void CSwapClass::Int(int* dest, int* src)

    int temp = *dest;
    *dest = *src;
    *src = temp;
}
void CSwapClass::String(char* dest, char* src, int size)
{
    char* pcTemp = new char[size];
    for(int i = 0 ; i < size ; i++)
    {
         pcTemp[i] = dest[i];
         dest[i] = src[i];
         src[i] = pcTemp[i];
    }
    delete [] pcTemp;
}



- App 소스코드 (Class)

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

#include "..\SwapClassDll\SwapClass.h"
#pragma comment(lib, "..\\Debug\\SwapClassDll.lib")

int main()
{
    int a = 10, b = 20;
    CSwapClass swaper;
 
    printf("\t\ta : %d, b : %d\n", a, b);
    swaper.Int(&a,&b); 
    printf("swap --> \ta : %d, b : %d\n", a, b);
    return 0;
}


  위와 같이 사용한다면 class 또한 사용이 가능합니다. 만약에 암시적으로 사용하고 싶다면, LoadLibrary() 함수와 GetProcAddress () 함수를 사용해야 하야 합니다. 하지만 GetProcAddress() 함수에 들어가는 두번째 매개변수는 함수명 또는 서수가 들어가야 합니다. C++의 함수는 사용 할 수 없기 때문에 [extern "C"] 를 사용해야 합니다. 그래서 그 함수에서는 new CSwapClass()를 반환한다면 사용이 가능합니다. 


 

LibraryExample.zip




※ 암시적 연결을 사용해서 함수 호출을 할려했더니 저는 애러는 나지 않았는데 정확한 주소
  를 못 얻어오더군요.. 좀 더 공부를 해봐야 할 듯  합니다. 

지금까지 암시적/명시적 연결로 DLL을 사용하는 방법을 알아봤습니다. 모두 열공하세요.

ps. Error C2491이 발생하신 분은 한번 보시기 바랍니다.(Microsoft 영문)
      링크 : http://support.microsoft.com/kb/815647/en-us/

'Windows 개발' 카테고리의 다른 글

OutputDebugString 응용  (0) 2014.11.11
tid 구하기  (0) 2014.10.19
(WinAPI) 문자열 함수(멀티바이트->유니코드->TCHAR)  (0) 2014.08.09
특정 폴더 밑으로 텍스트파일 추가  (0) 2014.06.06
파일 처리 함수들  (0) 2013.10.28
Posted by wakira
,

strcpy -> wcscpy -> _tcscpy (문자열을 복사) 

strncpy -> wcsncpy -> _tcsncpy ( 사이즈 만큼 복사) 

strlen -> wcslen -> _tcslen (문자열 길이 확인)  

strcat -> wcscat -> _tcscat (두 문자열 이어 붙이기) 

strncat -> wcsncat -> _tcsncat (사이즈 만큼 이어 붙이기) 

strcmp -> wcscmp -> _tcscmp (문자열 비교) (반환 값(-1, 0, 1)) 

strncmp -> wcsncmp? -> _tcsncmp ( 사이즈 만큼 문자열 비교) 

stricmp -> wcsicmp -> _tcsicmp (대소문자를 구별하지 않고 문자열을 비교) 

strnicmp -> wcsnicmp -> _tcsnicmp (사이즈 만큼 대소문자를 구별하지 않고 문자열을 비교)

strchr -> wcschr -> _tcschr (문자 찾기) 

strrchr -> wcsrchr -> _tcsrchr (문자 찾기 (문자열 끝에서 부터 검색)) 

strstr -> wcsstr -> _tcsstr (문자열 찾기)

strpbrk -> wcspbrk -> _tcspbrk (문자 찾기 (두번째 인수를 찾고자 하는 문자들의 집합(문자열)으로 구성)   

strtok -> wcstok -> _tcstok (문자열 자르기 (두번째 인수를 집합(문자열)으로 구성 가능)) => 해당 문자가 NULL로 치환 됨

strset -> wcsset -> _tcsset (문자 치환, 첫째 인수의 모든 문자를 두번째 인수 문자로 변경함) => "abc" -> "bbb" 

strnset -> wcsnset -> _tscnset (사이즈 만큼 문자 치환) 

strupr -> wcsupr -> _tcsupr (대문자로 치환)

strlwr -> wcslwr -> _tcslwr (소문자로 치환) 

strrev -> wcsrev -> _tcsrev (문자열 역정렬) => "가나다라마" -> "마라다나가"

'Windows 개발' 카테고리의 다른 글

tid 구하기  (0) 2014.10.19
dll 분석  (0) 2014.09.21
특정 폴더 밑으로 텍스트파일 추가  (0) 2014.06.06
파일 처리 함수들  (0) 2013.10.28
Thread 비교, 사용법  (0) 2013.10.27
Posted by wakira
,

#include <stdio.h>

#include <Windows.h>

#include <locale.h>

#include <tchar.h>

#include <atlstr.h>


int Folder_access(CString p);

int Make_file(CString path);


int main()

{

   CString root(_T("C:\\virustest\\"));

   Folder_access(root);

   return 0;

}


int Folder_access(CString p)

{

   WIN32_FIND_DATA finddata;

   HANDLE hfind;

   CString wild(_T("\\*"));


   _wsetlocale(LC_ALL, _T("korean"));


   if (PathIsDirectoryEmpty(p))

   {

      Make_file(p+"\\");

      printf("empty\n");

      return 0;

   }

   else

   {

      hfind = FindFirstFile(p + wild, &finddata);


      do

      {

         if ((finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)

         {

            CString d(finddata.cFileName);

            if (d != _T(".") && d != _T(".."))

            {


               Make_file(p + '\\');

               Folder_access(p + '\\' + d);

               _tprintf(_T("%s\n"), finddata.cFileName);

            }

         }

      } while (FindNextFile(hfind, &finddata));


   }


   FindClose(hfind);


   return 0;

}


int Make_file(CString path)

{

   FILE *f;


   CString name(_T("hack.txt"));

   path += name;

   const char *str;

   str = (CStringA)path;


   fopen_s(&f, str, "w");

   fprintf(f, "hello!");

   fclose(f);


   return 0;

}

'Windows 개발' 카테고리의 다른 글

dll 분석  (0) 2014.09.21
(WinAPI) 문자열 함수(멀티바이트->유니코드->TCHAR)  (0) 2014.08.09
파일 처리 함수들  (0) 2013.10.28
Thread 비교, 사용법  (0) 2013.10.27
Windows7 SSDT Hooking  (0) 2013.08.23
Posted by wakira
,