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

Так сложилось, что по мере рабочей необходимости мне приходится интенсивно использовать 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++ кодом или для задач оптимизации – самое оно.
Поделиться публикацией

Похожие публикации

Комментарии 16
    +4
    Согласно статьи, C++/CLI просто так не познать, сначала нужно познать C#, правда тем, кто владеет C#, вряд ли захочется возиться с C++/CLI :)
      +2
      В принципе, чтобы изучить совсем просто — лучше да, знать один из языков .Net-стека. А насчет того, чтобы возиться — если нужна производительность или доступ к плюсовому коду, то уж придется разбираться.
      Вообще язык очень специфический, но свои задачи решает очень красиво. Я вообще плюсами серьезно начал из-за него заниматься:)
        0
        Чем вас не устроил C# + инъекции нативного кода?
          +2
          Они меня вполне устраивают сами по себе. Просто их порой недостаточно для написания «бутылочных горлышек»
      +1
      Неплохо, но статья полезна для симпл ознакомления, до шага «разобрался» еще нужно дойти.
        0
        Если народу будет интересно и хватит кармы — то в следующей статье буду раскрывать тонкости, которые остались за кадром.
        В целом, изучение С++ CLI сводится именно к пониманию синтаксиса и этих тонкостей, если знать как устроен классический С++ и дотнет, ибо больше там и нет ничего.
        +1
        Плюсанул за проделанный труд, но с идеологическими выкладками бы поспорил. Думаю, статье они могут только навредить (Хотя, могу ошибаться).
          0
          Это не идеология, скорее попытка аргументированного разъяснения для чего эта штуковина полезна, для чего — нет. Может не совсем удачно получилось, все-таки первый опыт написания статьи.
          +3
          Честно, всегда думал, что CLI == Command Line Interface
            0
            Может в качестве статьи с номером 0 стоило бы в картинках описать, как создать хелло ворлд на C++/CLI?
              +3
              Хм, резоное замечание, только хеллоуворлд ничем не интересен. Возможно, имеет смысл привести реальный пример простенького приложения, которое показывает преимущества языка, благо такое есть под рукой.
                0
                Было бы просто замечательно :)
                  0
                  Да, так было бы даже лучше.
                0
                >> .Net С++ уникален тем, что на нем можно писать код, который напрямую использует выделение и освобождение памяти, работу с указателями.

                C# тоже позволяет это делать, если разрешить режим unsafe.
                И судя по моему опыту модули, написанные на C++/CLI порой медленнее, чем те же самые модули, написанные на чистом C#. Возможно, конечно, преимущества С++ будут видны на кастомных аллокаторах, но что то я не уверен. Так что единственное, где правда нужен CLI — это использование старого С++ кода в новых проектах.
                  0
                  C# тоже позволяет это делать, если разрешить режим unsafe.

                  Но при этом все выделение и освобождение памяти все равно отдано на откуп GC. А если будет много объектов, к которым будет доступ по указателям — то это сделает невозможной дефрагментацию кучи.

                  И судя по моему опыту модули, написанные на C++/CLI порой медленнее, чем те же самые модули, написанные на чистом C#

                  Знаете, на ассемблере можно написать так, что будет работать медленнее, чем на пхп… Тут вопрос в уровне познаний того, кто писал. В общем случае C++ CLI предоставляет больший простор для написания оптимального кода, важно им правильно распорядиться.
                  0
                  Спасибо, хороший гайд. Жаль у меня его не было, когда я подключал два года назад плюсовую библиотеку к проекту на шарпе :)

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое