[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로 이름이 바뀌었습니다. 또한 문법적인 면에서도 차이가 있습니다.