Visual C++ 10에서 디버깅하기

Visual C++ 10 2012.04.25 00:28 Posted by 흥배

예전에 Visual C++ 10과 관련된 책을 집필할 계획이 있어서 그때 작성한 글인데 책 출간 계획이 사라졌으므로(한참 전에)

그냥 공개합니다. 글은 초보자를 타겟으로 하고 있으며 Visula C++ 10의 디버깅 기능을 설명하고 있습니다.


도움이 되었으면 좋겠습니다^^



6장 디버깅.pdf


문서를 동의 없이 수정하지 않고, 출처만 남겨 주신다면 문서는 마음대로 배포하셔도 괜찮습니다^^


저작자 표시
신고

데이터셋을 시퀸스(연속적인)한 값으로 채우고 싶을 때는 iota 알고리즘을 사용합니다.

앞서 소개한 알고리즘들은 <algorithm> 헤더 파일에 정의 되어 있는 것에 반해 iota 알고리즘은 <numeric> 헤더 파일에 정의 되어 있습니다.

 

itoa

template<class ForwardIterator, class T>

  void iota(ForwardIterator first, ForwardIterator last, T value);

 

 

아래는 예제 코드와 결과 입니다.

#include <iostream>

#include <vector>

#include <numeric>

using namespace std;

 

int main()

{

           vector<int> Numberlist;

           Numberlist.push_back( 2 );

           Numberlist.push_back( 5 );

           Numberlist.push_back( 7 );

           iota( Numberlist.begin(), Numberlist.end(), 2 );

 

           for( auto IterPos = Numberlist.begin(); IterPos != Numberlist.end(); ++IterPos )

           {

                     cout << *IterPos << endl;

           }

 

           return 0;

}

 

< 결과 >

 

위 예제를 보면 아시겠지만 iota의 세 번째 인자의 값이 시작 값이고, 이후에 값이 하나씩 증가합니다.

 

저작자 표시
신고

[StartD2D-1] Good-bye~~ GDI…

DirectX 11 2011.03.17 08:00 Posted by 조진현

 

'윈도우즈 7 운영체제가 왜 좋은 것일까?'

 

이 물음에 프로그래머라면, 어떤 대답을 할 수 있을까요?

여러 가지 대답들이 존재하겠지만, 그 중에 하나인 부분을 지금부터 진행하려 합니다.

지금부터 언급하는 내용은 모니터에 무엇인가를 표현하는 것과 관련이 있습니다.

즉, 드로잉( Drawing ) 작업에 관한 내용입니다.

 

혹시 여러분들은 'GDI' 와 'DirectX' 이들 단어에 대해서 들어본 적이 있습니까?

컴퓨터를 가지고 작업을 경험했던 이들이라면, 들어봤을 법한 용어들입니다.

 

여러분들이 사용하는 컴퓨터의 모니터는 어떤 과정을 거쳐서 우리에게 전달되는 것일까요?

분명히 이것은 하드웨어적인 영역일 수도 있습니다.

조금 더 애플리케이션 프로그래머 관점으로 이 호기심을 확장해 보겠습니다.

'어떻게 하면 모니터 화면을 제어할 수 있을까?'

'어떻게 하면 내가 원하는 모습으로 모니터의 화면에 나타낼 수 있을까?'

'윈도우즈 운영체제에서 화면을 무엇인가를 그리는 시스템은 어떻게 구성되어 있을까?'

 

 

위의 그림은 XP를 포함한 이전 세대의 Windows 운영체제( 이하 XP세대 )가

화면을 제어하는 시스템 구조입니다.

즉, 하드웨어 추상화 계층이라고도 합니다.

XP세대의 운영체제에서는 화면에 무엇인가를 그리기 위해서는

GDI 와 DirectX API, 이 두 가지를 통해서 제어가 가능했습니다.

최종적으로 GDI 와 DirectX API 는 드라이버와 통신을 하면서,

우리의 명령어를 처리해서, 하드웨어로 결과를 보내주게 됩니다.

 

그런데 왜 동일한 일을 하는 구조가 2개씩이나 존재하는 것일까요?

그것은 컴퓨터의 탄생과 발전이 예상을 벗어났기 때문에 생긴 일입니다.

초창기 컴퓨터의 구조는 굉장히 간단했습니다.

아래의 그림을 살펴보겠습니다.

 

 

컴퓨터라는 것은 결국 CPU 에게 연산을 부탁해서 처리된 결과를 메모리에 저장을 합니다.

그리고 이들에 대한 제어를 우리는 I/O 장치들을 통해서 수행하고 확인합니다.

초창기 컴퓨터는 프로세싱 유닛이 CPU 오직 하나였습니다.

그래서 API들은 프로세싱 유닛이 CPU 하나임을 전제로 개발되었습니다.

우리가 지금 배우고 있는 Windows API 역시 CPU가 한나인 경우에

포커스를 두어서 개발되었던 API 였습니다.

 

하지만 오늘날의 컴퓨터는 어떠합니까?

컴퓨터에서 CPU의 수는 2개 이상이고, 앞으로도 그 개수는 증가할 것입니다.

거기다 이제는 프로세싱 유닛이 CPU 뿐만 아니라, GPU 도 활용이 가능한 시대입니다.

메모리는 또 얼마나 발전하고 있습니까?

굉장히 방대한 용량의 메모리를 여러 개 꽂아서 사용하고 있으며,

그래픽 카드에는 비디오 메모리라는 것도 별도로 존재합니다.

컴퓨터의 큰 구조는 변한 것이 없지만, 기능이 점점 세분화 되고 있는 것입니다.

 

이런 하드웨어들의 변화들은 결국 API를 만드는 사람들의 개발 패러다임을 변화시킵니다.

그러한 패러다임의 변화는 애플리케이션 개발자들에게 새로운 학습을 요구합니다.

즉, 여러분들은 그런 변화에 맞추어서 더 많은 공부를 해야 하는 것입니다.T.T

 

 

초창기 Windows 운영체제 차원에서 화면에 그리기 위한 방법은,

GDI 관련 API 를 사용하는 것뿐 이였습니다.

운영체제 차원에서 모든 것을 관리하고 싶었기 때문에,

MS측에서도 별다른 문제를 느끼지 못했을 것입니다.

그런데 예측을 빗나간 부분이 있었는데, 그것은 게임 이였습니다.

게임 개발자들은 GDI 라는 훌륭한 모델이 있음에도 불구하고,

여전히 MS-DOS 기반으로 화면을 제어하고 있었던 것 이였습니다.

이유는 간단했습니다.

GDI 를 활용하면 분명히 쉬운 방법으로 개발을 할 수 있었지만,

DOS 기반의 방법보다 상대적으로 느렸습니다.

MS-DOS를 활용하면 하드웨어를 개발자가 직접 제어할 수 있기 때문에,

Windows 운영체제의 관리를 받는 GDI 보다는 훨씬 빨랐던 것입니다.

( 이 당시만 해도, CPU 사이클을 줄이는 것이 큰 최적화 이슈였습니다. )

MS-DOS를 빨리 벗어나려 했던 MS 입장에서는 적지 않은 충격(?)을 받게 됩니다.

물론 이런 문제는 게임뿐만 아니라,

멀티미디어 관련 개발에서도 나타났던 문제였습니다.^^

 

그렇게 해서 등장한 대한이 바로 DirectX 입니다.

처음 명칭이 GameSDK 였음에서 알 수 있듯이 게임 개발자를 위한 API 였습니다.

이 DirectX는 Windows 95 운영체제부터 추가되어서 활용되기 시작했습니다.

어쩌면, GDI 모델을 완전히 수정할 여유나 계획은 없었을지도 모릅니다.

어찌되었든 현재까지 GDI 와 DirectX는 이렇게 공존하면서 XP 운영체제까지 이르게 되었습니다.

 

XP 세대까지는 화면에 무엇인가를 그리는 작업을 기준으로 봤을 때는,

애플리케이션은 DirectX를 활용하는 애플리케이션과 GDI를 활용하는 애플리케이션으로 나눌 수 있었습니다.

 

GDI( Graphics Device Interface ) 라는 용어에서 보듯이,

일반적인 윈도우 애플리케이션에서 모니터 화면에 무엇인가를 출력하기 위해서는

GDI 라는 것을 이용해서 구현합니다.

이는 2D API 라고 볼 수 있습니다.

GDI를 통해서 애플리케이션과 디바이스 드라이버( Device driver ) 사이를
제어
할 수 있습니다.

XP 세대에서 화면에 보여지는 대부분의 구성은 바로 이 GDI 를 활용한 것입니다.

GDI는 소프트웨어적으로 처리되기 때문에,

그래픽 하드웨어와 상관없이 처리할 수 있다는 큰 장점이 있습니다.

즉, CPU가 그래픽 처리에 필요한 연산을 수행합니다.

 

일반적인 윈도우 애플리케이션은 그래픽 하드웨어를 직접적으로 접근( access )할 수 없습니다.

반드시 GDI를 통해서 디바이스 드라이버에 접근해야 합니다.

GDI 의 최대 강점은 바로 디바이스 독립적( device-dependent ) 이라는 것입니다.

세상의 모든 PC가 동일한 사양을 갖추고 있으면 좋겠지만,

PC들은 저마다 다른 성능과 사양을 갖추고 있기 때문에,

이를 모두 제어하는 것은 굉장히 어렵고 힘든 일입니다.

하지만, GDI 코드는 어떠한 장치에 상관없이 화면에 공통된 그래픽을 표현할 수 있습니다.

왜냐하면 CPU가 이들에 대한 처리를 모두 수행하기 때문입니다.

GDI는 Windows 운영체제의 매우 오래된 기술 중 하나이며,

현재까지도 가장 널리 이용되는 윈도우 그래픽 API 라고 할 수 있습니다.

하지만 최근의 멀티코어 환경과 GPU의 발전은 많은 변화들을 가져왔으며,

GDI도 이러한 변화에 예외가 될 수 없었습니다.

급기야 이제는 DirectX가 GDI를 서서히 대체해 나가는 방향으로
가는 길이 정해지고 말았습니다.T.T

Good-bye!! GDI~~

신고

이번이 SafeInt 라이브러리에 대한 4번째 글이면서 마지막 글입니다. 이전 회의 제 글을 보셨다면 SafeInt가 어떤 것인지, 어떻게 사용하는지 대부분 알게 되셨습니다. 이번에는 SafeInt의 함수 버전에 대해서 설명합니다.

 

 

SafeInt 함수

SafeInt 라이브러리에는 SafeInt 클래스의 인스턴스를 만들지 않고 사용할 수 있도록 몇 개의 함수를 지원하고 있습니다. SafeInt 함수는 정수 오버플로우가 발생하지 않도록 단일 수치 연산을 보호하고 싶을 때 사용합니다. 그리고 복수의 수치 연산을 보호하고 싶을 때는 SafeInt 클래스를 사용하고 함수 버전들을 반복하여 사용하는 것보다는 SafeInt 클래스를 사용하는 것이 더 효율적이라고 합니다.

 

함수

설명

SafeAdd

두 개의 값을 더한다

SafeCast

다른 형으로 캐스팅한다

SafeDivide

두 개의 값으로 나눈다

SafeEquals, SafeGreaterThan, SafeGreaterThanEquals, SafeLessThan, SafeLessThanEquals, SafeNotEquals

2개의 값을 비교한다. 이 함수들을 사용하면 서로 형이 다른 두 개의 값을 형 변환하지 않고 비교할 수 있다

SafeModulus

나머지를 구한다

SafeMultiply

두 개의 값을 곱한다

SafeSubtract

두 개의 값을 뺀다.

 

 

함수들의 이름만 봐도 어떤 것인지 알 수 있고, 이미 어떤 역할을 하는지 아실 테니 따로 길게 설명은 하지 않겠습니다. 아주 간단한 예제를 보여드릴 테니 그것을 보고 대충 어떻게 사용하는지 이해하고 부족한 부분은 MSDN을 참고해 주세요

 

#include <iostream>

#include <safeint.h>

using namespace msl::utilities;

 

 

int main()

{

           unsigned char X1 = 123;

           unsigned char X2 = 200;

           unsigned char X3 = 0;

           if( false == SafeAdd( X1, X2, X3 ) ) {

                     std::cout << "Overflow 발생!!" << std::endl;

           }

          

           unsigned int AA = 100;

           short BB = 101;

           if( false == SafeEquals( AA, BB ) ) {

                     std::cout << "AA BB는 서로 다릅니다" << std::endl;

           }

 

           getchar();

           return 0;

}

< 결과 >


 

 


참고

SafeInt 함수

http://msdn.microsoft.com/ko-kr/library/dd575188.aspx

 

저작자 표시
신고

SafeInt의 예외처리 두 번째 방법은 기본 예외처리 정책을 컴파일 타임에서 선언하는 것입니다.

앞 글에서 우리가 만든 예외처리 클래스를 사용하기 위해 SafeInt를 정의할 때 예외처리 클래스를 템플릿 파라미터의 인자로 넘겼는데 이번에 소개하는 방법은 이 예외처리 클래스 템플릿 인자를 미리 정의해 놓는 것입니다.

 

방법은 SafeInt 헤더파일을 선언하기 전에 #define 문으로 _SAFEINT_DEFAULT_ERROR_POLICY에 예외처리에 사용할 클래스를 선언합니다.

 

// MySafeIntException 이 우리가 정의한 예외처리 클래스입니다.

#define _SAFEINT_DEFAULT_ERROR_POLICY MySafeIntException

 

#include <safeint.h>

 

이렇게 #define 문으로 SafeInt에서 사용할 예외처리 클래스를 선언하고 예외처리 클래스를 정의하면 SafeInt를 선언할 때 템플릿 파라미터로 형만 선언하면 이후 SafeInt를 사용하다가 예외가 발생하면 #define에서 선언한 예외처리 클래스를 호출합니다.

 

#include <iostream>

#define _SAFEINT_DEFAULT_ERROR_POLICY MySafeIntException

 

#include <safeint.h>

using namespace msl::utilities;

 

 

class MySafeIntException : public SafeIntException

{

public:

           static void SafeIntOnOverflow()

           {

                     std::cout << "Overflow 발생!!" << std::endl;

           }

 

           static void SafeIntOnDivZero()

           {

                     std::cout << "0으로 나누기 발생!!" << std::endl;

           }

};

 

 

int main()

{

           SafeInt<unsigned int> X1(1234567);

           SafeInt<unsigned int> X2(1234567);

          

           SafeInt<unsigned int> X3 = X1 * X2;

          

           getchar();

           return 0;

}

 

이전 회에서 소개한 방법과 별 차이 없이 #define 문을 사용한다는 것만 다르니 쉽게 이해했으리라 생각합니다.

 

이제 SafeInt를 사용할 때는 예외처리를 위해 우리는 3가지 방식을 사용할 수 있습니다,

1. try{} catch{}

2. SafeInt에서 사용할 예외처리 클래스를 정의 후 SafeInt 선언 시에 사용

3. #define 문을 사용하여 SafeInt에서 사용할 기본 예외처리 클래스 선언

 

SafeInt 라이브러리는 클래스만 있는 것이 아닙니다. 함수로도 지원합니다. SafeInt의 함수 버전은 SafeInt에 대한 마지막 글이 될 다음 포스팅을 통해서 설명하겠습니다.

 

저작자 표시
신고

이전 글에서 SafeInt를 사용하여 정수 연상을 할 때 오버플로우가 발생할 때 예외처리를 하지 않으면 릴리즈 모드에서는 크래쉬가 발생한다고 하였습니다. 그래서 SafeInt를 사용할 때 꼭 예외처리를 사용해야 합니다.

 

< 코드 1. SafeInt 연산 시 예외처리 구현 >

#include <iostream>

#include <safeint.h>

 using namespace msl::utilities;

 

int main()

{

           SafeInt<unsigned int> X1(1234567);

           SafeInt<unsigned int> X2(1234567);

          

           try

           {

                     SafeInt<unsigned int> X3 = X1 * X2;

           }

           catch(SafeIntException e)

           {

                     std::cout << "overflow!!. ErrorCode : " << e.m_code << std::endl;

           }

 

           getchar();

           return 0;

}

 

< 결과 >

 

 <코드 1>의 결과를 보면 예외 처리에 의해서 프로그램이 크래쉬 되지 않음을 알 수 있습니다. 그런데 SafeInt를 사용할 때마다 <코드1> 처럼 매번 예외처리를 구현한다는 것은 너무 불편합니다. 불편하면 SafeInt를 기피하게 되겠죠. -_-

다행히 SafeInt는 이런 것도 다 감안해서 만들어져 있습니다. 사전에 예외처리를 미리 정의 해 놓으면 매번 예외처리를 정의할 필요가 없습니다.

SafeInt의 예외처리 정의는 두 가지 방법이 있습니다. 이번 회는 두 가지 방법 중 첫 번째 방법을 소개하고 두 번째 것은 다음 회에 소개 하겠습니다.

 

 

 

SafeInt의 예외처리 방법 1

SafeInt의 기본 예외 처리 클래스를 상속 받아서 우리가 원하는 방식으로 정의한 후 그것을 SafeInt의 생성자에 인자로 넘겨주면 SafeInt로 연산 작업을 할 때 예외가 발생하면 우리가 정의한 예외처리를 호출합니다. 백문이불여일견이라고 바로 다음의 코드를 봐 주세요. 아주 간단합니다.

 

< 코드 2. SafeInt의 예외처리 방법 1 >

#include <iostream>
#include <safeint.h>

using namespace msl::utilities;

 

class MySafeIntException : public SafeIntException

{

public:

           static void SafeIntOnOverflow()

           {

                     std::cout << "Overflow 발생!!" << std::endl;

           }

 

           static void SafeIntOnDivZero()

           {

                     std::cout << "0으로 나누기 발생!!" << std::endl;

           }

};

 

int main()

{

           SafeInt<unsigned int, MySafeIntException> X1(1234567);

           SafeInt<unsigned int, MySafeIntException> X2(1234567);

          

           SafeInt<unsigned int, MySafeIntException> X3 = X1 * X2;

          

           getchar();

           return 0;

}

 

< 결과 >

 

 <코드 2>의 결과를 보면 SafeInt로 연산 작업을 할 때 예외처리를 같이 정의하지 않아도 오버플로우로가 발생하면 우리가 정의한 클래스의 멤버 함수를 호출 합니다.

 

SafeInt의 생성자에 인자로 넘기는 예외처리 클래스는 꼭 SafeIntException 클래스를 상속 받고 static void SafeIntOnOverflow()static void SafeIntOnDivZero()를 재정의해야 합니다.

 

이것으로 <코드 1>의 예외처리 방식보다는 좀 편리해졌습니다. 그런데 아마 지금도 마음에 들지 않는 분이 있을 것 같습니다. SafeInt를 생성 할 때마다 매번 예외처리 클래스를 인자로 넘기는 것도 귀찮은 분이 있을 것 같네요. 이런 분들을 위해서 SafeInt는 또 하나더 예외처리 방법을 지원합니다. 그것은 다음 회에서 설명하겠습니다.^^
저작자 표시
신고
#include <iostream> 

int main()

{

           unsigned int X1 = 1234567;

           unsigned int X2 = 1234567;

           unsigned int X3 = X1 * X2;

           std::cout << "X3 = " << X3 << std::endl;

 

           unsigned __int64 BigX1 = 1234567;

           unsigned __int64 BigX2 = 1234567;

           unsigned __int64 BigX3 = BigX1 * BigX2;

           std::cout << "BigX3 = " << BigX3 << std::endl;

 

           getchar();

           return 0;

}

 

<코드1>를 실행하면 변수 X3 BigX3의 값이 서로 같을까요? 혹시 같다고 생각하시는 분들은 unsigned int의 최대 값이 얼마인지 MSDN에서 검색해 보세요... 네 결과는 서로 다릅니다. 둘 다 계산에 사용하는 값은 같지만 결과가 다르게 나오는 이유는 unsigned int로는 X1 X2를 곱해서 나온 값을 보관할 수 없기 때문입니다. X3 = X1 * X2에서 X3는 오버플로우가 발생하여 상위 비트가 삭제되어 올바른 계산 값이 저장되지 않습니다.

 

<코드 1의 결과>


 

<코드 1>의 코드는 사실 별로 길지 않은 코드이기 때문에 실행하기 전에 오버플로우가 발생하리라는 것을 충분히 예상할 수 있고, 혹은 실행 후에 X3의 값이 예상하지 못한 값이 들어가 있어도 문제를 쉽게 파악할 수 있을 것입니다. 그러나 우리가 만드는 애플리케이션은 복잡하고 긴 코드를 가지고 있습니다. <코드 1>과 같은 오버플로우에 의해서 버그가 발생하면 쉽게 버그를 찾기 힘들고 특히 오버플로우에 의해 애플리케이션이 오 동작하여 크래쉬가 발생할 수도 있습니다.

 

안전한 애플리케이션을 만들기 위해서는 안전한 코드를 만들어야 합니다. 보통 안전한 코드를 생각하면 포인터 조작과 문자열 조작을 주로 중요하게 다루고 VC++에서도 안전한 문자열 조작 위해 ‘_s’가 붙은 문자열 조작함수를 사용하도록 VC++에서 종용하고 있어서 요즘은 대 부분 이것을 사용하고 있습니다. 그러나 정수 계산에 대해서는 안전한 코드를 위해 지원해 주는 것이 없었습니다.

 


SafeInt 란?

VC++ 10에서는 안전한 정수 계산을 위해서 새로운 라이브러리를 지원해 줍니다. 이 라이브러리의 이름은 SafeInt 입니다. SafeIntC++의 템플릿으로 만들어서 char 형에서 __int64 형까지 8비트에서 64비트 사이의 크기를 가진 모든 정수 형을 사용할 수 있습니다.

SafeInt 라이브러리를 사용하면 결과를 담을 변수의 형 보다 큰 정수 값 연산을 하거나 0으로 나누기 연산을 할 때 발생하는 오버플로우를 감지 할 수 있습니다.


 

SafeInt 사용

SafeInt를 사용하기 위해서는 헤더 파일 safeint.h 를 포함하고 msl::utilities 이름 공간을 선언해야 합니다.


 < 코드 2 >

#include <iostream>

#include <safeint.h>

using namespace msl::utilities;

 

int main()

{

           SafeInt<unsigned int> X1(1234567);

          

           unsigned int x2 = 1234567;

           SafeInt<unsigned int> X2(x2);


           SafeInt<unsigned int> X3 = X1 * X2;     

        

           getchar();

           return 0;

}

 

<코드 2>를 디버그 모드에서 실행하면 아래와 같은 ASSERT 메시지가 발생합니다.


 

이유는 오버플로우가 발생했기 때문입니다. 그러나 릴리즈 모드에서는 ASSERT 메시지가 발생하지 않습니다. 다만 크래쉬가 발생합니다. -_-;;

오버플로우에 의해서 디버그 모드에서는 ASSERT 메시지, 릴리즈 모드에서는 크래쉬가 발생하는 이유는

SafeInt<unsigned int> X3 = X1 * X2;

에서 예외가 발생하기 됩니다. SafeInt를 사용하는 경우 오버플로우가 발생하면 예외를 발생시키기 때문에 try{} catch{}로 예외를 처리해 주지 않으면 안됩니다. 예외 처리가 올바르게 하면 오버플로우가 발생했을 때 발생하는 문제를 올바르게 대처하던가 어디에서 어떻게 오버플로우가 발생했는지 쉽게 알 수 있습니다.

 

그리고 SafeInt는 일반 정수형과 같이 연산을 할 수도 있다.

<코드 3>

#include <iostream>

#include <safeint.h>

 

using namespace msl::utilities;

 

int main()

{

           SafeInt<unsigned int> X1(1234567);

          

           unsigned int X2 = 123;

           SafeInt<unsigned int> X3 = X1 * X2;

          

           getchar();

           return 0;

}


 

이번 회에는 간단하게 정수 연산 시의 오버플로우 문제와 SafeInt가 무엇인지, SafeInt의 간단한 사용 방법만 설명하였습니다.

다음 회는 <코드 2>에서 발생하는 예외를 어떻게 처리하는지 설명하겠습니다.

 

저작자 표시
신고

앞서 C++/CLI로 만든 클래스 라이브러리를 사용하여 아주 간단한 콘솔 환경의 서버 프로그램을 만들어 보겠습니다.

 

C#에서 Console 프로젝트를 생성합니다.

생성 후 솔루션 탐색기에서 참조에 클래스 라이브러리를 추가합니다.


 

HalfNetworkNET 클래스를 상속 받는 클래스를 생성합니다. 저는 HFServer라는 클래스를 만들었습니다.

그런 후 상속 받은 HalfNetworkNET 클래스의 일부 함수를 재정의 합니다.


 

이제 Main 부분만 남았습니다.

namespace TestServer_HFNetworkNET

{

    class Program

    {

        static void Main(string[] args)

        {

            HFServer Server = new HFServer();

 

            // 서버 초기화 및 설정 후 Port를 연다

Server.Init();

            short nRet = Server.Open();

 

            if (HalfNetwork_NET.ERRORCODE.NONE != nRet)

            {

                Console.WriteLine("Failed Server Open!");

                return;

            }

 

            Console.WriteLine("Server Start !!!");

 

            // 네트웍 처리를 한다.

            while (true)

            {

                Server.Run();

            }

 

            Console.WriteLine("End Server");

 

            // 서버 종료 처리를 한다

            Server.End();

        }

    }

}

 

 

이것으로 아주 간단한 Echo 서버가 만들어졌습니다.

자세한 코드는 첨부된 프로젝트를 보시기 바랍니다.

 

이 프로그램을 실행하면 아래와 같습니다.



프로그램이 올바르게 실행되는지 테스트 하기 위해서는 HalfNetwork 솔루션에 있는 TestClient 프로젝트를 빌드한 후 실행하면 됩니다.

오른쪽이 클라이언트 입니다. 여기서는 10개를 접속 후 0.5초마다 패킷을 보내면 서버(왼쪽)는 받은 데이터를 그대로 보냅니다.


종료하면 종료된 세션을 콘솔에 출력합니다.


닷넷 플랫폼에서 서버 프로그램을 만드는 경우 닷넷 프레임웍에서 제공하는 API를 사용해도 좋지만 기존에 만들어져 편리하고 고성능 라이브러리를 사용하는 것도 아주 괜찮은 선택입니다.

C++로 만들어진 라이브러리를 C++/CLI를 사용하여 필요한 부분만 랩핑하여 클래스를 만들면 닷넷에서 마음껏 사용할 수 있습니다.


 

 

저작자 표시
신고

HalfNetwork_NET.h, HalfNetwork_NET.cpp

 

HalfNetwork ACE를 사용하고 있습니다. 그래서 프로그램이 시작할 때 ACE ACE::Init()를 호출하고, 종료하기 전에는 ACE::fini()를 호출해야 합니다. 이 두 함수는 각각

static void Init_ACE();

static void End_ACE();

에서 호출하고 있습니다.

 

 

네트웍 설정 및 소켓 생성 및 사용은

short Opne();

에서 합니다.

 

내용을 보면 네트웍 설정 정보를 ini 파일을 읽어서 설정합니다.

ini 파일의 내용은 아래와 같습니다.

server_port=15001;

client_port=25251;

workerthread=0;

receivebufferlength=2048;

updateterm=20;

 

server_receive_buff_size=65536;

client_receive_buff_size=4096;

 

client_init_accept_count=126;

 

그리고 이 라이브러리는 서버에서 사용하는 것을 생각했기 때문에 서버-클라이언트 연결과 서버-서버 연결을 가정하여 클라이언트 접속을 위한 Port와 서버 접속을 위한 Port 두 개를 생성합니다.

 

Windows 플랫폼에서는 네트웍 성능을 최대한 내기 위해서는 IOCP를 사용해야 합니다.

HalfNetwork에서 IOCP를 사용하기 위해서는 Proactor 방식을 사용해야 합니다.

if( false == NetworkInstance->Create<HalfNetwork::ProactorFactory>() )

{

          return ERRORCODE::CREATE_PROACTORFACTORY;

}

 

 

설정 작업이 끝나면 Port를 오픈합니다.

if( false == NetworkInstance->Open(&config) ) {

        return ERRORCODE::SERVER_OPEN;

}

 

 

리모트에서 온 데이터를 받기 위해서는

void ProcessRecvQueue();

를 호출합니다.

 

서버가 종료되기 전까지는 ProcessRecvQueue();는 계속 호출해야합니다.

그래서 ProcessRecvQueue() Update()에서 호출하고 있습니다.

 

 

리모트의 접속, 데이터 받기, 연결 끊어짐을 처리하기 위해서는 HalfNetwork_NET 클래스를 상속 받은 클래스가 아래의 함수를 재정의 해야합니다.

virtual void OnAccept_Client( unsigned int StreamID, String^ IP ) abstract;

virtual void OnRead_Client( unsigned int StreamID, array<Byte>^ RecvData ) abstract;

virtual void OnDisConnect_Client( unsigned int StreamID ) abstract;

virtual void OnAccept_Server( unsigned int StreamID, String^ IP ) abstract;

virtual void OnRead_Server( unsigned int StreamID, array<Byte>^ RecvData ) abstract;

virtual void OnDisConnect_Server( unsigned int StreamID ) abstract;

 

이것으로 간단하게 HalfNetwork 라이브러리를 사용하기 위해 닷넷용 클래스 라이브러리를 만드는 것은 끝났습니다.

다음에는 C#으로 이것을 사용하도록 해보겠습니다.

 

 

저작자 표시
신고

C++/CLI용의 클래스 라이브러리 프로젝트를 생성합니다.

저는 HalfNetwork_NET 이라는 이름으로 만들었습니다.

 

프로젝트 설정 하기

앞서 C++에서 만든 HalfNetwork lib 파일을 사용하기 위해 프로젝트 속성에서 몇 가지를 설정합니다.

 

1. HalfNetwork 프로젝트의 헤더 파일을 포함합니다.


 

2. lib 파일의 위치를 설정합니다.


 

이것으로 프로젝트 설정은 끝났습니다.

 

 

코딩 하기

HalfNetwork_NET 프로젝트는 아주 간단하게 만들 생각이므로(만약 실전에서 사용한다면 사용할 프로젝트에 맞게 필요한 기능을 만들어야겠죠) 저는 총 6개의 파일만 사용합니다.

자세한 내용은 첨부되는 코드를 직접 보시기 바라며 여기서는 간단한 설명만 하겠습니다.

 

Stdafx.h

ErrorCode.h

ServerNetwork.h

ServerNetwork.cpp

HalfNetwork_NET.h

HalfNetwork_NET.cpp

 

 

Stdafx.h

여기에서는 HalfNetwork를 사용하기 위해 필요한 파일을 포함합니다.

#include <stdio.h>

#include <string>

#include <vector>

 

#include <ace/Singleton.h>

#include <ace/ace.h>

#include "HalfNetworkType.h"

#include "HalfNetworkDefine.h"

 

 

ErrorCode.h

에러 코드 값을 선언한 파일입니다.

참고로 enum을 사용하지 않고 literal을 사용했는데 이유는 enum을 정의할 때 타입을 지정했는데도 사용할 때 타입캐스팅을 요구해서(왜 그런지 잘 모르겠네요) 사용하기 편하기 위해서 C++에서 const와 같은 역할을 할 literal을 사용했습니다.


 

ServerNetwork.h, ServerNetwork.cpp

아주 단순하게 네트웍으로 리모트에 Send하는 기능만 가지고 있습니다.

HalfNetwork Send를 사용하기 위해서는 네트웍으로 보낼 데이터를 ACE_Message_Block 객체를 만든 후 복사합니다.

bool ServerNetwork::Send( const DWORD StreamID, array<Byte>^ SendData )

{

           int nLength = SendData->Length;

           pin_ptr<Byte> pData = &SendData[0];

                    

           ACE_Message_Block* block = NetworkInstance->AllocateBlock( nLength );

           block->copy( (const char*)pData, nLength );

           NetworkInstance->SendRequest( StreamID, block );

 

           pData = nullptr;

           return true;

}

 파라미터 중 StreamID는 연결된 세션의 인덱스이고 SendData는 보낼 데이터입니다.

 

 

이번 회는 여기까지 하고 나머지는 다음 회에 이어서 설명하겠습니다.

 

 

저작자 표시
신고

이제 본격적으로 들어갑니다.^^

 

HalfNetwork 다운로드

먼저 HalfNetwork 라이브러리를 프로젝트 사이트에 가서 받습니다.

http://code.google.com/p/halfnetwork/downloads/list


 

다운로드가 끝나면 압축을 풉니다.

저는 제 컴퓨터의 E:\Projects\HalfNetwork 에 압축을 풀어서 안에 ExternalLib 폴더와 HalfNetwork 폴더가 만들어졌습니다.

 

 

HalfNetwork 빌더하기

E:\Projects\HalfNetwork\HalfNetwork 폴더에 가면 VS 솔루션 파일이 있습니다. 그 중 VS2010 솔루션인 HalfNetwork_vc10.sln 을 선택합니다.

프로젝트를 열면 ‘HalfNetwork’ 프로젝트를 시작 프로젝트로 선택합니다.

C++에서만 사용하는 경우라면 그대로 빌드를 하면 됩니다. 그러나 (방법 1)C++/CLI에서 사용하기 위해서는 프로젝트 속성에서 [C/C++] – [코드 생성]에서 런타임 라이브러리값을 다중 스레드 DLL’로 바꾸어야 합니다.


 

C++/CLI에서만 사용하지 않을 생각이라면 구성을 하나 더 만들기를 추천합니다.

(방법 2)저는 기존의 구성에 ‘DebugMDd’(디버그 모드의 경우)라는 것을 만들어서 런타임 라이브러리값을 바꾸었습니다.



그리고 기존 구성에서 만들어진 출력 파일과 이름이 겹치지 않도록 이름도 변경하도록 합니다.


(Debug 모드에서의 대상 이름 속성의 값은 ‘$(ProjectName).x86.debug’였습니다)

 

그럼 이제 빌드를 합니다. VS2010만 제대로 설치되어 있다면 아무런 문제 없이 빌드가 될 것입니다. C++/CLI에서 사용하기 위해서 약간 수정을 했지만 만약 C++에서만 사용한다면 HalfNetwork는 솔루션 파일을 연 후 빌드를 선택하는 것으로 준비는 끝납니다.

 

빌드가 끝나면 E:\Projects\HalfNetwork\HalfNetwork\Bin32 폴더에 lib 파일이 만들어져 있습니다.


저는 (방법 2)로 빌드를 했기 때문에 HalfNetwork.x86.debugMDd.lib 라는 파일이 만들어졌습니다.

 

이것으로 HalfNetwork lib 파일을 만드는 것은 끝났습니다.

 

다음 회에서는 이번에 만든 lib 파일을 사용하여 닷넷용 클래스 라이브러리를 만들어 보겠습니다.

 

저작자 표시
신고

MSDN을 보다 보니 C++/CLI의 델리게이트에 네이티브용 함수를 할당할 수 있는 방법이 있어서 소개합니다. 아래의 코드는 MSDN에 있는 것입니다.

 

#pragma unmanaged

extern "C" void printf(const char*, ...);

class A {

public:

   static void func(char* s) {

      printf(s);

   }

};

 

#pragma managed

public delegate void func(char*);

 

ref class B {

   A* ap;

 

public:

   B(A* ap):ap(ap) {}

   void func(char* s) {

      ap->func(s);

   }

};

 

int main() {

   A* a = new A;

   B^ b = gcnew B(a);

   func^ f = gcnew func(b, &B::func);

   f("hello");

   delete a;

}

< http://msdn.microsoft.com/ja-jp/library/9cy3ccxx%28v=VS.80%29.aspx >

 

위 코드 중 #pragma unmanaged 지시어 이하는 컴파일러에서 비관리코드로 취급합니다. 그리고 #pragma managed 이하는 관리코드로 취급합니다. 내용이 간단하고 어려운 부분이 없기 때문에 따로 자세한 설명은 생략하겠습니다.

 


C++/CLI는 단순하게 닷넷 플랫폼에서 사용할 수 있는 C++ 언어라기 보다는 C++ 언어의 부족한 부분을 진화 시킨 언어라고도 생각할 수 있는 부분이 꽤 있습니다. 그러나 C++/CLI는 C++과 C#의 중간의 애매한 위치에 있어서 양쪽 프로그래머 모두에게 별로 호응을 받지 못하는 것 같습니다. 그래서 C++/CLI 관련 글을 제가 처음에 생각했던 것보다는 조금 일찍 끝낼려고 합니다.

C++/CLI를 사용하는 대부분의 프로그래머들은 아마 기존의 비관리 코드를 관리코드에서 사용하고 싶을 때라고 생각합니다. C++/CLI의 기능 소개는 이번으로 일단 끝내고 앞으로는 비관리코드와의 연계에 대해서 실제 사례 보여주면서 설명하려고 합니다.


사례는 오픈 소스 네트워크 라이브러리인 HalfNetworkC++/CLI를 사용하여 관리코드에서 사용할 수 있도록 wrapping한 후 이것을 관리코드에서 사용할 예정입니다.

HalfNetwork는 온라인 게임 서버 프로그래머인 임영기님이 만든 것으로 ACE 라는 오픈 소스 네트워크 라이브러리를 사용하기 편하게 만든 라이브러리입니다.

 

소스 위치 http://code.google.com/p/halfnetwork/

문서 http://code.google.com/p/halfnetwork/w/list http://jacking.tistory.com/category/HalfNetwork

임영기님 블로그 http://javawork.egloos.com/

 

요즘 공부할 것은 너무 많은데 따라갈 시간은 부족해서 다음 글은 언제쯤 올리지 정확하게 알 수 없지만 최대한 빨리 다음 글들을 올려서 C++/CLI을 2010년 안에는 끝내고 내년에는 새로운 주제로 시작하겠습니다^^

 

저작자 표시
신고

C++/CLI은 네이티브 C++과 다르게 자료구조 배열을 사용하기 위해서는 array 컨테이너를 사용합니다.

array 컨테이너는 기본적으로 non-CLI 오브젝트는 사용할 수가 없습니다. 그러나 non-CLI 오브젝트가 포인터라면 사용할 수 있습니다.

 

아래는 array 컨테이너에 non-CLI 오브젝트를 사용한 경우입니다.

using namespace System;

 

class CNative

{

public:

           CNative()

           {

                     Console::WriteLine(__FUNCTION__);

           }

           ~CNative()

           {

                     Console::WriteLine(__FUNCTION__);

           }

};

 

int main(array<System::String ^> ^args)

{

           array<CNative>^ arr = gcnew array<CNative>(2);

           return 0;

}

 빌드하면 위에 이야기 했듯이 아래와 같은 빌드 에러가 발생합니다.


 

그럼 이번에는 non-CLI 오브젝트의 포인터를 사용해 보겠습니다.

#include "stdafx.h"

#include <iostream>

using namespace System;

 

class CNative

{

public:

           CNative()

           {

                     Console::WriteLine(__FUNCTION__);

           }

           ~CNative()

           {

                     Console::WriteLine(__FUNCTION__);

           }

};

 

int main(array<System::String ^> ^args)

{

           array<CNative*>^ arr = gcnew array<CNative*>(2);

           for(int i=0; i<arr->Length; i++)

           {

                     arr[i] = new CNative();

           }

 

           getchar();

           return 0;

}


이번에는 빌드에 문제가 없어서 아래와 같이 실행결과가 나옵니다.


 

그러나 위의 실행 결과를 보면 이상한 점을 발견하실 수 있을 것입니다. 그것은 CNative 오브젝트의 생성자는 호출하지만 파괴자는 호출되지 않은 것입니다. 이것은 array 컨테이너는 CLI 객체이므로 GC에서 관리하지만 non-CLI 오브젝트를 포인터 타입으로 사용한 것은 GC에서 관리하지 않으므로 만약 array GC에서 소멸 되기 전에 array에 담겨있는 non-CLI 오브젝트를 메모리(new로 할당한)를 해제하지 않으면 메모리 해제가 되지 않아서 메모리 릭이 발생합니다.

 

그래서 아래와 같이 array GC에서 소멸되기 전에 메모리를 해제하도록 해야합니다.

#include "stdafx.h"

#include <iostream>

using namespace System;

 

class CNative

{

public:

           CNative()

           {

                     Console::WriteLine(__FUNCTION__);

           }

           ~CNative()

           {

                     Console::WriteLine(__FUNCTION__);

           }

};

 

int main(array<System::String ^> ^args)

{

           array<CNative*>^ arr = gcnew array<CNative*>(2);

           for(int i=0; i<arr->Length; i++)

           {

                     arr[i] = new CNative();

           }

 

           for(int i=0; i<arr->Length; i++)

           {

                     delete arr[i];

           }

          

           getchar();

           return 0;

}

 

실행 결과


이번에는 CNative의 파괴자가 제대로 호출되고 있습니다.



출처

도서 "C++/CLI In Action"

C++/CLI를 공부하시는 분들은 "C++/CLI In Action" 책을 꼭 한번 보시기를 추천합니다.

저작자 표시
신고

트위터에서 @All2one님을 통해서 GCC 컴파일러에서 RValue Reference Move Semantics를 사용했을 경우와 그렇지 않은 경우 STL에서 얼마만큼의 성능 차이가 나는지 테스트를 한 결과를 보았습니다.

사이트 주소는 http://cpp-next.com/archive/2010/10/howards-stl-move-semantics-benchmark/

입니다.

 

이것은 GCC 컴파일러를 사용한 경우라서 저는 VC++을 사용하여 어떤 결과가 나오는지 궁금해서 테스트 해 보았습니다.

RValue Reference을 사용하지 않는 가장 최신의 컴파일러는 VC++ 9(VS2008)이므로 VC++10 VC++9를 같은 코드로 컴파일한 후 실행하였습니다.

 

먼저 테스트한 컴퓨터의 하드웨어 사양은 아래와 같습니다.


 

테스트 코드는 아래와 같습니다(GCC로 테스트한 것과 같은 코드입니다).

#include <vector>

#include <iostream>

#include <time.h>

#include <set>

#include <algorithm>

 

const unsigned N = 3001;

 

extern bool some_test;

 

std::set<int>

get_set(int)

{

    std::set<int> s;

    for (int i = 0; i < N; ++i)

        while (!s.insert(std::rand()).second)

            ;

    if (some_test)

        return s;

    return std::set<int>();

}

 

std::vector<std::set<int> >

generate()

{

    std::vector<std::set<int> > v;

    for (int i = 0; i < N; ++i)

        v.push_back(get_set(i));

    if (some_test)

        return v;

    return std::vector<std::set<int> >();

}

 

float time_it()

{

    clock_t t1, t2, t3, t4;

    clock_t t0 = clock();

    {

    std::vector<std::set<int> > v = generate();

    t1 = clock();

    std::cout << "construction took " << (float)((t1 - t0)/(double)CLOCKS_PER_SEC) << std::endl;

    std::sort(v.begin(), v.end());

    t2 = clock();

    std::cout << "sort took " << (float)((t2 - t1)/(double)CLOCKS_PER_SEC) << std::endl;

    std::rotate(v.begin(), v.begin() + v.size()/2, v.end());

    t3 = clock();

    std::cout << "rotate took " << (float)((t3 - t2)/(double)CLOCKS_PER_SEC) << std::endl;

    }

    t4 = clock();

    std::cout << "destruction took " << (float)((t4 - t3)/(double)CLOCKS_PER_SEC) << std::endl;

    std::cout << "done" << std::endl;

    return (float)((t4-t0)/(double)CLOCKS_PER_SEC);

}

 

int main()

{

    std::cout << "N = " << N << '\n';

    float t = time_it();

    std::cout << "Total time = " << t << '\n';

}

 

bool some_test = true;

 

 

< 결과 >


 


 


이 결과를 보면 생성과 알고리즘을 사용했을 때 많은 차이가 나는 것을 알 수 있습니다.

기존에 STL의 알고리즘을 자주 사용한 경우라면 VC++ 10으로 컴파일만 해도 어느 정도의 성능 향상을 얻을 수 있을 것 같습니다.

 

저작자 표시
신고

[Step. 14] 인터페이스 ( interface )

C++/CLI 2010.10.01 09:30 Posted by 흥배

인터페이스는 비관리코드에서는 순수가상함수만을 가진 클래스와 같습니다. ‘interface class’라는 키워드를 사용하여 정의하면 이 클래스에는 아래와 같은 형만 멤버로 가질 수 있습니다.

 

함수

프로퍼티

이벤트

 

또한 선언만 가능하지 정의는 할 수 없습니다.

 


C++/CLI ref class C#이나 Java와 같이 다중 상속은 할 수 없지만 인터페이스를 사용하면 다중 상속(즉 인터페이스를 상속)을 할 수 있습니다.

 

interface class IA {

public:

    void funcIA();

};

interface class IB {

public:

    void funcIB();

};

interface class IC {

public:

    void funcIC();

};

ref class A {

    int i;

};

ref class B : A, IA, IB, IC {

public:

    virtual void funcIA() {   }

    virtual void funcIB() {   }

    virtual void funcIC() {   }

};

int main() {

    B^ b = gcnew B;

 

    IA^ ia = b;

    IB^ ib = b;

    IC^ ic = b;

    b->funcIA();

    ia->funcIA();

    ib->funcIB();

    ic->funcIC();

    return 0;

}

 



출처

http://cppcli.shacknet.nu/cli:interface

 

 

저작자 표시
신고

[Step. 15] static 생성자, initonly, literal

C++/CLI 2010.09.24 09:00 Posted by 흥배

static 생성자

 

static 생성자는 클래스의 생성자에서 static 멤버를 초기화 하고 싶을 때 사용합니다.

ref class, value class, interface에서 사용할 수 있습니다.

 

#include "stdafx.h"

#include <iostream>

 

using namespace System;

 

ref class A {

public:

    static int a_;

    static A()

    {

        a_ += 10;

    }

};

ref class B {

public:

    static int b_;

    static B()

    {

//        a_ += 10; // error

        b_ += 10;

    }

};

ref class C {

public:

    static int c_ = 100;

    static C()

    {

        c_ = 10;

    }

};

 

int main()

{

    Console::WriteLine(A::a_);

    A::A();

    Console::WriteLine(A::a_);

 

    Console::WriteLine(B::b_);

 
    Console::WriteLine(C::c_);


     getchar();

    return 0;

}

 

< 결과 >


 

static 생성자는 런타임에서 호출하기 때문에 클래스 A의 멤버 a_는 이미 10으로 설정되어 있습니다. 그리고 이미 런타임에서 호출하였기 때문에 명시적으로 A::A()를 호출해도 실제로는 호출되지 않습니다.

 

클래스 B의 경우 static 생성자에서 비 static 멤버를 호출하면 에러가 발생합니다.

 

클래스 C의 경우 static 멤버 c_를 선언과 동시에 초기화 했지만 런타임에서 static 생성자를 호출하여 값이 10으로 설정되었습니다.

 

 

 

initonly

 

initonly로 선언된 멤버는 생성자에서만 값을 설정할 수 있습니다. 그리고 initonly static로 선언된 멤버는 static 생성자에서만 값을 설정할 수 있습니다.

 

 

ref class C
{
public:
    initonly static int x;
    initonly static int y;
    initonly int z;
    static C()
    {
        x = 1;
        y = 2;
        // z = 3; // Error
    }
    C()
    {
        // A = 2; // Error
        z = 3;
    }
    void sfunc()
    {
        // x = 5; // Error
        // z = 5; // Error
    }
};


int main()
{
    System::Console::WriteLine(C::x);
    System::Console::WriteLine(C::y);
    C c;
    System::Console::WriteLine(c.z);
    return 0;
}

 

 

 

literal

 

literal로 선언된 멤버는 선언과 동시에 값을 설정하고 이후 쓰기는 불가능합니다. 오직 읽기만 가능합니다.

using namespace System;
ref class C
{
public:
    literal String^ S = "Hello";
    literal int I = 100;
};

int main()
{
    Console::WriteLine(C::S);
    Console::WriteLine(C::I);
    return 0;
}



참고
http://cppcli.shacknet.nu/cli:static%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF
http://cppcli.shacknet.nu/cli:initonly
http://cppcli.shacknet.nu/cli:literal





저작자 표시
신고

[Step. 13] parameter array

C++/CLI 2010.09.10 09:00 Posted by 흥배

비관리코드에서 로그 기능을 구현할 때 주로 가변 길이 인수를 사용합니다.

void LOG( char* szText, ... )

{

}

 


위와 같은 가변 길이 인수를 C++/CLI에서는 parameter array라는 것으로 구현합니다.

void LOG( … array<Object^>^ Values )

{

for each( Object^ value in Values )

{

   ….

}

}

 

int main()

{

LOG( 23 );

LOG( 2, 1, “error” );

 

return 0;

}

 


parameter array를 사용하면 이전 보다 안전하고, 하나의 형이 아닌 다양한 형을 인자로 받아 들일 수 있어서 유연성이 높습니다.

 

그러나 하나의 함수에서 parameter array는 하나 밖에 사용하지 못합니다. 하지만 parameter array가 아닌 형이라면 여러 개 사용할 수 있습니다. 다만 이 때는 parameter array는 가장 마지막 인수가 되어야 합니다.

 

void LOG( int nLogLevel, … array<Object^>^ Values )

{

}

 

 


참고

http://cppcli.shacknet.nu/cli:parameter_array

 

저작자 표시
신고

'C++/CLI' 카테고리의 다른 글

[Step. 14] 인터페이스 ( interface )  (1) 2010.10.01
[Step. 15] static 생성자, initonly, literal  (1) 2010.09.24
[Step. 13] parameter array  (1) 2010.09.10
[Step. 12] for each  (1) 2010.09.03
[Step. 11] 열거형( enum )  (2) 2010.08.27
[Step. 10] 이벤트 ( event )  (0) 2010.08.20

[Step. 12] for each

C++/CLI 2010.09.03 09:00 Posted by 흥배

데이터셋에 있는 요소를 열거할 때 비관리코드에서는 보통 for문이나 while문을 자주 사용합니다.

그러나 C++/CLI에서는 for each을 사용하여 데이터셋에 있는 요소들을 열거할 수 있습니다.

for each에서 사용할 수 있는 것은 배열 이외에도 아래의 형으로 구현한 것들을 사용할 수 있습니다.

 

1. IEnumerable 인터페이스를 구현한 클래스

2. STL의 이터레이터와 같은 것을 가지고 있는 클래스

 


참고로 VC++ 8(VS 2005)에서는 비관리코드에서도 for each 문을 지원하고 있습니다.

for each를 사용할 때 주의해야 할 점은 열거하는 요소를 변경할 수는 없다는 것입니다.

 

#include "stdafx.h"

#include <iostream>

#include <vector>

#include <map>

 

using namespace std;

using namespace System;

using namespace System::Collections;

 

 

int main(array<System::String ^> ^args)

{

     // 배열

     array< int >^ Nums = { 0, 1, 2, 3, 4, 5 };

     for each(int value in Nums)

     {

          value = 2;

          Console::WriteLine( value );

     }

    // 위에서 for each 내부에서 요소의 값을 바꾸었지만 아래의 출력 값을 보면

    // 바뀌지 않은 것을 알 수 있습니다.

    for each(int value in Nums)

    {

          Console::WriteLine( value );

    }

 

   

    // 리스트

    ArrayList^ NumList = gcnew ArrayList();

    NumList->Add(1);

    NumList->Add(2);

    NumList->Add(3);

 

    for each(int value in NumList)

    {

        Console::WriteLine(value);

    }

 

 

    // vector

    vector<int> vi;

    vi.push_back(3);

    vi.push_back(4);

   

    for each(int i in vi)

    {

        Console::WriteLine(i);

    }

 

 

    // map

    map<const char*, int> num;

    num["ten"] = 10;

    num["hundred"] = 100;

 

    for each( pair<const char*, int> c in num )

    {

        Console::WriteLine(gcnew String(c.first) + c.second.ToString());

    }

 

 

    // 해쉬 테이블

    Hashtable^ ht = gcnew Hashtable();

    ht["aaa"] = "111";

    ht["bbb"] = "222";

    for each(DictionaryEntry^ dic in ht)

    {

        Console::WriteLine(dic->Key->ToString() + dic->Value->ToString());

    }

    

 

    getchar();

    return 0;

}

저작자 표시
신고

'C++/CLI' 카테고리의 다른 글

[Step. 15] static 생성자, initonly, literal  (1) 2010.09.24
[Step. 13] parameter array  (1) 2010.09.10
[Step. 12] for each  (1) 2010.09.03
[Step. 11] 열거형( enum )  (2) 2010.08.27
[Step. 10] 이벤트 ( event )  (0) 2010.08.20
[Step. 09] 델리게이트 (delegate)  (2) 2010.08.12

[Step. 11] 열거형( enum )

C++/CLI 2010.08.27 09:00 Posted by 흥배


비관리 코드의 열거형

 

비관리 코드에서 열거형을 정의할 때는 다음과 같습니다.

 

enum WEAPON_TYPE

{

GUN = 1,

SWORD = 2,

BOW = 3

};

 


열거형은 정수형으로 int 형에 대입할 수 있습니다.

int nUsedWeapon = GUN;

 

그런데 저는 위의 방식으로 사용할 때 ‘GUN’이라고 사용하기 보다는 ‘GUN’이 어떤 열거형에 속하는지 표시할 수 있도록 좀 더 다른 방식으로 사용하고 있습니다.

 

struct WEAPON

{

enum TYPE

{

GUN = 1,

SWORD = 2,

BOW = 3

};

};

 

int nUsedWeapon = WEAPON::GUN;

 

이렇게 저는 열거형을 조금 이상한 방법으로 사용하고 있는데 C++/CLI에서는 그럴 필요가 없어졌습니다. C++/CLI는 제가 딱 원하는 방식을 정식으로 지원하고 있습니다.

 


 

C++/CLI의 열거형

 

enum class WEAPON

{

   GUN = 1,

SWORD = 2,

BOW = 3

};

 

int nUsedWeapon = static_cast<int>(WEAPON::GUN);

 

C++/CLI의 열거형은 비관리코드와 비교해서 다른 점은 위에서 알 수 있듯이 암묵적으로 int 형에 대입할 수 없습니다. 왜냐하면 열거형은 정수형이 아니고 object이기 때문입니다.

그래서 캐스팅을 해야 합니다.

 

그리고 C++/CLI의 열거형은 정수형을 명시적으로 정할 수 있습니다.

enum class WEAPON : short

{

   GUN = 1,

SWORD = 2,

BOW = 3

};

 


< 추가 > - 2010. 12. 10

VC++10에서 열거형의 타입을 바로 위의 코드와 같이 명시적으로 지정해도 사용할 수가 없습니다.

enum을 사용할 때 타입 캐스팅을 해야합니다. 왜 이런지 저도 자세한 이유는 모르겠습니다.

C++/CLI에서는 enum 보다는 literal을 사용하는 것이 더 좋을 것 같다고 생각합니다.


public ref class WEAPON
{
public:
        literal short GUN = 1;
        literal short SWORD = 2;

        literal short BOW = 3;

};




참고

http://msdn.microsoft.com/ko-kr/library/ms235243.aspx

 

 

 



저작자 표시
신고

'C++/CLI' 카테고리의 다른 글

[Step. 13] parameter array  (1) 2010.09.10
[Step. 12] for each  (1) 2010.09.03
[Step. 11] 열거형( enum )  (2) 2010.08.27
[Step. 10] 이벤트 ( event )  (0) 2010.08.20
[Step. 09] 델리게이트 (delegate)  (2) 2010.08.12
[Step. 08] 프로퍼티 ( property )  (8) 2010.08.06

[Step. 10] 이벤트 ( event )

C++/CLI 2010.08.20 09:00 Posted by 흥배
이벤트는 그 이름처럼 이벤트를 처리할 때 사용하는 것으로 델리게이트와 조합하여 사용합니다.

보통 이벤트를 설명할 때 button 클래스를 예제로 사용하므로 저도 이것을 사용하여 간단하게 설명하겠습니다.^^

button 클래스의 여러 이벤트 중 clicked 이벤트를 구현할 때 이벤트로 불러질 클라이언트는 OnClick()이라는 멤버함수를 정의한 후 이것을 button 클래스의 clicked에 등록합니다.

 

delegate void ClickHandler();
ref class button {
public:
    event ClickHandler^ Clicked;
    button() {
        btnForTest = this;
    }
public:
    void someoneclicked() {
        Clicked();
    }
    static button^ btnForTest;
};

ref class Client {
private:
    button^ btn;
public:
    Client() {
        btn = gcnew button;
        btn->Clicked += gcnew ClickHandler(this, &Client::OnClick);
    }
private:
    void OnClick() {
        Console::WriteLine("someone clicked.");
    }
};
int main()
{
    Client^ client = gcnew Client;
   
button::btnForTest->someoneclicked();
    return 0;
}
< 출처 : http://cppcli.shacknet.nu/cli:event >
 
 
event는 몇 개의 제약이 있습니다. 
1. Clicked 호출은 button 밖에 할 수 없습니다. 
2. 클라이언트가 Clicked에 대한 조작은 += -=만 할 수 있으며 Clicked() Clicked = nullptr 같은 
것은 할 수 없습니다.
 
 
참고
http://cppcli.shacknet.nu/cli:event






저작자 표시
신고