[Step. 11] 열거형( enum )

C++/CLI 2010. 8. 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. 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. 8. 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






[Step. 09] 델리게이트 (delegate)

C++/CLI 2010. 8. 12. 09:30 Posted by 알 수 없는 사용자
델리게이트는 비관리코드에서는 함수 포인터와 같은 기능을 합니다.

 

#include <iostream>

delegate void TestDelegate();

ref class TEST

{

public:

void TestMethod()

{

  System::Console::WriteLine(“TEST::TestMethod()”);

}

};

 

int main()

{

TEST^ test = gcnew TEST();

TestDelegate^ testDelegate = gcnew TestDelegate( test, &TEST::TestMethod );

 testDelegate();


 getchar();

    return 0;

};

 

 

정적 멤버함수의 델리게이트

 

#include <iostream>

 delegate void TestDelegate();

 ref class TEST

{

public:

static void TestMethod()

{

  System::Console::WriteLine(“TEST::TestMethod()”);

}

};

 

int main()

{

TestDelegate^ testDelegate = gcnew TestDelegate( &TEST::TestMethod );

 

testDelegate();

 

getchar();

return 0;

};

 

 

복수 개의 델리게이트

 

델리게이트는 하나가 아닌 여러 개를 설정할 있습니다.

 

#include <iostream>

delegate void TestDelegate();

 

ref class TESTA

{

public:

void TestMethod()

{

  System::Console::WriteLine(“TESTA::TestMethod()”);

}

};

 

ref class TESTB

{

public:

static void TestMethod()

{

  System::Console::WriteLine(“TESTB::TestMethod()”);

}

};

 

ref class TESTC

{

public:

void operator()()

{

  System::Console::WriteLine(“TESTC::operator()”);

}

};

 

int main()

{

TestDelegate^ testDelegate;

TESTA testA;

TESTC testC;

 

testDelegate = gcnew TestDelegate( testA, &TESTA::TestMethod );

testDelegate += gcnew TestDelegate(&TESTB::TestMethod );

testDelegate += gcnew TestDelegate( testC, &TESTC::operator );

 

testDelegate();

 

getchar();

return 0;

};

 

 

델리게이트의 비교와 삭제

 ==으로 델리게이트를 비교하면 이것은 핸들의 비교가 아닌 델리게이트가 가지고 있는 함수를 비교하는 것입니다. -=을 사용하여 설정한 델리게이트를 제거할 수도 있습니다.

using namespace System;

delegate void MyDele(int);


void func1(int i)

{

    Console::WriteLine("func1");

}


void func2(int j)

{

    Console::WriteLine("func2");

}


int main()

{

    MyDele^ dele;

    dele += gcnew MyDele(&func1);

    dele += gcnew MyDele(&func2);

    MyDele^ dele2 = gcnew MyDele(func1);

    dele2 += gcnew MyDele(&func2);

    if ( dele == dele2 )  {

        Console::WriteLine("TRUE");

    } else {

        Console::WriteLine("FALSE");

    }

   

    dele -= gcnew MyDele(&func1); 

   

    dele(1); 

   

   return 0;

}

< 예제 출처 : http://cppcli.shacknet.nu/cli:delegate >

 

위의 예의 델리게이트들은 모두 void를 반환하고 파라미터가 없는 것인데 당연하듯이 반환 값이나 파라미터를 가질 수 있습니다.




델리게이트의 비동기 실행

 

델리게이트는 비동기 실행을 지원합니다. 비동기 실행은 처리를 요청한 후 종료를 기다리지 않은 호출한 곳으로 제어를 넘겨줍니다. 델리게이트 함수가 긴 시간을 필요로 하는 작업인 경우 비동기 실행을 이용하면 프로그램의 응답성을 높일 수 있습니다.

 

델리게이트의 비동기 실행은 스레드를 사용합니다. 이런 경우 비동기 실행을 할 때마다 스레드의 생성과 소멸에 부담을 느낄 수도 있지만 델리게이트는 닷넷의 기능을 잘 활용하여 스레드를 생성/삭제하지 않고 스레드 풀에 있는 스레드를 사용하므로 스레드 사용에 대한 부담이 작습니다.

 

비동기 실행을 할 때는 주의해야 할 점이 있습니다. 비동기 실행을 하는 경우 델리게이트에는 꼭 하나의 함수만 등록해야 합니다. 만약 2개 이상 등록하였다면 예외가 발생합니다.

 

비동기 실행은 BeginInvoke()를 사용하고, 만약 종료를 기다리고 싶다면 EndInvoke()를 사용합니다.

 

#include "stdafx.h"

#include <iostream>

 

using namespace System;

 

delegate void MyDele(void);

 

void myfunc(void)

{

    System::Threading::Thread::Sleep(3000);

}

 

 

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

{

     MyDele^ dele = gcnew MyDele(&myfunc);

            Console::WriteLine(L"1");

     IAsyncResult^ result = dele->BeginInvoke(nullptr,nullptr);

            Console::WriteLine(L"2");

    dele->EndInvoke(result);

            Console::WriteLine(L"3");

   

    getchar();

    return 0;

}

 

위 코드를 실행하면 '2'가 찍힌 이후 3초가 지난 이후에 3이 찍힙니다.





참고
http://cppcli.shacknet.nu/cli:delegate
http://cppcli.shacknet.nu/cli:delegate%E3%81%9D%E3%81%AE2



VS2010 C++ 프로젝트의 디렉토리 설정

Visual C++ 10 2010. 8. 9. 09:00 Posted by 알 수 없는 사용자

VS2008까지는 도구’ – ‘옵션메뉴를 통해서 VC++ 디렉토리를 설정하였습니다.

이렇게 설정한 정보는 모든 VC++ 프로젝트에 적용됩니다.

 

그러나 VS2010에서는 각 프로젝트 별로 VC++ 디렉토리를 설정합니다.

 


각 프로젝트 마다 독립적으로 설정을 할 수 있어서 편한 부분도 있지만 때로는 모든 프로젝트에 공통적으로 적용되어야 하는 경우는 매번 설정하는 것이 귀찮을 수 있습니다.

( 예로 DirectX boost 라이브러리 등 )

 

이런 경우 속성 매니저를 통해서 VC++ 디렉토리를 설정하면 모든 프로젝트에 공통적으로 적용할 수 있습니다.

 

1. 일단 아무 프로젝트 하나를 열어 놓습니다.

2. 메뉴의 보기’ -> ‘속성 관리자를 선택합니다.



3. 속성 관리자에서 ‘Microsoft.Cpp.Win32.user’를 더블 클릭해서 열어 놓습니다.


 

여기서 설정한 정보는 모든 프로젝트에 공통적으로 적용됩니다.

 

 

[Step. 08] 프로퍼티 ( property )

C++/CLI 2010. 8. 6. 09:30 Posted by 알 수 없는 사용자

보통 비관리 클래스를 정의할 때 캡슐화를 위해서 멤버 변수는 최대한 private 접근으로 한 후 외부에서의 접근을 위해서 get set 멤버 함수를 정의합니다.

 

class Character

{

public:

…….

   void SetCharCd( const int nCharCd ) { m_nCharCd = nCharCd; }

   int GetCharCd() { return m_nCharCd; }

…….

private:

   int m_nCharCd;

   …….

};

 


클래스의 멤버를 하나 정의할 때마다 그에 대응하는 get, set 멤버 함수를 정의하는 것은 좀 귀찮은 일이기도 합니다. 그래서 관리코드에서는 이 작업을 쉽게 해주는 property가 생겼습니다.

 

ref class Character

{

public:

…….

    property int CharCd

{

   void set( int nCharCd ) { m_nCharCd = nCharCd; }

   int get() { return m_nCharCd; }

}  

…….

private:

   int m_nCharCd;

   …….

};

 

위 코드에서는 get set을 둘 다 정의 했는데 둘 중 하나만 정의 해도 괜찮습니다.

또 위의 set은 아주 간단하게 그냥 대입만 하고 있는데 좀 더 로직을 넣을 수도 있습니다.

 

ref class Character

{

public:

…….

    property int CharCd

{

   void set( int nCharCd )

{

  if( nCharCd < 0 ) {

     m_nCharCd = 0;

  } else {

     m_nCharCd = nCharCd;

  }

}

 

   int get() { return m_nCharCd; }

}  

…….

private:

   int m_nCharCd;

   …….

};

 

 


property 선언과 정의 나누기


일반적으로 클래스의 멤버 선언은 헤더 파일에 정의는 cpp 파일에 하듯이 property도 선언과 정의를 나눌 수 있습니다.

 

ref class Character

{

public:

…….

    property int CharCd

{

   void set( int nCharCd );

   int get();

}  

…….

private:

   int m_nCharCd;

   …….

};

 

 

void Character::CharCd::set( int nCharCd )

{

if( nCharCd < 0 ) {

m_nCharCd = 0;

} else {

   m_nCharCd = nCharCd;

  }

}

 

void Character::CharCd::get()

{

return m_nCharCd;

}

 



 

get set의 접근 지정자 다르게 하기

 

위에서 property를 정의할 때 get set은 모두 public 였습니다.

이것을 각각 다르게 접근 지정자를 정할 수 있습니다.

 

ref class Character

{

public:

…….

    property int CharCd

{

protected:

   void set( int nCharCd )

{

  if( nCharCd < 0 ) {

     m_nCharCd = 0;

  } else {

     m_nCharCd = nCharCd;

  }

}

 

    public:

   int get() { return m_nCharCd; }

}  

…….

private:

   int m_nCharCd;

   …….

};

 

 

 

 

참고

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

http://vene.wankuma.com/prog/CppCli_Property.aspx

 

 

[Upgrade to VC++ 10] _WIN32_WINNT 버전 문제

Visual C++ 10 2010. 8. 2. 09:00 Posted by 알 수 없는 사용자

VS.NET(VS2002)에서 MFC 프로젝트로 만들었던 프로그램을 VC++10 프로젝트로 변환하여 컴파일 했더니 에러가 발생하면서 아래의 경고가 나왔습니다.

 

C:\Program Files\Microsoft Visual Studio 10.0\VC\atlmfc\include\atlcore.h(35):#error This file requires _WIN32_WINNT to be #defined at least to 0x0403. Value 0x0501 or higher is recommended.

 

에러 내용은 프로젝트에서 정의된 _WIN32_WINNT 버전이 0x403인데 atlcore.h 버전이 최소 0x0501 이상이 되어야 한다는 것입니다.

 

그래서 _WIN32_WINN 정의한 stdafx.h 파일을 열어보니

#define _WIN32_WINNT 0x0400

되어 있었더군요. 그래서 일단 이것을 최신이 좋다라는 생각에 아래와 같이 했습니다. ^^;;

#define _WIN32_WINNT 0x0600

 

그랬더니 이제는 아래와 같은 에러가 나오더군요. -_-;

c:\program files\microsoft visual studio 10.0\vc\atlmfc\include\afxcmn3.inl(29): error C2065: 'CCM_SETWINDOWTHEME' : 선언되지 않은 식별자입니다.

 

그래서 바로 구글링 들어갔습니다.

쉽게 저와 같은 에러가 나와서 질문을 올린 글을 찾았고 답변도 보았습니다.

문제 해결은 stdafx.h 파일에 정의된 버전의 숫자를 아래와 같이 하면 된다고 하더군요

// Modify the following defines if you have to target a platform prior to the ones specified below.
// Refer to MSDN for the latest info on corresponding values for different platforms.
#ifndef WINVER // Allow use of features specific to Windows 95 and Windows NT 4 or later.
#define WINVER 0x0501 // Change this to the appropriate value to target Windows 98 and Windows 2000 or later.
#endif

#ifndef _WIN32_WINNT // Allow use of features specific to Windows NT 4 or later.
#define _WIN32_WINNT 0x0501 // Change this to the appropriate value to target Windows 98 and Windows 2000 or later.
#endif

#ifndef _WIN32_WINDOWS // Allow use of features specific to Windows 98 or later.
#define _WIN32_WINDOWS 0x0501 // Change this to the appropriate value to target Windows Me or later.
#endif

#ifndef _WIN32_IE // Allow use of features specific to IE 4.0 or later.
#define _WIN32_IE 0x0500 // Change this to the appropriate value to target IE 5.0 or later.
#endif

 

이렇게 하니 문제 없이 빌드가 성공 되었습니다.

 

주위에서 VC++의 새로운 버전이 나와도 쉽게 사용하지 못하는 경우가 오래 전에 만들었던 프로젝트를 포팅 할 수 없어서 이전 버전을 어쩔 수 없이 사용한다는 이야기를 종종 듣습니다.

그러나 저는 운이 좋아서인지 2002버전부터 순차적으로 새 버전의 VC++을 사용할 수 있어서 VC++6에서 VS2002로 넘어갈 때만 빌드 문제를 겪었습니다.

그래서 이런 포팅에 대한 문제는 잘 알지 못합니다. 이번에는 예전에 만들었던 코드를 C++0x 코드로 바꾸고 싶어서 오래 전에 만들었던 프로젝트를 VC++ 10로 포팅하면서 정말 정말 오랜만에 이런 문제를 겪어 보게 되고 해결 방법을 포스팅 할 수 있었습니다.

 

혹시 앞으로 또 이런 경우가 발생하면 바로 바로 공유하도록 하겠습니다.  





1. 비관리 클래스에서 관리 클래스를 멤버로

 

비관리 클래스에서 관리 클래스를 멤버로 가지고 싶을 때는 ‘gcroot’라는 템플릿을 사용합니다.

 

#include "stdafx.h"

#include <iostream>

#include <vcclr.h>

 

using namespace System;

 

class TEST

{

public:

   TEST() {}

   ~TEST() {}

 

   gcroot< String^ > m_str;

};

 

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

{

    TEST test;

    test.m_str = gcnew String("Hello VSTS 2010");

 

    Console::WriteLine( test.m_str);

   

    getchar();

    return 0;

}

 



‘gcroot’를 사용하기 위해서는

#include <vcclr.h>

를 포함해야 합니다.

 

비관리 클래스에서 관리 클래스인 String을 다음과 같이 멤버로 선언합니다.

gcroot< String^ > m_str;


그리고 사용하기 위해서는 할당을 합니다.

test.m_str = gcnew String("Hello VSTS 2010");

 

 



2. 관리 클래스에서 비관리 클래스를 멤버로

 

관리 클래스에서 비관리 클래스를 멤버로 가질 때는 비관리 클래스를 포인터로 선언하여 비관리 힙에 동적할당을 합니다.

 

#include "stdafx.h"

#include <iostream>

#include <vcclr.h>

 

using namespace System;

 

class TEST

{

public:

       TEST() {}

       ~TEST() {}

};

 

ref class refTEST

{

public:

       refTEST() {}

       ~refTEST() {}

 

       TEST* test;

};

 

int main()

{

       refTEST^ refTest = gcnew refTEST();

       refTest->test = new TEST;

 

       return 0;

}

 

 

 

참고

http://msdn.microsoft.com/ko-kr/library/481fa11f%28v=VS.80%29.aspx

http://blog.naver.com/scor7910/40048083284

 


 

[Step. 06-2] 관리코드의 문자열과 비관리코드의 문자열 변환

C++/CLI 2010. 7. 23. 20:30 Posted by 알 수 없는 사용자

3) String^ C/C++ 문자열로 변환

 

1)번에서는 C/C++의 문자열을 String^로 변환하는 방법에 대해서 설명했습니다.

이번에는 String^ char* wchr_t*로 변환하는 방법에 대해서 설명합니다.

 

아래의 예제 코드를 봐 주세요

 

#include <string>

#include <msclr\marshal_cppstd.h>

 

using namespace System;

using namespace msclr::interop;

 

int main()

{

           System::String^ s0 = L"비주얼스튜디오2010 팀블로그";

          

           // 방법 1

           std::string tmp = marshal_as<std::string>(s0);

           const char* s1 = tmp.c_str();

           std::cout << "String^ -> string : " << s1 << std::endl;

 

           // 방법 2

           const char* s2;

           const wchar_t* s3;

           {

                     marshal_context ctx;

                     s2 = ctx.marshal_as<const char*>(s0);

                     s3 = ctx.marshal_as<const wchar_t*>(s0);

            

                     std::cout << "String^ -> char* : " << s2 << std::endl;

                    

                     setlocale(LC_ALL, "");

                     std::wcout << "String^ -> wchar_t : " << s3 << std::endl;

           }

 

           getchar();

           return 0;

}

 

String^ char* wchr_t*로 변환하는 방법은 두 가지가 있습니다.

 


첫 번째 std::string 사용


가장 간단한 방법입니다만 불필요한 std::string을 사용해야 단점이 있습니다.

std::string tmp = marshal_as<std::string>(s0);

const char* s1 = tmp.c_str();

std::cout << "String^ -> string : " << s1 << std::endl;

 

 

두 번째 marshal_context 사용


첫 번째 방법에서 std::string을 사용한 이유는 다름이 아니고 메모리 확보 때문입니다.

마샬링을 통해서 char* wchar_t*에 메모리 주소를 저장합니다. 문자열 그 자체를 복사하는 것이 아닙니다. 그래서 변환한 문자열을 저장할 메모리 주소를 확보하고 사용 후에는 해제를 해야 합니다. 메모리 확보와 해제를 위해서 marshal_context를 사용합니다.

marshal_context는 변환에 필요한 메모리를 확보하고, 스코프를 벗어날 때 메모리를 해제합니다.

const char* s2;

const wchar_t* s3;

{

           marshal_context ctx;

           s2 = ctx.marshal_as<const char*>(s0);

           s3 = ctx.marshal_as<const wchar_t*>(s0);

}

 

String^ C/C++ 문자열로 변환할 때는 std::string + marshal_as marshal_context 둘 중 하나를 선택하여 사용합니다.

 




참고

http://msdn.microsoft.com/en-us/library/bb384865.aspx

http://msdn.microsoft.com/ko-kr/library/bb531313%28VS.90%29.aspx

http://codezine.jp/article/detail/4774

[Step. 06-1] 관리코드의 문자열과 비관리코드의 문자열 변환

C++/CLI 2010. 7. 16. 08:30 Posted by 알 수 없는 사용자

관리코드와 비관리코드를 혼합해서 사용할 때 서로간에 문자열을 주고 받아야 하는 경우가 종종 있을 것입니다. 관리코드와 비관리코드간에 문자열을 서로 어떻게 변환하여 주고 받는지 알아보겠습니다.

 

 

1) C/C++ 문자열을 String^으로 변환

 

먼저 아래의 변환 예제 코드를 봐 주세요

 

#include <msclr\marshal.h>

 

using namespace System;

using namespace msclr::interop;

 

int main()

{

           const char* message = "Forever Visual C++";

           String^ result1 = marshal_as<String^>( message );

           Console::WriteLine("char -> System::String : {0}", result1);

 

           const wchar_t* Wmessage = L"Visual C++이여 영원하라";

           String^ result2 = marshal_as<String^>( Wmessage );

           Console::WriteLine("wchar -> System::String : {0}", result2);

 

           getchar();

           return 0;

}

 


관리코드와 비관리코드 간의 문자열 변환에는 msrshal_as를 사용하여 마샬링합니다.

C/C++ 문자열을 마샬링하기 위해서는

#include <msclr\marshal.h>

파일을 포함하고,


using namespace msclr::interop;

네임스패이스를 선언합니다.

 

사용 방법은 아주 간단합니다.

marshal_as< 변환할 문자 타입 >( 원본 문자열 );

 


ANSI 문자열을 관리코드의 문자열로 변환할 때는 아래와 같이 합니다.

const char* message = "Forever Visual C++";

String^ result1 = marshal_as<String^>( message );

 

유니코드를 관리코드의 문자열로 변환할 때는 아래와 같이 합니다.

const wchar_t* Wmessage = L"Visual C++이여 영원하라";

String^ result2 = marshal_as<String^>( Wmessage );

 

 



2) STL string String^간의 변환

 

이것도 marshal_as를 사용합니다.

아래의 예제 코드를 봐 주세요

 

#include <iostream>

#include <string>

#include <msclr\marshal_cppstd.h>

 

using namespace System;

using namespace msclr::interop;

 

int main()

{

           std::string s0 = "비주얼스튜디오2010 팀블로그";

           std::cout << "string : " << s0 << std::endl;

 

           System::String^ s1 = marshal_as< System::String^ >(s0);

           Console::WriteLine("std::sting->System::String : {0}", s1);

 

           std::wstring s2 = marshal_as< std::wstring >(s1);

           setlocale(LC_ALL, "");

           std::wcout << "System::String->std::wstring : " << s2 << std::endl;

          

           getchar();

           return 0;

}

 

STL의 문자열과 변환하기 위해서는 다음의 헤더 파일을 포함해야 합니다.

#include <msclr\marshal_cppstd.h>

 

마샬링하는 방법은 앞에 설명한 C/C++ 문자열 변환과 같습니다.

System::String^ s1 = marshal_as< System::String^ >(s0);

 

주제와 좀 관계 없는 것으로 콘솔창에 유니코드 문자열을 출력하는 방법은 아래와 같습니다.

setlocale(LC_ALL, "");

std::wcout << "System::String->std::wstring : " << s2 << std::endl;

 setlocale로 국가를 설정하고(직접 나라를 지정할 수도 있고, 아니면 위처럼 시스템 설정에 따라가도록 할 수도 있습니다), ‘cout’ 대신 ‘wcout’를 사용합니다.

 

 



관리코드 문자열과 비관리코드 문자열간의 변환에 따른 성능


C++로 만드는 프로그램은 보통 고성능을 원하는 프로그램이므로 보통 C++ 프로그래머는 성능에 민감합니다. 마샬링은 공짜가 아닙니다만 많은 양을 아주 빈번하게 마샬링 하는 것이 아니면 성능에 너무 신경 쓰지 않아도 됩니다. 다만 기본적으로 관리코드의 문자열은 유니코드입니다. 그래서 비관리코드의 문자열이 ANSI 코드라면 유니코드를 사용했을 때 보다 더 많은 시간이 걸립니다(정확한 수치는 잘 모르지만 ANSI가 유니코드보다 3배정도 더 걸린다고도 합니다). 그래서 관리코드와 비관리코드를 같이 사용할 때는 가능한 유니코드를 사용하는 것이 훨씬 좋습니다.

 

 

아직 설명할 것이 많이 남아 있습니다. 다음을 기다려주세요^^

[Step. 05] 관리 코드의 array를 비관리 코드에 포인터로 전달

C++/CLI 2010. 7. 9. 08:30 Posted by 알 수 없는 사용자

아마 C++ 프로그래머가 C++/CLI를 사용할 때 가장 신경 쓰이는 부분이 관리 코드를 어떻게 하면 비관리 코드와 연동하는 방법이라고 생각합니다.

 

그래서 아직 C++/CLI의 델리게이트 등 C++/CLI의 특징을 설명하지 않은 것이 많지만 이런 것은 C#를 공부하면 배울 수 있는 것이므로 급하지 않다고 생각합니다. 그래서 관리 코드와 비 관리 코드의 연동에 대해서 앞으로 몇 차례에 걸쳐서 설명하려고 합니다.

 

이번은 첫 번째로 간단하게 array로 만든 배열을 비관리 코드의 포인터로 어떻게 전달하는지 설명하겠습니다.

 

먼저 코드를 봐 주세요^^

#include <Iostream>

 

using namespace System;

 

void DumpNativeArray( int* pArrNums, int length )

{

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

    {

        std::cout << pArrNums[i] << std::endl;

    }

}

 

int main()

{

array< int >^ ArrNums = gcnew array<int>(3);

ArrNums[0] = 1;

ArrNums[1] = 2;

ArrNums[2] = 3;

 

     pin_ptr<int> pNative = &ArrNums[0];

     DumpNativeArray(pNative,ArrNums->Length);

     pNative = nullptr;

 

getchar();

     return 0;

}

 

위 코드의 핵심은

pin_ptr<int> pNative = &ArrNums[0];

입니다.

 

앞서 설명한 pin_ptr을 사용하였습니다. pin_ptr을 사용하여 관리 힙에 할당된 객체가 이동하지 못하도록 고정합니다. 고정하는 이유는 비관리 코드로 메모리 주소를 넘기기 때문에 관리 힙에서 이동이 되면 안되기 때문입니다.


또 여기서 자세히 봐야 되는 것이 있습니다.

pin_ptr<int> pNative = &ArrNums[0];


pNative&ArrNums이 아닌 &ArrNums[0]을 대입하였습니다. 이유는 관리 코드에서는 &ArrNums ArrNums의 요소가 아닌 ArrNums 오브젝트 자체를 가리키는 것이기 때문입니다.

&ArrNums[0]을 대입해야 ArrNums에 들어가 있는 요소의 첫 번째 주소를 비관리 코드에 주소를 넘길 수 있습니다.

 



제가 요즘 바빠서 이번은 아주 간단하게 이것으로 끝내겠습니다.^^;

다음에는 문자열 변환에 대해서 설명하겠습니다.

 

 

참조

http://mag.autumn.org/Content.modf?id=20050507224044

 

 

[Step. 04] nullptr, interior_ptr, pin_ptr

C++/CLI 2010. 6. 25. 08:30 Posted by 알 수 없는 사용자

nullptr

 

C/C++에서 포인터를 초기화 할 때 ‘NULL’을 사용합니다. 그러나 VC++ 10에는 C++0x에서는 포인터를 초기화 할 때 NULL 대신 새로 생긴 ‘nullptr’을 사용할 수 있게 되었습니다.


C++/CLI는 이전부터 nullptr이 있었습니다.

C++/CLI에서는 ref 클래스의 핸들을 초기화 할 때는 nullptr을 사용합니다.

C++/CLI, C++0x nullptr C/C++ 처럼 ‘0’이 아니라는 것을 잘 기억하시기 바랍니다.

 

 

 

interior_ptr

 

interior_ptr은 관리 힙(managed heap. GC겠죠) 상의 value type나 기본형을 가리키는 포인터라고 할 수 있습니다. interior_ptrvalue type나 기본형을 비관리 코드의 포인터처럼 사용하고 싶을 때 사용하면 좋습니다.

 

< 코드 1. >

ref class REFClass

{

public:

    int nValue;

};

 

void SetValue( int* nValue )

{

    *nValue = 100;

}

 

 

int main()

{

    REFClass^ refClass = gcnew REFClass;

    SetValue( &refClass->nValue );  // 에러

}

 

위 코드를 빌드 해 보면 SetValue( &refClass->nValue ); 에서 빌드 에러가 발생합니다. 매니지드 힙에 있는 것은 그 위치가 변하므로 비 관리 코드의 포인터를 넘길 수가 없습니다. 그럼 <코드 1>를 정상적으로 빌드 하기 위해서 interior_ptr를 사용해 보겠습니다.

 

< 코드 2. >

ref class REFClass

{

public:

    int nValue;

};

 

void SetValue( interior_ptr<int> nValue )

{

    *nValue = 100;

}

 

 

int main()

{

    REFClass^ refClass = gcnew REFClass;

    SetValue( &refClass->nValue );

}

 


<코드 2> SetValue의 파라미터로 비관리 코드의 참조나 포인터를 넘길 수도 있습니다.

< 코드 3. >

#include <iostream>

 

 

void SetValue( interior_ptr<int> nValue )

{

    *nValue = 100;

}

 

int main()

{

           int nValue = 50;

           SetValue( &nValue );

 

           std::cout << nValue << std::endl;

 

           getchar();

           return 0;

}

 

그리고 interior_ptr에 대신 C++/CLI의 참조(‘%’)를 사용하는 방법도 있습니다.

 

 

 

 

pin_ptr

 

pin_ptr은 관리 힙 상의 value type나 기본형을 비관리 코드에서 포인터로 사용하고 싶을 때 사용하는 기능입니다. 가장 필요한 경우가 C++/CLI에서 기존의 비관리 코드로 만들어 놓은 라이브러리를 사용할 때입니다.

 

< 코드 4. >

ref class REFClass

{

public:

    int nValue;

};

 

void SetValue( int* pValue )

{

    *pValue = 100;

}

 

 

int main()

{

    REFClass^ refClass = gcnew REFClass;

pin_ptr<int> pValue = &refClass->nValue;

    SetValue( pValue );

pValue = nullptr;

}

 

pin_ptr에 메모리 주소를 할당하는 것을 ‘pin’이라고 부르고 사용이 끝난 후 nullptr로 초기화 하는 것을 ‘unpin’ 이라고 부릅니다. pin_ptr 사용이 끝난 후 가능한 빨리 unpin 해주는 것이 좋습니다.

 

 

 

 

interior_ptr pin_ptr의 차이점

 

interipor_ptr pin_ptr은 둘 다 관리 힙 상의 value type이나 기본형을 가리키는 포인터로 사용되지만 interior_ptr은 관리 힙 상에서 인스턴스가 이동하여도 올바르게 추적할 수 있는 포인터로 런타임의 지배하에 있습니다(즉 인스턴스가 관리 힙 상에서 이동하여도 괜찮습니다).


pin_ptr은 관리 힙 상의 value type을 비관리 코드에서 사용하고 싶을 때 사용합니다. 당연히 이 때는 관리 힙에 있는 인스턴스가 이동하면 안되므로 인스턴스의 이동을 금지합니다.

 

interipor_ptr pin_ptr의 같은 점 : 포인터처럼 사용할 수 있다.

interipor_ptr pin_ptr 다른 점 : interipor_ptr은 관리 코드 상에서 포인터로 사용하고, pin_ptr는 비관리 코드에 포인터로 넘길 때 사용합니다.

 

 

interipor_ptr pin_ptr을 공부했으니 다음에는 C++/CLI에서 비관리 C++과 혼합해서 사용할 때 어떻게 해야 하는지 설명하겠습니다.

 

 

 

 

참고

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

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

http://cppcli.shacknet.nu/cli:interior_ptr%E3%81%A8pin_ptr%E3%81%AE%E9%81%95%E3%81%84

 

[step.03] 배열

C++/CLI 2010. 6. 18. 08:30 Posted by 알 수 없는 사용자

프로그래밍 할 때 가장 자주 사용하는 자료구조가 바로 배열입니다. 배열을 사용하지 않고 프로그래밍 하기는 힘들죠^^.

그래서 이번에는 C++/CLI에서의 배열에 대해서 이야기하려고 합니다.

 

 

C++/CLI에서의 배열은 ‘array’

 

비관리 C++에서는 배열은 ‘[]’을 사용합니다.

int Nums[10];

char szName[20] = {0,};

 

그러나 C++/CLI에서의 배열은 ‘array’라는 클래스를 사용합니다.

 

int 형의 3개의 요소를 가지는 배열은 아래와 같이 정의합니다.

array< int >^ A1 = gcnew array< int >(3);

array< int >^ A2 = gcnew array< int >(4) { 1, 2, 3 };

array< int >^ A3 = gcnew array< int >{ 1, 2, 3 };

 

다음은 간단한 사용 예입니다.

< 코드 1. >

int main()

{

           array< int >^ Numbers = gcnew array< int >(5);

                    

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

           {

                     Numbers[ i ] = i;

 

                     System::Console::WriteLine( Numbers[i] );

           }

 

           getchar();

           return 0;

}

 

 

 

array에 유저 정의형 사용하기

 

array에는 기본형(int, float )만이 아닌 유저 정의형도 사용할 수 있습니다. 다만 비관리 클래스는 안됩니다. 오직 관리 클래스(ref class)만 가능합니다. 또 그냥 ref 클래스를 그대로 넣을 수는 없는 클래스의 핸들을 사용해야 합니다(ref 클래스는 GC에 동적 할당을 하기 때문이겠죠).

 

ref class refTest

{

};

 

array< refTest >^ arrTest;    // 에러

array< refTest^ >^ arrTest;   // OK

 

 

 

 

for each 사용하기

 

앞서 <코드1>의 예제에서는 배열의 모든 요소를 순환하기 하기 위해 ‘for’문을 사용했습니다. 그러나 .NET에서는 for문 보다 ‘for each’문을 사용하는 것이 성능이나 안정성 등에서 더 좋습니다(다만 for each를 사용하면 내부에서 값을 변경할 수 없다는 문제는 있습니다).

 

< 코드 2. >

#include <iostream>

 

 

int main()

{

           array< int >^ Numbers = gcnew array< int > { 10, 11, 12, 13, 14 };

                    

           for each( int nValue in Numbers )

           {

                     System::Console::WriteLine( nValue );

           }

 

           getchar();

           return 0;

}

 

 

 

다 차원 배열

 

array는 너무 당연하게 다 차원 배열도 만들 수 있습니다.

 

< 코드 3. 2차원 배열의 예 >

int main()

{

           array<int,2>^ a2 = gcnew array<int,2>(4,4);

           array<int,2>^ b2 = gcnew array<int,2>{ {1,2,3,4},{1,2,3,4},{1,2,3,4},{1,2,3,4} };

 

           getchar();

           return 0;

}

 


 

 


array의 기능은 .NET 라이브러리의 array 클래스를 사용하므로 자세한 사용방법은 MSDN에서 .NET 라이브러리 부분을 참고 하시가 바랍니다.

 

 

다음에는 nullptr, interior_ptr, pin_ptr 설명과 관리코드의 타입을 비관리 코드로 넘길 때 어떤 작업을 하는지 이야기 하겠습니다.^^

 

 

[Step.02-2] 클래스(class), 핸들(^), 그리고 구조체(struct)

C++/CLI 2010. 6. 11. 08:30 Posted by 알 수 없는 사용자

 

gcnew로 생성하지 않기

 

C++/CLI는 클래스를 생성할 때 ‘gcnew’를 사용하지 않고 생성할 수도 있습니다.

 

< 리스트 1. ‘gcnew’를 사용하지 않고 클래스 생성하기 >

#include "stdafx.h"

#include <stdio.h>

 

using namespace System;

 

ref class ManagedTest

{

public:

           ManagedTest() { Console::WriteLine(L"New ManagedTest"); }

           ~ManagedTest() { Console::WriteLine(L"delete ManagedTest"); }

          

           void func() { Console::WriteLine(L"Call func() - {0}", nNumber ); }

 

           int nNumber;

};

 

void foo1()

{

           ManagedTest MTest;

           MTest.nNumber = 1;

           MTest.func();

}

 

void foo2()

{

           ManagedTest^ MTest = gcnew ManagedTest();

           MTest->nNumber = 2;

           MTest->func();

}

 

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

{

           foo1();

           foo2();

          

           getchar();

           return 0;

}


< 결과 >


<리스트 1> ManagedTest MTest; 는 비 관리 C++처럼 GC가 아닌 스택 영역에 생성하는 것으로 착각할 수도 있겠지만 전혀 아닙니다. 클래스를 생성하면 언제나 GC 영역에 만들어집니다.

위의 코드는 그냥 ‘gcnew’ 사용을 우리가 생략하고 컴파일러가 대신 써 준다고 생각하시면 됩니다.

 

void foo1()

{

           ManagedTest MTest;

           MTest.nNumber = 1;

           MTest.func();

}

은 컴파일러에 의해서 아래의 코드로 바뀝니다.

void foo1()

{

           ManagedTest^ MTest = gcnew ManagedTest();

           MTest->nNumber = 1;

           MTest->func();

           delete MTest;

}

 

위의 코드를 보시면 아시듯이 gcnew를 사용하지 않고 클래스를 생성하면 우리가 직접 delete를 쓰지 않아도 되는 편리함을 얻을 수 있습니다.

gcnew를 사용하지 않는 것은 C#에서 ‘using’을 간략화 시킨 것으로 생각하면 좋습니다.


< C# using 문 사용 예 >

using (Graphics g = this.CreateGraphics())

{

    g.DrawLine(Pens.Black, new Point(0,0), new Point(3,5));

}

 

 

 

 

value 클래스

 

관리 클래스는 복사 생성자와 대입 연사자를 가지지 못하므로 아래의 코드는 컴파일 에러가 발생합니다.

 

< 리스트 2. >

#include "stdafx.h"

#include <stdio.h>

 

using namespace System;

 

ref class C {

    int i;

};


void func(C c) {}


int main()

{

    C c;

    C d;

    func(c);   // 에러

    d = c;     // 에러

}

 

그러나 클래스를  ‘ref’가 아닌 ‘value’ 클래스로 정의하면 위의 코드는 컴파일 할 수 있습니다.

 

#include "stdafx.h"

#include <stdio.h>

 

using namespace System;

 

value class C {

    int i;

};

void func(C c) {}

int main()

{

    C c;

    C d;

    func(c);   // 에러

    d = c;     // 에러

}

 

value 클래스는 클래스간 복사를 할 수 있는 능력이 있습니다. 이 복사는 비트 단위의 복사로 이른바 ‘memcpy’와 같은 복사입니다.

value 클래스는 복사를 할 수 있으므로 value 클래스의 멤버는 절대 복사 가능한 멤버만 가질 수 있습니다. 그래서 아래와 같은 value 클래스 C는 컴파일 에러가 발생합니다.

 

ref class A

{

  int i;

};

 

value class C

{

  A a;

};

 

 

value 클래스의 특징으로는 ref 클래스가 GC에서 만들어지는 것과 달리 스택에 만들 수 있습니다.

value class C

{

};

 

C c;

 

위 코드에서 C 클래스는 스택에 만들어집니다(물론 gcnew를 사용하면 GC에 만들어집니다)

 

value 클래스의 특징은 좀 더 있는데 위에 설명한 것들과 포함해서 아래와 같이 정리할 수 있습니다.

 


value 클래스의 특징

 

1. 기본 생성자를 가질 수 없다.

2. 복사 생성자를 가질 수 없다.

3. 대입 연산자를 가질 수 없다.

4. 소멸자를 가질 수 없다.

5. finalize를 가질 수 없다.

6. 클래스간 복사를 할 수 있다.

7. 복사 불가능한 것을 멤버로 가질 수 없다(ref 클래스 등).

8. 스택에 생성할 수 있다.

9. 다른 클래스를 계승할 수 없다( interface는 가능하다)

 

 

 


관리 클래스를 파라메터로 넘기기

 

ref class A

{

           int i;

};

 

void foo1( A a )

{

}

 

함수 foo1의 파라미터 정의는 클래스 A value 클래스일 때만 사용할 수 있습니다. 그러므로 아래와 같이 foo1의 파라미터를 정의해야 합니다.

void foo1( A^ a )

{

}

 

 

 

 

그 외….

 

C++/CLI에서 참조는 ‘%’을 사용합니다. 이것은 비 관리의 ‘&’와 구별이 됩니다.

%는 아래와 같은 경우에 유용하게 사용할 수 있습니다.

void foo( A^ a )

{

}

 

A a;

// foo( a );  // 에러

foo( %a ); // 성공


참조는 C#에서는 'ref', VB.NET에서는 'ByRef'와 같다고 생각하시면 됩니다.


// 값을 참조로 넘기는 경우

void valuebyref(int%i)
{
   i=5;
}

// 참조형을 참조로 넘기기
void refbyref(String^%s)
{
   s="newstring";
}

void main()
{
   int i=1;

   valuebyref(i);

   String^s="basestring";
   refbyref(s);
}
( 위 코드는 http://adversaria-june.blogspot.com/2006/08/ccli_26.html 에서 인용했습니다 )

 



구조체


C++/CLI에서의 구조체가 클래스와 다른 점

1. 'value class'와 같습니다. 물론 비 관리코드에서는 기존의 C++과 같다

2. 구조체는 상속 받을 수 없다.

 

 

 

 

[Step 02-1] 클래스(class), 핸들(^), 그리고 구조체(struct)

C++/CLI 2010. 6. 4. 08:30 Posted by 알 수 없는 사용자

C C++의 큰 차이점의 하나가 바로 C++에만 있는 클래스입니다. 또 클래스는 객체 지향 프로그래밍 언어에서 자주 볼 수 있습니다.

 

C++/CLI에서 관리 클래스는 ‘ref’라는 키워드를 사용하여 만듭니다.

 

비 관리 클래스

class Test

{

};

 

관리 클래스

ref class Test

{

};

 

관리 클래스는 ‘ref’를 제외하고는 외관상으로는 비 관리 클래스와 비슷하지만 관리와 비 관리가 많이 다르듯이 관리 클래스는 비 관리 클래스와 다른점이 있습니다. 그러므로 이것을 잘 파악하고 있어야 합니다.

 

 

 

관리 클래스의 특징

 

1. 정적 할당은 안 된다. 무조건 가비지컬렉션(GC)에 동적으로 생성한다.

 

2. 복사 생성자를 만들 수 없다.

 

3. ‘^’(핸들이라고 부른다)‘gcnew’를 사용하여 클래스를 생성한다. 당근 메모리 해제는 GC에서 관리한다.

 

4. 핸들은 네이티브의 ‘*’(포인터) ‘&’(참조)와 비슷한 것으로 더 안정스럽다.

 

5. ‘delete’는 명시적으로 클래스에서 사용하고 있는 리소스를 해제할 때 사용하는 것으로 소멸자가 호출 되는 것이지 메모리에서 해제하는 것은 아니다. delete GC에 있는 메모리를 해제하는 것은 아니다.

 

6. delete로 소멸자를 호출하면 GC에 의해서 진짜 파괴될 때 소멸자는 호출되지 않는다.

(소멸자를 선언한 클래스는 자동적으로 IDisposable 인터페이스를 구현한다. 소멸자는 컴파일러에 의해서 Dispose() 메소드로 치환된다).

 

7. 소멸자 이외에 'finalize'를 선언할 수 있다. finalize GC에서 인스턴스가 파괴될 때 호출된다. delete로 소멸자를 호출한 경우에는 finalize는 호출되지 않는다.

 

8. finalize‘!’ 키워드를 사용한다.

 

 

 

관리 클래스 사용해 보기

 

< 코드 1. 관리 클래스 정의 및 사용 >

#include "stdafx.h"

 

using namespace System;

 

ref class ManagedTest

{

public:

           ManagedTest() { Console::WriteLine(L"New ManagedTest"); }

           ~ManagedTest() { Console::WriteLine(L"delete ManagedTest"); }

           !ManagedTest() { Console::WriteLine(L"finalize ManagedTest"); }

};

 

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

{

           Console::WriteLine(L"1.");

          ManagedTest^ MTest1 = gcnew ManagedTest();

           delete MTest1;

 

           Console::WriteLine(L"2.");

           ManagedTest^ MTest2 = gcnew ManagedTest();

          

           return 0;

}

 

< 결과 >





<코드 1> 설명



< 그림 1. <코드 1>의 소스 코드와 결과 >

 

<그림 1> 코드를 보면 3번의 ‘gcnew’, 4번의 ‘^’를 사용하여 ManagedTest를 생성하였습니다.

<코드 1>에는 없지만 핸들은 포인터와 같은 것으로 관리 클래스의 멤버를 사용할 때는 ‘->’를 사용합니다.

5 delete를 사용하면 소멸자가 호출되어서 6번이 출력됩니다.

MTest2는 소멸자를 호출하지 않아서 8번에서 정의한 finalize가 프로그램이 종료할 때 호출됩니다(7).


 

<코드 1>은 아주 짧고 단순한 코드이지만 관리 클래스의 대부분의 특징을 다 나타내고 있습니다. 관리 클래스에 대한 설명은 아직 남아 있습니다. 이것은 다음 회에 또 설명하겠습니다.



앞서 두 번을 걸쳐서 C++/CLI에 대해서 잘 모르는 분과 싫어하는 분을 위해서 제 생각이나 MSDN에 있는 글을 정리해서 포스팅 했습니다.

 

이제 본격적으로 C++/CLI에 대해서 설명해 나가겠습니다(이 글을 보는 분들은 C++을 알고 있다고 가정을 하겠습니다).

 

 

 

1. ‘C++/CLI가 뭐야?’

 

라고 질문을 하면 가장 초 간단한 답은 ‘.NET에서 C++를 사용하기 위한 언어라고 말할 수 있습니다. 그런데 이 답은 너무 간단하고 없어 보이죠? ^^;

그래서 좀 유식하게 보일 수 있도록 고급스럽게 답해 보겠습니다(또는 복잡하게).

 

C++/CLI에서 CLI‘Common Language Infrastructure’의 약자입니다.

 

C++/CLI CLI 환경에서 돌아가는 프로그램을 만들기 위한 언어입니다. C++/CLI는 마이크로소프트(이하 MS)가 만들었지만 공업 표준화 단체인 ECMA에 의해서 표준 언어로 제정 되어 있습니다.

 C++/CLI MS가 만들었기 때문에 현재까지는 실행 환경이 Windows .NET 플랫폼이지만 언어 사양 상으로는 Windows .NET 플랫폼에만 사용할 수 있는 것이 아닙니다. 이론적으로는 Windows 이외의 Unix Linux, Mac에서도 실행할 수 있습니다(누구라도 Windows 이외서도 사용할 수 있도록 언어 사양을 따라서 구현만 하면 됩니다).

 

C++/CLI C++로 만든 프로그램을 거의 그대로 컴파일 할 수 있습니다.

C++의 표준 기능에 CLI를 위한 추가 기능이 더해져 있습니다.

 

 

 

2. 가장 많은 프로그래밍 언어로 만드는 프로그램 만들기

 

가장 많은 프로그래밍언어로 만드는 프로그램은 무엇일까요? 제가 생각하기에는 그 유명한 ‘Hello World’라고 생각합니다. 제가 공부한 대부분의 프로그래밍 언어 책에는 첫 번째 예제 프로그램이 Hello World였습니다.

 

그래서 C++/CLI도 관례(?)에 맞추어서 ‘Hello World’ 프로그램을 만들어 보겠습니다.

(VS의 마법사 기능에 의해서 코딩을 하나도 하지 않고 프로그램이 만들어집니다.)


< 그림 1. CLR 콘솔 어플리케이션을 선택합니다 >


< 리스트 1. ‘Hello World’ >

#include "stdafx.h"

 

using namespace System;

 

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

{

    Console::WriteLine(L"Hello World");

    return 0;

}

 

<리스트 1>의 코드는 <그림 1>에서 ‘OK’ 버튼을 누른 후 자동으로 생성되는 코드입니다. 이것을 빌드 후 실행을 하며 아래와 같은 결과가 나옵니다.



 

그런데 문제는 실행과 동시에 종료되어서 결과를 볼 틈이 없습니다.

예전에 C++에서는 이런 경우 코드의 아래에 ‘getchar()’를 사용하여 결과를 보고 종료시켰습니다.

그런데 다들 아시겠지만 ‘getchar()’라는 함수는 .NET 라이브러리에 있는 함수가 아닌 네이티브의 함수입니다. C#이라면 바로 사용하지 못하겠지만 C++/CLI는 이런 네이티브의 함수를 바로 사용할 수 있습니다.

 

<리스트 2. getchar() 사용 >

#include "stdafx.h"

#include <stdio.h>

 

using namespace System;

 

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

{

    Console::WriteLine(L"Hello World");

                 

getchar();

    return 0;

}

 

<리스트 2>.NET 라이브러리와 네이티브가 자연스럽게 공존하고 있습니다.

이런 것이 C++/CLI이기 때문에 가능한 것입니다.

 

C++/CLI로 만들어진  'Hello World' 소스 코드를 보면 C++ 프로그래머라면 몇개 처음보는 것이 있지만 이름만 봐도 대충 어떤 의미를 가지고 있는지 쉽게 파악할 수 있어서 소소 코드가 전혀 어렵지 않을 것입니다.

C++/CLI는 기존의 C++에서 CLI가 더해진 것으로 간단하게 말하면 이 더해진 'CLI'만 공부하면 C++/CLI는 마스터합니다.



앞으로 더해진 'CLI' 부분에 대해서 설명해 나가겠습니다.

다음에는 C++의 트레이드마크인 클래스 C에서부터 친숙한 struct C++/CLI에서는 어떤 의미를 갖고 어떻게 사용되는지 알아 보겠습니다.

.NET에서의 C++/CLI의 의미

C++/CLI 2010. 5. 21. 08:30 Posted by 알 수 없는 사용자

C++/CLI에 대한 기술 아티클이 별로 없는 편입니다. 좀 오래 되었지만 MSDN 매거진 2005 1월에 연재된 'Visual C++ 의 프로그래밍 모델과 컴파일러 최적화를 사용한 어플리케이션의 강화'( http://msdn.microsoft.com/ko-kr/magazine/cc163855%28en-us%29.aspx )라는 글을 통해서 간단하게 정리해 보겠습니다.

이 글을 통해서 C++/CLI의 특징이나 .NET 언어 중에서 어떤 특징을 가지고 있는지 알 수 있습니다.

 

 

 

유연한 프로그래밍 모델

 

C++ C#에 비해 언어적 표현력이나 라이브러리가 부족하다는 단점이 있지만 C#에 비해서 프로그래머가 가질 수 있는 자유도가 무척 높습니다(때로는 이것 때문에 많은 문제를 일으키지만).

C#으로 프로그래밍을 할 때는 무조건 객체 지향 프로그래밍 모델만 사용해야 합니다. 그러나 C++는 절차형 프로그래밍, 객체 지향 프로그래밍, 제너릭 프로그래밍, 메타 프로그래밍 등 프로그래머가 원하는 모델을 선택하여 프로그래밍 할 수 있습니다.

.NET에서 C++/CLI를 사용하면 이런 C++의 장점을 바로 얻을 수 있습니다.

 

 

 

어떤 .NET 언어보다도 뛰어난 성능

 

보통 .NET에서 프로그래밍 할 때 어떤 언어를 사용하나 동일한 성능을 낸다고 생각하지만(즉 언어는 달라도 컴파일 결과로 나오는 MSIL은 동일하다고) 이것은 잘 못된 생각입니다.

C++ 컴파일러 팀은 수년에 걸쳐 네이티브 코드의 최적화로부터 얻었던 지식을 C++/CLI의 최적화에 적용할 수 있도록 많은 노력을 했습니다. 그 결과 다른 .NET 언어보다 최적화된 MSIL를 만들어냅니다.

 

VC++은 어느 컴파일러보다 최상의 최적화를 제공하고 이것은 네이티브 뿐만이 아닌 매니지드 코드에 대해서도 같습니다. VC++ 컴파일러는 네이티브 코드의 모든 최적화 방법을 MSIL에도 적용하여 다른 .NET 언어보다 더 뛰어난 최적화를 할 수 있습니다.

 

.NET에서 가장 최적화된 .NET 코드를 만들어 내는 것은 C++/CLI 입니다.

 

 

 

네이티브 코드와 상호 운용 가능

 

사실 C++/CLI .NET에서 가장 큰 의미를 갖는 것이 바로 이 부분이라고 생각합니다.

.NET의 다른 언어에서 네이티브 코드를 사용하려면 네이티브 코드를 DLL로 만들어서 P/Invoke로 호출해야 합니다. 그러나 C++/CLI는 네이티브 코드와 매니지드 코드를 혼합하여 사용할 수 있습니다. 네이티브 함수로부터 매니지드 함수를 호출하는 경우 특별한 구문을 기술할 필요가 없습니다.

 

네이티브에서 매니지드 호출 또는 매니지드에서 네이티브를 호출하는 경우 서로간의 경계를 넘어가야 하므로 비용이 발생하는데 이런 호출을 최대한 줄여야 성능에 좋습니다. C#의 경우에는 서로간의 호출을 줄여야 하는 경우 인터페이스 변경 등이 필요하나 C++/CLI /clr 스위치를 사용하는 것으로 쉽게 변경할 수 있습니다. 이러한 결과로 네이티브와 매니지드 간의 호출에 발생하는 비용을 최소화 할 수 잇습니다.

 

매니지드 코드와 네이티브 코드의 상호 운용에서 가장 비싼 비용은 마샬링입니다. C#의 경우 P/Invoke를 호출할 때 CLR에 의해서 암묵적으로 마샬링이 실행됩니다. 그러나 C++/CLI는 프로그래머가 필요에 따라서 명시적으로 마샬링을 할 수 있어서 한번 마샬링한 데이터를 복수로 사용할 수 있어서 마샬링에 의한 비용을 줄일 수 있습니다.

 

 

 

.NET 프로그램의 처음 실행 시의 딜레이

 

.NET으로 만든 프로그램과 네이티브로 만든 프로그램의 차이 중의 하나가 .NET으로 만든 프로그램은 처음 실행 시에 CLR을 읽기 위해 딜레이가 발생하는 것입니다. 그러나 C++/CLI는 이런 문제를 회피할 수 있습니다.

VC++에는 DLL 딜레이 로딩 이라는 기능이 있습니다. 링커 옵션에서 /DELAYLOAD:dll에 딜레이 로딩을 할 .NET 어셈블리를 지정하면 네이티브 프로그램과 동일한 정도의 속도록 실행 시킬 수 있습니다.

 

 

 

좀 더 C++/CLI가 다른 .NET 언어보다 좋은 점이 있지만 이것으로 줄이겠습니다.

C++/CLI를 아시는 분들은 언어적 위치의 애매함에 의해서 좋지 않은 인상을 가진 분들이 많으리라 생각하는데 제가 소개한 장점을 통해서 조금이나마 좋은 인상을 얻으셨는지 모르겠네요^^.

 


C++/CLI가 다른 .NET 언어보다 어떤 특징을 가지고 있는지 좀 더 자세하게 알고 싶다면 위에 소개한 MSDN 매거진의 원문(영어)을 보시던가 또는 제가 발 번역한 글을 보시기 바랍니다.

 

Visual C++ 의 프로그래밍 모델과 컴파일러 최적화를 사용한 어플리케이션의 강화

http://jacking75.cafe24.com/MSDN_MagaZine/C++Rule_200501.htm

 

그리고 아래의 글도 참고하시기 바랍니다.

C++/CLI: .NET 프레임워크 프로그래밍을 위한 가장 강력한 언어

http://anyflow.net/entry/CCLI-NET-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%9D%84-%EC%9C%84%ED%95%9C-%EA%B0%80%EC%9E%A5-%EA%B0%95%EB%A0%A5%ED%95%9C-%EC%96%B8%EC%96%B412


http://anyflow.net/entry/CCLI-NET-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%9D%84-%EC%9C%84%ED%95%9C-%EA%B0%80%EC%9E%A5-%EA%B0%95%EB%A0%A5%ED%95%9C-%EC%96%B8%EC%96%B422

 

 

C++/CLI 의 설계 원리와 발전 과정

http://msdn.microsoft.com/ko-kr/magazine/cc163484.aspx

C++/CLI는 미운 오리새끼 or 백조

C++/CLI 2010. 5. 11. 08:30 Posted by 알 수 없는 사용자

안녕하세요. 저는 작년에 스터디에 합류하면서 C++0x VC++ 10 Concurrency Runtime에 대해서 글을 적고 강연을 했었습니다. 아직 C++0x Concurrency Runtime에 대한 모든 것을 다 다루지 못했지만 이번에 새로운 멤버들이 합류하여 이 분들이 제가 이전에 맡았던 부분을 맡아서 할 예정이므로 저는 새로운 것을 하려고 합니다.

 

제가 하려는 것은 C++/CLI에 대한 것입니다.

사실 원래 작년부터 저는 C++/CLI을 공부하면서 정리해 보려고 했는데 작년 4월에 스터디에 합류하면서 계속 미루어졌습니다.

 

 

보통 .NET에서 프로그래밍을 한다고 하면 C#으로 프로그래밍 한다고 생각합니다. 그리고 네이티브 프로그래밍을 한다고 하면 C++입니다. 그럼 C++/CLI는 무엇일까요?

 

이름으로 유추 해 볼까요? ‘C++/CLI의 첫 단어는 ‘C++’입니다. ‘C++’ 이라고 하니 딱 네이티브 냄새가 나는군요. 두 번째 단어는 ‘CLI’입니다. C#으로 프로그래밍 하시는 분들은 ‘CLI’가 무엇을 뜻하는지 아시죠? ‘CLI’ .NET의 냄새를 풀풀 풍깁니다. ‘C++/CLI’라는 단어는 네이티브와 .NET의 냄새가 동시에 나지 않나요? 만약 그렇다고 생각하신 분들은 냄새를 잘 맡은 것입니다.^^

 

‘C++/CLI’가 네이티브와 .NET의 냄새를 동시에 풍기듯이 프로그래밍 측면에서도 ‘C++/CLI’는 네이티브와 .NET의 중간 지점에 있는 언어라고 할 수 있습니다.

 

 

 

폭 넓은 또는 이도 저도 아닌 어중간한

 

어떤 영역에서 중간 정도의 위치에 있으면 좋은 말로는 한쪽에 쏠리지 않으면서 양쪽 모든 것을 다 할 수 있다고 들을 수 있지만, 나쁜 말로는 이도 저도 아니라는 말도 들을 수 있습니다.

 

C++/CLI도 그런 것 같습니다.

 

먼저 C++/CLI의 나쁜 점은 .NET 프로그래머와 네이티브 프로그래머 양쪽 모두가 썩 마음에 들어하지 않는 언어라는 것입니다. .NET 프로그래머가 보기에는 이미 C#이라는 좋은 언어가 있어서 다른 언어는 눈에 잘 들어오지도 않고, 네이티브에는 관심이 없는 분들도 많습니다.

 

네이티브 프로그래머에게는 C++라는 걸출한 언어가 있고 일의 특성상 .NET으로는 하기 힘들어서 .NET 프로그래머가 사랑하는 C#도 들어올 공간이 없을 정도입니다. 그런데 C++/CLI를 보니 ‘C++’이라는 단어에 반가움을 가지고 봤더니 C++와 비슷한 것 같으면서 문법이 미묘하게 다른 것이 꽤 마음에 들지 않습니다(C++ 프로그래머가 보기에는 해괴하게 생겼습니다).

 

C#, C++ 어느 하나도 제대로 마스터하기 힘든데 C++/CLI를 제대로 사용하기 위해서는 .NET 프로그래머는 C++을 알아야 하고, C++ 프로그래머는 .NET을 알아야 합니다. 이 바쁜 세상에 하나도 제대로 마스터하기 힘든데 두 개나 알아야 합니다. -__-

 

혹시 여기까지 읽고 “C++/CLI 나쁜 언어군. 이런 잉여 언어라고 생각하고 창을 닫으려고 하는 건 아니겠죠? 조금만 더 기다려 보세요. 이제 좋은 말을 좀 할 테니 이것도 보세요^^

 

그럼 이제 좋은 점을 말해 보겠습니다.

.NET C++(네이티브) 둘 다 각각의 장점을 많이 가지고 있지만 완벽하지는 않습니다.

.NET 프로그래머 입장에서는 아직 세상에는 C++ C로 만들어진 라이브러리가 많고, .NET C++에 비해서 아직은 성능이 낮기 때문에 .NET만으로는 부족한 경우가 있습니다. C++ 프로그래머 입장에서는 요즘 같이 생산성을 추구하는 환경에서 .NET에 비해 낮은 생산성 때문에.NET 환경이 부럽습니다. C++/CLI는 이런.NET 프로그래머와 C++(네이티브) 프로그래머의 아쉬운 부분을 해결해 줄 수 있습니다.

 

물론 C++/CLI를 사용하지 않아도 .NET에서 네이티브 라이브러리를 사용하고, C++ 프로그래머는 .NET으로 만들어진 라이브러리를 사용할 수 있지만 규모가 작지 않은 경우라면 C++/CLI를 사용하는 것보다 까다롭습니다.

 

 

제가 일하고 있는 게임 업계에서는 주력 언어는 C++이고, 예전이나 지금 만들고 있는 대 부분의 라이브러리는 C++을 사용하여 만들고 있습니다. 그러나 게임 개발을 위한 회사에서 사용할 인하우스 툴을 만들 때는 C++만큼의 고 성능을 필요로 하지 않으면서 C++로 만든 라이브러리를 사용하고, 빠르게 개발하고 유지보수가 간편하기를 원하므로 .NET과 네이티브가 결합되기를 바랍니다. 바로 이럴 때가 ‘C++/CLI’가 적격입니다.

 

C++/CLI의 가장 큰 단점은 C# 프로그래머가 보기에도 이상하고, C++ 프로그래머가 보기에도 생김새가 이상해서 흥미를 일으키지 못한다는 점과, C++/CLI를 올바르게 알기 위해서는 .NET C++을 모두 알아야 된다는 점이라고 생각합니다.

그러나 이 단점이라는 부분이 .NET C++ 양쪽 모두를 아는 프로그래머에게는 어느 한쪽만을 아는 프로그래머에 비해 양쪽의 장점을 적절하게 다 가져갈 수 있습니다.

 

.NET이나 C++ 한쪽만을 아는 사람에게는 C++/CLI는 미운 오리새끼이지만 양쪽 모두 아는 사람에게는 백조가 될 수 있습니다.

이 블로그를 통해서 아직까지는 미운 오리새끼로 취급 받고 있는 C++/CLI를 백조가 될 수 있도록 해보겠습니다.

 

 

적다 보니 생각보다 글이 길어졌네요. 첫 글을 너무 쓸데 없이 주절댄 것은 아닌가 모르겠습니다.^^;

 

다음부터는 .NET에서의 C++/CLI의 의미에 대해서 정리하고, C++/CLI에 대한 설명, C++/CLI를 사용한 .NET과 네이티브의 결합, C++/CLI .NET Framework 4 사용 이라는 순서로 글을 올려보겠습니다.

 

 

ps : C++/CLI .NET이 처음 나올 때는 ‘Managed C++’이라는 이름으로 불렸습니다. 그러다가 VS 2005가 나오면서 C++/CLI로 이름이 바뀌었습니다. 또한 문법적인 면에서도 차이가 있습니다.

Visual C++ 10의 변화

Visual C++ 10 2010. 5. 2. 22:08 Posted by 알 수 없는 사용자

영문판 VS 2010(Visual C++ 10)은 나왔고, 한글판은 61일에 나온다고 합니다.

 

Visual C++ 10을 도입하려고 하시는 분들은 전체적으로 어떤 변화가 있는지 알고 싶을 것이고, 특히 구매를 위해 회사 윗 분들에게 보고를 해야 되는 분들은 관련된 문서를 만들어야 하는 분들도 있으리라 생각합니다.

 

VC++ 10 도입이나 내부 스터디에 사용하도록 간단하게 VC++ 10의 달라진 점을 정리하여 문서를 만들어 보았습니다.

 

이 문서는 러프하게 만들어진 문서이니 참고로 사용하여 더 좋은 문서를 만드시기를 바랍니다. 특히 윗 분들에게 보고 하기 위해서 PT문서를 만들 때는 이 문서에 왜 필요한지에 대해서 좀 더 강력한 메시지를 넣기를 바랍니다.^^






About Visual C++ 10

Visual C++ 10 2009. 10. 15. 08:30 Posted by 알 수 없는 사용자

지난 10 7일에서 9일까지 서울 코엑스에서 ‘KGC 2009’ 라는 게임 개발 컨퍼런스가 열렸습니다.

저는 여기서 ‘Visual C++ 10’에 대해서 강연을 했습니다.

 

이번 버전 10은 이전에 비해서 변화한 부분이 많아서 가장 큰 핵심인 ‘C++0x’‘Parallel’부분만 주제를 잡았는데도 시간 부족상 다 하지는 못했습니다.

 

아직 VSTS 2010 Beta1을 설치하지 않은 분들도 많으실 것 같아서 Demo까지 하려니 도저히 시간이 남지 않더군요.

 

이번에 제대로 하지 못한 ‘Parallel’ 부분은 꼭 다음에 기회가 되는대로 강연을 하도록 하겠습니다.^^





ps : 근래 바빠서 블로그에 포스팅을 하지 못했는데 곧 새로운 글을 올리겠습니다.




원래 저번 주에 글을 올릴 예정이었으나 근래에 제 몸 상태와 집 PC 상태가 메롱이 되어버려 한 주 늦게 글을 올립니다(혹시 기다리고 계시는 분이 있었는지 모르겠네요 ^^;;; )



for 문의 병렬화 

이번에는 PPL의 세 개의 알고리즘 중 parallel_for 알고리즘에 대해서 이야기 하겠습니다.

앞 글에서 간단하게 설명했듯이 parallel_for는 그 이름을 보면 유추 할 수 있듯이 for 문을 병렬화 한 알고리즘입니다.

 

아주 많은 횟수로 반복 작업을 해야할 때 하나의 스레드로 처리하는 것보다는 여러 스레드로 동시에 처리하면 훨씬 빨라지는 것은 당연하겠죠? 바로 이 때 사용하면 좋습니다.

하지만 parallel_for 알고리즘은 아무 곳에나 사용할 수는 없습니다. 루프의 반복 계산 사이에 리소스를 공유하지는 않으면서 루프의 본체가 있는 경우 사용하면 편리합니다.

( 앞의 계산 결과를 다음 계산에서 사용해야 된다면 병렬로 실행하기 힘듭니다 )

 

 

parallel-for의 원형

 

두 개의 오버로드 버전이 있습니다.

 

template < typename _Index_type, typename _Function >

_Function parallel_for( _Index_type _First,  _Index_type _Last, _Function _Func );

_Index_type _First : 시작 위치

_Index_type _Last : 마지막 위치

_Function _Func : 병렬 처리로 사용할 함수

 

 

template < typename _Index_type, typename _Function >

_Function parallel_for( _Index_type _First, _Index_type _Last, _Index_type _Step, _Function _Func );

_Index_type _First : 시작 위치

_Index_type _Last : 마지막 위치

_Index_type _Step : 증분 값

_Function _Func : 병렬 처리로 사용할 함수

 

파라미터 값을 보면 for에서 사용하는 것과 비슷하다는 것을 알 수 있을겁니다. 차이점은 첫 번째 버전의 경우 증분 값으로 1이 자동으로 사용된다는 것과 마지막 파리미터로 병렬 처리에 사용할 함수를 사용한다는 것입니다.

 

 

for와 비슷하므로 for를 사용하는 대 부분을 prarallel_for로 변경할 수 있습니다. 다만 parallel_for 알고리즘에서는 반복 변수의 현재 값이 _Last 보다 작으면 중단합니다 ( 보통 for 문과 다르게 ‘<’ 조건만 사용합니다 ).

또 _Index_type 입력 파라미터는 정수형이어야만 합니다.

parallel_for 파라미터가 1보다 작은 경우 invalid_argument_Step 예외를 던집니다.

 


 

초 간단 parallel_for 사용 방법

 

1. 필요한 헤더 파일 포함
  #include <ppl.h>


2.
네임 스페이스 선언

  using namespace Concurrency;

 

3. parallel_for에서 호출할 작업 함수 정의

 

4. parallel_for에서 사용할 data set 정의

 

5. parallel_for 사용

 

 

 그럼 아주 간단한 실제 사용 예제 코드를 볼까요?

 

#include <ppl.h>

#include <iostream>

 

using namespace Concurrency;

using namespace std;

 

 

int main()

{

    int CallNum = 0;

    int Numbers[50] = { 0, };


   
parallel_for( 0, 50-1, [&](
int n ) {

        ++CallNum;

        Numbers[n] += CallNum;

       }               

      );

 

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

    {

        cout << i << " : " << Numbers[i] << endl;

    }

 

    getchar();

    return 0;

}


 

위 예제는 Numbers라는 int 형 배열의 각 요소에 CallNum 이라는 변수를 더하는 것입니다. 간단하고 확실하게 parallel_for 사용 방법을 보이기 위해 허접한 예제를 만들게 되었음을 양해 바랍니다.^^;;; ( 다음에 기회가 되면 좀 더 멋지고 실용적인 예제를 보여드리도록 하겠습니다 )

예제에서는 코드를 간략화 하기 위해서 parallel_for의 마지막 파리미터로 람다 식을 사용했습니다.

위 예제를 '초 간단 parallel_for 사용 방법'의 순서에 비추어보면 아래 그림과 같습니다.

 

 


예제를 실행하면 아래와 같은 결과가 나옵니다.

 

(길어서 일부만 캡쳐 했습니다)

 

실행 결과를 보면 Numbers 배열의 각 요소의 값이 순서대로 증가되지 않았다라는 것을 알 수 있습니다. 만약 보통의 for 문이라면 Numbers[0] 1, Numbers[1] 2 라는 값으로 됩니다. 그러나 parallel_for는 병렬적으로 실행되므로 순서가 지켜지지 않습니다. CallNum 라는 변수는 parallel_for의 모든 스레드에서 접근하는 공유 변수이므로 동기화 되지 않았다라는 것도 유의해야 합니다.

 

Parallel_for를 사용할 때 순서대로 실행하지 않고, 공유 변수는 동기화 되지 않음을 잊지마시기를 바랍니다.

 

이것으로 (너무?)간단하게 parallel_for에 대해서 알아 보았습니다. 다음에는 parallel_for_each에 대해서 설명하겠습니다.




수정

1. 덧글의 ivyfore님이 알려주신대로

parallel_for( 0, 50-1, [&]( int n )가 아닌

 parallel_for( 0, 50, [&]( int n ) 가 되어야 합니다.