블로그 이미지
대갈장군

calendar

1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

Notice

제목이 무척 도발적이다. C++/CLI 10분만에 배우기... 글 읽는데만 10분 넘게 걸리는데 어떻게 10분만에 배운다는 거냐...

참고로 출처는 http://www.codeproject.com/Articles/19354/Quick-C-CLI-Learn-C-CLI-in-less-than-10-minutes (저자: Elias Bachaalany)

기본적으로 이 글의 취지는 C++/CLI를 빠르게 시작하기 위한 글이다. 고로 독자들이 C++이랑 .NET 배경지식이 어느정도 있다고 가정하고 글을 쓴다고 한다. 글을 일기전에 자고있는 당신의 머리 세포들을 깨워줄 좋은 글도 있다고 추천해주는 저자의 센스! 


아래에 포함되는 예제를 실행하려면 다음과 같이 하면 된다.

"C:\Program Files\Microsoft Visual Studio 8\Common7\Tools\vsvars32.bat"

요로코롬 하면 저 명령 이후로는 Visual Studio 2005 x86을 이용하여 컴파일을 하게 된다.


그리고 어떤 파일을 컴파일 하고 싶다면 다음과 같이...

cl your_file.cpp /clr

뒤에 붙은 /clr 스위치가 정겨워 보인다...


So, What is C++/CLI?

이것에 대한 대답은 이미 다른 글에서 많이 했는데 일단 이 글을 쓴 저자의 말을 옮겨 보면, C++은 알다시피 C의 Superset (상위셋)이라고 할 수있다. C++은 C에 OOP (객체 지향성) 및 Template를 추가한 것이다. 그렇다면 CLI는 무엇인가?


CLI은 Common Language Infrastructure의 줄임말로 그 뜻은 다음과 같다.

It is an open specification that describes the executable code and runtime environment that allows multiple high-level languages to be used on different computer platforms without being rewritten for specific architectures.

CLI라 함은 실행 코드와 런타임 환경을 정의하는 오픈된 명세로써 많은 고레벨 언어가 운영체제가 다른 플랫폼에서 코드의 재작성 없이 사용될 수 있도록 해주는 것이란다.


Handles and Pointers

아마도 한번이라도 C++/CLI를 써 본 사람이라면 '^' 요 마크가 친숙 할 것이다. 변수 선언시에 꼭 따라 붙는 놈이다. 알다시피 C++에서는 포인터를 선언할 때 * 마크를 사용한다. C++/CLI에서는 ^를 이용해 핸들을 선언한다. 자, "*" 는 Native Pointer를 말하고 이것은 CRT 힙에 상주한다. 반면 "^"는 핸들을 선언하는 것이고 "Safe Pointer"라 불리며 Managed 힙 공간에 상주한다. 이 핸들이라고 하는 녀석은 단순히 생각해서 C++의 레퍼런스와 같은 개념이긴 하지만 C++과는 다른게 (Native 와는 다른게) 제대로 해제하지 않더라도 메모리 리크 (유출)이 발생하지 않는다. 왜냐면 가비지 컬렉터 (GC)가 알아서 메모리를 수거해주기 때문이다. 오우 나이스!


특정 클래스나 값 타입의 레퍼런스를 만들기 위해서는 gcnew라는 키워드를 사용해서 선언해야 한다. 다음과 같이..

System::Object ^x = gcnew System::Object();

nullptr이 흔히 사용하는 null의 CLI 버전이고 ^ 심볼과 함께 % 심볼도 사용된다. 


N* pn = new N; // allocate on native heap
N& rn = *pn; // bind ordinary reference to native object
R^ hr = gcnew R; // allocate on CLI heap
R% rr = *hr; // bind tracking reference to gc-lvalue

C++에서 *과 &이 한 묶음 이듯이 CLI에서는 ^이랑 %가 한 묶음이다. 


Let us get started: Hello World

자 이제 가장 단순한 프로그램 하나 만들어보자. 

#using <mscorlib.dll>

using namespace System;

int main(array<System::String ^> ^args)
{
  System::Console::WriteLine("Hello world");
  return 0;
}

다른 건 뭐 다 봐줄만 한데 main의 입력 함수로 들어오는 놈이 예사롭지 않아보인다. 하지만 C++ 코드를 떠올려보면 저것이 의미하는 것이 그렇게 복잡한 것만은 아니다. 


Classes and UDTs

Classes

클래스를 어떻게 선언하는지 일단 살펴보자. 해줘야 할 것은 단 한가지 뿐. Protection Modifier (Private / Public) 바로 다음에 ref 키워드만 붙이면 끄읏!

public ref class MyClass
{
private:
public:
  MyClass()
  {

  }
}

헐 이렇게 쉬울 수가? 이렇게 선언된 클래스를 네이티브 클래스로 생성하려면 원래 사용하던 방식 그대로 클래스를 선언하면 그만이다. 자, 이제 궁금한 것은 C++/CLI 도 파괴자를 가지고 있느냐 하는 것인데 정답은 Yes다. 하지만 컴파일러는 파괴자 호출을 IDispose 인터페이스를 투명하게 구현한 다음 Dispose() 함수로 유도하게 된다. 추가적으로 GC에 의해 호출되는 Finalizer라고 불리는 녀석이 있는데 이것은 !MyClass() 와 같이 C++ 파괴자 함수 스타일이다. 이 Finalizer에서 사용자는 파괴자가 호출되었는지 체크해봐야 한다.


#using <mscorlib.dll>

using namespace System;

public ref class MyNamesSplitterClass
{
private:
  System::String ^_FName, ^_LName;
public:
  MyNamesSplitterClass(System::String ^FullName)
  {
    int pos = FullName->IndexOf(" ");
    if (pos < 0)
      throw gcnew System::Exception("Invalid full name!");
    _FName = FullName->Substring(0, pos);
    _LName = FullName->Substring(pos+1, FullName->Length - pos -1);
  }

  void Print()
  {
    Console::WriteLine("First name: {0}\nLastName: {1}", _FName, _LName);
  }
};

int main(array<System::String ^> ^args)
{
  // local copy

  MyNamesSplitterClass s("John Doe");
  s.Print();

  // managed heap

  MyNamesSplitterClass ^ms = gcnew MyNamesSplitterClass("Managed C++");
  ms->Print();

  return 0;
}

Value types

Value type이라 함은 기본으로 제공되는 타입말고 추가로 만들어내는 타입들을 말한다. 모든 value type들은 System::ValueType으로 부터 파생된다. 이 value type들은 스택에 저장될 수 있고 = 연산자를 통해서 할당 가능하다.


public value struct MyPoint
{
  int x, y, z, time;
  MyPoint(int x, int y, int z, int t)
  {
    this->x = x;
    this->y = y;
    this->z = z;
    this->time = t;
  }
};

Enums

마찬가지로 열거형도 다음과 같이 정의 가능하다.


public enum class SomeColors { Red, Yellow, Blue};

혹은 엘리먼트의 타입을 정의할 수도 있다.


public enum class SomeColors: char { Red, Yellow, Blue};


Arrays

배열의 선언은 사실 복잡해 보이지만 다음과 같이 쉽다.


cli::array<int> ^a = gcnew cli::array<int> {1, 2, 3};


기존 C++과의 차이라면 array<> 키워드가 쓰이고 있고 ^ 심볼로 핸들 지정을 하며 new 대신 gcnew를 사용한다는 점.


또 다른 형태의 배열 선언 방식은 다음과 같다.


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

처음 방식보다 단순화 되었는데 cli 네임스페이스 명시를 빼고 100개의 배열을 선언한 다음 처음 3개만 값 1,2,3으로 초기화 했다. 각각의 배열 요소에 접근하려면 foreach를 다음과 같이 사용할 수 있다.


for each (int v in a)
{
  Console::WriteLine("value={0}", v);
}

자, 이제 4x5x2 형태의 3차원 배열을 선언해보자... 초기값은 모두 0.


array<int, 3> ^threed = gcnew array<int, 3>(4,5,2);

Console::WriteLine(threed[0,0,0]);

오오오... 생각보다 쉽다. 


이제 스트링 클래스를 배열로 선언해보자..


array<String ^> ^strs = gcnew array<String ^> {"Hello", "World"}


이런 형태를 초기화 하기 위해서는 다음과 같이 하면 된다.


array<String ^> ^strs = gcnew array<String ^>(5);
int cnt = 0;

// We use the tracking reference to access the references inside the array
// since normally strings are passed by value

for each (String ^%s in strs)
{
    s = gcnew String( (cnt++).ToString() );
}


나와 있는 설명처럼 % 심볼이 사용되었는데 그 이유는 일반적으로 스트링의 경우 값으로 전달되기 때문에 레퍼런스를 이용해야 메모리로 직접적인 접근이 가능하기 때문이다. 



Parameter Arrays

헛, 이것도 가능했단 말인가? C에서 printf() 함수와 같이 파라미터가 들어가는 함수의 사용도 가능하다. 단, 사용할 수 있는 조건은 C와 동일하다. 


using namespace System;

void avg(String ^msg, ... array<int> ^values)
{
  int tot = 0;
  for each (int v in values)
    tot += v;
  Console::WriteLine("{0} {1}", msg, tot / values->Length);
}

int main(array<String ^> ^args)
{
  avg("The avg is:", 1,2,3,4,5);
  return 0;
}


Properties

C#에서 사용되는 형태의 프로퍼티

public ref class Xyz
{
private:
  int _x, _y;
    String ^_name;
public:
  property int X
    {
      int get()
        {
          return _x;
        }
        void set(int x)
        {
          _x = x;
        }
    }
  property String ^Name
  {
    void set(String ^N)
    {
      _name = N;
    }
    String ^get()
    {
      return _name;
    }
  }
};


Wrapping Around a Native C++ Class

자, 이제 C++ class를 어떻게 CLI에서 감싸는지 보자


// native class

class Student
{
private:
  char *_fullname;
  double _gpa;
public:
  Student(char *name, double gpa)
  {
    _fullname = new char [ strlen(name+1) ];
    strcpy(_fullname, name);
    _gpa = gpa;
  }
  ~Student()
  {
    delete [] _fullname;
  }
  double getGpa()
  {
    return _gpa;
  }
  char *getName()
  {
    return _fullname;
  }
};

위 코드는 Native class의 한 예다. 이것을 C++/CLI 클래스로 바꾸려면 다음의 가이드를 따르면 된다.

  • Managed Class를 만들고 그것의 멤버 변수가 Native Class를 가리키도록 해라
  • 생성자 혹은 다른 적절한 위치에서 Native Class를 Native Heap (new 키워드를 사용하여) 에 생성해라
  • 생성자를 호출할 때 필요한 변수와 입력 인자를 전달하라. 이때 몇몇 입력 값은 Unmanaged 에서 Manged Type으로 Marshal (변환)이 필요할 것
  • Managed Class로 부터 드러내고 싶은 함수의 Stub를 생성하라
  • Managed Class의 파괴자에 Native Pointer 를 지우는 것을 잊지 마라 (메모리 누수)

자, 이제 위 Native Class의 C++/CLI 버젼을 보자.


// Managed class

ref class StudentWrapper
{
private:
  Student *_stu;
public:
  StudentWrapper(String ^fullname, double gpa)
  {
    _stu = new Student((char *) 
           System::Runtime::InteropServices::Marshal::StringToHGlobalAnsi(
           fullname).ToPointer(), 
      gpa);
  }
  ~StudentWrapper()
  {
    delete _stu;
    _stu = 0;
  }

  property String ^Name
  {
    String ^get()
    {
      return gcnew String(_stu->getName());
    }
  }
  property double Gpa
  {
    double get()
    {
      return _stu->getGpa();
    }
  }
};


중간에 좀 후덜덜한 부분이 있긴 하지만 그래도 생각보다 어렵진 않다.


Wrapping Around C Callbacks

여기서는 예제용으로 EnumWindows() API 함수를 Wrap 할 건데 다음이 코드의 핵샘이다.

  • Manged class를 하나 만드는데 Delegates와 함께 만들거나 Native Callback에 도달하면 호출되는 함수랑 만들거나.
  • 방금 만든 Managed Class로 향하는 레퍼런스를 가지는 Native Class 만든다. 이것은 vcclr.h 헤더에 있는 gcroot_atuo를 이용하면 가능하다
  • 이제 Native C Callback 함수를 만들고 넘길때 Context Parameter (우리 예제에서는 lParam) 로 넘긴다. (Native Class로의 포인터)
  • 이제 Native Callback 내부에서 Native Class 인 Context를 가지고 있으므로 이제 Managed Class로 향하는 레퍼런스를 얻어올수 있고 필요한 함수를 호출 할 수 있다.

using namespace System;

#include <vcclr.h>


// Managed class with the desired delegate

public ref class MyClass
{
public:
  delegate bool delOnEnum(int h);
  event delOnEnum ^OnEnum;

  bool handler(int h)
  {
    System::Console::WriteLine("Found a new window {0}", h);
    return true;
  }

  MyClass()
  {
    OnEnum = gcnew delOnEnum(this, &MyClass::handler);
  }
};


이제 Native Class를 만들어보자. 이 클래스 내부에는 Managed Class 레퍼런스 (m_clr)이 존재하고 직접적인 Callback 프로시져가 있다.


class EnumWindowsProcThunk
{
private:
  // hold reference to the managed class

  msclr::auto_gcroot<MyClass^> m_clr;
public:

  // the native callback

    static BOOL CALLBACK fwd(
    HWND hwnd,
    LPARAM lParam)
  {
      // cast the lParam into the Thunk (native) class,
      // then get is managed class reference,
      // finally call the managed delegate

      return static_cast<EnumWindowsProcThunk *>(
            (void *)lParam)->m_clr->OnEnum((int)hwnd) ? TRUE : FALSE;
  }

    // Constructor of native class that takes a reference to the managed class

  EnumWindowsProcThunk(MyClass ^clr)
  {
    m_clr = clr;
  }
};

마지막으로 실질적으로 사용하는 코드는,


int main(array<System::String ^> ^args)
{
  // our native class

  MyClass ^mc = gcnew MyClass();

    // create a thunk and link it to the managed class

  EnumWindowsProcThunk t(mc);

    // Call Window's EnumWindows() C API with the pointer
    // to the callback and our thunk as context parameter

  ::EnumWindows(&EnumWindowsProcThunk::fwd, (LPARAM)&t);

  return 0;
}


The Other Way Round: From Manged to C Callbacks

이 것은 더 쉽다. 왜냐면 이미 Managed Class 내부에 Native Class로 향하는 포인터를 가지고 있기 때문이다. 다음과 같이 하면 된다.

  • Native Callback를 트리거 (발생)시킬 적절한 Delegates를 가지는 Managed Class를 만든다
  • Native Class (Callback 을 포함하는)와 이전의 Managed Class (이벤트 발생시키는 놈)을 연결해줄 Managed Class를 만든다
  • Callback을 핸들링 할 Native Class를 만든다
예제용으로 TickGenerator라는 Managed Class를 만들었는데 이 TickGenerator라는 클래스는 OnTick이라는 이벤트를 만들어내고 INativeHandler 클래스 (인터페이스) 가 Managed Class인 TickGeneratorThuck로 부터 호출된다. MyNativeHandler 클래스는 사용자 자신의 핸들러을 어떻게 셋팅하는지 보여주기 위한 INativeHandler의 간단한 구현이다.


Tick Generator Delegate는 다음과 같다


public delegate void delOnTick(int tickCount);

그리고 Managed tick generator class는 다음과 같다.


ref class TickGenerator
{
private:
  System::Threading::Thread ^_tickThread;
  int _tickCounts;
  int _tickFrequency;
  bool _bStop;

  void ThreadProc()
  {
    while (!_bStop)
    {
      _tickCounts++;
      OnTick(_tickCounts);
      System::Threading::Thread::Sleep(_tickFrequency);
    }
  }

public:
  event delOnTick ^OnTick;

  TickGenerator()
  {
    _tickThread = nullptr;
  }

  void Start(int tickFrequency)
  {
    // already started

    if (_tickThread != nullptr)
      return;

    // p.s: no need to check if the event was set,
    // an unset event does nothing when raised!

    _tickCounts = 0;
    _bStop = false;
    _tickFrequency = tickFrequency;

    System::Threading::ThreadStart ^ts = 
      gcnew System::Threading::ThreadStart(this, &TickGenerator::ThreadProc);
    _tickThread = gcnew System::Threading::Thread(ts);
    _tickThread->Start();
  }
  
  ~TickGenerator()
  {
    Stop();
  }

  void Stop()
  {
    // not started?

    if (_tickThread == nullptr)
      return;
    _bStop = true;

    _tickThread->Join();
    _tickThread = nullptr;
  }
};


#pragma unmanaged
// Create a simple native interface for handling ticks

// Native classes implement this class to add custom OnTick handlers

class INativeOnTickHandler
{
public:
  virtual void OnTick(int tickCount) = 0;
};


class MyNativeHandler: public INativeOnTickHandler
{
public:
  virtual void OnTick(int tickCount)
  {
    printf("MyNativeHandler: called with %d\n", tickCount);
  }
};


#pragma managed
// Create the managed thunk for binding between the native
// tick handler and the tick generator managed class

ref class TickGeneratorThunk
{
private:
  INativeOnTickHandler *_handler;
public:
  TickGeneratorThunk(INativeOnTickHandler *handler)
  {
    _handler = handler;
  }

  void OnTick(int tickCount)
  {
    _handler->OnTick(tickCount);
  }
};


int main(array<System::String ^> ^args)
{
  // Initiate the native handler

  MyNativeHandler NativeHandler;

  // Create the tick generator class

  TickGenerator ^tg = gcnew TickGenerator();

  // Create the thunk and bind it with our native handler

  TickGeneratorThunk ^thunk = gcnew TickGeneratorThunk(&NativeHandler);

  // Bind the ontick event with the thunk's onclick event

  tg->OnTick += gcnew delOnTick(thunk, &TickGeneratorThunk::OnTick);

  // Start the tick generator

  tg->Start(1000);

  // Wait for user input

  Console::ReadLine();

  // Stop the generator

  tg->Stop();

  return 0;
}


글을 다 쓰고 보니 좀 글의 내용과 제목이 어울리지 않는다는 생각이 든다. 애초에 C++/CLI를 빠르게 습득하는 것을 타이틀로 했다면 특정 언어간의 호출에 치중하기 보다는 기본적인 타입의 사용과 다양한 예제를 통해 보다 일반적이고 기본적인 개념 설명을 하는 것이 더 좋았을듯 하다... 아무튼, 일단 글을 퍼오기는 했으니 저장은 하지만 썩 마음에 들지는 않는다. 물론 초반에 간단간단하고 명료한 설명은 참 좋았다...




posted by 대갈장군
2013. 4. 25. 01:30 프로그래밍/MSDN

출처: http://msdn.microsoft.com/ko-kr/magazine/cc163681(en-us).aspx


젠장, 이건 한국어 선택해도 한국어로 안나오는 페이지네... 결국 해석해야 할듯...


C++/CLI is a self-contained, component-based dynamic programming language that, like C# or Java, is derived from C++. Unlike those languages, however, we have worked hard to integrate C++/CLI into ISO-C++, using the historical model of evolving the C/C++ programming language to support modern programming paradigms. You can say that C++/CLI is to C++ as C++ is to C. More generally, you can view the evolution leading to C++/CLI in the following historical context:
C++/CLI는 'Self-contained (스스로 모두 내포하고 있는 - 즉, 완전체)'하고 컴포넌트에 기반한 동적 프로그래밍 언어다. 마치 C# 또는 자바처럼 C++에 근간을 두고 있는 언어들 처럼 말이다. 하지만 그런 언어들 (C#이나 Java)과는 달리 우리 (Microsoft사)는 C++/CLI를 ISO-C++ (표준 C++을 말함) 형태로 만들기 위해서 무척이나 노력했단다... 뭐 쉽지 않았을 것 같다. C++ 에서 C++/CLI로 가는 것은 C에서 C++로 가는 것 만큼이나 큰 변화였다는 이야기. 다음 문서들을 보게 되면 얼마나 큰 변화가 있는지 알거다... 허허.. 그걸 언제 다 읽어봐.... 제일 마지막꺼만 읽어보면 괜춘할듯.
  • BCPL (Basic Computer Programming Language)
  • B (Ken Thompson, original UNIX work)
  • C (Dennis Ritchie, adding type and control structure to B)
  • C with Classes (~1979)
  • C84 (~1984)
  • Cfront, release E (~1984-to universities)
  • Cfront, release 1.0 (1985-to the world )—20th birthday
  • Multiple/Virtual Inheritance (MI) programming (~1988)
  • Generic Programming (~1991) (templates)
  • ANSI C++/ ISO-C++ (~1996)
  • Dynamic Component programming (~2005) (C++/CLI)

What is C++/CLI?
C++/CLI represents a tuple. C++ refers, of course, to the C++ programming language invented by Bjarne Stroustrup at Bell Laboratories. It supports a static object model that is optimized for the speed and size of its executables. However, it doesn't support run-time modification of the program other than heap allocation. It allows unlimited access to the underlying machine, but very little access to the types active in the running program and no real access to the associated infrastructure of that program. Herb Sutter, a former colleague of mine at Microsoft and the chief architect of C++/CLI, refers to C++ as a concrete language.
CLI refers to the Common Language Infrastructure, a multitiered architecture supporting a dynamic component programming model. In many ways, this represents a complete reversal of the C++ object model. A runtime software layer, the virtual execution system, runs between the program and the underlying operating system. Access to the underlying machine is fairly constrained. Access to the types active in the executing program and the associated program infrastructure—both as discovery and construction—is supported. The slash (/) represents a binding between C++ and the CLI. The details surrounding this binding make up the general topic of this column.
So, a first approximation of an answer to what is C++/CLI is that it is a binding of the static C++ object model to the dynamic component object model of the CLI. In short, it is how you do .NET programming using C++ rather than C# or Visual Basic®. Like C# and the CLI itself, C++/CLI is undergoing standardization under the European Computer Manufacturers Association (ECMA) and eventually under ISO.
The common language runtime (CLR) is the Microsoft version of the CLI that is specific to the Windows® operating system. Similarly, Visual C++® 2005 is the implementation of C++/CLI.
As a second approximation of an answer, I would say that C++/CLI integrates the .NET programming model within C++ in the same way as, back at Bell Laboratories, we integrated generic programming using templates within the then existing C++. In both of these cases your investment in an existing C++ codebase and in your existing C++ expertise are preserved. This was an essential baseline requirement of the design of C++/CLI.
자, 이제부터 시작이다. 머리 싸매고 읽어보자. C++/CLI는 튜플이다. 여기서 튜플이라 함은 내 생각에 서로 다른 타입이 하나의 오브젝트로 뭉쳐져 있다는 의미로 해석된다. 즉, C++과 CLI 라는 서로 다른 두 타입을 하나로 합쳐놓은 짬뽕이라는 것. C++/CLI이라는 단어에서 C++ 부분은 당연히 유명하신 Bjarne Stroustrup께서 Bell 연구소에서 만드신 것을 지칭하는 것이다. 이거 모르면 당신은 간첩? 이 C++은 기본적으로 실행파일의 속도와 크기를 위해 최적화 되어 있는 'Static Object Model'을 지원한다. Static 이라는 단어가 좀 뜬금없이 들릴수 있다. 사실 내 생각에 이 Static이라는 단어는 상대적인 의미라고 본다. 과거에 없었던 현재 시점에 어떤 기술에 비해 Static (정적)하다는 것이지 C++이 나왔을 당시에는 대박 혁명적 언어였다...

아무튼, 조금 있다 설명할 CLI를 이해하면 왜 C++이 기분나쁘게 '정적'이라고 불리는지 알게 될 것이다. 자, C++의 문제점은 Heal Allocation (힙 메모리 할당)을 제외하고는 Run-time에 변경을 허락하지 않는다. 자, Run-Time이라 함은 프로그램이 실행된 직후부터 멈추는 순간까지를 말한다. 고로 C++은 컴퓨터 자체 하부 구조에는 이른바 'Unlimited Access' 즉 무한 접근을 허락하지만 실행중인 프로그램에는 아주 미미하고 제한적인 접근만을 허락하며 프로그램이 작동하기 위해 사용하는 Infrastructure (예를 들자면 Windows 같은거)에는 실질적인 접근을 제공하지 않는다. Herb Sutter 이라는 내 친구가 있는데 이 친구가 C++/CLI를 설계를 책임진 사람이란다. 아무튼 이 친구가 말하기를 "C++은 Concrete Language다" 라고 말했단다. 직역하자면 C++은 딴딴한 언어다....... 응?? 즉, 정적이다. 별로 유동적이 않다는 것.

이제 우리의 친구 CLI를 살펴보자.  CLI는 Common Language Infrastructure의 줄임말이다. 공용 언어 기반이란 뜻이네? 풀어서 설명하자면 동적 요소 프로그래밍(Dynamic Component Programming) 을 지원하는 멀티스레드를 하는 아키텍쳐란다. 이걸 단박에 이해한다면 당신은 이미 능력자. 이런 정의 자체가 이미 C++과 정반대된다. Runtime Software Layer 다른 말로는 가상 실행 시스템 (Virtual Execution System)은 프로그램과 프로그램을 실행하는 운영체제 가운데 낑겨 있는 녀석이다. 고로 컴퓨터 자체로 접근하는 것은 제한이 된다. (C++과 많이 다르다) CLI에서는 실행중인 프로그램의 타입으로 액티브하게 접근하는 것과 연관된 프로그램 infrastructure에 접근하는 것 모두 지원된다. C++/CLI 사이에 들어가는 /가 의미하는 것은 C++과 CLI의 결합을 의미한단다. 이것에 대해서 이 글에서 이야기 할꺼래.

자, 이제 C++/CLI가 뭐요? 라는 질문의 첫번째 대답을 하겠다. C++/CLI는 정적인 C++ Object Model을 동적인 Component Object Model 인 CLI와 결합한 것이다. 정적 객체 모델 C++ 더하기 동적 요소 객체 모델 CLI. 줄여서 말하자면, C#이나 Visual Basic 대신 C++ 이라는 언어를 사용해서 .NET programming을 하는 것을 말한다. 오오오... 정답! 마음에 드는 말이고 모든 것을 분명하게 선을 긋는 아주 정확한 정의다.

CLR (Common Language Runtime)은 Windows라는 운영체제를 위한 Microsoft사의 CLI라고 보면 된다. 이거 어렵게 들릴지 모르겠지만 내가 예전에 써놓은 .Net Framework에 대한 글을 보면 이해가 쉽게 될것이다. 

두번째 근접한 대답으로는 C++/CLI는 과거에 벨 연구소해서 C에서 C++로 진화할때 제네릭 프로그래밍을 C에 추가한 것 처럼 C++이라는 언어에 .NET 프로그래밍 모델을 추가한 것이다. 이로써 내가 열씸히 작성해 놓은 C++ 코드들이 다 무용지물이 되지 않게 되고 C++/CLI로 흘러감으로써 엄청난 이득을 취할 수 있겠다. 그럴까??? 정말로?? ㅋㅋ

Learning C++/CLI
There are three aspects in the design of a CLI language that hold true across all languages: a mapping of language-level syntax to the underlying Common Type System (CTS), the choice of a level of detail to expose the underlying CLI infrastructure to the manipulation of the programmer, and the choice of additional functionality to provide, beyond that supported directly by the CLI.
The first item is largely the same across all CLI languages. The second and third items are where one CLI language distinguishes itself from another. Depending on the kinds of problems you need to solve, you'll choose one or another language, or possibly combine multiple CLI languages. Learning C++/CLI involves understanding each of these aspects of its design.

CLI 언어의 디자인에는 모든 언어에 공통으로 적용되는 세가지 중요한 관점이있다.
첫째는 CTS (Common Type System)이라 불리는 .Net Framework가 지원하는 공통 타입 체계로 언어 레벨의 문법을 맵핑하는 것이고 둘째는 프로그래머에게 하부 구조인 CLI infrastructure로의 접근 디테일을 얼마만큼 드러낼 것인가이고 셋째는 CLI에 의해 직접적으로 지원되는 것을 넘어서는 추가적인 능력을 선택하는 것이다. 
일반적으로 첫번째 관점은 모든 CLI 언어에 공통적인 요소이다. 둘째와 셋째가 CLI 언어를 구분 짓게 되는 요소들인데 어떤 문제에 직면했느냐에 따라서, 또 어떤 언어를 선택하느냐에 따라서 당신은 여러개의 CLI 언어를 사용할지도 모른다. C++/CLI를 배운다는 것은 이 모든 세가지 디자인 관점을 이해하는 것을 필요로 한다. 즉, 이해력 만렙 찍어야 한다. 
참고로 CTS는 .NET Framework가 가지고 있는 한가지의 요소로써 서로 다른 언어들이 표현하는 같은 형태의 타입들을 모아모아 일관된 형태로 표현하는 일종의 명세(Specification)같은 거라고 생각하면 대략 괜찮다. 사실, 모든 발전된 형태의 언어나 프레임워크는 새로운 것이 아니라 기존의 것을 잘 포장하고 싸놓은 것이다. C++이 추상화와 상속을 통해 객체 지향을 하는 것처럼 .NET Framework도 언어들 간에 추상화와 단일화된 규약을 통해 통합을 시도한 것이라고 보면 된다. 

Mapping C++/CLI to the CTS?
It is important when programming C++/CLI to learn the underlying CTS, which includes these three general class types:
  • The polymorphic reference type, which is what you use for all class inheritance.
  • The non-polymorphic value type, which is used for implementing concrete types requiring runtime efficiency, such as the numeric types.
  • The abstract interface type, which is used for defining a set of operations common to a set of either reference or value types that implement the interface.
CTS는 다음과 같은 세가지 일반적 클래스 타입을 가진다.
- 다형적 레퍼런스 타입: 클래스 상속을 위해 사용되는 것
- 다형적이지 않은 값 타입: 이러한 녀석은 runtime 효율성을 위해서 필요한 concrete type들인데 예를 들자면 숫자 타입들 (int, float)
- 추상적 인터페이스 타입: 이것은 인터페이스를 구현하는 레퍼런스나 값 타입의 공통된 operation들을 정의하기 위해 사용되는 것
말은 참 어렵게 써놓긴 했는데 단순하게 생각하면 C++ 클래스의 상속과 추상 클래스를 떠올리면 거의 맞아 떨어진다. 

와우, 아래의 글을 좀 읽어봤는데 생각보다 내용이 깊다. 물론 CLI와 .NET Framework의 관계를 이해하는데는 엄청난 도움이 될 것이 분명하지만 이 글을 읽게된 이유는 코드 프로젝트 때문이기 때문에 일단 여기서 멈추고 나머지 부분은 차후에 번역해야 겠다.
This design aspect, the mapping of the CTS to a set of built-in language types, is common across all CLI languages although, of course, the syntax varies in each CLI language. So, for example, in C#, you would write
abstract class Shape { ... } // C#
to define an abstract Shape base class from which specific geometric objects are to be derived, while in C++/CLI you write
ref class Shape abstract { ... }; // C++/CLI
to indicate the exact same underlying CLI reference type. The two declarations are represented exactly the same way in the underlying IL. Similarly, in C#, you write
struct Point2D { ... } // C#
to define a concrete Point2D class, while in C++/CLI you write:
value class Point2D { ... }; // C++/CLI
The family of class types supported with C++/CLI represents an integration of the CTS with the native facilities, and that determines your choice of syntax. For example:
class native {};
value class V {};
ref class R {};
interface class I {};
The CTS also supports an enumeration class type that behaves somewhat differently from the native enumeration, and support is provided for both of those as well:
enum native { fail, pass }; 
enum class CLIEnum : char { fail, pass}; 
Similarly, the CTS supports its own array type that again behaves differently from the native array. And again Microsoft provides support for both:
int native[] = { 1,1,2,3,5,8 }; 
array<int>^ managed = { 1,1,2,3,5,8 };
No CLI language is closer to or more nearly a mapping to the underlying CTS than another. Rather, each CLI language represents a view into the underlying CTS object model.

CLI Level of Detail
The second design aspect that must be considered when designing a CLI language is the level of detail of the underlying CLI implementation model to incorporate into the language. What kind of problems will the language be tasked to solve? Does the language have the tools necessary to do this? Also, what sort of programmers is the language likely to attract?
Take, for example, the issue of value types occurring on the managed heap. Value types can find themselves on the managed heap in a number of circumstances:
  • Through implicit boxing, when an instance of a value type is assigned to an Object or when a virtual method is invoked through a value type that is not overridden.
  • When that value type is serving as a member of a reference class type.
  • When that value type is being stored as the element type of a CLI array.
Whether the programmer should be allowed to manipulate the address of a value type of this sort is a CLI language design consideration that must be addressed.

What Are the Issues?
Any object located on the managed heap is subject to relocation during the compaction phase of a sweep of the garbage collector. Any pointers to that object must be tracked and updated by the runtime; the programmer cannot manually track it herself. Therefore, if you were allowed to take the address of a value type that might be on the managed heap, there would need to be a tracking form of pointer in addition to the existing native pointer.
What are the trade-offs to consider? On the one hand, there's simplicity and safety. Directly introducing support in the language for either one or a family of tracking pointers makes it a more complicated language. By not supporting this, the available pool of programmers is expanded because less sophistication is required. In addition, allowing the programmer access to these ephemeral value types increases the possibility of programmer error—she may purposely or inadvertently do dangerous things to the memory. By not supporting tracking pointers, a potentially safer runtime environment is created.
On the other hand, efficiency and flexibility must be considered. Each time you assign the same Object with a value type, a new boxing of the value occurs. Allowing access to the boxed value type allows in-memory update, which may provide significant performance improvements. Without a form of tracking pointer, you cannot iterate over a CLI array using pointer arithmetic. This means that the CLI array cannot participate in the Standard Template Library (STL) iterator pattern and work with the generic algorithms. Allowing access to the boxed value type allows significant design flexibility.
Microsoft chose to provide a collection of addressing modes that handle value types on the managed heap in C++/CLI:
int ival = 1024;
int^ boxedi = ival; 

array<int>^ ia = gcnew array<int>{1,1,2,3,5,8};
interior_ptr<int> begin = &ia[0];

value struct smallInt { int m_ival; ... } si;
pin_ptr<int> ppi = &si.m_ival;
The typical C++/CLI developer is a sophisticated system programmer tasked with providing infrastructure and organizationally critical applications that serve as the foundation over which a business builds its future. She must address both scalability and performance concerns and must therefore have a system-level view into the underlying CLI. The level of detail of a CLI language reflects the face of its programmer.
Complexity is not in itself a negative quality. Human beings are more complicated than single-cell bacteria, and that is certainly not a bad thing. However, when the expression of a simple concept is made complicated, that is usually considered to be a bad thing. In C++/CLI, the CLI team has tried to provide an elegant way to express complex subject matter.

Additional Functionality
A third design aspect is a language-specific layer of functionality above and beyond what is directly supported by the CLI. This may require a mapping between the language-level support and the underlying implementation model of the CLI. In some cases, this just isn't possible because the language cannot intercede with the behavior of the CLI. One example of this is the virtual function resolution in the constructor and destructor of a base class. To reflect ISO-C++ semantics in this case would require a resetting of the virtual table within each base class constructor and destructor. This is not possible because virtual table handling is managed by the runtime and not by the individual language.
So this design aspect is a compromise between what would be preferable to do, and what is feasible. The three primary areas of additional functionality that are provided by C++/CLI are the following:
  • A form of Resource Acquisition is Initialization (RAII) for reference types, in particular, to provide an automated facility for what is referred to as deterministic finalization of garbage- collected types that hold scarce resources.
  • A form of deep-copy semantics associated with the C++ copy constructor and copy assignment operator; however, these semantics could not be extended to value types.
  • Direct support of C++ templates for CTS types in addition to the CLI generic mechanism. In addition, a verifiable version of the STL for CLI types is provided.
Let's look at a brief example: the issue of deterministic finalization. Before the memory associated with an object is reclaimed by the garbage collector, an associated Finalize method, if present, is invoked. You can think of this method as a kind of super-destructor since it is not tied to the program lifetime of the object. This is called finalization. The timing of just when or even whether a Finalize method is invoked is undefined. This is what is meant by nondeterministic finalization of the garbage collector.
Nondeterministic finalization works well with dynamic memory management. When available memory gets sufficiently scarce, the garbage collector kicks in and solves the problem. Nondeterministic finalization does not work well, however, when an object maintains a critical resource such as a database connection, a lock of some sort, or perhaps native heap memory. In this case, it would be great to release the resource as soon as it is no longer needed. The solution that is currently supported by the CLI is for a class to free the resources in its implementation of the Dispose method of the IDisposable interface. The problem here is that Dispose requires an explicit invocation, and therefore is not likely to be invoked.
A fundamental design pattern in C++ is the aforementioned Resource Acquisition is Initialization, which means that a class acquires resources within its constructor. Conversely, a class frees its resources within its destructor. This is managed automatically within the lifetime of the class object.
Here's what reference types should do in terms of the freeing of scarce resources:
  • Use the destructor to encapsulate the necessary code for the freeing of any resources associated with the class.
  • Have the destructor invoked automatically, tied with the lifetime of the class object.
The CLI has no notion of the class destructor for a reference type. So the destructor has to be mapped to something else in the underlying implementation. Internally, then, the compiler performs the following transformations:
  • The class has its base class list extended to inherit from the IDisposable interface.
  • The destructor is transformed into the Dispose method of IDisposable.
That represents half of the goal. A way to automate the invocation of the destructor is still needed. A special stack-based notation for a reference type is supported; that is, one in which its lifetime is associated within the scope of its declaration. Internally, the compiler transforms the notation to allocate the reference object on the managed heap. With the termination of the scope, the compiler inserts an invocation of the Dispose method—the user-defined destructor. Reclamation of the actual memory associated with the object remains under the control of the garbage collector. Figure 1 shows an example.
ref class Wrapper {
    Native *pn;
public:
    // resource acquisition is initialization
    Wrapper( int val ) { pn = new Native( val ); } 

    // this will do our disposition of the native memory
    ~Wrapper(){ delete pn; }

    void mfunc();
protected:

    // an explicit Finalize() method—as a failsafe
    !Wrapper() { delete pn; }
};

void f1() 
{
   // normal treatment of a reference type
   Wrapper^ w1 = gcnew Wrapper( 1024 );

   // mapping a reference type to a lifetime
   Wrapper w2( 2048 ); // no ^ token !

   // just illustrating a semantic difference
   w1->mfunc(); 
   w2.mfunc();

   // w2 is disposed of here
}

// 
// ... later, w1 is finalized at some point, maybe
C++/CLI is not just an extension of C++ into the managed world. Rather, it represents a fully integrated programming paradigm similar in extent to the earlier integration of the multiple inheritance and generic programming paradigms into the language. I think the team has done an outstanding job.

So, What Did You Say About C++/CLI?
C++/CLI represents an integration of native and managed programming. In this iteration, that has been done through a kind of separate but equal community of source-level and binary elements, including Mixed mode (source-level mix of native and CTS types, plus a binary mix of native and CIL object files), Pure mode (source-level mix of native and CTS types, all compiled to CIL object files), Native classes (can hold CTS types through a special wrapper class only), and CTS classes (can hold native types only as pointers). Of course, the C++/CLI programmer can also choose to program in the CLI types alone, and in this way provide verifiable code that can be hosted, for example, as a stored procedure in SQL Server 2005.
So, returning to the question, what is C++/CLI? It is a first-class entry visa into the .NET programming model. With C++/CLI, there is a C++ migration path not just for the C++ source base but for C++ expertise as well. I find great satisfaction in that.

posted by 대갈장군

Machine Learning에 대한 설명이 매우 잘 되어 있는 사이트.


http://openclassroom.stanford.edu/MainFolder/CoursePage.php?course=MachineLearning


다만 영어라는 점...

posted by 대갈장군
2013. 4. 13. 05:34 프로그래밍/MSDN

요즘 작업중인 프로젝트는 C++/CLI를 이용한 몇개의 프로젝트인데 중간 중간 C# 폼이 사용된다. 물론 Managed code와 Unmanaged code (Native code)를 함께 사용하기 위해서는 C++/CLI를 사용해야 한 다는 것 쯤은 나도 알고 있지만 왜 그럴까에 대한 진지한 생각은 해본적이 없다.


Stackoverflow 사이트를 돌아다니다 보니 좋은 글이라고 링크해 놓았길래 한번 번역해본다.


출처 - http://msdn.microsoft.com/en-us/magazine/dd315414.aspx


1. 언제 Managed-Native Interop이 사용되어야 하는가?

뭐 주절주절 써놓았는데 중간에 딱 한 문단이 마음에 확 와닿는다. 


These three applications address the three most common reasons to use interop: to allow managed extensibility of preexisting native applications, to allow most of an application to take advantage of the benefits of managed code while still writing the lowest-level pieces in native code, and to add a differentiated, next-generation user experience to an existing native application.


세가지 예시를 들면서 언제가 Managed 코드와 Native 코드를 섞어서 쓰는게 좋은가에 대해서 설명한 다음에 위와 같이 정의했다. 


"이러한 세가지 어플리케이션은 interop (Managed code와 Native code를 섞어쓰는 것을 말함)를 사용하는 가장 흔한 세가지 이유를 나타낸다: 원래 존재하던 native 어플리케이션에 managed 확장 가능성을 허락해 주는 것이 첫째이고, 둘째는 native 코드의 저레벨 코딩을 계속 쓰면서 동시에 managed code의 이점을 취할 수 있는 것이고, 마지막으로 셋째는 현존하는 native 어플리케이션에 차별화된, 또는 차세대 사용자 경험을 추가할 수 있기 때문이다."


영어를 한국어로 번역하다보면 적절한 한국어 찾기가 뜻을 이해하는 것보다 훨씬 더 어렵다. 젠장. 


2. Interop Technologies: 세가지 경우


There are three main interop technologies available in the .NET Framework, and which one you choose will be determined in part by the type of API you are using for interop and in part by your requirements and need to control the boundary. Platform Invoke, or P/Invoke, is primarily a managed-to-native interop technology that allows you to call C-style native APIs from managed code. COM interop is a technology that allows you either to consume native COM interfaces from managed code or export native COM interfaces from managed APIs. Finally there is C++/CLI (formerly known as managed C++), which allows you to create assemblies that contain a mix of managed and native C++ compiled code and is designed to serve as a bridge between managed and native code.


.NET 프레임워크에서는 세가지의 interop 기술들이 사용가능한데 다음과 같다.

첫째는 Platform Invoke 혹은 P/Invoke라고 불리는 기술인데 이것은 주로 managed code에서 C-style의 native APIs (C 함수들)를 불러다 쓰기 위한 용도다. 

둘째는 COM interop인데 이것은 native COM 인터페이스를 managed code로 부터 사용하거나 혹은 native COM 인터페이스를 managed APIs(함수들)로 부터 Export 하기 위해서 쓴다. 마지막으로 대망의 C++/CLI가 있는데 (과거에는 managed C++로 명명되었죠) 이것은 manged code와 native C++ code가 공존하는 형태로써 managed와 native 코드 간의 '다리'역활을 위한 용도로 디자인 되었다.


시간만 많으면 세가지 용도 모두에 대해서 쓰고 싶지만 일단 중요한게 C++/CLI이니까 그것만 자세히 살펴보겠다.


3. Interop Technologies: C++/CLI


C++/CLI is designed to be a bridge between the native and managed world, and it allows you to compile both managed and native C++ into the same assembly (even the same class) and make standard C++ calls between the two portions of the assembly. When you use C++/CLI, you choose which portion of the assembly you want to be managed and which you want to be native. The resulting assembly is a mix of MSIL (Microsoft intermediate language, found in all managed assemblies) and native assembly code. C++/CLI is a very powerful interop technology that gives you almost complete control over the interop boundary. The downside is that it forces you to take almost complete control over the boundary.
C++/CLI can be a good bridge if static-type checking is needed, if strict performance is a requirement, and if you need more predictable finalization. If P/Invoke or COM interop meets your needs, they are generally simpler to use, especially if your developers are not familiar with C++.
There are a few things to keep in mind when considering C++/CLI. The first thing to remember is that if you are planning to use C++/CLI to provide a faster version of COM interop, COM interop is slower than C++/CLI because it does a lot of work on your behalf. If you only loosely use COM in your application and don't require full fidelity COM interop, then this is a good trade-off.
If, however, you use a large portion of the COM spec, you'll likely find that once you add back the pieces of COM semantics you need into your C++/CLI solution, you'll have done a lot of work and will have performance no better than what is provided with COM interop. Several Microsoft teams have gone down this road only to realize this and move back to COM interop.
The second major consideration for using C++/CLI is to remember that this is only intended to be a bridge between the managed and native worlds and not intended to be a technology you use to write the bulk of your application. It is certainly possible to do so, but you'll find that developer productivity is much lower than in a pure C++ or pure C#/Visual Basic environment and that your application runs much slower to boot. So when you use C++/CLI, compile only the files you need with the /clr switch, and use a combination of pure managed or pure native assemblies to build the core functionality of your application.

C++/CLI는 원래 native와 managed 세계를 이어주는 브릿지 (다리) 역활 용으로 디자인 되었다. 그리고 이것은 managed 와 native C++ code 둘 다 동시에 하나의 공통 어셈블리 (심지어는 같은 클래스로) 컴파일 될 수 있게 해주었고 standard C++ 호출을 어셈블리의 두 영역간에 할 수 있게 해주었다. C++/CLI를 사용할때 당신은 어셈블리의 어떤 부분이 managed인지 혹은 native인지 결정할 수 있다. 이로써 결과물로 나오는 어셈블리는 MSIL (Microsoft intermediate language)와 native assembly 코드의 짬뽕이 된다. (MSIL은 managed code를 위해 사용되는 .NET Framework의 기반 언어 기술이다.) C++/CLI는 매우 강력한 interop 기술로써 당신에 거의 완벽에 가까운 interop 경계 컨트롤을 제공한다. (내 맘대로 왔다리 갔다리 할수 있다는 말). 문제는 모든 컨트롤을 제공하므로 일일이 내가 알아서 다 컨트롤 해야 한다.... 젠장 장점이 단점이네?ㅋㅋ


C++/CLI는 만약 어느 정도의 퍼포먼스가 요구되고 보다 예측 가능한 결과가 필요할 때, 그리고 static-type 체킹이 필요한 경우 좋은 다리 역활을 한다. 만약 P/Invoke나 COM interop이 내가 요구하는 것을 충족한다면 C++/CLI보다는 P/Invoke나 COM이 더 사용하기 쉽다. 특히 C++을 잘 모른다면... 더더욱.


C++/CLI을 사용할 때 명심해야 할게 몇가지 있다. 첫째는 C++/CLI가 COM interop보다는 빠르다는 점이다. (왜 같은 의미의 문장을 두번 이어서 썼는지 모르겠지만 암튼 C++/CLI가 COM보다 빠르다는 이야기) 왜냐면 COM은 사용자를 대신해서 많은 업무를 수행하기 때문이다. 만약 COM을 아주 약하게 사용한다면 (쬐끔만), 그러면 COM 쓰는 것도 괜찮다.


하지만 만약 COM의 많은 부분을 가져다 쓰게 되면 C++/CLI interop은 사실상 무용지물이 된다. 속도를 위해서 C++/CLI를 선택했으나 COM이 전체적인 퍼포먼스를 낮추기 때문에 애시당초 걍 COM interop으로 가는게 더 좋다는 말이네. 


C++/CLI를 사용할때 두번째 주의할 것은 이 interop은 다리 역활로 만들어진 것이지 프로그램을 그렇게 작성하시오라고 만든 것이 아니라는 점이다. 뭐 이건 당연한 이야기다. C++로 다 짤수 있는 프로그램을 굳이 C++/CLI로 짜서 다른 managed code랑 썪어 버릴 필요가 없다는 말이다. 이 점이 사실 뼈아프게 들리는 이유는 C++로 작성된 거대한 프로그램에서 몇가지 추가하고 싶은 기능이 있는데 그것이 C#과 같은 managed code로 작성하는 것이 내 입장에서는 더 편하게 느껴질때가 종종 있다. 이때 좀 갈등을 하는데 사실 그냥 좀 불편하더라도 Windows API를 이용하는 것이 C#을 이용하는 것보다 나은것 같다.


C#의 문제점은 폼안에 존재하는 각각의 컨트롤이 폼이 소유하고 있는 스레드를 제외하고는 스레드에 안전하지 않다는 점이다. 즉, C#은 설계 당시부터 다른 언어 (특히 C++)와의 섞어 쓰기에 대해 큰 고려를 안했던 것 같다. 물론 돌아가는 방법을 제공하기는 하지만 좀 완벽하지 못하다.


오늘은 여기까지만 쓰고 다음에 나머지 부분에 대해서 마무리하도록 해야겠다.

posted by 대갈장군
prev 1 next