2015.09.02 07:08


1. 함수의 원형

본격적으로 내용을 다루기에 앞서 함수 포인터의 원형과 포인터의 대상이 될 함수와의 원형은 같아야 한다.
이 점은 일반 서적에서 다루고 있는 내용이라 다 알 것이라고 생각되므로 함수의 원형에 대하여 간단하게
Q/A 식으로 짚고 넘어가겠다. 이해가 안되면 무조건 외우는 것도 한가지 방법이라고 생각됨.
마지막으로 함수 포인터는 함수가 아니라 원하는 함수의 주소를 담고 있는 변수이다. 근본적으로는 포인터
(변수 포인터)에 다른 변수의 주소를 넣는 것과 별로 다를 것이 없다. 다만 함수 포인터의 경우는 변수의
주소가 아닌 함수의 주소라는 점만 다를 뿐이다.

int a;
char c[5];
float* f;
"Hello, World"

Q) 위의 코드에서 a, c, f,
"Hello, World"의 원형은?
A)
int, char [], float *, const char *

만일 이 문제들을 못맞추었다면 다시 한번 책을 속독하기를 권함.

void foo();


const char* bar(int num);


class MyClass {
public:
   
void Func1(const string& txt);
   
static void Func2(const string& txt);
};

Q) foo, bar, MyClass::Func1, MyClass::Func2의 원형은?
A)
void (*)(), const char* (*)(int), void (MyClass::*)(const string&), void (*)(const string&)


2. 함수 포인터(전역 함수 포인터) / 멤버 함수 포인터

가. 어떻게 정의/선언하고 사용하는가?

    함수 포인터의 대상이 되는 함수의 원형을 적고 괄호안의 * 연산자의 뒤에 원하는 이름을 붙여서 사용.
    (괄호가 있는 이유는 없을 경우 함수의 리턴형이 * 연산자에 의해 원래 리턴형의 포인터로 처리되기
    때문에, 강력한 우선 순위를 가지는 괄호로 함수 포인터임을 명시.)

    예 1)

   
void foo();
   
void (*foo_ptr)();             // 로 정의/선언 후
    foo_ptr = &foo;                
// 로 함수의 주소를 대입 cf) void (*foo_ptr)() = &foo;
    (*foo_ptr)();                  
// 로 사용 cf) foo_ptr(); 도 가능

   
const char* bar(int num);
   
const char* (*bar_ptr)(int);   // 혹은 const char* (*bar_ptr)(int num);로 정의/선언
    bar_ptr = &bar;                
// 호출하려는 함수의 주소를 넣고
    (*bar_ptr)(5);                
// 로 사용 cf) bar_ptr(5); 도 가능

    예 2)

   
class MyClass {
   
public:
       
void Func1(const string& txt);
       
static void Func2(const string& txt);
    };

   
void (MyClass::*Func1Ptr)(const string&) = &MyClass::Func1;
   
void (*Func2Ptr)(const string&) = &MyClass::Func2;        

    MyClass mc;
    (mc->*Func1Ptr)(
"abc");
    (*Func2Ptr)(
"abc");            // 형태로 사용 cf) Func2Ptr("abc"); 도 가능

    예 3)

    만일 MyClass::Func1, MyClass::Func2에 대한 함수 포인터를 MyClass의 멤버 변수로 하려면

   
class MyClass {
   
public:
       
void Func1(const string& txt);
       
static void Func2(const string& txt);
       
void (MyClass::*Func1Ptr)(const string&); // 내부라도 MyClass 스코프임을 밝혀야 함.
       
void (*Func2Ptr)(const string&);
    };

    MyClass mc;
    mc.Func1Ptr = &MyClass::Func1;
// 클래스내에서 setter를 사용하면 그냥 &Func1
    mc.Func2Ptr = &MyClass::Func2;
// 편의상 public으로 지정해서 대입했음.

    (mc.*(mc.Func1Ptr))(
"abc");    // 또는 (mc.*mc.Func1)("abc");
    (*mc.Func2Ptr)(
"abc");         // 전역 함수 포인터와 유사하므로 mc.Func2Ptr("abc") 가능.

    cf) 다른 멤버 함수에서 멤버 함수 포인터를 사용할 때는
    (
this->*Func1Ptr)("abc");
    Func2Ptr(
"abc");               // (*Func2Ptr)("abc")

    물론 꼭 MyClass의 멤버 변수로 존재할 필요는 없다. 다른 클래스의 멤버 변수로도 가능하다.

    주의 : 위의 경우는 꼭 필요한 상황인지, 설계상의 문제는 없는지 다시 한 번 고려할 것.
           다른 방법으로도 충분히 해결되는 문제임.


나.
typedef의 사용법 & 함수 포인터 배열

    함수 포인터의 배열은 무척 간단하다. 위의 예제의 foo 함수의 경우에는

   
void (*foo_ptr[])() = {foo1, foo2, foo3, ...};     // 선언&정의 + 대입
    (*foo_ptr[i])();                                  
// 사용

    로 하면 된다. 다른 함수 포인터도 유사하다.

    위에서 밝힌대로 함수 포인터는 변수이다. 변수는 형을 가진다. 그러면 함수 포인터의 형은 무엇인가?
    호출하려는 함수의 원형이 그 함수 포인터의 형이다. 즉 
typedef를 사용할 수 있다는 말이 된다.
    foo 함수에 대한 함수 포인터의 경우를 예로 들면

   
typedef void (*foo_ptr)();

    형태로 사용한다. 다만
void (*foo_ptr)();는 foo_ptr이 함수 포인터의 이름인데 반해
   
typedef void (*foo_ptr)(); 에서 foo_ptr은 함수 포인터의 형, void (*)()을 뜻하게 된다.
   
int a처럼 선언하듯이 typedef를 쓰면 foo_ptr fp; 뿐만 아니라 배열 선언을 foo_ptr fp[5];
    처럼 쓸 수 있고, 한결 깔끔하고 편리해지게 된다.
    개인적으로 함수 포인터와
typedef는 꼭 같이 쓰기를 권유한다.

    예 1)

   
class MyClass {
   
public:
       
void Func1(const string& txt);
       
static void Func2(const string& txt);
    };

   
typedef void (MyClass::*Func1Ptr)(const string&);
   
typedef void (*Func2Ptr)(const string&);

    Func1Ptr fptr = &MyClass::Func1;      
// 한결 더 의미가 명확해지고 코드 중복을 피할 수 있다.
    Func2Ptr ftpr2 = &MyClass::Func2;

    MyClass mc;
    (mc.*fptr)(
"abc");
    (*fptr2)(
"abc");

    예 2)

   
class MyClass {
       
typedef void (MyClass::*Func1Ptr)(const string&);
       
typedef void (*Func2Ptr)(const string&);
   
public:
       
void Func1(const string& txt);
       
static void Func2(const string& txt);
        MyClass() : fptr(Func1), fptr2(Func2) { }
       
void Invoke(const string& txt)
        {
            (
this->*fptr)(txt);
            (*fptr2)(txt);
        }
   
private:
        Func1Ptr fptr;
        Func2Ptr fptr2;
    };

    MyClass mc;
    mc.Invoke(
"abc");


참고 사이트
http://www.newty.de/fpt/fpt.html#defi

신고


Posted by injunech
2015.09.02 06:50


< 함수 포인터 >

먼저 이 글은 포인터에 대한 이해를 필요로 한다.

포인터에 대한 기본지식이 있다고 가정하고 글을 쓰도록 하겠다.


int GetAreaEx( int x, int y )
{
    return x * y;
}



우선 이런 간단한 함수가 있다. 우리는 이 함수를 호출하기 위해 명시적으로

GetAreaEx( x, y );

이런식으로 기술해야 한다.

하지만 예를 들어 GetArea2, GetArea3, ..., GetAreaN 이런식으로 비슷한 함수가 존재하고

이를 상황에따라 다르게 호출해야 한다면 이 방식으로는 관리도 어려울 뿐더러 효율성도 떨어지고 코드량도 많이질 것이다.

또한 외부(스크립트 등)에서 어떤 특정한 함수를 호출하려 할때도 방법이 묘연할 것이다.


int (*GetArea)( int, int );

이 선언은 무엇일까?

언뜻보기에는 함수를 선언하는 것 같다.

이 선언은 함수에 대한 포인터를 선언한 것이다.

변수의 주소를 담는 포인터와 마찬가지로 함수포인터는 함수의 주소를 담는다.

GetArea = GetAreaEx; // 함수포인터 GetArea에 GetAreaEx()의 주소를 담는다
int nArea = (*GetArea)( x, y ); // (*GetArea)( x, y ); 로 GetAreaEx()함수를 호출하고 리턴받은 값을 nArea에 대입



이런식으로 GetAreaEx를 호출할 수 있다.

유의할점은 *GetArea를 꼭 ()로 감싸주어야 한다는 사실이다.

빼먹으면 컴파일러가 함수포인터를 통한 호출로 인식하지 못한다.


int (*GetArea[])( int, int ) = { GetAreaEx, GetArea2, GetArea3, ..., GetAreaN };

이것은 함수포인터 배열을 정적으로 선언한 것이다. 이렇게 배열로 기능이 비슷한 함수들을 묶어놓았다.

void CallFunc( int nState, int x, y )
{
    int nResult = (*GetArea[nState])( x, y );
}



그리고 그 함수들을 상황에 맞게 호출한다.

만약 함수포인터를 쓰지 않는다면

void CallFunc( int nState, int x, int y )
{
    int nResult;
    switch( nState )
    { 
         case STATE_EX:
              nResult = GetAreaEx( x, y );
         break;
         case STATE_2:
              nResult = GetArea2( x, y );
         break;
         case STATE_3:
              nResult = GetArea3( x, y ); 
         break;
    }
}


위와 같이 기술해야 할 것이다.

두 방식의 차이점과 함수포인터의 이점을 알 수 있겠는가

그렇다면 함수포인터 배열을 동적으로 할당하는 방법은 없을까?

다음과 같은 방법으로 할당할 수 있다.

int (**GetArea)( int, int ); // 함수포인터의 포인터
GetArea = new (int (*[N])( int, int )); // N은 배열의 크기



그리고 다음과 같이 사용하면 된다.

GetArea[0] = GetAreaEx;
GetArea[1] = GetArea2;
GetArea[2] = GetArea3;
...

int nResult = (*GetArea[nState])( x, y );



물론 사용후 delete [] GetArea; 해서 해제하는것을 잊으면 안된다.



< 클래스 멤버함수의 함수포인터화 >

함수포인터는 함수의 주소값을 담는다고 했다.

그렇다면 클래스 멤버함수의 주소값도 단순히 함수포인터에 담아서 호출할 수 있지 않을까?

int (*func)();
func = CFunc::GetArea;


하지만 이 방법은 GetArea()멤버함수가 static으로 선언되었을 때만 가능하다.

static으로 선언되지 않은 멤버함수(멤버변수를 건들여야 하는 멤버함수)를 이 방법으로 담으려 한다면 컴파일 에러가 뜰 것이다.

여기에 다음과 같은 해결방법이 있다.

첫번째 방법은

class CFunc
{
public:
    static int GetArea( CFunc * cls, int x, int y );
};



위와 같이 선언하고 호출할때 해당 인트턴스의 포인터를 넘겨줘서

int GetArea( CFunc * cls, int x, int y )
{
    int a = cls->GetZ();
}



이런식으로 멤버변수를 읽거나 쓸수 있겠지만 이 방식으로는 한계가 있다.

Get, Set 같은 public 외부함수로 억세스하지 않으면 private나 protected안에 선언되어 있는

멤버변수는 건드릴 수 없다.

두번째는 멤버함수의 소속을 명시화하는 방법이다.

int (CFunc::*func)( int, int );
func = CFunc::GetArea;
CFunc A;
(A.*func)( x, y );



위와 같은 방법으로 해결가능하다. 물론 호출할 인스턴스가 명확해야 한다.

세번째는 클래스 안에 함수포인터를 멤버변수로 두고 별도의 함수포인터를 컨트롤하는 멤버함수를 만드는 방법이 있다.

이 방법이 멤버함수 관리가 가장 쉬우며 효율적이다.

class CFunc
{
public:
    int (CFunc::*pFunc)( int, int );
    int GetArea( int x, int y );
    void CallFunc( void ) { (this->*pFunc)( x, y ); } // CallFunc 함수호출시 자체 오버헤드를 줄이기 위해 inline
    CFunc();
    ~CFunc() {}
};

CFunc::CFunc()
{
    pFunc = GetArea;
}
int CFunc::GetArea( int x, int y )
{
    return x * y;
}


위와 같다면 CallFunc(); 로 GetArea 호출이 가능해진다.

지금은 단순히 한개의 멤버함수 호출만 할뿐 의미가 없다. 이제 실제 효율적으로 쓰이게 배열을 써보자.

class CFunc
{
public:
    int (CFunc::**pFunc)( int, int );
    int GetArea( int x, int y );
    void CallFunc( int nState, int x, int y ) { (this->*pFunc[nState])( x, y ); }
    CFunc();
    ~CFunc();
};

CFunc::CFunc()
{
    // init
    pFunc = new (int (CFunc::*[10])( int, int )); // 동적할당, 10에는 원하는 멤버함수 갯수만큼
    // 0번은 남겨둔다.

    pFunc[1] = GetArea;
    pFunc[2] = GetAreaEx;
    pFunc[3] = GetArea2;
    pFunc[4] = GetArea3;
    ...
    pFunc[9] = GetArea9;
}
CFunc::~CFunc()
{
    delete [] pFunc; // 해제
}
int CFunc::GetArea( int x, int y )
{
    return x * y;
}


자, 이제 함수하나의 호출로 상황에따라 여러 멤버함수를 호출할 수 있는 기반이 마련되었다.

CFunc A;
A.CallFunc( nState, x, y );



이렇게...

어떠한가. 함수포인터의 위력이 느껴지는가?




< STL을 이용한 함수포인터 관리 >

우리는 지금까지 함수포인터를 동적으로 배열을 할당해서 써왔다.

함수 포인터를 STL(Standard Template Library)을 써서 관리해보자.

클래스의 멤버함수의 함수포인터화에서 3번째 방법을 조금 개선시켜 보겠다.



단순히 인덱스(숫자)를 이용한 관리라면 deque정도가 괜찮을듯 싶으나,

만약 함수의 이름을 문자열로 호출하고 싶다면 map을 써볼 수 있다.

(만약 FuncCall( "GetArea", x, y ); 이런식으로 멤버함수를 호출하고 싶다면)

map은 내부적으로 트리구조를 가지고 있다.

그래서 따로 정적/동적으로 배열을 할당하지 않아도 입력된 값을 비교해서 스스로 자신의 크기를 늘린다.

mapValue["GetArea"] = 99;

이런식으로 []안에 숫자 뿐만아니라 비교할 수 있는 모든 것이 들어갈 수 있다.

먼저 map을 사용하기 위해

#include < map >
using namespace std;



를 선언한다. map은 표준 네임스페이스를 사용하므로 std의 이름공간을 활용한다.

map< []안에 들어갈 타입, 입력될 데이터타입, 비교 논리 > mapFunctor;

선언방법은 이렇게 되는데 비교 논리는 첫번째 인수가 클래스이고 안에 비교오퍼레이터가 있다면 생략가능하다

자, 이제 해보자.



struct ltstr
{
    bool operator() ( const char * s1, const char * s2 ) const
    {
         return strcmp( s1, s2 ) < 0;
    }
};

class CFunc
{
public:
    typedef int (CFunc::*_Func)( int, int );
    map< const char *, _Func, ltstr > mapFunctor;
    int GetArea( int x, int y );
    void CallFunc( const char * szFuncName, int x, int y ) 
    { 
         (this->*mapFunctor[szFuncName])( x, y ); 
    }

    CFunc();
    ~CFunc();
};

CFunc::CFunc()
{
    // init
    mapFunctor["GetArea"] = GetArea;
    mapFunctor["GetAreaEx"] = GetAreaEx;
}
CFunc::~CFunc()
{
    // map 클리어
    mapFunctor.clear();
}
int CFunc::GetArea( int x, int y )
{
    return x * y;
}


char * 대신 string을 사용한다면 string안에 내부적으로 비교 오퍼레이터함수가 있기 때문에

map< string, _Func > mapFunctor;

이렇게 선언하고 사용할 수 있을 것이다.

이제 A.CallFunc( "GetAreaEx", x, y ); 란 호출로 GetAreaEx를 호출할 수 있다.

이 방식은 여러가지로 응용가능한데 스킬명에 의한 화면효과 호출이라던지

C로 미리 작성된 내부 함수를 외부 스크립트로 호출한다던지 할때 유용하게 쓰일 수 있다.

(스크립트 호출일 경우 함수이름을 인덱스화 해서 deque를 쓰는게 속도상 더 유리할 듯 하다)






출처 : http://zeph.tistory.com/155



신고


Posted by injunech
2013.07.27 00:44



오늘 울 회사 과장님께서 나에게 VC++ 에서 프로젝트 기본값 -> 문자 집합을 멀티바이트로 하는지 물어보셨다.

난 당연히 멀티바이트로 쓴다고 말씀드렸다.

그리고 왜 물어보시냐고 여쭤보니깐 문자 집합을 멀티 바이트로 해놓으면 나중에 ANSI -> 유니코드, 유니코드 -> ANSI로 변환할 때 문제가 없기 때문이라고 말씀하셨다.

과장님께서 클라이언트에서 다국어 처리를 할 때 입력은 ANSI로 받고, ANSI로 받은 것을 내부에서 유니코드로 변경하고, 이것을 출력할 때나 서버로 전송할 때는 다시 ANSI로 변경한다고 하셨다.

왜 그렇게 하냐고 여쭤보니.. ANSI로 다국어 문자를 받으면 처리하기가 애매한 경우가 많은데 이것을 유니코드로 변경해서 필터링이나 짤라주기 등을 해주고, 이것을 다시 ANSI로 변경하면 괜찮다는 것이다.

음.. 듣고보니 그런거 같다.

ANSI로는 다국어 처리를 하기란 상당히 까다로울 거 같은데.. 어떤 나라는 글자 하나가 1BYTE고, 어떤 나라는 글자 하나가 2, 3, 4, 5, 6 이렇게 다양한데 이런걸 다 ANSI로 1BYTE씩 파씽해서 처리하기가 상당히 까다로운거 같다. 유니코드로 변경하면 글자가 몇 바이트인지 신경 쓸 필요가 없으니깐 좋은거 같다.


퍼옴 : http://blog.daum.net/ohkuetai/5993663



비슷한듯하면서 헷갈릴수 있는데..

유니코드는 분명 모든 문자에 대해 동일한 코드체계로 가져간다는 것이다.


멀티바이트는 일반적으로 안시코드(1바이트 문자셋)로 표기되지 않는 문자셋을 표기하기 위해 2개이상의 바이트를 사용한거를 말하는 것이다. .. 그래서 1바이트문자와 2바이트문자가 같이 등장하게 된다.


VISUAL C++같은데서는 _MBCS, _UNICODE 정의와 T-매크로로 두가지 버전에 대해 코드레벨에서 호환되도록 작성할 수 있게 해준다.


그렇지만! 분명 멀티바이트/유니코드는 그 차이를 분명히 알고 써야 한다!!!



최근엔 유니코드로만 해서 _MBCS 처리를 별로 할일이 없긴 하지만, 그래도 멀티바이트 처리를 할땐 그 차이를 알고 조심해야 된다는 것을 강조하고 싶다.


암생각없이 strlen("한글abc") 이라고 하는데.. 이게, _mbstrlen("한글abc")하고, _wcslen(L"한글abc") 하고..3가지 버전이 어떤 값을 뱉어낼지 알아야 한다는 것이다.


assert(strlen("한글abc") == 7); // 7 bytes

assert(_mbstrlen("한글abc") == 5); // 7 bytes

assert(_wcslen(L"한글abc") == 5); // 10 bytes


그나마 _tcslen  이 있긴하지만, 내가 지금 필요한게 byte인지, 글자개수 인지를 정확히 알아야지 제대로 쓸수 있다. (안그럼?? 뻑나겠지. ^^)


..


이제 멀티바이트 사용할 때 유용한 함수들(그래서 여기저기 잘/많이 사용하게 되는)을 보면..

 

int _ismbbtrail( unsigned int c );

int _ismbblead( unsigned int c );

 

같은게 있다..  텍스트편집기를 직접 만드는데, 안시빌드를 한다! 그러면 위의 두 함수 없이 어떻게 backspace/delete 키를 처리할까~ ^^


퍼옴 : http://blog.naver.com/jooyunghan?Redirect=Log&logNo=100004007609



VS 2005 부터는 프로젝트 생성시 기본적으로 유니코드 문자집합을 선택하게 되어 있습니다.
처음엔 속성에서 일일이 멀티 바이트 문자집합으로 바꿔주기도 했지만 곧 그것도 
귀찮더군요. 그래서 현재는 그냥 유니코드에 맞는 코딩을 하고 있습니다. 

윈도우 프로그래밍에선 유니코드나 멀티바이트나 별 차이는 없습니다.

단지 문자나 문자열 앞에 L 또는 TEXT() 매크로를 사용해주는 것만으로도 충분합니다.

그 외엔 운영체제에서 자체 지원하는 

lstrcpy(strcpy)
lstrcmp(strcmp)
lstrcat(strcat)
lstrlen(strlen)

정도만 기억하시면 충분합니다. 

제 경우는 문자열엔 TEXT()를 문자엔 L을 붙여서 코딩하고 있습니다.
둘의 차이는 거의 없기에 어떻게 사용하든 본인의 자유겠죠.
(int 를 제외한 변수를 선언할때도 주의해주셔야 합니다만...
주의할 거래봐야 char 를 TCHAR로 선언해주는 것 정도죠)

하지만 콘솔 프로그래밍으로 넘어가면 이건 그렇게 만만치가 않습니다.

VS 2005 이전에 유니코드를 지원하던 앞에 w가 붙는 함수가
2005로 넘어오면서 w..._s 형으로 다시 바뀐 겁니다. 

_s 형으로 바뀐 건 좋은데 가끔은 받아들이는 인수의 수조차 바뀐 것들이 
있어서 사람을 미치게 합니다. 

예를 들면 

wstrcat_s 
예전엔 2개의 인수만을 받던 strcat 함수가 3개의 인수를 받도록 바껴버린거죠.
물론 여전히 2개의 인수를 받고 유니코드를 지원하는 lstrcat을 사용하면 그만이죠

하지만 어딘가 제가 모르는 저런 식의 함수가 분명 있을 겁니다. 
(콘솔 프로그램의 의미가 거의 바닥을 치는데 이런 예기는 사실 별 의미가 없죠.)

그래도 혹시나 콘솔 프로그램을 작성하시다가 유니코드를 지원하는 함수를 
알 필요가 있을 땐 MSDN을 이용해 보십시오. 영어를 몰라도 
대충보면 어떤 함수를 사용해야 하는지 찾으실 수 있으실 겁니다. 

서론은 여기까지 하고 이 글을 적는 본론으로 들어가죠

제가 콘솔 프로그램을 하나 만들었습니다. 물론, 유니코드를 사용했죠.

그런데 막상 실행해보니 도스창에 한글이 찍히지 않는 겁니다. 
에러 경고 하나도 뜨지 않는데 한글을 안 찍히니 미칠 지경이더군요. 

미친듯이 찾아 본 결과 답을 찾았습니다. 물론, 어째서 그런지는 모릅니다.
전혀요; 

_wsetlocale(LC_ALL,TEXT("Korean"));

이걸 적어줘야 하더군요. 함수 이름만으로는 지역변경 - 한국 쯤 되는군요;

위 함수의 정의는 locale.h 안에 들어 있습니다. 

위 함수를 적어주고 컴파일 하니 한글 출력이 되더군요. 
(망할 유니코드...)

그 외에 이런 경우도 있습니다. 멀티바이트 유니코드 안 따지는 함수들이 
가끔 있습니다. 그 형태 그대로 유지해서 어느 쪽에나 적응가능하죠.

그런데 가끔 이 함수들이 인수로 받아들이는 데이타형이 유니코드에 맞지 않을때가 있습니다.

예를 들면 소켓 프로그래밍의 send,recv 함수 같은 게 있죠
send,recv 함수는 두번째 인수로 (const char*) 형을 받습니다.

이 경우 

TCHAR a[10];

send(socket,(char*)a,......);
//에러

send(socket,(char FAR*)a,......);
//성공

이런 식으로 적어주어야 합니다. recv 역시 마찬가지로 char FAR*
형으로 캐스팅해주면 유니코드 문자열을 넘겨줄 수 있습니다.

지금까지 제가 유니코드를 사용해 코딩하면서 겪은 문제점은 이걸로 대충 다 적었습니다. 

아... 마지막으로 강조해야 할 부분 유니코드는 한 문자가 1바이트가 아니라 2바이트란 거죠.
(멀티바이트는 1바이트 문자셋과 2바이트 문자셋이 섞여 있죠)

유니코드에선 심지어 영어도 2바이트를 차지합니다. 

크게 중요해 보이지 않는 거 같지만 이걸 깜박하면 자신도 모르게 에러를 
내 버릴 수가 있습니다. (특히 메모리 활당 부분)

그럼, 별 의미 없는 글이나마 읽어주셔서 감사합니다.

p.s : wprintf 함수의 경우 유니코드 문자열을 출력할때는 %s 가 아니라 %S를 써야 합니다
안 그럼 알 수 없는 외계어가 출력되죠.

신고


Posted by injunech
2013.07.09 17:07



BroadcastReceiver.cpp


BroadcastSender.cpp


신고

'Programming > CPP' 카테고리의 다른 글

함수 포인터 및 클래스 멤버함수의 함수포인터화  (0) 2015.09.02
멀티바이트와 유니코드  (0) 2013.07.27
BroadCasting 참고 코드  (0) 2013.07.09
Virtual Key Code  (0) 2013.07.06
키보드 이벤트 처리하기  (0) 2013.07.05
Visual Studio 2012 단축키  (0) 2013.07.05


Posted by injunech
2013.07.06 15:09


KeyKey Code
048
149
250
351
452
553
654
755
856
957


KeyKey Code
A65
B66
C67
D68
E69
F70
G71
H72
I73
J74
K75
L76
M77
N78
O79
P80
Q81
R82
S83
T84
U85
V86
W87
X88
Y89
Z90


KeyKey Code
F1112
F2113
F3114
F4115
F5116
F6117
F7118
F8119
F9120
F10121
F11122
F12123


KeyKey Code
Number Pad 096
Number Pad 197
Number Pad 298
Number Pad 399
Number Pad 4100
Number Pad 5101
Number Pad 6102
Number Pad 7103
Number Pad 8104
Number Pad 9105


KeyKey Code
BACKSPACE8
TAB9
ENTER13
SHIFT16
CTRL17
ALT18
PAUSE/BREAK19
CAPS LOCK20
ESCAPE27
PAGE UP33
PAGE DOWN34
END35
HOME36
LEFT ARROW37
UP ARROW38
RIGHT ARROW39
DOWN ARROW40
INSERT45
DELETE46
LEFT WINDOW KEY91
RIGHT WINDOW KEY92
SELECT KEY93
MULTIPLY106
ADD107
SUBTRACT109
DECIMAL POINT110
DIVIDE111
NUM LOCK144
SCROLL LOCK145
SEMI-COLON186
EQUAL SIGN187
COMMA188
DASH189
PERIOD190
FORWARD SLASH191
GRAVE ACCENT192
OPEN BRACKET219
BACK SLASH220
CLOSE BRAKET221
SINGLE QUOTE222


신고

'Programming > CPP' 카테고리의 다른 글

멀티바이트와 유니코드  (0) 2013.07.27
BroadCasting 참고 코드  (0) 2013.07.09
Virtual Key Code  (0) 2013.07.06
키보드 이벤트 처리하기  (0) 2013.07.05
Visual Studio 2012 단축키  (0) 2013.07.05
소켓통신  (0) 2013.07.05


Posted by injunech
2013.07.05 21:50


키보드 이벤트 처리하기

본장은 키보드로부터 문자키와 펑션키등이 입력되었을 때 이벤트를 처리하는 방법에 대해서 설명합니다. 키보드에 있는 모든키와 시스템에서 사용하는 키들을 직접 제어 할수 있는 방법을 배우시게 될것입니다. 한들윈도우에서는 한영전환이 자동으로 내장되어 있습니다. 키보드 입력키로 현재 한글상태인지 영문상태인지 확인할 수가 없습니다. 이것을 확인하는 방법은 IME를 이용하는 것인데 본장에서는 키보브에서 한영 전환이 되는 것을 알아보는 방법까시 설명하게습니다.

본장을 통해서 키보드에서 발생하는 모든 이벤트를 만드는 방법을 알수 있을것입니다.


키보드 입력 메시지

키보드로부터 키가 입력될때에는 표1과 같은 내용의 메시지들이 전달됩니다.

(표1) 키눌림 메시지


  메시지

내용

WM_CHAR

문자키가 눌려졌  을경우

WM_KEYDOWN

키보드에서 키가 눌려짐

WM_KEYUP

키보드에서 키가 눌려졌다 띄어짐

WM_SYSKEYDOWN

Alt키와 조합된 시스템 키가 눌려짐

WM_SYSKEYUP

Alt키와 조합된 시스템 키가 눌려졌다 띄어짐

WM_CHAR은 문자키가 눌려졌을 때 발생하는 메시지입니다. 보통 알파벳과 한글 그리고 숫자키와 등 ANSI 코드에 포함된 키들의 눌려졌을 때 발생하는 메시지입니다. 쉽게 말해서 F1-F12키나 또는 Insert,Home,화살표키등을 제외한 일반적으로 화면에 나타나는 키들을 생각하시면 됩니다.

WM_KEYDOWN은 키보드에서 키가 눌려 졌을 경우 발생됩니다. 키가 눌려진 상태에서는 WM_KEYDOWN이 키를 띄었을 경우 WM_KEYUP가 발생됩니다.

만일 'a'라는 키가 눌렀다 띄었다고 가정을 한다면 제일 처음으로 발생되는 메시지가 WM_KEYDOWN입니다. 이후에 WM_CHAR가 발생되고 그다음 WM_KEYUP가 발생됩니다. 만일 Shift키를 누르면서 'A'키를 키보드에서 설정하였다면 어떻게 메시지가 발생될까요? 먼저 Shift키가 눌려졌다는 WM_KEYDOWN 이 발생되며 다음으로 'A'에 해당하는 WM_KEYDOWN 그다음으로 ‘A'에 해당하는 WM_CHAR 그다음으로 'A' 에 해당하는 WM_KEYUP 그다음으로 Shift 에 해당하는 WM_KEYUP 가 발생됩니다. 이런식으로 WM_CHAR와 WM_KEYDOWN과 WM_KDYUP는 상호 연관되어 발생됩니다. 그러나 F1키 같은 펑션키를 눌렀을경우에는 WM_CHAR은 발생되지 않습니다.

WM_SYSKEYDOWN은 시스템 키를 눌렀을 때 발생됩니다. 이키란 Alt와 함께 조합된 키늘 말합니다. Alt+F4는 종료키며 Alt+Tab는 어플리케이션 프로그램의 상호 전환인 것 같은 Alt와 조합된 키를 시스템 키라고 하며 이것은 특별하게 WM_SYSKEYDOWN과 WM_SYSKEYUP로 분리하여 메시지를 설정하였습니다.

WinMain에서  다음과 같은 문장을 보신적이 있을것입니다.

while(GetMessage(&msg,NULL,0,0))

{

       TranslateMessage(&msg);

       DispatchMessage(&msg);

}

여기에서 TranslateMessage는 WM_KEYDOWN을 받아서 현재 눌려지 키의 값을 가지고 문자를 만들어 내는 기능을합니다. 예를 들어 Caps Lock가 켜지지 않은 상태에서  Shift 키를 누른상태에서 A자를 눌렀다면 실제 코드는 “A"입니다. 그러나 Shift키를 누르지 않은 상태에서 A자를 눌렀다면 이코드는 ”a"인것입니다. 이렇게 TranslateMessage는 WM_KEYDOWN에의해서 WM_CHAR를 만들어 내는 작업을 합니다. WM_CHAR이나 WM_KEYDOWN 메시지와 함께 넘겨주는 wParam에는 key값이 설정되어 있습니다. lParam에는 이키들의 정보가 들어 있는데 키의 정보는 표2와 같습니다.


    바이트 번호

(상위첫바이트가 0)

키 값

내용

  0

Transition Statate

키가 눌려지면 1 띄어지면 0

  1

Previous Key State

이전에 어떤 키가 눌려졌는가에 대한 값

2

Context Code

Alt키가 눌려지면 1 그렇지 않으면 0

3-6

사용하지 않음

 

  7

Extended Key Flag

확장 키보드에서 키가 눌려지면

  1 그렇지 않으면 0

  8-15

OEM Scan Code

키보드 스캔코드

  16-31

Repeat Count

킷값의 반복횟수

       (표2) lParam 에 설정되어 있는 값


lParam의 상위 0비트가 1로 설정되어 있으면 키가 눌려진것이고 0이면 키가 띄어진 형태입니다. 두 번째 비트는 Previous Key State로 이전에 어떤 키가 눌려졌는가에 대한 값입니다. 이런 의미라고 생각한다면 어플리케이션이 처음 시작되고 키를 누른다음부터는 Previous Key State는 어떤 키가 눌려진상태는 0이고 띈상태에서 1로 설정됩니다. Contex Code란 Alt키가 눌려졌을 경우 1로 그렇지 않을 경우 0이됩니다. Extended Key Flag는 확장키보드 즉 숫자판이 있는 키보드에서 NumLock나 / 또는 Enter키가 눌려졌을 경우 1로 그렇지 않을 경우 0으로 설정됩니다. OEM Scan Code는 현재 눌려진 키보드 스캔코드 값입니다. 일반적으로 문자 값으로 보아도 되지만 윈도우에서느 가상키를 사용하고 이가상키는 wParam으로부터 얻을수 있고 또한 문자값은 WM_CHAR와 함께 발생된 wParam으로부터 얻을수 있기 때문에 사용하지 않습니다. 16-31은 킷값의 반복횟수가 저장됩니다. 이것은 키를 누른 메시지가 처리되기전에 킷값이 또 입력되었을 때 연속적으로 메시지 큐에 저장하는 것이 아니라 WM_KEYDOWN 메시지를 하나로 묶고 반복 계수를 증가시킴으로써 현재 키가 몇번의 반복으로 눌려졌다는 것을 알려줍니다.

이반복 계수를 무시하게 되면 키를 연속적으로 몇번을 눌러도 그것이 수행되지 않았을 경우 잃어 버리게 됩니다. 결국 WM_MOUSEMOVE처럼 현재성을 중시하여 이전에 들어왔던 메시지가 무시되는 것입니다. 그러나 키보드 입력에서는 킷값을 놓치지 말아야 할경우가 있습니다. 이럴경우에는 반복계수를 확인하여 그만큼 연속적으로 키를 설정해 주어야 합니다.


가상키 코드

가상 키코드란 windows.h에 정의한 키코드 값들입니다. 표3은 가상키의 일부분입니다.

  키 코드

  내용

코드값

VK_CANCEL

Ctrl+Break를 눌렀을때

03

VK_BACK

Backspace 키

08

VK_TAB

Tab키

09

VK_RETURN

Enter 키

13

VK_SHIFT

Shift키

16

VK_CONTROL

Ctrl 키

17

VK_MENU

Alt 키

18

VK_CAPITAL

Caps Lock 키

20

VK_ESCAPE

Esc 키

27

VK_SPACE

Space 바

32

VK_PRIOR

Page Up 키

33

VK_NEXT

Page Down 키

34

VK_END

End 키

35

VK_HOME

Home 키

36

VK_LEFT

좌측 화살표

37

VK_UP

위쪽 화살표

38

VK_RIGHT

우측 화살표

39

VK_DOWN

아랫쪽 화살표

40

VK_INSERT

Insert키

45

VK_DELETE

Delete키

46

VK_F1 ~ VKF10

F1-F10

112-121

VK_NUMLOCK

Num Lock

144

VK_SCROLL

Scroll Lock

145

        (표3) 가상키 코드


WM_KEYDOWN 키가 눌려졌을 경우 해당되는 키가 눌려진 것에 대한 프로그램을 제작한다면 위의 정의 코드를 이용하여 다음과 같이 할수 있습니다.


case WM_KEYDOWN:

       switch(wParam)

       {

               case VK_F1:

                               //F1 키가 눌려졌을 때 내용

                               break;

               case VK_F2:

                               //F2 키가 눌려졌을 때 내용

                               break;

               case VK_RETURN:

                               //Enter 키가 눌려졌을 때 내용

                               break;

               case VK_SPACE:

                               //Space 바가 눌려졌을 때 내용

                               break;

               :

       }

       break;


이외에서 마우스 버튼이 눌려지게 되면 VK_LBUTTON,VK_RBUTTON,VK_MBUTTON의 값이 들어오게 됩니다. 여기에서 VK_MBUTTON은 마우스 중간 버튼을 의미하는데 이값이 들어오기 위해서는 특정 마우스 드라이버가 설정되어 있거나 또는 다른 방법에 의해서 중앙버튼이 활성화 된상태에서만 가능합니다. 실제로 윈도우에서는 마우스 중앙버튼을 거히 사용하지 않기 때문입니다.


키상태 얻기

WM_KEYDOWN 이나 WM_SYSKEYDOWN은 Shift키나 Ctrl키 Alt키 Caps Lock키 Scroll Lock키등의 눌려진 상태를 wParam이나 또는 lParam을 통해서 알려주지 않습니다. 이런 키들이 눌려진 상태를 알고자 할 경우 GetKeyState 함수를 사용합니다.


SHORT GetKeyState(

int nVirtKey //가상키값

);


예를 들어서 Shift키를 가 눌려졌는가를 알기 위해서는 다음과 같이 합니다.


short code;

code=GetKeyState(VK_SHIFT);

만일 code가 음수일경우에는 Shift키가 눌려진것이고 음수가 아닐경우에는 눌려지지 않은것입니다. GetKeyState함수는 표3에 정의되어 있지않는 특수한 경우의 키상태로 알수 있습니다. 즉 좌측 Alt키 우측 Alt 좌측 Shift 우측 Shift 좌측 Ctrl키 우측 Ctrl 키등을 얻을수 잇습니다. 이 키에 대한 정의 값은 다음과 같습니다.


VK_LSHIFT VK_RSHIFT

VK_LCONTROL VK_RCONTROL

VK_LMENU VK_RMENU


만일 좌측 Alt키가 눌려진 것을 알고자 한다면 다음과 같이 할수 있을것입니다.


short code;

code=GetKeyState(VK_LMENU);



키정보 얻기 예제 KeyEx

KeyEx는 키가 눌려 졌을 때 lParam에 있는 정보와 wParam에 있는 정보를 화면에 출력하는 예제입니다. lParam은 비트연산을 이용하여 각 비튼의 값의 정보를 얻어서 화면에 출력을 합니다.

(프로그램 소스)

//시스템 키입력 예제

//KeyEx.c

#include <windows.h>


void main();


LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;


int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "KeyEx" ;

     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;


     wndclass.cbSize        = sizeof (wndclass) ;

     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

     wndclass.lpfnWndProc   = WndProc ;

     wndclass.cbClsExtra    = 0 ;

     wndclass.cbWndExtra    = 0 ;

     wndclass.hInstance     = hInstance ;

     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

     wndclass.lpszMenuName  = NULL ;

     wndclass.lpszClassName = szAppName ;

     wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;


     RegisterClassEx (&wndclass) ;


     hwnd = CreateWindow (szAppName,    

                           "키입력예제:KeyEx",  

                    WS_OVERLAPPEDWINDOW, 

                    CW_USEDEFAULT,       

                    CW_USEDEFAULT,       

                    CW_USEDEFAULT,       

                    CW_USEDEFAULT,       

                    NULL,                

                    NULL,                

                    hInstance,           

                           NULL) ;                    


     ShowWindow (hwnd, iCmdShow) ;

     UpdateWindow (hwnd) ;


     while (GetMessage (&msg, NULL, 0, 0))

          {

          TranslateMessage (&msg) ;

          DispatchMessage (&msg) ;

          }

     return msg.wParam ;

     }



LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

     {

     HDC         hdc ;

     PAINTSTRUCT ps ;

        int scancode;

        char temp[80];

switch (iMsg)

      {

       case WM_CREATE :

            return 0 ;

       case WM_PAINT :

          hdc = BeginPaint (hwnd, &ps) ;

          EndPaint (hwnd, &ps) ;

           return 0 ;

     case WM_KEYDOWN:

     case WM_SYSKEYDOWN:

     case WM_KEYUP:

     case WM_SYSKEYUP:

     case WM_CHAR:

       hdc=GetDC(hwnd);

       //문자 코드 얻기

       wsprintf(temp,"문자코드=%c",wParam);

       TextOut(hdc,0,20,temp,strlen(temp));

       //Transition 값 얻기

       ((lParam&0x80000000)==0x80000000) ? wsprintf(temp,"Transition=1") :wsprintf(temp,"Transition=0");

       TextOut(hdc,0,40,temp,strlen(temp));

       //Previous Key 값 얻기

       ((lParam&0x40000000)==0x40000000) ? wsprintf(temp,"PreviousKey=1") :wsprintf(temp,"PreviousKey=0")  ;

       TextOut(hdc,0,60,temp,strlen(temp));

       //ContenxCode 값 얻기

       ((lParam&0x20000000)==0x20000000) ? wsprintf(temp,"ContextCode=1") :wsprintf(temp,"ContextCode=0")  ;

       TextOut(hdc,0,80,temp,strlen(temp));

       //ExtendKey값 얻기

       ((lParam&0x01000000)) ? wsprintf(temp,"ExtendKey=1") :wsprintf(temp,"ExtendKey=0")  ;

       TextOut(hdc,0,100,temp,strlen(temp));

       //Scan code 값 얻기

       scancode=HIWORD(lParam)&0xff;

       wsprintf(temp,"Scancode=%04d",scancode);

       TextOut(hdc,0,120,temp,strlen(temp));

       wsprintf(temp,"RepeatCount=%d",LOWORD(lParam));

       TextOut(hdc,0,140,temp,strlen(temp));

       ReleaseDC(hwnd,hdc);

       return 0;

case WM_DESTROY :

            PostQuitMessage (0) ;

             return 0 ;

       }


     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

     }

(프로그램 소스끝)

그림 1은 KeyEx 출력 결과입니다.












     (그림 1) KeyEx 출력 결과



카렛 만들기

카렛이란 윈도우에서 키보드입력시 사용되는 커서를 이야기 합니다. 도스 시절는 글자를 입력할 때 화면에 깜빡이는 직선 바를 커서라고 하였으나 윈도우에서는 카렛이라고 합니다. 윈도우에서 커서는 마우스 커서를 의미하며 워드 프로세서등에 나타나는 키보드 입력시의 커서를 카렛이라고 합니다.

카렛을 만들고 보여주고 그리고 삭제할경우에는  다음과 같은 순서로 됩니다.


//카렛을 만든다.

CreateCaret(hwnd,NULL,xsize,ysize);

//카렛을 보여준다

ShowCaret(hwnd);

//카렛의 위치를 설정한다

SetCaretPos(pos*xsize,0);

   : 문자 입력을 받는동안 카렛의 위치를 바꾸어 준다.

//카렛을 감춘다

HideCaret(hwnd);

//카렛을 삭제한다.

DestroyCaret();


먼저 CreateCaret 함수를 이용하여 카렛을 만듭니다. 첫 번째 인자는 윈도우 핸들이며 두 번째 인자는 HBITMAP 형입니다. 이곳에 비트맵을 설정하면 비트맵 모양의 카렛이 설정됩니다. xsize와 ysize는 카렛의 가로와 세로 크기입니다. 보통 카렛을 만들고자 할 경우 설정된 폰트의 가로세로크기와 같게 만드는 것이 보기에 좋습니다. 그렇기 때문에 xsize와 ysize를 얻기 위해서는 GetTextMetrics 사용하는 것이 좋습니다.


GetTextMetrics(hdc,&tm);

        xsize=tm.tmAveCharWidth;

        ysize=tm.tmHeight;


GetTextMetrics는 GDI편에서 설명하였듯이 문자의 크기 정보를 리턴합니다. 여기에서 tmAveCharWidth 는 문자들의 평균 폭이며 tmHeight는 문자의 높이이므로 이크기에 맞게 카렛을 설정하는 것이 적당합니다.

이렇게 카렛을 만든다음 ShowCaret하시면 카렛이 나타나고 카렛을 특정위치로 이동시키고자 할경우에는 SetCaretPos를 하시면 됩니다.

SetCaretPos는 키보드에서 문자를 입력할 때 계속적으로 이동해야 합니다. 예를들어 "Ab“ 라는 입력이 있었다면 "Ab"다음의 위치로 커서가 이동해야 합니다. 윈도우에서는 고정 폰트를 사용하지않고 가변 폰트를 사용하기 때문에 글자의 폭이 다 다릅니다. 그렇기 때문에 문자의 수를 파악하여 카렛 위치를 이동시키면 커서가 문자열 바로 뒤로 붙지 않습니다. 예를 들어서 "Aj"라는 입력을 했을 경우 평균 폭이 10이라고 가정을 하고 문자열이 두 개이므로 첫 번째 위치부터 20포인트 이후로 이동한다면 "Aj" 보다 더 뒤에 커서가 위치할것입니다.  그이유는 평균 폭이 10이지 'j'같은 글자는 10이하는 작은 폭을 가지고 있기 때문입니다. 카렛 위치를 이동할 때 현재 위치까지의 문자열 전체 폭을 얻고 그위치로 이동하는 것이 좋습니다. 문자열의 폭을 얻는 함수는 GetTextExtentPoint 이며 이함수는 다음과 같습니다.


BOOL GetTextExtentPoint(

HDC hdc, // 디바이스 컨텍스트 핸들

LPCTSTR lpString, // 텍스트 문자열

int cbString, // 텍스트 문자열에서 특정위치까지의 번호

LPSIZE lpSize // 리턴되는 문자열의 가로와 세로 크기

);


예를 들어 "Abjced" 라는 문자열에서 j라는 문자열까지의 가로와 세로의 크기를 얻고자 한다면 다음과 같이 할수 있습니다.

  SIZE size;

  GetTextExtentPoint(hdc,"Abjced",2,&size);


위와 같이하면 "Abj"의 문자열의 가로크기와 세로크기가 size의 cx와 cy에 저장되게 됩니다. 보통 cy는 크게 필요가 없을것입니다. 문자열의 높이는 가변 폰트라 하더라도 동일하게 간주해도 되기 때문입니다. 그러나 cx의 값은 매우 중요합니다. 이값을 얻어서 SetCaretPos 에 넣어주니까요.

WM_CHAR메세지에 의해서 문자가 들어 올 경우 이문자를 버퍼에 놓고 커서를 문자열 끝으로 이동한다고 할 경우 다음과 같을 것입니다.


  case WM_CHAR:

           //키값을 szBuff에 넣는다

               code=wParam;

               szBuff[pos]=code;

               pos++;

               szBuff[pos]=0x00;

               hdc=GetDC(hwnd);

               //현재 위치의 문자열 크기를 얻는다.

               GetTextExtentPoint(hdc,szBuff,pos,&size);

               //카렛 을 이동시킨다.

               SetCaretPos(size.cx,0);

               ReleaseDC(hwnd,hdc);

               break;


WM_SETFOCUS 와 WM_KILLFOCUS

포커스를 갖는다는 것은 현재 윈도우가 가장 상위에 활성화 되어 있다는 의미이며 입력을 받을수 있다는 의미입니다. 카렛은 입력 포커스를 받는 상황하에서만 활성화 될 수 있습니다. 입력포커스를 받는다는 것을 어떻게 알수가 있을까요. 윈도우가 입력포커스를 받는 상황이 되면 WM_SETFOCUS가 발생되면 포커스가 사라지면 WM_KILLFOCUS가 발생됩니다. 버튼이나 여러 자원들이 설정되어 있지 않는 그냥 윈도우 상태에서는 WM_SETFOCUS는 윈도우가 활성화 될 때 발생되며 윈도우가 다른 윈도우에 의해서 가려지거나 사라지거나 할 경우 WM_KILLFOCUS가 발생됩니다. 다른 윈도우에 의해서 가려져 비활성화 되다가 다시 활성화 되면 또다시 WM_SETFOCUS가 발생됩니다. 카렛을 만들고 화면에 보이고자 할경우에는 WM_SETFOUCUS에 그리고 WM_KILLFOCUS가 발생되면 카렛을 감추고 삭제하는 것이 일반적으로 카렛을 사용하는 방법입니다.

WM_SETFOCUS :

                카렛을 만들고 화면에 나타냄

               break;

WM_KILLFOCUS:

               카렛을 감추고 화면에서 삭제한다.

               break;

위의 카렛을 만들고 보여주는 부분은 WM_SETFOCUS에 감추는 부분은 WM_KILLFOCUS에 넣는 것이 이상적일것입니다.

WM_SETFOCUS는 카렛을 만들경우에만 사용되는 것이 아니라 여러개의 차일드 윈도우를 컨트롤 할 때도 사용됩니다. 이부분에 대해서는 뒤의 차일드 윈도우 컨트롤 부분에서 자세히 설명할것입니다.


한줄 문자 입력기 예제 TypingEx

TypingEx는 윈도우 클라이언트 영역(0,0) 좌표에서 총 80개의 문자열을 입력받는 타이핑 예제입니다. 지금까지 설명한 카렛과 WM_SETFOCUS와 WM_KILLFOCUS를 이용하여 1줄짜리의 간단한 에디터를 만든것입니다. 이에디터는 커서가 이동이 되지 않으며 또한 Backspace도 가동되지 않습니다. 다만 입력하는데로 문자가 화면에 출력될것입니다. 이 예제를 통해서 에디터를 어떻게 만드는가에 대한 기본을 알수 있을것입니다.

(프로그램 소스)

//타이핑 예제

//TypingEx.c

#include <windows.h>


void main();


LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;


int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "TypingEx" ;

     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;


     wndclass.cbSize        = sizeof (wndclass) ;

     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

     wndclass.lpfnWndProc   = WndProc ;

     wndclass.cbClsExtra    = 0 ;

     wndclass.cbWndExtra    = 0 ;

     wndclass.hInstance     = hInstance ;

     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

     wndclass.lpszMenuName  = NULL ;

     wndclass.lpszClassName = szAppName ;

     wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;


     RegisterClassEx (&wndclass) ;


     hwnd = CreateWindow (szAppName,    

                           "타이핑예제:TypingEx",  

                    WS_OVERLAPPEDWINDOW, 

                    CW_USEDEFAULT,       

                    CW_USEDEFAULT,       

                    CW_USEDEFAULT,       

                    CW_USEDEFAULT,       

                    NULL,                

                    NULL,                

                    hInstance,           

                           NULL) ;                    


     ShowWindow (hwnd, iCmdShow) ;

     UpdateWindow (hwnd) ;


     while (GetMessage (&msg, NULL, 0, 0))

          {

          TranslateMessage (&msg) ;

          DispatchMessage (&msg) ;

          }

     return msg.wParam ;

     }



LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

     HDC         hdc ;

     PAINTSTRUCT ps ;

        TEXTMETRIC tm;

        static int xsize=0,ysize=0;

        static int pos=0;

        static char szBuff[80];

        SIZE  size;

        char code;

        char temp[80];

        HRGN rgn;

     switch (iMsg)

          {

          case WM_CREATE :

                               strcpy(szBuff,"");

               return 0 ;

                 case WM_SETFOCUS:

                               hdc=GetDC(hwnd);

                               GetTextMetrics(hdc,&tm);

                               xsize=tm.tmAveCharWidth;

                               ysize=tm.tmHeight;

                               //카렛을 만든다.

                               CreateCaret(hwnd,NULL,xsize,ysize);

                               //카렛을 보여준다

                               ShowCaret(hwnd);

                               //카렛의 위치를 설정한다

                               SetCaretPos(pos*xsize,0);

                               ReleaseDC(hwnd,hdc);

                               return 0;

                 case WM_KILLFOCUS:

                           //카렛을 감춘다

                               HideCaret(hwnd);

                               //카렛을 삭제한다.

                               DestroyCaret();

                               return 0;

          case WM_PAINT :

                  hdc = BeginPaint (hwnd, &ps) ;

                          TextOut(hdc,0,0,szBuff,strlen(szBuff));

                  EndPaint (hwnd, &ps) ;

               return 0 ;

                 case WM_CHAR:

                           //키값을 szBuff에 넣는다

                               code=wParam;

                               szBuff[pos]=code;

                               pos++;

                               szBuff[pos]=0x00;

                               //버퍼 크기는 80이므로 80이 넘으로 다시 0

                               if(pos>=80)

                                       pos=0;

                               hdc=GetDC(hwnd);

                               //현재 위치의 문자열 크기를 얻는다.

                               GetTextExtentPoint(hdc,szBuff,pos,&size);

                               //카렛 을 이동시킨다.

                               SetCaretPos(size.cx,0);

                               //문자열 코드를 화면에 출력한다.

                               wsprintf(temp,"char:%03d",wParam);

                               TextOut(hdc,0,20,temp,strlen(temp));

                               ReleaseDC(hwnd,hdc);

                               //무효화 사각형 영역을 만든다.

                               rgn=CreateRectRgn(0,0,500,20);

                               InvalidateRgn(hwnd,rgn,TRUE);

                               DeleteObject(rgn);

                               break;

          case WM_DESTROY :

               PostQuitMessage (0) ;

               return 0 ;

          }


     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

(프로그램 소스끝)

본예제의 출력 결과는 그림 2와 같습니다.













     (그림 2)TypingEx 예제 출력 결과






한글 입출력에 대한 컨트롤

TypingEx를 실행시키면 한글이 완벽하게 완성되었을경우에만 화면에 나타나는 것을 보게 될것입니다. 이것은 조금 불만스러운 느낌을 줍니다. 워드프로세서에서는 한글이 완벽하게 구현이 되지 않더라도 화면에 나타나는데 TypingEx는 그렇게 되지 않으니까요? 실제로 WM_CHAR는 한글이 완벽하게 설정되었을 때만 발생됩니다. 그전에는 발생이 되지 않기 때문에 한글 입력상태에서는 키보드 한문자 한문자에 대해서 WM_CHAR로 알수가 없게 되는 것입니다. 이때는 WM_KEYDOWN으로는 알수가 있으나 문제는 현재가 한글 입력상태인지 또는 영문 입력상태인지를 알수가 없다는 것입니다. 이부분을 해결하기 위해서는 IMM를 핸들링 해야 합니다. IMM이란 Input Method Manager  의 약자입니다. 즉 한글과 같이 여러 키의 조합으로 글자를 만들 경우 사용되는 함수 라이브러리라고 볼수 있습니다. IMM에 대한 여러 함수는 imm.h에 설정되어 있습니다. 본책에서는 IMM에 대한 모든 개념 설명은 하지 않습니다. 단 필요한 부분 즉 현재 입력모드가 한글인가? 영문인가? 와 한글일 경우 완성된 문자가 아니더라도 화면에 출력하는 방법에 대해서 설명을 합니다. 사실 이부분만 알아도 한글을 출력하는데는 문제가 없기 때문입니다.



한영 전환키를 눌렀을때의 메시지

한영 전환키를 눌렀을때는 WM_IME_NOTIFY 발생됩니다. 이때 lParam에는 0값이 wParam에는 6과 8이 교차적으로 나타납니다. 이값으로는 한영전환을 알수가 없습니다. ImmGetConversionStatus 함수를 이용하여 상태를 얻을수는 있습니다. WM_IME_NOTIFY는 한영전환에 대한 여러 Notify를 알려주기 때문에 이때마사 imm.h설정되어 있은 여러 함수를 이용하여 상태를 얻을수 있습니다. 이렇게 복잡한 방법이 아니더라도 한영전환을 알수 있는 또하나의 방법이 있습니다. 그것은 플러그를 만드는것입니다. 프로그램이 시작되어 입력 포커스를 받은 처음상태는 영문 상태입니다. 이때부터 WM_IME_NOTIFY가 발생될때마다 플러그를 변화시키는 것입니다.


//전역 변수로 한영 플러그를 설정하고 초기값을 FALSE로 (영문상태)

BOOL hanFlag=FALSE;


  case WM_IME_NOTIFY:

               hanFlag=1-hanFlag;//한영 플러그 전환

       break;

위와 같이 하면 WM_IME_NOTIFY를 받았을 경우 플러그가 전환됨으로 한글과 영문 상태를 알수가 있습니다. 즉 한영키를 전환하면 1로 설정되면서 한글상태라는 것을 알려주고 다시 한영키를 누르면 0으로 설정되면서 영문상태를 알려주는 것입니다.



한글 조합시작과 종료 메시지

한글 조합의 시작 메시지는 WM_IME_STARTCOMPOSITION 이며 종료 메시지는 WM_IME_ENDCOMPOSITION입니다. 또한 현재 키가 입력되고 조합중이라는 메시지는 WM_IME_COMPOSITION입니다. 이 3개의 메시지를 이용하여 한글을 출력할 수가 있습니다.


                //한글 조합 시작

          case  WM_IME_STARTCOMPOSITION:

                 ://이때는 한글 모드라는 것을 완벽하게 알수 있습니다.

                //한글 조합중

          case WM_IME_COMPOSITION:

                //이때는 현재 한글이 조합중이므로 완성되지 못한 한글 코드를 알수

                // 있습니다.

                        break;

                //한글 조합 완료 즉 영문모드로 전환했을경우

             case WM_IME_ENDCOMPOSITION:

                //이때는 조합이 끝이므로 영문 모드라는 것을 알수 있습니다.

                                break;


WM_IME_STARTCOMPOSITION은 한글 모드가 시작되면서 첫키를 눌렀을 때 발생하는 메시지입니다. 이때는 분명히 한글 모드라는 것을 알수가 있는것입니다.

WM_IME_COMPOSITION은 현재 한글이 조합중이라는 것을 알수 있습니다.

이때 wParam / 256 이 한글의 상위 비트이며 wParam % 256 한글의 하위비트입니다. 완성이 되지 못한 이글자를 두바이트 이상의 문자열로 만들어 화면에 출력하면 비완성된 문자가 화면에 나타납니다.


                  case WM_IME_COMPOSITION:

                                hdc=GetDC(hwnd);

                                //비완성된 문자를 Han에 받는다

                                Han[0] = (BYTE)(wParam / 256);

                                Han[1] = (BYTE)(wParam % 256);

                                Han[2]=0x00;

                                //화면에 Han를 출력한다.

                                TextOut(hdc,size.cx,0,Han,strlen(Han));

                                ReleaseDC(hwnd,hdc);

                                break;


WM_IME_ENDCOMPOSITION 한글 조합이 끝났다는 의미인데 한영전환키를 눌러서 영문 모드로 전환하였을 경우 발생합니다. 좀더 안전을 기하기 위해서 이때는 한영 전환 플러그 hanFlag를 FALSE로 설정하여 영문모드라는 것을 확인하는 것이 좋을것입니다.


             case WM_IME_ENDCOMPOSITION:

                            hanFlag=FALSE;

                                break;


이렇게 함으로써 한글을 화면에 출력할수 있는 것입니다.


한글출력 1줄 에디터 예제 HanType

HanType는 한글 출력을 하면서 현재 모드가 한글인지 영문인지를 판독할수 있는예제입니다. 이예제는 "imm.h"를 꼭 포함시켜야 합니다.프로그램으 소스는 다음과 같습니다.

(프로그램 소스)

//한글타이핑 예제

//HanType.c

#include <windows.h>

#include "imm.h" //꼭 포함 시킬것




LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;


int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "HanType" ;

     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;


     wndclass.cbSize        = sizeof (wndclass) ;

     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

     wndclass.lpfnWndProc   = WndProc ;

     wndclass.cbClsExtra    = 0 ;

     wndclass.cbWndExtra    = 0 ;

     wndclass.hInstance     = hInstance ;

     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

     wndclass.lpszMenuName  = NULL ;

     wndclass.lpszClassName = szAppName ;

     wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;


     RegisterClassEx (&wndclass) ;


     hwnd = CreateWindow (szAppName,    

                           "타이핑예제:Hantype",  

                    WS_OVERLAPPEDWINDOW, 

                    CW_USEDEFAULT,       

                    CW_USEDEFAULT,       

                    CW_USEDEFAULT,       

                    CW_USEDEFAULT,       

                    NULL,                

                    NULL,                

                    hInstance,           

                           NULL) ;                    


     ShowWindow (hwnd, iCmdShow) ;

     UpdateWindow (hwnd) ;


     while (GetMessage (&msg, NULL, 0, 0))

          {

          TranslateMessage (&msg) ;

          DispatchMessage (&msg) ;

          }

     return msg.wParam ;

     }

//전역 변수로 한영 플러그를 설정하고 초기값을 FALSE로 (영문상태)

BOOL hanFlag=FALSE;


LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

     HDC         hdc ;

     PAINTSTRUCT ps ;

        TEXTMETRIC tm;

        static int xsize=0,ysize=0;

        static int pos=0;

        static char szBuff[80];

        SIZE  size;

        char code;

        char temp[80];

        char Han[5];

        HRGN rgn;

     switch (iMsg)

          {

          case WM_CREATE :

                               strcpy(szBuff,"");

               return 0 ;

                 case WM_SETFOCUS:

                               hdc=GetDC(hwnd);

                               GetTextMetrics(hdc,&tm);

                               xsize=tm.tmAveCharWidth;

                               ysize=tm.tmHeight;

                               CreateCaret(hwnd,NULL,xsize,ysize);

                               ShowCaret(hwnd);

                               SetCaretPos(pos*xsize,0);

                               ReleaseDC(hwnd,hdc);

                               return 0;

                 case WM_KILLFOCUS:

                               HideCaret(hwnd);

                               DestroyCaret();

                               return 0;

          case WM_PAINT :

                  hdc = BeginPaint (hwnd, &ps) ;

                          TextOut(hdc,0,0,szBuff,strlen(szBuff));

                  EndPaint (hwnd, &ps) ;

               return 0 ;

                 case WM_CHAR:

                               code=wParam;

                               szBuff[pos]=code;

                               pos++;

                               szBuff[pos]=0x00;

                               if(pos>=80)

                                       pos=0;

                               hdc=GetDC(hwnd);

                               GetTextExtentPoint(hdc,szBuff,pos,&size);

                               SetCaretPos(size.cx,0);

                               wsprintf(temp,"char:%03d",wParam);

                               TextOut(hdc,0,20,temp,strlen(temp));

                               ReleaseDC(hwnd,hdc);

                               rgn=CreateRectRgn(0,0,500,20);

                               InvalidateRgn(hwnd,rgn,TRUE);

                               DeleteObject(rgn);

                               break;

                               //한글 조합 시작

                 case  WM_IME_STARTCOMPOSITION:

                               wsprintf(temp,"조합상태");

                               hdc=GetDC(hwnd);

                               TextOut(hdc,0,60,temp,strlen(temp));

                               ReleaseDC(hwnd,hdc);

                               break;

                               //한글 조합중

                 case WM_IME_COMPOSITION:

                               hdc=GetDC(hwnd);

                               GetTextExtentPoint(hdc,szBuff,pos,&size);

                               //비완성된 문자를 Han에 받는다

                               Han[0] = (BYTE)(wParam / 256);

                               Han[1] = (BYTE)(wParam % 256);

                               Han[2]=0x00;

                               //화면에 Han를 출력한다.

                               TextOut(hdc,size.cx,0,Han,strlen(Han));

                               ReleaseDC(hwnd,hdc);

                               break;

                               //한글 조합 완료 즉 영문모드로 전환했을경우

             case WM_IME_ENDCOMPOSITION:

                           hanFlag=FALSE;

                               wsprintf(temp,"영문");

                               hdc=GetDC(hwnd);

                               TextOut(hdc,0,40,temp,strlen(temp));

                               ReleaseDC(hwnd,hdc);

                               break;

                 case WM_IME_NOTIFY:

                               hanFlag=1-hanFlag;//한영 플러그 전환

                               if(hanFlag)

                               {

                                       wsprintf(temp,"한글");

                                       hdc=GetDC(hwnd);

                                       TextOut(hdc,0,40,temp,strlen(temp));

                                       ReleaseDC(hwnd,hdc);

                               }

                               break;

          case WM_DESTROY :

               PostQuitMessage (0) ;

               return 0 ;

          }


     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}

(프로그램 소스끝)

그림 3은 HanType 출력 결과입니다.









       (그림 3)HanType 출력 결과


한글 키 컨트롤 및 상태 얻기

HanType 예제에서는 완벽하게 한글 전환이 되지 않습니다. 입력을 하다가 space바를 누르게 되면 갑자기 영문상태로 바뀌거나 잠시 잠깐 한글과 영문상태가 잘못 출력되는 오류가 나오게 됩니다. 이제 완벽하게 한글입출력을 할수 있는 방법을 알아보겠습니다.

첫 번째로  현재 상태가 한글인지 영문인지를 알고자 할 경우 이때는 다음과 같은 방법을 이용합니다.

HIMC data; //IMM핸들 변수 설정


//한글인지 영문인지를 알아본다

//현재 윈도우의 IMM 핸들러을 얻는다.

data=ImmGetContext(hwnd);

//한영상태를 얻는다.

//hanFlag가 1이면 한글 0이면 영문임

hanFlag=ImmGetOpenStatus(data);

//IMM핸들을 해제 한다.

ImmReleaseContext(hwnd,data);


ImmGetContext 함수를 이용하여 IMM핸들러를 얻은다음 ImmGetOpenStatus함수를 이용하여 현재 입력모드를 알아내면 됩니다. 이때 리턴되는 값이 1이면 한글이고 0이면 영문으로 설정된것입니다. ImmGetContext와 쌍인 ImmReleaseContext함수를 이용하여 IMM 핸들을 해제시켜 주어야 합니다.


두 번째로 우리가 한영전환을 마음대로 하고자 할경우가 있습니다. 이방법은  ImmSetConversionStatus 함수를 이용하면 됩니다.


        //핸들러 얻기

        data=ImmGetContext(hwnd);

        //한글전환시

        ImmSetConversionStatus(data,1,0);

        //영문 전환시

        ImmSetConversionStatus(data,0,0);

        //핸들러 해제


ImmSetConversionStatus함수의 두 번째 인자값을 1로 하였을 경우 한글로 설정되고 0으로 하였을 경우 1로 설정됩니다. 많일 프로그램이 시작되면서 바로 한글모드로 하고 싶다면 ImmSetConversionStatus함수의 두 번째 인자를 1로 설정하고 실행시키면 됩니다.



한글전환 예제  HanType2

HanType2는 좌측 마우스 버튼을 클릭하면 토글로 한영이 전환되며 완벽하게 현재의 한영 상태를 알아내는 1줄 한글 입력 에디터 예제입니다.

이예제에서는 imm32.lib 라이브러리가 필요합니다. 프로젝트에서 Alt+F7키를 누르거나 Project메뉴에서  Settings항목을 선택하고 카드철에서 Link를 선택하고 그림 4와 같은 화면이 나올 때 Object/library modules: 항목에 imm32.lib를 입력하여 주셔야 합니다.














          (그림 4) Settings 화면


프로그램 소스는 다음과 같습니다.

(프로그램 소스)

//한글타이핑 예제

//HanType.c

#include <windows.h>

#include "imm.h"




LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;


int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

                    PSTR szCmdLine, int iCmdShow)

     {

     static char szAppName[] = "HanType" ;

     HWND        hwnd ;

     MSG         msg ;

     WNDCLASSEX  wndclass ;


     wndclass.cbSize        = sizeof (wndclass) ;

     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

     wndclass.lpfnWndProc   = WndProc ;

     wndclass.cbClsExtra    = 0 ;

     wndclass.cbWndExtra    = 0 ;

     wndclass.hInstance     = hInstance ;

     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

     wndclass.lpszMenuName  = NULL ;

     wndclass.lpszClassName = szAppName ;

     wndclass.hIconSm       = LoadIcon (NULL, IDI_APPLICATION) ;


     RegisterClassEx (&wndclass) ;


     hwnd = CreateWindow (szAppName,    

                           "타이핑예제:HanType",  

                    WS_OVERLAPPEDWINDOW, 

                    CW_USEDEFAULT,       

                    CW_USEDEFAULT,       

                    CW_USEDEFAULT,       

                    CW_USEDEFAULT,       

                    NULL,                

                    NULL,                

                    hInstance,           

                           NULL) ;                    


     ShowWindow (hwnd, iCmdShow) ;

     UpdateWindow (hwnd) ;


     while (GetMessage (&msg, NULL, 0, 0))

          {

          TranslateMessage (&msg) ;

          DispatchMessage (&msg) ;

          }

     return msg.wParam ;

     }

//전역 변수로 한영 플러그를 설정하고 초기값을 FALSE로 (영문상태)

BOOL hanFlag=FALSE;

HIMC data;


LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)

{

     HDC         hdc ;

     PAINTSTRUCT ps ;

        TEXTMETRIC tm;

        static int xsize=0,ysize=0;

        static int pos=0;

        static char szBuff[80];

        SIZE  size;

        char code;

        char temp[80];

        char Han[5];

        HRGN rgn;

     switch (iMsg)

          {

          case WM_CREATE :

                               strcpy(szBuff,"");

               return 0 ;

                 case WM_SETFOCUS:

                               hdc=GetDC(hwnd);

                               GetTextMetrics(hdc,&tm);

                               xsize=tm.tmAveCharWidth;

                               ysize=tm.tmHeight;

                               CreateCaret(hwnd,NULL,xsize,ysize);

                               ShowCaret(hwnd);

                               SetCaretPos(pos*xsize,0);

                               ReleaseDC(hwnd,hdc);

                               return 0;

                 case WM_KILLFOCUS:

                               HideCaret(hwnd);

                               DestroyCaret();

                               return 0;

          case WM_PAINT :

                  hdc = BeginPaint (hwnd, &ps) ;

                          TextOut(hdc,0,0,szBuff,strlen(szBuff));

                  EndPaint (hwnd, &ps) ;

               return 0 ;

                 case WM_CHAR:

                               code=wParam;

                               szBuff[pos]=code;

                               pos++;

                               szBuff[pos]=0x00;

                               if(pos>=80)

                                       pos=0;

                               hdc=GetDC(hwnd);

                               GetTextExtentPoint(hdc,szBuff,pos,&size);

                               SetCaretPos(size.cx,0);

                               wsprintf(temp,"char:%03d",wParam);

                               TextOut(hdc,0,20,temp,strlen(temp));

                               ReleaseDC(hwnd,hdc);

                               rgn=CreateRectRgn(0,0,500,20);

                               InvalidateRgn(hwnd,rgn,TRUE);

                               DeleteObject(rgn);

                               break;

                 case WM_KEYDOWN:

                               //한글인지 영문인지를 알아본다

                           //현재 윈도우의 IMM 핸들러을 얻는다.

                               data=ImmGetContext(hwnd);

                               //한영상태를 얻는다.

                               //hanFlag가 1이면 한글 0이면 영문임

                           hanFlag=ImmGetOpenStatus(data);

                               //IMM핸들을 해제 한다.

                               ImmReleaseContext(hwnd,data);

                               if(hanFlag)

                               {

                                       wsprintf(temp,"한글");

                                       hdc=GetDC(hwnd);

                                       TextOut(hdc,0,40,temp,strlen(temp));

                                       ReleaseDC(hwnd,hdc);

                               }

                               else

                               {

                                       wsprintf(temp,"영문");

                                       hdc=GetDC(hwnd);

                                       TextOut(hdc,0,40,temp,strlen(temp));

                                       ReleaseDC(hwnd,hdc);

                               }


                               break;

                               //한글 조합 시작

                 case  WM_IME_STARTCOMPOSITION:

                               wsprintf(temp,"조합상태");

                               hdc=GetDC(hwnd);

                               TextOut(hdc,0,60,temp,strlen(temp));

                               ReleaseDC(hwnd,hdc);

                               break;

                               //한글 조합중

                 case WM_IME_COMPOSITION:

                               hdc=GetDC(hwnd);

                               GetTextExtentPoint(hdc,szBuff,pos,&size);

                               //비완성된 문자를 Han에 받는다

                               Han[0] = (BYTE)(wParam / 256);

                               Han[1] = (BYTE)(wParam % 256);

                               Han[2]=0x00;

                               //화면에 Han를 출력한다.

                               TextOut(hdc,size.cx,0,Han,strlen(Han));

                               ReleaseDC(hwnd,hdc);

                               break;

                               //한글 조합 완료 즉 영문모드로 전환했을경우

             case WM_IME_ENDCOMPOSITION:

                               wsprintf(temp,"영문");

                               hdc=GetDC(hwnd);

                               TextOut(hdc,0,40,temp,strlen(temp));

                               ReleaseDC(hwnd,hdc);

                               break;

                 case WM_LBUTTONDOWN:

                               hanFlag=1-hanFlag;//한영 플러그 전환

                               //핸들러 얻기

                               data=ImmGetContext(hwnd);

                               //한영 전환을 한다.

                               ImmSetConversionStatus(data,hanFlag,0);

                               //핸들러 해제

                               ImmReleaseContext(hwnd,data);

                               if(hanFlag)

                               {

                                       wsprintf(temp,"한글");

                                       hdc=GetDC(hwnd);

                                       TextOut(hdc,0,40,temp,strlen(temp));

                                       ReleaseDC(hwnd,hdc);

                               }

                               else

                               {

                                       wsprintf(temp,"영문");

                                       hdc=GetDC(hwnd);

                                       TextOut(hdc,0,40,temp,strlen(temp));

                                       ReleaseDC(hwnd,hdc);

                               }

                               break;

          case WM_DESTROY :

               PostQuitMessage (0) ;

               return 0 ;

          }


     return DefWindowProc (hwnd, iMsg, wParam, lParam) ;

}


(프로그램 소스끝)

그림 5는 HanType2 의 출력 결과입니다.














         (그림 5) HanType2출력 결과



(잔소리)

지금까지 C언어를 배우고 윈도우에서 기본적인 내용을 공부하셨습니다. 혹이 근심된 마음에서 말씀드리는데 지금까지 공부한내용이 조금은 알겠는데 확실히 모르겠다 하시는 분들이 계신다면 주저 없이 다음으로 넘어가주시기를 바랍니다. “에이 처음부터 다시 확실하게 공부하고 넘어가자” 라고 다시 처음으로 가지 말아주시기 바랍니다. 컴퓨터 프로그램은 절대 줄그어 가면서 한 Chapter 한 Chapter이해해가면서 완벽을 기하는 방법으로 공부하시면 절대 안됩니다. 그냥 페인트 칠하는 기법으로 한번 쫘악 보시고 다시 필요한 부분을 또한 번 보고 이렇게 함으로써 이해가 되는 것입니다. 지금까지 공부하실 때 “어떤 내용은 어느 Chapter에 있더라” 이것만 알아도 됩니다. 이렇게 알면서 뒤의것을 공부하시면 앞에 것을 또한번 이해할수 있습니다. 그리고 책을 다보신 후에(3편까지만 보아도 됩니다) 자신이 한번 프로그램을 만들어 보세요. 그리고 막히는 부분이 있다면 “아! 책 어느부분에 있구나” 하시고 그부분을 다시 보시던지 그래도 모르면 그냥 예제를 부분 카피 하셔서 사용해도 됩니다.  윈도우 프로그래밍은 사실 언어만 이해하면 이론적으로 공부하는 부분은 작고 그냥 시키는대로 하는게 더많습니다. 즉 어떻게 해야 하는가가 많다는 이야기입니다. 그것은 기술력에 해당되지 않습니다. 노우하우 (어떤면에서는 노우웨어 (Know Where)에 해당되는 것입니다. 앞으로 본책의 뒤로가면서 이론적 부분이 점점 사라지고 “이렇게 하면 됩니다” 라는 말이 많이 나올것입니다. 이게 사실이기 때문입니다. 왜냐면 마이크로 소프트사에서 주는 라이브러리를 사용하여 프로그램을 짜기 때문에 그들이 제공하는 라이브러리 함수를 순서에 맞게 설정하는 것 외에는 그렇다할 알고리듬이 없기 때문입니다. SDK라이브러리에 어떤 기술력이 있다고 괸히 어려운 함수 쓸려고 노력하시는 분들에게 한마디 하고 싶습니다. “그시간에 알고리듬 공부를 더하세요!”

(잔소리끝)

신고

'Programming > CPP' 카테고리의 다른 글

멀티바이트와 유니코드  (0) 2013.07.27
BroadCasting 참고 코드  (0) 2013.07.09
Virtual Key Code  (0) 2013.07.06
키보드 이벤트 처리하기  (0) 2013.07.05
Visual Studio 2012 단축키  (0) 2013.07.05
소켓통신  (0) 2013.07.05


Posted by injunech
2013.07.05 14:29


편집 관련 단축키

편집 관련 단축키들은 익혀두면 다른 문서를 작성 할때도 많이 도움이 될 것이다.

문서 편집 관련 단축키들은 사용하면 사용할 수록 코딩 속도가 빨라진다.

Ctrl + F : 찾기

중요도 : ★★★☆☆

활용 : 특정 클래스, 함수, 변수 등을 검색한다.

Ctrl + Shift + F : 파일에서 찾기

중요도 : ★☆☆☆☆

활용 : 특정 클래스, 함수, 변수 등을 검색한다. 현제 프로젝트가 아닌 파일에서도 찾을 수 있다.

Ctrl + Space : NameSpace

중요도 : ★★★★★

활용 : 현재 접근 가능한 클래스명, 함수명, DEFINE문 등의 목록을 보여준다.

코드를 치는 도중 Ctrl + Space를 누르면 이전에 선언된 것과

가까운 목록을 보여주며 선택을 함으로써 쉽게 완성이 가능하다.

목록이 하나뿐이라면 그것으로 자동 완성 해준다.

Ctrl + Shift + Space : Parameter

중요도 : ★★★★★

활용 : 커서가 클래스명의 뒤에 있을 때 접근가능한 변수, 함수 목록을 보여준다.

코드를 치는 도중 Ctrl + Shift + Space를 누르면 이전에 선언된 것과

가까운 목록을 보여주며 선택을 함으로써 쉽게 완성이 가능하다.

목록이 하나뿐이라면 그것으로 자동 완성 해준다.

Home : 커서가 위치한 줄의 코드 시작 위치로

중요도 : ★★★★★

활용 : 커서의 위치와 관계없이 해당 줄의 코드 시작위치로 커서를 옮긴다.

블록을 잡기 위해서, 커서의 빠른 이동 등에 많이 사용된다.

End : 커서가 위치한 줄의 코드 끝 위치로

중요도 : ★★★★★

활용 : 커서의 위치와 관계없이 해당 줄의 코드 마지막위치로 커서를 옮긴다.

Shift + Home : 커서가 위치한 줄 모두 블록

Shift + End : 커서가 위치한 줄 모두 블록

중요도 : ★★★★★

활용 : 커서 위치가 가장 앞에 있다면 Shift + End를 이용해 그 줄을 블록하고

커서 위치가 가장 뒤에 있다면 Shift + Home를 이용해 그 줄을 블록한다.

Shift + ← or → or ↑ or ↓ : 블록 잡기

중요도 : ★★★★☆

활용 : 여러 줄을 블록할 때 많이 사용한다.

드래그 + Alt : 현재 커서 위치부터 움직인 커서 위치까지 블록

중요도 : ★★★★☆

활용 : 마우스 드래그 중 Alt를 누르면 드래그하는 영역을 사각형으로 봤을 때 그 영역을 블록하게 된다.

특정 부분을 선택하고 싶거나, 탭을 먹이고 싶을 때 많이 사용된다.

Ctrl + ←, → : 구분단위로 커서 이동

중요도 : ★☆☆☆☆

활용 : 현재 줄에서 커서를 좌우로 이동할 때 변수, 공백, 탭, 등의 구분단위로 이동하게된다.

보다 빠른 커서위치를 조정하고, 블록할 때 사용된다.

Ctrl + Shift + ← or → : 구분단위로 블록

중요도 : ★☆☆☆☆

활용 : 현재 줄에서 블록을 할 때 변수, 공백, 탭 등의 구분단위로 블록영역을 설정한다.

보다 빠르게 블록 영역을 설정할 때 편리하다.

Ctrl + M, L : 전체 + 버튼으로 만들기

중요도 : ★★★☆☆

활용 : 현재 소스 전체를 {}영역 별로 +버튼으로 만들어준다.

+버튼은 소스가 삭제되는 것은아니라 감춰두는 것으로 볼 수 있다.

소스가 길어 함수 정의나 클래스 정의 부분을 빨리 찾고 싶을 때 많이 사용된다.

Ctrl + M, M : 커서 위치가 속한 곳을 + 버튼으로 만들기

중요도 : ★★☆☆☆

활용 : 커서의 위치를 {}단위로 판단하여 커서가 속한 곳을 +버튼으로 만든다.

Ctrl + M, L : 선택된 곳을 +버튼으로 만들기

중요도 : ★★☆☆☆

활용 : 블록을 선택된 영역을 +버튼으로 만든다.

Shift + Delete : 커서가 위치한 줄 삭제

중요도 : ★★★★☆

활용 : 커서가 위치한 줄을 삭제한다.

빠르게 현재 줄을 삭제할 때 많이 활용된다.

Ctrl + L : 커서가 위치한 줄 삭제, 선택된 줄단위로 삭제

중요도 : ★★★★☆

활용 : 위 단축키와 비슷하지만 영역을 선택했을 때 여러줄을 줄단위로 삭제 한다.

Ctrl + K, C : 선택 영역 주석 달기

중요도 : ★★★★☆

활용 : 선택 영역의 주석을 한 단계씩 추가한다.

Ctrl + K, U : 선택 영역 주석 없애기

중요도 : ★★★★☆

활용 : 선택 영역을 주석을 한 단계씩 감소시킨다.

Alt + F8 : 선택 영역 코드 탭 정리하기

중요도 : ★★★★☆

활용 : 선택한 영역의 코드들의 탭이 뒤죽박죽일 때 사용하면 편리하다.

디버깅 관련 단축키

F7 : 빌드

중요도 : ★★★☆☆

활용 : 이번 빌드 상태와 비교하여 수정된 소스에 대해 다시 빌드한다.

Ctrl + Alt + F7 : 전체 다시 빌드

중요도 : ★★★☆☆

활용 : 현재 솔루션 전체를 다시 빌드한다. 링크가 꼬엿을 때 외엔 잘 사용하지 않는다.

F5 : 빌드 + 실행

중요도 : ★★★★★

활용 : F7을 누른후 실행한 결과와 같다.

Ctrl + F5 : 빌드 없이 실행

중요도 : ★☆☆☆☆

활용 : 최근에 빌드된 상태의 실행 파일을 실행시킨다.

소스 수정없이 다시 실행 시키고 싶을 때 빌드 시간 없이 실행 하므로 빠르다

F9 : 브레이크 포인트 설정

중요도 : ★★★★★

활용 : 현재 커서가 위치한 줄에 중단점을 설정한다.

중단점이 걸리면 디버그시 해당 코드를 실행하기전에 중지되어 사용자에게 코드 위치를 보여준다.

F10 : 줄단위 실행

중요도 : ★★★★★

활용 : 디버깅 모드에서 현재 디버깅하고있는 소스의 줄단위로 진행 시킨다.

F11 : 코드 단위 실행

중요도 : ★★★★★

활용 : 디버깅 모드에서 현재 진행중인 커서위치의 코드를 실행한다.

커서위치의 코드내에 함수가 있다면 그 함수의 내부로 들어가게 된다.

F12 : 정의로 이동

중요도 : ★★★★★

활용 : 변수, 함수, 클래스 등의 선언부로 이동한다.

눈에 보이는 변수, 함수 등의 정체를 확인하는데 많이 사용된다.

Ctrl + '-'키 : 이전 커서 위치로

중요도 : ★★★★★

활용 : 이전 커서위치로 이동하게 된다.

보통 F12로 변수를 탐색한후, 다시 돌아오는데 많이 사용한다.

Ctrl + Shift + '-'키 : 다음 커서 위치로

중요도 : ★☆☆☆☆

활용 : 위의 단축키와 반대 되는 개념이다.

Ctrl + F2 : 커서가 위치한 줄에 책갈피 설정

중요도 : ★★★☆☆

활용 : 현재 문서에서 커서가 위치한 줄에 책갈피를 설정한다.

책갈피는 관심있는 코드를 메모해놓고 쉽게 접근하기 위해 사용한다.

F2 : 다음 설정된 책갈피로 커서 이동

중요도 : ★★★☆☆

활용 : 현재 문서에서 설정된 책갈피가 있을 때 순차적으로 책갈피를 탐색한다.

Ctrl + Shift + F2 : 설정된 책갈피 모두 삭제

중요도 : ★★★☆☆

활용 : 현재 문서에 설정되어 있는 책갈피를 모두 삭제한다.

Ctrl + F10 : 커서 위치까지 실행

중요도 : ★★☆☆☆

활용 : 현재 커서가 위치한 곳까지 실행하게 된다.

편집상태라면 빌드 + 커서 위치까지 실행된다.

한손으로 누르기 힘든 단축키라 우클릭 메뉴를 이용해도 좋다.

기타

마우스 우클릭 - Find All Refrence : 모든참조 찾기

중요도 : ★★★★☆

활용 : 현재 커서가 위치한곳의 변수나 함수등이 사용된 곳을 프로잭트에서 모두 찾아 표시한다.

LifeCycle 을 알아보는데도 좋다.

Alt + P + P : 프로젝트 속성

중요도 : ★☆☆☆☆

활용 : 프로젝트의 속성을 본다. 프로젝트 속성을 보는일은 많이 없으므로 큰 활용도는 없다.

마지막으로 지금까지 단축키 목록을 나열하겠다.

Ctrl + F : 찾기

Ctrl + Shift + F : 파일에서 찾기

Ctrl + Space : NameSpace

Ctrl + Shift + Space : Parameter

Home : 커서가 위치한 줄의 코드 시작 위치로

End : 커서가 위치한 줄의 코드 끝 위치로

Shift + Home : 커서가 위치한 줄 모두 블록

Shift + End : 커서가 위치한 줄 모두 블록

Shift + ← or → or ↑ or ↓ : 블록 잡기

드래그 + Alt : 현재 커서 위치부터 움직인 커서 위치까지 블록

Ctrl + ←, → : 구분단위로 커서 이동

Ctrl + Shift + ← or → : 구분단위로 블록

Ctrl + M, L : 전체 + 버튼으로 만들기

Ctrl + M, M : 커서 위치가 속한 곳을 + 버튼으로 만들기

Ctrl + M, L : 선택된 곳을 +버튼으로 만들기

Shift + Delete : 커서가 위치한 줄 삭제

Ctrl + L : 커서가 위치한 줄 삭제, 선택된 줄단위로 삭제

Ctrl + K, C : 선택 영역 주석 달기

Ctrl + K, U : 선택 영역 주석 없애기

F7 : 빌드

Ctrl + Alt + F7 : 전체 다시 빌드

F5 : 빌드 + 실행

Ctrl + F5 : 빌드 없이 실행

F9 : 브레이크 포인트 설정

F10 : 줄단위 실행

F11 : 코드 단위 실행

F12 : 정의로 이동

Ctrl + '-'키 : 이전 커서 위치로

Ctrl + Shift + '-'키 : 다음 커서 위치로

Ctrl + F2 : 커서가 위치한 줄에 책갈피 설정

F2 : 다음 설정된 책갈피로 커서 이동

Ctrl + Shift + F2 : 설정된 책갈피 모두 삭제

Ctrl + F10 : 커서 위치까지 실행

Alt + F8 : 선택 영역 코드 탭 정리하기

마우스 우클릭 - Find All Refrence : 모든참조 찾기

Alt + P + P : 프로젝트 속성

신고

'Programming > CPP' 카테고리의 다른 글

멀티바이트와 유니코드  (0) 2013.07.27
BroadCasting 참고 코드  (0) 2013.07.09
Virtual Key Code  (0) 2013.07.06
키보드 이벤트 처리하기  (0) 2013.07.05
Visual Studio 2012 단축키  (0) 2013.07.05
소켓통신  (0) 2013.07.05


Posted by injunech
2013.07.05 06:58


제목/목차

1. 소개
2. 클라이언트와 서버간 통신 개요
3. 간단한 서버와 클라이언트 구현
3.1 서버 - 기다리는 소켓 만들기
3.2 클라이언트 - 서버로 연결
3.3 서버 - 클라이언트의 연결 시도 받아들이기
3.4 클라이언트와 서버 - 자료 주고받기
4 만든 클라이언트와 서버를 컴파일하고 테스트
4.1 파일 목록
4.2 컴파일과 테스트
5. 결론

1. 소개

소켓은 프로세스간에 자료를 교환하는 수단이다. 프로세스는 같은 컴퓨터에 있거나 네트웍으로 연결된 서로 다른 컴퓨터에 있을 수 있다. 소켓이 연결되면 한쪽이 연결을 닫을 때까지 양편 모두 자료를 보낼 수 있다.

나는 작업중인 프로젝트에 소켓이 필요해서 소켓 API 함수를 감싸는 C++ 클래스를 개발하고 다듬었다. 일반적으로 자료를 요청하는 프로그램을 클라이언트, 요청에 응답하는 프로그램을 서버라고 한다. 내가 만든 ClientSocket과 ServerSocket, 주된 두 클래스를 사용하여 클라이언트와 서버가 서로 자료를 교환할 수 있다.

이 글의 목표는 프로그램에서 ClientSocket과 ServerSocket 클래스를 사용하는 방법을 알리는 것이다. 먼저 클라이언트와 서버간 통신에대해 간단히 다룬 다음, 이 두 클래스를 사용하여 간단한 서버와 클라이언트 예제를 만들 것이다.

2. 클라이언트와 서버간 통신 개요

코드로 들어가기전에 전형적인 클라이언트와 서버간 연결 단계를 간단히 살펴보자. 다음 표는 단계들을 보여준다.

서버클라이언트
1. 기다리는 소켓(listening socket)을 만들고 클라이언트에서 연결을 기다린다.
2. 클라이언트 소켓을 만들고 서버로 연결을 시도한다.
3. 클라이언트의 연결 시도를 받아들인다.
4. 자료를 주고받는다.4. 자료를 주고받는다.
5. 연결을 닫는다.5. 연결을 닫는다.

기본적으로는 이렇다. 먼저 서버가 기다리는 소켓을 만들고 클라이언트의 연결 시도를 기다린다. 클라이언트가 자체 소켓을 만들고 서버와 연결을 시도한다. 서버는 연결을 받아들이고, 자료 교환이 시작된다. 소켓 연결을 통해 모든 자료가 전달되면 양쪽 중 하나가 연결을 닫는다.

3. 간단한 서버와 클라이언트 구현

이제 코드로 들어갈 시간이다. 이제부터 개요에서 다룬 단계를 모두 수행하는 클라이언트와 서버를 만든다. 우리는 일반적으로 일어나는 순서대로 - 예를 들어 먼저 소켓을 기다리는 서버 부분을 만든 다음 서버로 연결하는 클라이언트 부분을 만드는 등 - 구현한다. 전체 코드는 simple_server_main.cppsimple_client_main.cpp에서 볼 수 있다.

미리 소스코드를 살펴보고 실행해보려면 여기를 읽어봐라. 프로젝트 파일 목록과 어떻게 컴파일하고 테스트하는지를 설명한다.

3.1 서버 - 기다리는 소켓 만들기

첫번째 할 일은 클라이언트에서 들어오는 요청을 기다리는 간단한 서버를 만드는 것이다. 다음은 서버 소켓을 만드는 코드이다.

목록 1 : 서버 소켓 만들기 ( simple_server_main.cpp의 일부 )
#include "ServerSocket.h"
#include "SocketException.h"
#include 

int main ( int argc, int argv[] )
{
  try
    {
      // Create the server socket
      ServerSocket server ( 30000 );

      // rest of code -
      // accept connection, handle request, etc...

    }
  catch ( SocketException& e )
    {
      std::cout << "Exception was caught:" << e.description() << "\nExiting.\n";
    }

  return 0;
}


이것만으로 끝났다. ServerSocket 클래스의 생성자는 기다리는 소켓을 만드는데 필요한 소켓 API를 부른다. 자세한 사항을 감추기때문에 로컬 포트에 기다리기위해 이 클래스 객체를 생성하기만 하면 된다.

try/catch 문을 주목하라. ServerSocket과 ClientSocket 클래스는 C++의 예외처리 기능을 사용한다. 클래스 함수가 어떤 이유에서건 문제가 생기면 SocketException.h에 정의된 SocketException 형의 예외를 발생한다. 이 예외를 처리하지 않으면 프로그램이 끝나기때문에 처리해주는 것이 좋다. 위에서처럼 SocketException의 description() 함수를 사용하여 오류문을 얻을 수 있다.

3.2 클라이언트 - 서버로 연결

전형적인 클라이언트와 서버간 연결의 두번째 단계는 클라이언트가 서버로 연결을 시도하는 일이다. 코드는 방금 전에 본 서버 코드와 비슷하다.

목록 2 : 클라이언트 소켓 만들기 ( simple_client_main.cpp의 일부 )
#include "ClientSocket.h"
#include "SocketException.h"
#include 
#include 

int main ( int argc, int argv[] )
{
  try
    {
      // Create the client socket
      ClientSocket client_socket ( "localhost", 30000 );

      // rest of code -
      // send request, retrieve reply, etc...

    }
  catch ( SocketException& e )
    {
      std::cout << "Exception was caught:" << e.description() << "\n";
    }

  return 0;
}


ClientSocket 클래스 객체를 만들기만하면 리눅스 소켓을 만들어서 생성자에 주어진 호스트와 포트로 연결한다. ServerSocket 클래스와 같이 생성자가 어떤 이유에서건 문제가 생기면 오류가 발생한다.

3.3 서버 - 클라이언트의 연결 시도 받아들이기

클라이언트와 서버간 연결의 다음 단계는 서버에서 일어난다. 서버가 클라이언트의 연결 시도를 받아들이면, 두 소켓 양단간 통신 채널이 열린다.

우리는 간단한 서버에 이 기능을 추가해야 한다. 다음은 수정된 버전이다.

목록 3 : 클라이언트 연결 받아들이기 ( simple_server_main.cpp의 일부 )
#include "ServerSocket.h"
#include "SocketException.h"
#include 

int main ( int argc, int argv[] )
{
  try
    {
      // Create the socket
      ServerSocket server ( 30000 );

      while ( true )
	{
	  ServerSocket new_sock;
	  server.accept ( new_sock );

	  // rest of code -
	  // read request, send reply, etc...

	}
    }
  catch ( SocketException& e )
    {
      std::cout << "Exception was caught:" << e.description() << "\nExiting.\n";
    }

  return 0;
}

연결을 받아들이기위해 accept 함수를 호출하면 된다. 이 함수는 연결 시도를 받아들이고, 연결에 대한 소켓 정보를 new_sock에 채운다. 우리는 다음 절에서 어떻게 new_sock을 사용하는지 볼 것이다.

3.4 클라이언트와 서버 - 자료 주고받기

이제 서버는 클라이언트의 연결 요청을 받아들였고, 소켓 연결을 통해 자료를 주고받을 시간이다.

C++의 고급 기능중 하나가 연산자를 오버로딩하는 - 간단히 말해서, 연산자가 특정 작업을 하도록 만드는 - 능력이다. 나는 ClientSocket과 ServerSocket 클래스의 <<와 >> 연산자를 오버로딩하여, 연산자를 사용하여 소켓에서 자료를 읽고쓸 수 있게 만들었다. 다음은 간단한 서버의 수정된 버전이다.

목록 4 : 간단한 서버 구현 ( simple_server_main.cpp의 일부 )
#include "ServerSocket.h"
#include "SocketException.h"
#include 

int main ( int argc, int argv[] )
{
  try
    {
      // Create the socket
      ServerSocket server ( 30000 );

      while ( true )
	{

	  ServerSocket new_sock;
	  server.accept ( new_sock );

	  try
	    {
	      while ( true )
		{
		  std::string data;
		  new_sock >> data;
		  new_sock << data;
		}
	    }
	  catch ( SocketException& ) {}

	}
    }
  catch ( SocketException& e )
    {
      std::cout << "Exception was caught:" << e.description() << "\nExiting.\n";
    }

  return 0;
}

new_sock 변수는 모든 소켓 정보를 저장하고 있어서, 클라이언트와 자료를 교환하는데 사용한다. "new_sock >> data;" 줄은 "new_sock에서 자료를 읽어서 'data'라는 문자열 변수에 저장한다"는 뜻이다. 비슷하게 다음 줄은 'data'에 있는 자료를 소켓을 통해 클라이언트로 보낸다.

주의를 기울였다면 여기서 만든 것이 echo 서버임을 알 수 있을 것이다. 클라이언트가 보낸 자료는 모두 다시 클라이언트에게 그대로 되돌려진다. 우리는 자료를 보내고 서버의 응답을 출력하는 클라이언트를 작성할 수 있다.

목록 5 : 간단한 클라이언트 구현 ( simple_client_main.cpp의 일부 )
#include "ClientSocket.h"
#include "SocketException.h"
#include 
#include 

int main ( int argc, int argv[] )
{
  try
    {

      ClientSocket client_socket ( "localhost", 30000 );

      std::string reply;
      try
	{
	  client_socket << "Test message.";
	  client_socket >> reply;
	}
      catch ( SocketException& ) {}

      std::cout << "We received this response from the server:\n\"" << reply << "\"\n";;

    }
  catch ( SocketException& e )
    {
      std::cout << "Exception was caught:" << e.description() << "\n";
    }

  return 0;
}

우리는 문자열 "Test Message."를 서버로 보내고, 서버의 응답을 표준출력으로 출력한다.

4. 만든 클라이언트와 서버를 컴파일하고 테스트

우리는 ClientSocket과 ServerSocket 클래스의 기본적인 사용법을 살펴봤다. 이제 프로젝트 전체를 컴파일하여 테스트해보자.

4.1 파일 목록

예제는 아래 파일들로 구성된다.

기타:
Makefile - 이 프로젝트의 Makefile
Socket.h, Socket.cpp - 소켓 API 함수를 구현한 Socket 클래스
SocketException.h - SocketException 클래스
서버:
simple_server_main.cpp - 주파일
ServerSocket.h, ServerSocket.cpp - ServerSocket 클래스
클라이언트:
simple_client_main.cpp - 주파일
ClientSocket.h, ClientSocket.cpp - ClientSocket 클래스

4.2 컴파일과 테스트

컴파일은 간단하다. 먼저 모든 프로젝트 파일을 하위디텍로리에 저장하고, 명령행 프롬프트에 다음과 같이 입력한다.

prompt$ cd directory_you_just_created
prompt$ make

그러면 프로젝트의 모든 파일을 컴파일하여 simple_server와 simple_client 출력 파일을 만든다. 두 출력 파일을 테스트하기위해 한 명령행 프롬프트에서 서버를 실행하고, 다른 명령행 프롬프트에서 클라이언트를 실행한다.

첫번째 프롬프트:
prompt$ ./simple_server
running....



두번째 프롬프트:
prompt$ ./simple_client
We received this response from the server:
"Test message."
prompt$

클라이언트는 서버로 자료를 보내고, 응답을 읽어서 위에서처럼 표준출력으로 출력한다. 클라이언트를 원하는만큼 실행할 수 있다. 서버는 매 요청에 응답한다.

5. 결론

소켓은 프로세스간에 자료를 보내는 간단하고 효율적인 방법이다. 이 글에서 우리는 소켓 통신을 살펴보고 서버와 클라이언트 예제를 만들어봤다. 이제 당신의 프로그램에 소켓 통신을 추가할 수 있다!

신고

'Programming > CPP' 카테고리의 다른 글

멀티바이트와 유니코드  (0) 2013.07.27
BroadCasting 참고 코드  (0) 2013.07.09
Virtual Key Code  (0) 2013.07.06
키보드 이벤트 처리하기  (0) 2013.07.05
Visual Studio 2012 단축키  (0) 2013.07.05
소켓통신  (0) 2013.07.05


Posted by injunech

티스토리 툴바