Pull to refresh

Кратко и быстро разбираемся с C++ CLI

Reading time7 min
Views110K
Так сложилось, что по мере рабочей необходимости мне приходится интенсивно использовать C++/CLI и, соответственно, довольно часто объяснять новичкам, что это, как работает, как использовать и, зачем вообще надо. Так что со временем появилось желание написать статью с обзором языка, ответами на некоторые распространенные вопросы и показать места, где могут удачно лечь грабли.

Что это?



Когда Microsoft создавала платформу .Net, то она решила дать программистам писать под нее на нескольких уже существующих языках, в синтаксис которых были внесены некоторые изменения — VB, C++. Речь пойдет именно о последнем. Точнее, если мне не изменяет память, в первой редакции язык назывался C++ with managed extensions. Название как бы само намекает на сущность — вот мы вам дали плюсы с расширениями и теперь вы сможете разрабатывать под .Net на уже известных C++, при этом оставив всю мощь исходного языка. Собственно, первая версия расширения синтаксиса была ужасна чуть более чем полностью, и делала код похожим на попытку закодировать и отправить в космос диалог Жириновского и Черномырдина:
//объявляем управляемый класс
public __gc class Class1
{
public:
  // метод, принимающий int по ссылке и управляемый массив
  int Method1(__gc int &refValue, __gc int[] managedArr);
};

* This source code was highlighted with Source Code Highlighter.

К тому же, в этом синтаксисе не было отличий между указателем на нативный тип и на управляемый (в обоих случаях использовалась «*»), не было ключевого слова для обозначения нулевого указателя и прочее. Это и толкнуло Microsoft на создание новой ревизии языка, о которой и пойдет речь в данной статье.
Замечание: эти две версии синтаксиса называются, как ни странно, «old syntax» и «new syntax», и какую именно использовать можно выбирать в настройках компиляции проекта. Впрочем, при создании новых сборок лучше использовать новый синтаксис, так как старый помечен как устаревший и просто плох.

Зачем нужно?



1) С помощью данного языка сферический программист в вакууме сможет разрабатывать полноценное .Net-приложение на любимых плюсах. Честно говоря, мне сложно представить этого извращенца, да и Microsoft такому подходу явно не способствует хотя бы тем, что не делает под C++ дизайнера визуальных компонент. Собственно, правильно делает, ибо для таких целей есть более выразительные языки, тот же C#. Так что это возможность скорее теоретическая.

2) Можно вызывать уже написанный на плюсах код. Действительно, поскольку у нас остались все возможности обычного C++, то можно создавать управляемые обертки для существующих классов на нативных плюсах. Это дает намного большие возможности по вызову неуправляемого кода, нежели PInvoke, который с классами работать не умеет.

3) Можно писать на C++ CLI модули, где производительность критична. Действительно, среди всего зоопарка языков под .Net С++ уникален тем, что на нем можно писать код, который напрямую использует выделение и освобождение памяти, работу с указателями. Например, так:
//управляемый класс, который может потом использоваться любой .Net–сборкой
public ref Class1
{
public:
  //этот метод так же можно использовать во всех .Net-сборках
  void Method1();
  {
    BYTE *buff = new BYTE[100];

    //do smth

    delete[] buff;
  }
};


* This source code was highlighted with Source Code Highlighter.


Как работает?



Все очень просто. При компиляции кода на С++/СLI получается сборка, содержащая как MSIL код, так и машинные команды, в которые превратились строки, написанные на «чистых» плюсах. «Но как же быть с кроссплатформеностью?» — спросите вы, и будете совершенно правы. Никак. В частности, это означает, что не получится собрать один и тот же бинарник для 32 и 64 битных версий (собрать все под «Any CPU»). Такова расплата за использование всех возможностей С++. Естественно, это относится к тому варианта, когда используется микс из управляемого и неуправляемого кода. Всего есть несколько вариантов компиляции:
• /clr — поддержка управляемого и нативного кода с использованием нового синтаксиса.
• /сlr:pure — нативный код не поддерживается. Однако при этом можно использовать небезопасный ) код, в той мере, как это можно делать, к примеру, в С#-сборках при использовании директивы unsafe.
• /clr:safe — Только управляемый безопасный код. Аналог — компиляция C#-сборки без использования unsafe.
• /clr:oldSyntax — аналог /clr, только используется старый синтаксис.

Как выглядит?



Вот примеры сравнения основных конструкций для С# и C++/CLI.

Объявление класса

C#:
public sealed class Class1 : Class2

* This source code was highlighted with Source Code Highlighter.

C++/CLI:
public ref class Class1 sealed : Class2

* This source code was highlighted with Source Code Highlighter.


Объявление структуры

C#:
public struct Class1 : IEquatable

* This source code was highlighted with Source Code Highlighter.

C++/CLI:
public value class Class1 : IEquatable

* This source code was highlighted with Source Code Highlighter.


Объявление интерфейса

C#:
public interface ISomeInterface

* This source code was highlighted with Source Code Highlighter.

C++/CLI:
public interface class ISomeInterface

* This source code was highlighted with Source Code Highlighter.


Объявление перечисления

C#:
public enum Enum1
    {
      Val1,
      Val2
    }

* This source code was highlighted with Source Code Highlighter.

C++/CLI:
public enum class Enum1
    {
      Val1,
      Val2
    }

* This source code was highlighted with Source Code Highlighter.


Создание управляемого объекта

C#:
object obj = new object();

* This source code was highlighted with Source Code Highlighter.

C++/CLI:
Object ^obj = gcnew Object();

* This source code was highlighted with Source Code Highlighter.

В C++/CLI для обозначения ссылок на управляемые объекты используется «^» вместо «*». Это очень удобно чтобы различать объекты, которые потом надо удалить и те, которые не надо. Также при создании локального ссылочного объекта можно использовать семантику стека:
Object obj();
Это имеет смысл либо при использовании объектов, реализующих IDisposable (речь об этом пойдет позже) либо для value-типов. Заметим, что в плане хранения и использования value-типов С++/CLI дает большую свободу, чем C#, поскольку программист может сам выбирать — использовать ссылку или значение. Таким образом вполне можно в некоторых ситуация сэкономить на количестве boxing/unboxing операций.

Создание управляемого массива

C#:
object[] arr = new object[100];

* This source code was highlighted with Source Code Highlighter.

C++/CLI:
array<Object ^> ^arr = gcnew array<Object ^>();

* This source code was highlighted with Source Code Highlighter.

Неуправляемые массивы при этом создаются как обычно.

Передача параметров в метод

C#:
void Method(int byValue, ref int byRef, out int outValue);

* This source code was highlighted with Source Code Highlighter.

C++/CLI:
void Method(int byValue, int %byRef, [out] int %outValue);

* This source code was highlighted with Source Code Highlighter.

Как видно из этого примера, если «^» — это аналог «*» из обычного C++, то «%» — это аналог «&». Причем аналогия весьма точная и прослеживается не только при передаче параметров, но и при получении ссылки например:
void Method(ValueType val)
{
  ValueType ^ref = %val;
}


* This source code was highlighted with Source Code Highlighter.


Переопределение метода

C#:
override void Method();
* This source code was highlighted with Source Code Highlighter.

C++/CLI
virtual void Method() override;

* This source code was highlighted with Source Code Highlighter.


Реализация шаблона IDisposable

С#:
class Class1 : Disposable
{
  public void Dispose()
  {
    this.Dispose(true);

    GC.SuppressFinalize(this);
  }

  protected virtual void Dispose(bool disposing)
  {
    if (disposing)
    {
      //release managed resources
    }
    
    //release unmanaged resources
  }

  ~Class1()
  {
    this.Dispose(false);
  }  
}

* This source code was highlighted with Source Code Highlighter.

C++/CLI:
ref class Class1
{
public:
  //Эквивалент Dispose
  ~Class1()
  {
    //release managed resources

    //call finalizer
    this->!Class1();
  }

  //Аналог финализатора
  !Class1()
  {
    //release unmanaged resources
  }
}


* This source code was highlighted with Source Code Highlighter.

В C++/CLI часть шаблона Disposable за нас реализует компилятор, и это с виду отличается от типичной реализация интерфейса. Более того, все тот же компилятор не позволит реализовать IDisposable напрямую. Впрочем, когда к этому привыкаешь, то понимаешь, что такое поведение довольно логично — избавляет от необходимости писать кучу повторяющегося кода. Так же в своем стремлении сделать освобождение ресурсов похожим на обычный С++ создатели языка пошли еще дальше и явный вызов Dispose можно сделать двумя способами:
obj->~Class1();

* This source code was highlighted with Source Code Highlighter.

и
delete obj;
* This source code was highlighted with Source Code Highlighter.

Так же можно использовать семантику стека для гарантированной очистки ресурсов:
C++/CLI:
{
  A();  

  Class1 obj;
  
  B();
}

* This source code was highlighted with Source Code Highlighter.
соответствует
C#:
{
  A();

  using (Class1 obj = new Class1())
  {
    B();
  }
}

* This source code was highlighted with Source Code Highlighter.


Что осталось за кадром?



Понятно, что в одну статью все поместить не удалось. Не рассмотренными остались такие вопросы как:
• Синтаксис делегатов, свойств, методов расширения, foreach и прочее
• Жонглирование из управляемого в неуправляемый и обратно объектами, массивами и прочим
• Что поддерживается и нет из того, что есть в С# и в обычном C++
• Особености компиляции приложений со сборками С++/CLI
• Вопросы производительности. Когда и в чем можно получить выигрыш? Где можно внезапно потерять?

Что почитать?



1. Сравнение синтаксиса С# и C++: www.cyberforum.ru/cpp-cli/thread83662.html
2. Основные специфичные конструкции языка: msdn.microsoft.com/en-us/library/xey702bw.aspx
3. Миграция на C++/CLI: msdn.microsoft.com/en-us/library/ms235289.aspx

Заключение



Среди всех остальных языков под .Net C++/CLI является довольно специфичным. Вряд ли на нем имеет смысл разрабатывать стандартное .Net приложение, зато в качестве коммуникации с существующим C++ кодом или для задач оптимизации – самое оно.
Tags:
Hubs:
+43
Comments16

Articles

Change theme settings