Исходники
Статьи
Языки программирования
.NET Delphi Visual C++ Borland C++ Builder C/С++ и C# Базы Данных MySQL MSSQL Oracle PostgreSQL Interbase VisualFoxPro Веб-Мастеру PHP HTML Perl Java JavaScript Протоколы AJAX Технология Ajax Освоение Ajax Сети Беспроводные сети Локальные сети Сети хранения данных TCP/IP xDSL ATM Операционные системы Windows Linux Wap Книги и учебники
Скрипты
Магазин программиста
|
Управляемый C++Не правда ли, название “Управляемый C++” вряд ли можно назвать логичным? Как можно сделать управляемым язык программирования, одно из основных достоинств которого – свободное манипулирование таким ресурсом программы, как адресное пространство? Тем не менее, от такой компании, как Microsoft, можно ожидать чего угодно. Поэтому не очень удивительно, что вместе с новой платформой .NET она предлагает нам и новое расширение языка C++ - Managed Extensions for C++, которое иногда именуют просто Managed C++ или даже MC++. Что касается последнего сокращения, то оставим его на растерзание другим ярым “поклонникам” этой компании. Итак, “Управляемый C++”. Давайте попробуем вместе прояснить ситуацию и разобраться, насколько он управляем, что под этим понимается и в чём необходимость его появления. Прежде всего, рассмотрим важные для нас особенности самой .NET Framework. Попытки создания всеобъемлющих сред разработки и исполнения программ делаются уже давно, возьмём тот же Smalltalk, Lisp или, наиболее яркого представителя последнего времени, Java. Обычно такие среды именуются виртуальными машинами (Virtual Machine, VM), но Microsoft выбрала другое название - Common Language Runtime (CLR), что можно перевести как “одна на всех среда исполнения”. Это и отличает CLR от обычных VM – отсутствие привязки к одному конкретному языку программирования. И хотя иногда, говоря C#, подразумевают .NET и наоборот, C# - это всего лишь один из длинной линейки языков, поддерживаемых CLR. Естественно, в этом ряду не мог не появится и C++. Среди основных задач подобных сред исполнения программ можно отметить следующие:
В свою очередь, C++ разрабатывался как универсальный язык, в том числе и для решения задач реального времени, низкоуровневого программирования и написания драйверов аппаратных устройств, в связи с чем применение в нём автоматической сборки мусора, неявной инициализации переменных и проверки допустимости значений аргументов является неоправданными из-за потери производительности. Что касается контроля типов, то вот что об этом говорит создатель C++ Бьерн Страуструп: “Механизм контроля доступа в C++ обеспечивает защиту от случайности, а не от преднамеренного обмана. Любой язык программирования, который поддерживает прямой доступ к памяти, обязательно оставляет любой элемент данных открытым для сознательного “жульничества”, нарушающего явные правила типов, сформулированные для этого элемента” Прямая работа с операционной системой и различными API, а также “мирное сосуществование” с другими языками являются одной из основных причин успеха C++. Таким образом, налицо явные противоречия между концепциями управляемой среды и такого языка программирования, как C++. Как же программистам из Редмонда удалось разрешить подобный конфликт и подружить CLR с “неуправляемым” C++? К счастью, C++ остался самим собой. Мы по-прежнему можем не инициализировать указатели, выходить за границы массивов, забывать освобождать выделенную память, и никакие CLR не могут нам в этом помешать. С другой стороны, C++ должен уметь работать с объектами CLR и позволять этим объектам работать со своим кодом, т.е. играть по правилам той среды, в которой он используется. Для решения этих задач разработчики из Microsoft пошли по пути расширения возможностей языка, добавления в него ряда ключевых слов, директив и опций компилятора. Как раз это расширение и получило название Managed Extensions for C++. Managed Extensions for C++ позволяет C++ программам использовать следующие объекты CLR:
Управляемые типы, массивы и указателиКак было отмечено выше, CLR поддерживает автоматическую сборку мусора и обеспечивает безопасную передачу данных между различными частями системы. Для обеспечения этих возможностей среда CLR должна владеть полной информацией о типах данных. Теперь мы имеем возможность создавать и использовать такие типы наряду с обычными типами C++, не прибегая к ухищрениям наподобие библиотек типов для COM-объектов. Объявление управляемого типа в MC++ производится с помощью ключевых слов __gc или __value. __gcИдентификатор __gc применяется для объявления сложных типов, массивов и указателей, размещаемых в куче среды исполнения CLR. Сокращение gc, скорее всего, происходит от garbage collection. Рассмотрим пример:
Ключевое слово __gc перед объявлением класса говорит компилятору, что наш класс является управляемым и подчиняется всем правилам среды CLR. В частности, нам не нужно вызывать деструктор для удаления объекта из памяти, эту работу за нас сделает CLR. Более того, если мы всё же вызовем оператор delete, то компилятор выдаст сообщение об ошибке, говорящее о том, что в нашем классе не определён деструктор. Определение деструктора может быть добавлено, и вызов оператора delete приведёт к его немедленному вызову, но, тем не менее, память, занятая объектом, освобождена не будет. Если же оператор delete не вызывается, то деструктор будет вызван CLR по своему усмотрению во время сборки мусора. Такое поведение деструкторов управляемых классов обусловлено тем, что компилятор фактически переименовывает их в метод Finalize, являющийся стандартным для среды CLR и вызываемый ей непосредственно перед удалением объекта из памяти. Здесь будет уместно заметить, что все управляемые типы CLR происходят от класса System::Object, который и содержит виртуальный метод Finalize. Компилятор добавляет это наследование автоматически, хотя вполне допустимо делать это явно. Ещё одной особенностью приведённого выше кода является то, что мы можем создать экземпляр класса только в управляемой куче, например, следующий код является некорректным и приведёт к ошибке компиляции:
__gc arraysТак же, как и для объектов управляемых типов, память для управляемых массивов выделяется в куче CLR. Массивы могут содержать переменное число элементов и всегда инициализируются при создании. Для объявления управляемых массивов используется специальный синтаксис:
Создать массив объектов Foo можно следующим образом
Как вы можете заметить, в примере с объектом Foo мы не использовали ключевого слова __gc. В этом нет необходимости, так как компилятор уже знает, что имеет дело с управляемым типом. Для возвращения массивов из функций и для объявления многомерных массивов в MC++ также применяется новый причудливый синтаксис:
Из последнего примера явно следует, что управляемые массивы не являются привычными для нас массивами C++. Скорее, это объекты со своим интерфейсом, и, к сожалению, мы не можем применять для них обычные в C++ методы работы с массивами. Это легко проверить на следующем примере:
Компиляция следующего кода выдаст ошибки в строчках с инициализацией указателей p1 и p2. В принципе, даже если управляемые массивы и являются объектами, то на C++ можно довольно просто сделать перегрузку соответствующих операторов и эмулировать работу с массивами C++. Но разработчики из Microsoft не пошли на этот вполне очевидный шаг, и на то были достаточно веские основания. Дело в том, что управляемая куча – довольно сложная система, и среди прочих возможностей она обеспечивает дефрагментацию памяти после сборки мусора. Это значит, что любой управляемый объект может быть перемещён в памяти в любое время. Уже сам по себе этот факт не позволяет использовать обычные C++ указатели с управляемыми объектами CLR и проясняет многие ограничения управляемой среды. __pinТем не менее, было бы странным не иметь доступа к памяти управляемых объектов. И такая возможность есть. Managed Extensions for C++ включает ещё одно ключевое слово - __pin, позволяющее объявлять pinning pointers на управляемые объекты. Один из переводов этого термина мог бы звучать, как “прикольные указатели”, но боюсь, что это может быть неправильно понято. Поэтому мы будем пользоваться оригиналом или, в крайнем случае, транслитерацией. Pin-указатели позволяют зафиксировать объект в памяти и сделать его неперемещаемым до тех пор, пока такой указатель существует как объект и его значение не равно нулю. Рассмотрим пример:
Здесь мы создаём управляемый массив, объявляем pin-указатель и работаем с ним уже “по всем правилам” C++. В частности, этот код заставил надолго задуматься мою тестовую программу, после чего её пришлось закрывать насильственным путём. Это наглядно демонстрирует необходимость аккуратной работы с управляемой памятью (впрочем, и неуправляемой тоже), т.к. даже управляемый C++ код, получив доступ к управляемой памяти таким способом, уже перестаёт быть контролируемым и может легко причинить вред всей системе. __nogcКак нетрудно догадаться, это ключевое слово обозначает обычный неуправляемый тип C++. Этот модификатор используется компилятором по умолчанию и приведён здесь лишь для полноты картины. __valueСогласитесь, размещать абсолютно все переменные в управляемой куче расточительно, особенно если это просто байт или целое, использующиеся в качестве счётчика. Как известно, размещение переменных в стеке является наиболее эффективным для простых переменных с коротким жизненным циклом. Идентификатор __value позволяет объявлять управляемые типы, которые, в отличие от gc-типов, могут размещаться как в управляемой куче, так и в стеке программы. В таблице 1 приведено соответствие между примитивными типами C++ и управляемыми типами CLR. Таблица 1.
С помощью модификатора __value можно объявлять как классы и структуры, так и управляемые перечислимые типы. Более того, это единственный способ объявлять перечисления, которые будет понимать CLR. Например:
Всё правильно, последняя строчка не содержит ошибки. CLR поддерживает типизированные перечисления, поэтому и в MC++ вполне допустимо задавать для них тип. Ещё одним важным отличием value-типов является то, что они не происходят от общего для CLR типов класса System::Object. Это затрудняет их использование с CLR-коллекциями и в многочисленных методах, принимающих в качестве параметра System::Object. Для разрешения использования value-типов как gc-классов в .NET используется так называемый boxing. __boxКлючевое слово __box создаёт обёртку для value-типов, после чего их можно использовать так же, как и gc-классы. Такие языки, как C# и VB.NET, создают обёртки для value-типов автоматически, в MC++ неявное преобразование запрещено из соображений производительности.
Все box-value-типы, кроме перечислений, являются производными от System::ValueType, который, в свою очередь является наследником System::Object. Базовый класс для перечислений – System::Enum. __gc pointersЕсли существуют управляемые объекты, то должны существовать и управляемые указатели на такие объекты. Более того, мы уже не раз их использовали в наших примерах. Мы выяснили также, что природа управляемых и обычных C++ объектов различна, то же самое справедливо и для указателей. По аналогии с gc-массивами мы можем смело констатировать, что управляемые указатели являются самостоятельными объектами и имеют лишь внешнее сходство с регулярными указателями C++. В частности, обычный для C++ способ преобразования указателей через void* заменён для gc-типов преобразованием к System::Object*, а для value-типов к System::Void*. Среди ограничений можно отметить то, что к управляемым указателям не может быть применена адресная арифметика (вместо этого следует использовать управляемые массивы) и, как мы уже выяснили, управляемые указатели могут быть преобразованы к обычным C++ указателям только через pinning pointers. Для преобразования одного типа управляемых указателей к другому можно использовать принятую в C++ семантику оператора dynamic_cast. В дополнение к этому MC++ определяет ещё один оператор __try_cast, основное отличие которого заключается в том, что в случае неуспеха этот оператор возбуждает исключение System::InvalidCastException. Применение операторов static_cast и reinterpret_cast также допустимо, но пользоваться ими стоит только в исключительных случаях, когда вы абсолютно уверены в том, что вы делаете. Оператор const_cast поддерживается без особых ограничений. ИнтерфейсыНеобходимость появления интерфейсов в .NET вызвана, в том числе и соображениями совместимости с технологиями COM. И если CLR-объекты могут работать с Win32-кодом посредством импорта DLL, то Win32-программы имеют возможность взаимодействовать с объектами .NET только через механизм COM-интерфейсов. Кстати, эта возможность даёт нам альтернативный способ разработки COM-компонентов, который к тому же является более лёгким и приятным занятием, чем использование MFC или ATL. Как известно, CLR не поддерживает множественного наследования классов. Возможно, это и правильно. Во-первых, не все языки его реализуют, а, во-вторых, механизм виртуальных базовых классов, обычно использующийся для разрешения конфликтов при множественном наследовании, значительно усложняет структуру таблицы виртуальных методов класса и делает вызовы виртуальных функций крайне неэффективными. С другой стороны, “облегчённый вариант” множественного наследования не порождает таких проблем и широко используется при разработке COM-компонентов вообще и с использованием библиотеки ATL в частности. В отличие от наследования классов CLR разрешает множественное наследование интерфейсов, но это наследование и сами интерфейсы имеют ряд важных ограничений.
Цель этих ограничений – избежать классических конфликтов при множественном наследовании, включая неоднозначность доступа к данным наследуемых объектов, и свести таблицу виртуальных методов к простому линейному массиву указателей на виртуальные функции, реализация которых будет осуществлена в наследуемых классах. Ко всему прочему MC++ разрешает двум или более наследуемым gc-интерфейсам иметь методы с идентичными именами и параметрами. Для того чтобы избежать неоднозначности при реализации этих методов можно использовать следующий синтаксис:
Кроме того, MC++ поддерживает для интерфейсов реализацию по умолчанию (default implementations):
СтрокиСтроки в CLR представлены классом System::String и ничем особенным не выделяются среди других объектов, за исключением принятого в MC++ нового префикса для объявления строковых констант – “S”. Этот префикс обозначает управляемую строковую константу, имеющую тип System::String*, и введён для повышения производительности. Следующий код справедлив для всех трёх объявляемых строк.
Доступ к управляемым строкам как к обычным неуправляемым символам может быть осуществлён следующим образом:
Делегаты и событияУпрощённо делегаты можно рассматривать как узаконенные указатели на функции. Но всё же они, как и всё в CLR, являются объектами и обладают своей дополнительной функциональностью. Объявление делегатов производится с помощью ключевого слова __delegate. С помощью делегатов могут быть вызваны любые методы управляемых классов, как обычные, так и статические. Это принципиально отличает делегаты от указателей на функции, так как делегат хранит не только указатель на функцию, но и информацию о конкретном объекте, у которого эта функция должна быть вызвана. Единственное условие – прототип метода должен совпадать с типом делегата.
Как следует из примера, один делегат может использоваться для обслуживания нескольких функций, т.е. делегат это не просто указатель, а список указателей. Ещё один момент – для TestDelegate2 мы опустили указание объекта, это допустимо, поскольку этот метод является статическим. Кроме того, с помощью делегатов можно вызывать даже функции Windows API, если они соответствующим образом объявлены:
Наиболее логичным применением делегатов является обработка событий, и, надо отдать должное редмондчанам, в этом вопросе они потрудились на славу. Теперь организовать генерацию событий и их обработку так же просто, как, например, переслать два байта. В дополнение к делегатам в CLR введена модель публикации/подписки на события (events). События объявляются с помощью ключевого слова __event.
Вспоминая COM трудно поверить, что это всё, что нужно для реализации механизма рассылки событий. Но не так просто обстоят дела для компилятора, фактически он генерирует примерно следующий код:
Здесь мы видим, что компилятор создаёт уже знакомый нам делегат и генерирует несколько методов, управляющих подпиской и генерацией событий. Чтобы покончить с делегатами, приведём пример, в котором участвуют источник событий и их получатель:
Для подписки на события используется оператор “+=”, для её отмены “-=”. СвойстваСвойства уже давно стали привычной вещью даже в C++, стандарт которого их всё ещё не поддерживает. Производители компиляторов на свой лад расширяют синтаксис языка, добавляя в него поддержку свойств. В таких же языках как Visual Basic и ObjectPascal, которые не слишком связаны стандартами, свойства применяются повсеместно. Технология COM, а точнее интерфейс IDispatch, также поддерживает свойства, которые с успехом используются даже скриптовыми языками. Никуда они не делись и в .NET. Для объявления свойств в MC++ служит ключевое слово __property.
Фактически в этом примере компилятор создаёт псевдопеременную X. Префиксы get_ и set_ являются обязательными в соответствии с соглашениями об именовании в .NET Developer Platform, но при желании мы можем использовать только один из них. Объявление свойства, которое мы рассмотрели, является скалярным объявлением и подчиняется следующим правилам:
Кроме того, в CLR допустимо объявление индексируемых свойств, для которых справедливо следующее:
МетаданныеМетаданные – это одно из фундаментальных понятий, на которых базируется платформа .NET. Обсудить все детали столь обширной темы в статье об MC++ просто нет никакой возможности, поэтому мы будем отталкиваться от следующего упрощения – метаданные представляют собой описания используемых в программе типов и методов в стандартном двоичном формате, хранящиеся в одном модуле вместе с кодом программы (сборке). Отдалённо это напоминает библиотеки типов из COM, но в отличие от них метаданные знают об используемых в вашей программе типах абсолютно всё. Буквально каждый ваш чих незамедлительно регистрируется в базе метаданных, будь то маленькая и скромная вспомогательная private-переменная или большой и важный public-метод. Компилятор генерирует информацию об управляемых типах автоматически, основываясь на их определении, что позволяет создавать самодостаточные в плане описания типы. Благодаря этому совершенно не важно, на каком языке программирования написан класс, от которого вы собираетесь наследоваться, и вас совершенно не должно волновать, из каких языков будет использоваться ваш код. Вполне естественно, что значительная часть Managed Extensions for C++ отвечает за управление генерацией метаданных. Импорт метаданныхПрограмма на MC++ может импортировать метаданные путём включения директивы #using специфицирующей файл, которым может быть:
Следующий пример демонстрирует импорт базовых классов .NET Developer Platform.
Видимость классовКлючевое слово public перед объявлением класса или структуры говорит компилятору, что класс будет виден любым программам, использующим для подключения сборки директиву #using. Если же класс помечен ключевым словом private, то он будет виден только внутри сборки. Это значение используется по умолчанию. Например:
Видимость полей и методов классаВнешняя и внутренняя видимость членов public-классов может быть различной. Это достигается путём применения пары спецификаторов доступа из public, private и protected. Из двух спецификаторов наиболее ограничивающий используется для внешней области видимости. Порядок следования спецификаторов не важен. Например, следующий пример определяет одинаковую (только внутри сборки) область видимости для обоих методов:
Пользовательские атрибутыАтрибуты представляют собой универсальное средство расширения метаданных. Любой класс или его элемент может быть помечен атрибутом, информация о котором будет сохранена в метабазе. Практическое применение атрибутов мы уже видели на примере [DllImport]. Этот атрибут говорит управляющей среде, что специфицированную им функцию следует искать в модуле user32.dll. Атрибуты могут использоваться не только самой CLR или компиляторами, доступ к ним возможен из любой программы. Так же мы можем определять и свои собственные атрибуты (Custom Attributes). Объявление пользовательского атрибута производится следующим образом:
Все пользовательские атрибуты должны быть помечены атрибутом attribute и происходить от класса System::Attribute или его наследников. Забавно, не правда ли? Вот вам ещё одно применение атрибутов – чтобы класс стал атрибутом, нужно его пометить атрибутом attribute. В остальном это просто тип данных, информация о котором так же сохраняется в метабазе. Когда же вы применяете этот атрибут к вашим объявлениям типов, его параметры сохраняются вместе с описанием вашего типа данных. Значение перечисления AttributeTargets позволяет указывать, где синтаксически можно использовать атрибут. Определение этого перечисления выглядит следующим образом:
Применение оператора "ИЛИ" также допускается. Обработка исключенийОбычный механизм проверки возвращаемого значения для выявления ошибок времени выполнения постепенно уходит в прошлое. В CLR ему не нашлось места совсем. В случае возникновения любой нестандартной ситуации компоненты .NET генерируют исключения, и даже при создании обёрток для COM-объектов возвращаемые значения HRESULT преобразуются в исключения типа System::Runtime::InteropServices::COMException. Все типы исключений в .NET имеют чёткую иерархию и происходят от базового класса System::Exception. Обычный блок try/catch может быть использован для обработки исключений как обычных типов C++, так и управляемых. Генерация исключений оператором throw тоже ничем особенным не отличается, за исключением того, что при использовании value-типов необходимо использовать boxing.
Когда для генерации исключения используется обычный тип C++, CLR создаёт для него обёртку типа System::Runtime::InteropServices::SEHException. Если ближайший подходящий оператор catch имеет неуправляемый тип, эта обёртка разворачивается, и обработка исключения происходит обычным для C++ образом. Это позволяет одновременно обрабатывать исключения как управляемых типов, так и неуправляемых. Но здесь есть один важный момент, если тип SEHExeption или его базовые типы встретятся первыми, то вы никогда не сможете поймать исключения неуправляемого типа. Из этого также следует, что обработчики исключений
и
фактически являются идентичными. Конструкция __finally, которая введена в компилятор Visual C++ как Microsoft Specific для обработки SEH (Structured Exception Handling) исключений, также поддерживается в полном объёме и имеет ту же семантику. Управляемые операторыCLR поддерживает операторы, и в MC++ их объявление допустимо. Но, к сожалению, использовать обычную семантику вызова операторов нельзя из-за принятой в MC++ работы с управляемыми объектами через указатели. Тем не менее, такие языки, как C# и VB.NET, лишены этого недостатка и игнорировать такую возможность не стоит. Нельзя также использовать и ключевое слово operator для объявления операторов в управляемых классах, для этого следует пользоваться предопределёнными в CLR именами. Далее приведено соответствие между операторами и их CLR именами. Унарные операторы
Бинарные операторы
Пример:
Кроме арифметических, логических и битовых операторов, CLR поддерживает два оператора преобразования (Conversion Operators): op_Implicit и op_Explicit. Разница между ними лишь в том, что оператор op_Implicit следует применять, когда преобразование идёт без потери информации, в противном случае следует использоваться op_Explicit:
Опции компилятора и препроцессорОпция компилятора /clrДля компиляции программы в управляемый код используется опция /clr. Эта опция создаёт управляемый код для всех функций, но не делает ваши классы управляемыми по умолчанию. Для этого необходимо явно использовать модификаторы __gc и __value. #pragma unmanaged, #pragma managedВполне допустимо использование управляемого и неуправляемого кода в одном модуле. Прагма unmanaged заставляет генерировать компилятор неуправляемый, “родной” для используемой платформы код. Естественно, в таком коде вы не можете использовать управляемые объекты.
_MANAGEDЭтот предопределённый макрос устанавливается компилятором в 1, когда используется опция /clr. Интересно, что прагма unmanaged никак не влияет на его значение, т.е. обе следующие функции будут возвращать 1:
Опция компилятора /FAsЭта опция не является новой в MC++, но она интересна прежде всего тем, что теперь компилятор может генерировать не только ассемблерный код, но и MSIL, “ассемблер .NET”. В частности, для последнего примера он сгенерировал MSIL для функции test1 и “родной” ассемблер для test2. РазноеМы уже достаточно много выяснили об MC++, но есть ещё несколько моментов, о которых следует упомянуть. __identifierЭто ключевое слово введено в расширение для того, чтобы мы имели возможность использовать любые другие ключевые слова в качестве идентификаторов. В следующем примере мы используем класс с именем “operator”:
__abstract__abstract говорит компилятору о том, что наш класс или интерфейс является абстрактным и создание экземпляра этого класса не допускается, он может использоваться только как базовый класс.
__sealedЭто ключевое слово запрещает использовать специфицируемый класс в качестве базового класса.
__typeofОператор __typeof возвращает объект System::Type, с помощью которого можно получить исчерпывающую информацию о заданном управляемом типе. Статические конструкторыУправляемый класс может иметь конструктор, который будет вызван средой CLR только один раз для всех объектов данного класса. Это полезно для инициализации статических переменных класса. Порядок вызова таких конструкторов не гарантируется, но вызов всегда будет сделан до создания первого объекта данного класса.
Атрибут [ParamArray]В CLR допустимо объявление функций с переменным числом параметров, но реализация этой возможности отличается от стандартного для C++ способа. На самом деле список аргументов передаётся как один параметр, являющийся управляемым массивом и помеченный атрибутом System::ParamArray. На MC++ это объявление выглядит следующим образом:
В C# использование атрибута ParamArray встроено в сам язык, и вместо него используется ключевое слово params:
Вызов нашего метода на C# будет выглядеть следующим образом:
Т.е. фактически компилятор C# преобразует список аргументов в массив и затем передаёт его в функцию. C++ такими способностями не обладает, и нам придётся явно создавать массив, явно его инициализировать и явно передавать в функцию:
Смешанный кодВ отличие от других CLR-языков MC++ позволяет легко смешивать управляемый и неуправляемый код. Это представляет определённый интерес, и далее мы проведём серию смелых экспериментов для выяснения механизмов их взаимодействия. Для примера возьмём следующий текст:
Нас будут интересовать различия работы с данными, вызов управляемой функции из неуправляемого кода и наоборот. Препарируем этот текст опцией компилятора /FAs и посмотрим, что у нас получилось:
Первое, на что следует обратить внимание – в одном модуле у нас нормально уживаются MSIL и ассемблер, из чего можно сделать вывод, что MC++ фактически содержит два кодогенератора. В обоих случаях вызов функций производится через специальные заглушки, что вполне понятно, единственный вопрос – это эффективность таких вызовов. Обращение к переменной происходит напрямую в обоих случаях, с той лишь разницей, что каждая функция делает это по-своему. Это лишний раз подтверждает способность CLR работать с памятью напрямую, что не совсем обычно для управляемой среды. С вызовами всё в порядке. При обсуждении делегатов мы рассмотрели объявление неуправляемой функции с помощью атрибута [DllImport], но теперь у нас могут возникнуть вполне законные сомнения в необходимости его применения и следующий пример это наглядно демонстрирует:
Макрос в начале примера необходим из-за конфликта имён, возникающего при подключении файла windows.h. Идём дальше. Нам удалось успешно вызвать MessageBox из user32.dll. Зададим теперь управляемому коду более сложную задачу – прямое создание и использование COM-объектов в обход всего того, что написано в документации об интеграции .NET и COM. В качестве примера создадим объект XML DOM Document и вызовем пару его методов:
Как и ожидалось, данный код выводит на консоль строчку "123". Интересно то, что это выглядит так же, как и обычная Win32 программа, к тому же она подчиняется тем же правилам. Например, вызов CoInitialize здесь также необходим, как и для любого приложения, являющегося COM-клиентом. Можно опять усомниться в эффективности вызовов между управляемым и неуправляемым кодом, но никто не мешает нам обрамить весь этот текст прагмами unmanaged/managed и сократить накладные расходы до одного вызова неуправляемой функции. Обрамлять в данном случае стоит и саму директиву #import, так как объявление функции в неуправляемой секции заставляет компилятор генерировать для неё неуправляемый код вне зависимости от места её реализации. Например, в следующем примере мы получим ошибку компиляции (как мы знаем, неуправляемый код не может использовать управляемые объекты), хотя сама функция определена в управляемой секции.
ЗаключениеТеперь пришло время ответить на наш главный вопрос: “Что же такое MC++?”. С одной стороны, вы можете смело использовать в своих программах все привычные возможности C++, шаблоны и множественное наследование, перегрузку операторов и прямую работу с памятью. Вся разница лишь в том, что компилятор будет генерировать управляемый код (MSIL) вместо ассемблера. Но! Это касается только обычных типов C++. Если же у вас возникнет необходимость (а она обязательно возникнет) в использовании gc- и value-типов, то вам не придётся заботиться об удалении объектов, CLR будет сама производить начальную инициализацию переменных и проверять допустимость значений аргументов во время исполнения. Платой за это будет следование всем ограничениям управляемой среды. Таким образом, фактически мы имеем два разных языка в одном, которые можно легко смешивать. Единственная проблема – теперь нам придётся постоянно помнить, с каким из них в данный момент мы имеем дело. Ещё один вопрос касается терминологии. Что такое “управляемый” и “неуправляемый” код? С неуправляемым всё ясно – это обычный “родной” код Windows/Intel. С управляемыми объектами тоже понятно – CLR может их полностью контролировать. Не понятно только, как быть с обычными C++ программами, которые не используют управляемые объекты, но компилируются в MSIL код. Пусть они тоже будут… управляемыми, хотя мы-то с вами точно знаем, что это не так :o)
|
Форум Программиста
Новости Обзоры Магазин Программиста Каталог ссылок Поиск Добавить файл Обратная связь Рейтинги
|