Architecture Net
ASP.NET
Платформа .NET включает также полностью переделанную версию популярной технологии ASP (Active Server Pages), известную теперь под названием ASP.NET. В основе ASP лежит интерпретируемый код сценариев, в который вставлены команды форматирования текста. Код сценариев реализуется на одном из языков с довольно ограниченными возможностями. А технология ASP.NET позволяет писать код на любом языке, поддерживаемом платформой .NET. К таким языкам относится С#, VB.NET, JScript и C++ с управляемыми расширениями. Поскольку полученный при этом код является компилируемым, интерфейсный код может быть отделен от бизнес-логики и помещен в отдельный файл.Технология ASP.NET предоставляет в распоряжение разработчиков Web-формы, которые чрезвычайно упрощают создание пользовательских интерфейсов при программировании в Web.
Перетаскивание (drag and drop) позволяет очень легко создавать макеты форм в среде Visual Studio.NET. Затем можно добавить код для обработки события формы, например, щелчка.
В технологии ASP.NET реализовано автоматическое определение функциональных возможностей броузера. Если броузер обладает широкими функциональными возможностями, обработка кода может быть выполнена на стороне клиента. В случае использования менее мощного броузера, обработку кода выполняет сервер, который затем генерирует стандартную HTML-страницу. Все эти процессы происходят достаточно прозрачно для разработчиков, использующих технологию ASP.NET.
В процессе создания Web-приложений использование Web-служб вместе с полнофункциональными компилируемыми языками программирования, такими как С#, VB.NET и управляемый C++, позволяет широко применять модели объектно-ориентированного программирования. Достичь этого при помощи языков подготовки сценариев, применяемых в ASP, и компонентов, построенных на основе модели компонентных объектов Microsoft (Component Object Model, COM) было бы невозможно.
CompEbook.ru Железо, дизайн, обучение и другие
Что такое Microsoft .NET?
Новая технология .NET, предложенная компанией Microsoft, отражает видение этой компанией приложений в эпоху Internet. Технология .NET обладает улучшенной функциональной совместимостью, в основе которой лежит использование открытых стандартов Internet. Кроме того, она повышает устойчивость классического пользовательского интерфейса операционной системы Windows — рабочего стола. Разработчикам программного обеспечения технология .NET предоставляет новую программную платформу и великолепные инструментальные средства разработки, в которых основную роль играет язык XML (extensible Markup Language — расширяемый язык разметки).Microsoft .NET — платформа, построенная на верхнем слое операционной системы. Технология .NET явилась главным объектом инвестиций компании Microsoft. С момента начала работ над этой технологией и до момента ее публичного анонсирования прошло три года. Несомненно, на развитие технологии .NET оказали влияние другие технологические достижения, в частности расширяемый язык разметки XML, платформа Java™, a также модель компонентных объектов Microsoft (Component Object Model — COM).
Платформа Microsoft .NET предоставляет:
CompEbook.ru Железо, дизайн, обучение и другие
Факторы, определяющие успех Web-служб
Перспектива Internet-приложений, как ее видит компания Microsoft, стала достоянием общественности. Окончательный успех инициативы, с которой выступила Microsoft, зависит от двух внешних факторов, которые не связаны со сферой программного обеспечения А именно, от степени развития инфраструктуры сети Internet и успеха предложенной модели предприятия. Вопрос о том, приобретет ли технология Web-служб широкое распространение, прямо зависит от наличия сетей с высокой пропускной способностью. Такие сети уже сейчас широкодоступны. И пропускная способность их в последующие несколько лет существенно увеличится. А вот что касается перспектив предложенной модели предприятия, то они нам пока еще неизвестны!Важно отдавать себе отчет в том, что технология .NET обладает гораздо более широкими возможностями, чем громко рекламируемые возможности Internet. Более устойчивая платформа, предназначенная для создания Windows-приложений, чрезвычайно мощная библиотека классов .NET Framework, а также инструментальные средства разработки — это именно те особенности технологии .NET, благодаря которым она выдержит испытание временем.
CompEbook.ru Железо, дизайн, обучение и другие
|
Глава 1. Что такое Microsoft .NET? |
CompEbook.ru Железо, дизайн, обучение и другие
Инструментальные средства разработки
Настоящим ключом к успеху в разработке программного обеспечения является наличие набора эффективных инструментальных средств разработки. Компания Microsoft уже давно предлагает замечательные инструментальные средства разработки, к числу которых принадлежат Visual C++ и Visual Basic. Платформа .NET объединяет средства разработки в единую интегрированную среду, которая имеет название Visual Studio.NET.Независимые разработчики могут и в дальнейшем разрабатывать расширения среды Visual Studio.NET, а также предлагать дополнительные языки программирования и соответствующие полноценные среды разработки, поддерживаемые платформой .NET. Программы на предложенных независимыми разработчиками языках программирования смогут взаимодействовать с программами на любых языках, поддерживаемых платформой .NET. Существующий набор инструментальных средств разработки обладает широкими возможностями, которые используются при создании Web-приложений и Web-служб. Обеспечивается также всесторонняя поддержка разработки приложений с базами данных.
CompEbook.ru Железо, дизайн, обучение и другие
Каркас NET Framework
Современный стиль программирования предполагает многократное использование кода, содержащегося в библиотеках. Объектно-ориентированные языки программирования облегчают создание библиотек классов. Получающиеся в результате библиотеки являются гибкими, им присущ высокий уровень абстракции. Эти библиотеки могут быть расширены путем добавления новых классов, а также путем образования новых классов на основе уже существующих. При этом новые классы наследуют функциональность существующих классов.В каркасе .NET Framework представлено более 2500 классов, содержащих повторно используемый код. Эти классы доступны в любом языке программирования, который поддерживается платформой. Библиотека классов .NET Framework является расширяемой На основе уже существующих базовых классов можно создать новые производные классы, причем производные классы могут быть реализованы на совершенно другом языке программирования.
В состав библиотеки классов .NET Framework, входят классы, которые используются при разработке Windows-приложений, Web-приложении, а также приложении с базами данных. В библиотеке классов .NET Framework имеются также классы, обеспечивающие взаимодействие с языком XML, с моделью компонентных объектов Microsoft (COM) и с любой платформой, поддерживающей интерфейс 32-разрядных Windows-приложении (Win32 API). Библиотека классов .NET Framework обсуждается в следующей главе, а также понемногу в остальных главах данной книги.
CompEbook.ru Железо, дизайн, обучение и другие
Microsoft и Web
Всемирная паутина (World Wide Web — WWW) рассматривалась компанией Microsoft как вызов, и он был принят. В самом деле, Web достаточно хорошо сосуществует с персональными компьютерами (ПК), — сегментом рынка, в котором компания Microsoft традиционно сильна. С помощью приложения, работающего на ПК, — броузера, — пользователь получает доступ к огромному миру информации. В основе построения всемирной сети лежит использование стандартов, в частности, языка гипертекстовой разметки HTML (HyperText Markup Language), протокола передачи гипертекста HTTP (HyperText Transfer Protocol) и языка XML (extensible Markup Language). Эти стандарты играют существенную роль при обмене информацией между различными пользователями, работающими на самых разнообразных компьютерных системах и устройствах.Несмотря на всю свою сложность, персональный компьютер, работающий под управлением операционной системы Windows, является устройством достаточно стандартизированным. В основе Web хотя и лежат стандартные протоколы, все же она представляет собой Вавилонскую башню, состоящую из многочисленных языков программирования, баз данных, различных сред разработки и разных устройств, работающих на основе этих протоколов. Такая взрывоопасная сложность технологии еще больше усиливает растущую нехватку профессионалов, которые могут на основе новых технологий строить необходимые системы. Платформа .NET предоставляет инфраструктуру, позволяющую программистам отвлечься от повторного изобретения решений общих проблем программирования и сконцентрироваться на создании необходимых приложений.
CompEbook.ru Железо, дизайн, обучение и другие
Новая платформа программирования
А теперь давайте рассмотрим вопросы, которые мы только что обсудили, с точки зрения технологии .NET как новой платформы программирования.Платформа NET имеет также несколько важных характерных особенностей, а именно:
CompEbook.ru Железо, дизайн, обучение и другие
Общеязыковая среда выполнения CLR (Common Language Runtime)
Среда выполнения предоставляет необходимые службы во время выполнения приложении. Традиционно каждой среде программирования соответствует своя среда выполнения. В качестве примера среды выполнения могут служить стандартная библиотека языка С. библиотека базовых классов Microsoft (MFC), среда выполнения языка Visual Basic, а также виртуальная машина Java (Java Virtual Machine). Среда выполнения платформы .NET получила название общеязыковой среды выполнения CLR (Common Language Runtime).Управляемый код и данные
Общеязыковая среда выполнения CLR (Common Language Runtime) предостаатяет в распоряжение .NET-кода ряд служб (включая и библиотеку классов .NET Framework, которая размещается на верхнем слое CLR). Для того чтобы воспользоваться этими службами, .NET-код должен иметь предсказуемое поведение и. к тому же, быть понятным общеязыковой среде выполнения CLR. Например, для того чтобы среда выполнения могла осуществить проверку границ массивов, все массивы в .NET имеют идентичный формат. Требования типовой безопасности могут налагать на .NET-код и другие ограничения.
Ограничения, которые накладываются на .NET-код, определяются общей системой типов (Common Type System, CTS), а также ее реатизацией в промежуточном языке IL, разработанном корпорацией Microsoft (Microsoft Intermediate Language— MSIL, или просто IL). Общей системой типов определены типы и операции, которые могут использоваться кодом, работающим в общеязыковой среде выполнения CLR. Так, именно общей системой типов (Common Type System, CTS) на используемые типы накладывается ограничение единичного наследования реализации. Код на промежуточном языке, разработанном корпорацией Microsoft (Microsoft Intermediate Language, MSIL), компилируется во внутренний (собственный) код платформы.
.NET-приложения содержат в себе метаданные, т.е. описание кода и данных, используемых приложением. Благодаря использованию метаданных возможно автоматическое преобразование данных в последовательную форму общеязыковой средой выполнения CLR при их сохранении.
Код, который может использовать службы, предоставляемые общеязыковой средой выполнения CLR, называется управляемым кодом.
Память для управляемых данных распределяется и освобождается автоматически. Такое автоматическое освобождение занимаемой памяти называется сборкой мусора (garbage collection). Сборка мусора решает все проблемы утечки памяти и им подобные.
Microsoft и Европейская Ассоциация производителей ЭВМ
[European Computer Manufacturers' Association (ЕСМА) имеет также другие названия Европейская Ассоциация производителей компьютеров (ЕАПК) и Европейская ассоциация изготовителей ЭВМ Европейская Ассоциация производители ЭВМ разрабатывает стандарты, соблюдаемые большинством фирм, выпускающих ЭВМ и программное обеспечение —Прим ред]
Корпорация Microsoft передала с целью стандартизации спецификацию языка С# и основные части библиотеки классов .NET Framework на рассмотрение Европейской Ассоциации производителей компьютеров (European Computer Manufacturers' Association — ЕСМА). Техническими требованиями этой независимой международной организации по стандартам определена независимая от платформы инфраструктура универсального языка CLI (Common Language Infrastructure). Общеязыковую среду выполнения CLR можно представить себе как инфраструктуру универсального языка CLI (Common Language Infrastructure), дополненную библиотеками базовых классов BCL (Basic Class Libraries). Библиотека базовых классов BCL (Basic Class Library) поддерживает фундаментальные типы общей системы типов CTS (Common Type System), а именно: ввод/вывод файлов, строки и форматирование. Поскольку общеязыковая среда выполнения CLR зависит от используемой платформы, в ней используются модели управления процессами и памятью базовой операционной системы.
Спецификацией (техническими требованиями) Европейской Ассоциации производителей компьютеров (European Computer Manufacturers' Association — ЕСМА) определен универсальный промежуточный язык CIL (Common Intermediate Language). Согласно этим требованиям, разрешено интерпретировать код на промежуточном языке CIL или компилировать его в собственный (внутренний) код.
Верифицируемый код
Управляемый код может быть проверен на предмет типовой безопасности. Код, удовлетворяющий требованиям типовой безопасности, разрушить не так легко. Например, структуры данных или другие приложения, которые находятся в памяти, не могут быть повреждены в результате перезаписи буфера. Политику безопасности можно применить к коду, удовлетворяющему требованиям типовой безопасности. Например, доступ к некоторым файлам или средствам пользовательского интерфейса может быть разрешен или запрещен. Выполнение кода, происхождение которого неизвестно, можно запретить.
Однако, не все приложения, для работы которых требуется общеязыковая среда выполнения CLR, обязаны удовлетворять требованиям типовой безопасности. В частности, такая ситуация реализуется для приложений, написанных на C++. Управляемый код, написанный на C++, может использовать возможности, предоставляемые общеязыковой средой выполнения CLR, например, сборку мусора. Но так как на C++ может быть создан и неуправляемый код, то нет никаких гарантий относительно того, что приложение, написанное на C++, будет удовлетворять требованиям типовой безопасности. В управляемом коде, написанном на C++, нельзя выполнять арифметические операции над управляемыми указателями, или приводить тип управляемого указателя к неуправляемому. Поэтому управляемый код, написанный на C++, можно проверить на безопасность. Но может случиться так, что в этом же приложении, написанном на C++, будут выполняться арифметические операции над указателями или приведение типов управляемых указателей к неуправляемым. А это, по своей сути, ненадежно.
CompEbook.ru Железо, дизайн, обучение и другие
Открытые стандарты и возможность взаимодействия (функциональная совместимость)
Современная вычислительная среда состоит из множества аппаратных и программных систем. В качестве компьютеров могут использоваться мэйнфреймы и высокопроизводительные серверы, рабочие станции и персональные компьютеры, маленькие мобильные устройства, такие как карманные компьютеры, часто называемые персональными цифровыми помощниками (Personal Digital Assistance, PDA) и даже сотовые телефоны. К числу используемых операционных систем принадлежат традиционные операционные системы, под управлением которых работают мэйнфреймы, различные клоны операционных систем Unix, Linux, несколько версий операционной системы Windows, операционные системы реального времени и специальные операционные системы, наподобие PalmOs, предназначенной для управления мобильными устройствами. На практике используются различные языки программирования, различные базы данных, различные инструментальные средства разработки приложений, а также различное промежуточное программное обеспечение (программное обеспечение, содействующее процессам обмена информацией между клиентом и сервером).В современной вычислительной среде очень немногие приложения являются самодостаточными островами. Даже небольшие обособленные приложения, развернутые на отдельном ПК, могут использовать Internet при регистрации программного продукта или для получения обновлений к нему. Ключом к функциональной совместимости приложений является применение существующих стандартов. Поскольку, как правило, приложения работают в сети, ключевым стандартом является протокол, используемый для обмена данными.
CompEbook.ru Железо, дизайн, обучение и другие
Приложения в эпоху Internet
Первоначально Web представляла собой огромное хранилище данных. Для получения страницы с нужной информацией, броузер делал соответствующий запрос. Затем Web-сервер доставлял запрошенную информацию в виде статической HTML-страницы. Даже после появления интерактивных Web-приложений, все еще используется язык HTML. С его помощью форматируется информация, отображаемая на экране.Язык XML предоставляет универсальный способ передачи данных, независимый от формата представления данных. Таким образом, именно язык XML может послужить отправной точкой на пути к достижению договоренности между компаниями относительно стандартов передачи документов и информации, в частности заказов на покупку и счетов. Тогда возникнут предпосылки для автоматизации бизнеса в сети Internet между сотрудничающими компаниями. В последнее время подобный вид электронной коммерции даже получил специальное название — B-to-B (Business-To-Business). Но язык XML всего лишь описывает данные, в нем не предусмотрено выполнение действий над данными. Именно для этой цели и нужны Web-службы.
CompEbook.ru Железо, дизайн, обучение и другие
Проблемы с Windows
Обслуживание персонального компьютера, работающего под управлением операционной системы Windows, является тяжелой и неприятной задачей, так как имеющиеся приложения достаточно сложны. Они состоят из многих файлов, в процессе инсталляции производятся записи в системном реестре, создаются ярлыки и так далее. Различными приложениями могут использоваться одни и те же динамически подключаемые библиотеки (DLL). При инсталляции нового приложения динамически подключаемая библиотека, уже используемая существующим приложением, может быть перезаписана. Вследствие этого старое приложение может быть повреждено (ситуация, известная как "проклятие (ад) динамически подключаемых библиотек (DLL)"). Деинсталляция приложения также является довольно сложной задачей, которая часто выполняется не до конца автоматически.Постепенно персональный компьютер становится все менее стабильным, иногда он требует радикального лечения. При этом приходится переформатировать жесткий диск и начинать установку программного обеспечения с самого начала. Использование персональных компьютеров дает огромную экономическую выгоду. Действительно, стандартные приложения недороги и в то же время достаточно мощны, а аппаратные средства дешевые. Но величина сэкономленных средств уменьшается за счет затрат на сопровождение программного обеспечения.
Первоначально операционная система Windows была разработана еще в те времена, когда персональные компьютеры не были связаны в сеть, и вопрос безопасности не стоял так остро. Несмотря на то, что средства безопасности были встроены в Windows NT и Windows 2000, соответствующую им модель программирования на практике использовать непросто. Ели не верите, ответьте на вопрос: вы когда-либо передавали что-либо, кроме пустого указателя NULL в качестве аргумента LPSECURITY_ATTRIBUTES, используемому в Win32?
CompEbook.ru Железо, дизайн, обучение и другие
Протоколы обмена
Сокеты, используемые протоколом TCP/IP, высокостандартизированы и широкодоступны. Но программирование с применением сокетов рассматривается программистами как слишком низкоуровневое. Именно необходимость программирования на низком уровне препятствует продуктивному написанию устойчивых распределенных приложений. Протокол удаленного вызова процедур RPC (Remote Procedure Call) имеет несколько более высокий уровень. Но протокол удаленного вызова процедур RPC (Remote Procedure Call) является достаточно сложным, и к тому же существует масса его разновидностей. Приобрели популярность такие протоколы высокого уровня, как CORBA (Common Object Request Broker Architecture — архитектура посредника объектных запросов), RMI (Remote Method Invocation — технология удаленного вызова методов), а также распределенная модель компонентных объектов DCOM (Distributed Component Object Model). Эти протоколы все еще сложны и для организации их работы требуется наличие специальной среды как на стороне сервера, так и на стороне клиента. Им присуши также и другие недостатки. Например, в процессе использования данных протоколов возможно возникновение проблем при прохождении пакетов данных через брандмауэры (системы сетевой защиты).Тем не менее, один протокол получил повсеместное распространение. Это протокол передачи гипертекстовых файлов HTTP (Hypertext Transfer Protocol). Именно по причине повсеместного распространения протокола HTTP, компании Microsoft и другим производителям сетевого программного обеспечения пришлось разработать новый протокол, получивший название SOAP (Simple Object Access Protocol — простой протокола доступа к объектам). Для кодирования запросов методов объектов и сопутствующих данных в протоколе SOAP используются тексты на языке XML (extensible Markup Language). Огромным достоинством протокола SOAP является его простота. Вследствие своей простоты этот протокол может быть легко реализован на многих устройствах. Протокол SOAP (Simple Object Access Protocol) может работать на верхнем слое любого стандартного протокола. Но именно возможность его работы на верхнем слое таких стандартных Internet-протоколов, как протокол передачи гипертекстовых файлов HTTP (Hypertext Transfer Protocol) и протокол SMTP (Simple Mail Transfer Protocol — простой протокол пересылки почты, или простой протокол электронной почты), позволяет пакетам данных проходить через системы сетевой защиты (брандмауэры) без каких-либо проблем, связанных с возможностью соединения.
CompEbook.ru Железо, дизайн, обучение и другие
Разработка приложений на разных языках
Как следует из ее названия, общеязыковая среда выполнения CLR поддерживает многие языки программирования. Для каждого такого языка должен быть реализован компилятор, который генерирует "управляемый код". Сама компания Microsoft реализовала компиляторы для управляемого C++, Visual Basic.NET, JScript, а также для совершенно нового языка программирования С#.Компиляторы для более чем дюжины других языков реализуются усилиями независимых разработчиков. К числу этих языков программирования принадлежит язык COBOL (его реализацией занимается компания Fujitsu) и язык Perl (его реализацией занимается компания ActiveState). Представьте себе, что миллиарды строк кода, написанных на языке COBOL, после некоторых усилий, связанных с переносом, станут доступными в среде .NET. Чтобы воспользоваться преимуществами среды .NET, программистам, которые пишут приложения на языке COBOL, не придется переучиваться и с начала изучать совершенно новый язык программирования.
CompEbook.ru Железо, дизайн, обучение и другие
это новая платформа, построенная на
Microsoft .NET — это новая платформа, построенная на верхнем слое операционной системы. Она обладает многими возможностями, которые позволяют создавать и развертывать как обычные, так и новые Web-ориентированные приложения. Web-службы позволяют использовать функциональные возможности приложений во всей сети Internet. Как правило, для организации взаимодействия с Web-службами задействован протокол SOAP (Simple Object Access Protocol — простой протокол доступа к объектам). Поскольку в основу протокола SOAP положены широко распространенные стандарты, в частности язык разметки гипертекста HTML (Hypertext Markup Language) и язык XML (extensible Markup Language), этот протокол характеризуется высокой степенью функциональной совместимости, а значит, и высокой способностью к взаимодействию.Платформа .NET использует управляемый код, для выполнения которого предназначена общеязыковая среда выполнения CLR. Общеязыковая среда выполнения CLR использует общую систему типов (Common Type System). Библиотека классов .NET Framework содержит огромное количество классов, которые в равной степени доступны в любом языке программирования, поддерживаемом платформой .NET. Ключевая роль в технологии .NET принадлежит языку XML Все функциональные возможности, которыми обладает платформа .NET, могут использоваться как для создания более устойчивых Windows-приложений, так и для построения Internet-приложений.
CompEbook.ru Железо, дизайн, обучение и другие
Роль языка XML
Язык XML в технологии .NET используется повсеместно. В глобальном видении развития приложений в эпоху Internet компания Microsoft также отводит ему особое место. Ниже перечислены некоторые применения языка XML в .NET.Язык XML используется для кодирования запросов к Web-службам и ответов, возвращаемых клиенту.
Язык XML может использоваться для моделирования данных в наборах данных, используемых в технологии доступа к данным ADO.NET.
Язык XML используется при создании конфигурационных файлов.
Для некоторых языков, поддерживаемых платформой .NET, документация на языке XML может быть сгенерирована автоматически.
Язык XML — лингва-франка (общепринятый язык) для корпоративных серверов, построенных на платформе .NET.
Язык XML используется технологией Web-служб для описания и передачи данных.
CompEbook.ru Железо, дизайн, обучение и другие
Стеклянный дом и тонкие клиенты
В последнее время приобрела привлекательность старая модель центральной вычислительной машины, в которой, как в стеклянном доме, под строгим и неусыпным контролем выполняются все необходимые приложения. Результатом явилась идея создания некоторого рода тонких клиентов. Но на самом деле широко разрекламированная идея "сетевого ПК" никогда не была принята до конца.Пользователям слишком дороги стандартные приложения для ПК, к тому же им хочется иметь свой персональный (локальный) компьютер, на котором так привычно хранить свои данные. Ведь без линии связи с очень высокой пропускной способностью не сможет удовлетворительно функционировать даже текстовый процессор, работающий на сервере. Проблема безопасности также является слишком сложной, чтобы ее можно было решить при помощи тонких клиентов. И поэтому не вызывает сомнения, что персональный компьютер еще долго будет занимать очень прочные позиции.
CompEbook.ru Железо, дизайн, обучение и другие
Устойчивая Windows
В связи со всей этой шумихой, поднятой вокруг платформы .NET и Internet, важно четко осознавать, что с появлением платформы .NET изменилась модель программирования. Следствием этого стала возможность создания намного более устойчивых Windows-приложений. Судьба приложения больше не зависит от обширных конфигурационных данных, хранящихся в хрупком системном реестре Windows. .NET-приложения содержат самоописание. Они содержат метаданные в своих исполняемых файлах. Различные версии компонентов могут быть развернуты и существовать одновременно. Благодаря глобальному кэшу сборки (Global Assembly Cache), разные приложения могут совместно использовать одни и те же компоненты. Управление версиями встроено в модель развертывания приложений. Частью платформы .NET является также простая модель безопасности.CompEbook.ru Железо, дизайн, обучение и другие
Важность инструментальных средств разработки
Не следует недооценивать значение инструментальных средств разработки приложений. Хорошей иллюстрацией тому может послужить случай, который произошел при работе над проектом языка Ada. Целью данного проекта было создание очень мощного языка программирования. Частью первоначального замысла было также создание стандартизованной среды программирования на языке Ada (Ada Programming Support Environment — APSE). Разработке языка программирования было уделено огромное внимание. В то же время гораздо меньше внимания было уделено надлежащей разработке среды программирования на языке Ada (APSE). Из-за этого у языка программирования Ada так и не появилась среда разработки, которая могла бы сравниться со средой разработки Visual Studio, Smalltalk, или с многочисленными интегрированными средами разработки, которые имеются для языка Java.Преимущество среды разработки Visual Studio.NET состоит в том, что она является стандартом. Следовательно, она будет тщательно настроена для того, чтобы сделать работу в этой среде продуктивной. Вниманию разработчиков будут предложены многочисленные тренинга, посвященные разработке приложений в данной среде, планируется также множество других акций. Компания Microsoft, по сравнению со многими более мелкими разработчиками, присутствующими на обширном рынке инструментальных средств, располагает гораздо большими ресурсами, которые она в состоянии выделить на поддержку среды Visual Studio.NET. Платформа Java характеризуется высоко стандартизированным языком программирования и интерфейсом прикладного программирования (API). В то же время, инструментальные средства разработки, без которых написание высокопроизводительных приложений немыслимо, не являются в ней стандартизированными.
CompEbook.ru Железо, дизайн, обучение и другие
Web-службы
Поддержка платформой .NET Web-служб является одним из наиболее важных ее свойств. Web-службы, построенные на основе промышленного стандартного протокола SOAP (Simple Object Access Protocol — простой протокол доступа к объектам), позволяют использовать функции ваших приложений в любом месте Internet. С точки зрения программиста, работающего в среде .NET, не существует различия между Web-службами и другими типами служб, которые реализуются с помощью классов в языках программирования, соответствующих спецификации .NET. Используемая при этом модель программирования остается неизменной, независимо от того, вызывается ли функция приложением, отдельным компонентом, установленным на этой же машине, или, как в случае с Web-службами, на другой машине.Эта присущая простота используемой модели программирования позволяет компаниям очень легко создавать и устанавливать приложения. При желании все необходимое для приложения может извлекаться из внешних источников, да и разработку приложения могут выполнить независимые разработчики. В результате этого удается избежать проблем, связанных с разработкой, развертыванием и сопровождением приложения. Иными словами, вы можете просто воспользоваться Web-службами, которые вам предлагают независимые разработчики. Эти Web-службы могли даже и не существовать в то время, когда вы проектировали свое приложение.
CompEbook.ru Железо, дизайн, обучение и другие
Windows на рабочем столе
Microsoft начинала с пользовательского интерфейса, который известен под названием рабочего стола. Современная среда Windows получила повсеместное распространение. Под эту среду написано бесчисленное множество приложений. Большинство пользователей на домашних компьютерах использует операционную систему Windows, по крайней мере отчасти. Microsoft удалось достичь многого. Но, тем не менее, все еще существуют значительные проблемы.CompEbook.ru Железо, дизайн, обучение и другие
Architecture Net
Библиотека классов .NET Framework
В предыдущем примере Serialize (Сериализация) используются классы SoapFor-matrer и FileStream. Они являются лишь двумя из более чем 2500 классов библиотеки .NET Framework. Классы библиотеки .NET Framework создают каркас (инфраструктуру) приложения и предоставляют системные службы .NET-приложениям. Ниже перечислены лишь некоторые из функциональных возможностей библиотеки классов .NET Framework:CompEbook.ru Железо, дизайн, обучение и другие
ILDASM — дисассемблер промежуточного языка Microsoft
Дисассемблер промежуточного языка Microsoft ILDASM (Microsoft Intermediate Language Disassembler) может отображать метаданные и инструкции языка MSIL, связанные с соответствующим .NET-кодом. Дисассемблер ILDASM является очень полезной утилитой, которая используется при отладке приложений. Он позволяет более глубоко понять инфраструктуру платформы .NET. Кроме того, дисассемблер промежуточного языка Microsoft ILDASM можно использовать для изучения кода библиотеки классов .NET Framework [Дисассемблер ILDASM можно найти в меню Tools (Сервис) Visual Studio.NET. Он находится также в подкаталоге Microsoft.NET\FrameworkSDK\Bm. Дисассемблер можно активизировать, щелкнув два раза на его названии в окне Проводника (Explorer) или с помощью командной строки. Если вы активизируете дисассемблер ILDASM с помощью командной строки (или из среды VS.NET), то используйте ключ /ADV для получения доступа к некоторым его дополнительным возможностям.]. На рис. 2.1 приведен фрагмент кода на языке MSIL, взятый из примера Serialize (Сериализация). В данном фрагменте описывается создание двух новых объектов Customer (Клиент) и их добавление в список [Откройте пример Senahze.exe и щелкните на знаке плюс (+) рядом с пунктом Test (Тестирование). Щелкните два раза на элементе Main (Главная), чтобы инициировать в MSIL главную процедуру.].Инструкция newobj создает новую объектную ссылку, используя параметр конструктора [Формально он не является параметром. В промежуточном языке IL используется стек; конструктор представляет собой лексему метаданных, записанную в стек.]. Инструкция stloc сохраняет значение в локальной переменной. Инструкция Idloc загружает значение локальной переменной [Подробно промежуточный язык Microsoft MSIL описан в документах Европейской Ассоциации производителей ЭВМ (European Computer Manufacturers' Association — ЕСМА). Особенно рекомендуется изучить раздел "Partition III- CIL Instruction Set", посвященный системе команд.]. Настоятельно рекомендуем вам поэкспериментировать с дисассемблером ILDASM и изучить его возможности.

Рис. 2.1. Фрагмент кода из примера Serialize (Сериализация)
CompEbook.ru Железо, дизайн, обучение и другие
JIТ-компиляция, или оперативная компиляция
Перед выполнением на конкретной машине, код на промежуточном языке Microsoft— MS1L (Microsoft Intermediate Language) транслируется оперативным компилятором, или ЛТ-компилятором (JIT — "just-in-time" или "как раз вовремя") в собственный (внутренний) код. Во время работы программы некоторые участки кода выполняться никогда не будут. Следовательно, более эффективной может оказаться трансляция кода из промежуточного языка MSIL в собственный (внутренний) код, осуществляемая по мере необходимости в процессе выполнения приложения. Собственный (внутренний) код при этом сохраняется с целью повторного его использования.После загрузки типа, к каждому его методу загрузчик присоединяет заглушку. При первом вызове заглушка передает управление ЛТ-компилятору, который генерирует собственный (внутренний) код и сохраняет адрес оттранслированного собственного (внутреннего) кода в заглушке. При последующих вызовах метода управление передается непосредственно собственному (внутреннему) коду.
CompEbook.ru Железо, дизайн, обучение и другие
Объектом является все
Если тип содержит метаданные, тогда среда выполнения может делать многие замечательные вещи. Но все ли объекты в .NET содержат метаданные? Да! Каждый тип, будь то тип, определенный пользователем (например, Customer (Клиент)) или тип, являющийся частью библиотеки классов .NET Framework (например, FileStream). является объектом среды .NET. Все объекты среды .NET являются производными от одного базового класса— системного класса Object (Объект). Поэтому все. что выполняется в среде .NET, имеет тип и, следовательно, содержит метаданные.Типы
Типы — сердце модели программирования, основанной на общеязыковой среде выполнения CLR. Тип аналогичен классу в большинстве объектно-ориентированных языков программирования и сочетает в себе используемую абстракцию данных и их поведение. Тип в общеязыковой среде выполнения CLR содержит:
поля (элементы данных);
методы;
свойства;
события.
Имеются также встроенные простые типы данных. Например, целочисленный тип данных, тип чисел с плавающей точкой, строки, и так далее.
В нашем примере код преобразования объектов в последовательную форму может просматривать список (типа ArrayList) объектов Customer (Клиент), и сохранять каждый объект, а также весь массив, к которому принадлежит объект. Это возможно благодаря тому, что метаданные содержат информацию как о типе объекта, так и о его размещении.
Из дальнейшего станет ясно, что благодаря тому, что все .NET-объекты являются производными от общего базового класса, открываются и некоторые другие возможности.
CompEbook.ru Железо, дизайн, обучение и другие
Общая система типов
Типы, передаваемые библиотеке классов .NET Framework, имеют некоторую общую природу. Эти типы определяются обшей системой типов (Common Type System — CTS). Общая система типов CTS определяет правила для типов и действий, которые поддерживает среда выполнения CLR. Именно общая система типов CTS накладывает на классы .NET ограничение единичного наследования реализации. Хотя общая система типов CTS определена для широкого множества языков программирования, не все эти языки должны поддерживать все свойства типов данных, предусмотренные в общей системе типов CTS. Например, в языке C++ множественное наследование разрешено для неуправляемых классов, но запрещено для управляемых.Промежуточный язык Microsoft (Microsoft Intermediate Language — MSIL, или просто IL) определяет систему команд, которая используется всеми компиляторами, транслирующими на язык платформы .NET. Этот промежуточный язык не зависит от используемой платформы. Код на языке MSIL затем преобразуется во внутренний (собственный) код платформы. Мы можем быть уверены, что классы библиотеки .NET Framework будут работать со всеми языками, поддерживаемыми платформой .NET. Особенности создаваемого приложения больше не накладывают ограничений на выбор языка программирования, а выбор языка программирования больше не ограничивает возможности создаваемого приложения.
Промежуточный язык MSIL и общая система типов CTS позволяют многим языкам программирования, компиляторы для которых могут генерировать код на языке MS1L, использовать библиотеку классов .NET Framework. Именно в этом состоит одно из наиболее заметных различий между платформами .NET и Java, которые в значительной степени используют одну и ту же философию.
CompEbook.ru Железо, дизайн, обучение и другие
Обзор платформы .NET
Платформа .NET содержит общеязыковую среду выполнения (Common Language Runtime — CLR). Общеязыковая среда выполнения CLR поддерживает управляемое выполнение, которое характеризуется рядом преимуществ. Совместно с общей системой типов (Common Type System — CTS) общеязыковая среда выполнения CLR поддерживает возможность взаимодействия языков платформы .NET. Кроме того, платформа .NET предоставляет большую полнофункциональную библиотеку классов .NET Framework.CompEbook.ru Железо, дизайн, обучение и другие
Приложения будущего
Даже если бы платформа .NET смогла устранить все проблемы прошлого, этого все равно было бы недостаточно. Постоянный рост требований со стороны клиентов к функциональным возможностям приложений является одним из непреложных законов программирования.Возможность беспрепятственной работы приложений в разных компьютерных сетях, обусловленная развитием Internet, стала императивом. Функциональные возможности компонентов должны быть доступны также и с других машин. При этом никто из программистов не хочет писать базовый каркас; все они жаждут писать приложения, предназначенные для непосредственного решения проблем своих клиентов.
CompEbook.ru Железо, дизайн, обучение и другие
Проблемы, связанные с разработкой Windows-приложений
Представьте себе симфонический оркестр, в котором группам струнных смычковых и ударных инструментов предстоит исполнить свои партии, используя при этом разные варианты партитуры. В таком случае, чтобы исполнить даже простейшую музыкальную композицию, музыкантам пришлось бы приложить героические усилия. Этот пример достаточно хорошо иллюстрирует деятельность разработчиков Windows-приложений. В процессе работы перед разработчиком возникает целый ряд вопросов. Использовать ли в приложении классы библиотеки базовых классов Microsoft (Microsoft Foundation Classes — MFC)? На каком языке писать приложение, на Visual Basic или на C++? Какой интерфейс для работы с базами данных использовать в приложении: открытый интерфейс взаимодействия с базами данных (Open Database Connectivity Interface — ODBC) или интерфейс OLE для баз данных, OLEDB? Использовать в приложении интерфейс модели компонентных объектов Microsoft (Component Object Model — COM) или интерфейс прикладного программирования (API) в стиле языка С? Если выбор сделан в пользу интерфейса модели компонентных объектов Microsoft (COM), какой тогда интерфейс использовать: IDispatch, дуальный (двойственный) интерфейс или только интерфейс с виртуальной таблицей? Какая роль во всем этом отводится Internet? До тех пор пока не появилась платформа .NET, довольно часто проект приложения искажался используемыми в процессе его реализации технологиями, которыми в тот период времени владели разработчики. Или же разработчику приходилось изучать еще одну технологию, которой было суждено через пару лет быть вытесненной следующей.Развертывание приложения может оказаться трудной и неприятной задачей. В процессе развертывания приложения должны быть сделаны соответствующие записи в системном реестре, который является достаточно хрупким, а его восстановление — нелегкий труд. К тому же не существует хорошей стратегии управления версиями компонентов. Новые версии приложений могут разрушить уже существующие программы и при этом остается лишь догадываться о том, что же собственно произошло. Чтобы избежать проблем, связанных с хранением сведений о конфигурации системы в системном реестре, в других технологиях для этой цели используются метабазы или сервер SQL Server.
Еще одной проблемой в Win32 является безопасность. Существующая модель безопасности тяжела для понимания. Еще более тяжело ее использовать на практике. Многие разработчики просто ее игнорируют. Разработчики, которые были вынуждены использовать существующую систему безопасности, пытались в этой трудной модели программирования делать все от них зависящее. Возрастающее значение безопасности, связанное с развитием Internet, грозит изменить плохую ситуацию на потенциальный кошмар.
Даже там, где компания Microsoft попыталась облегчить процесс разработки приложений, проблемы все еще оставались. Многие системные службы приходилось разрабатывать с самого начала, по существу создавая инфраструктуру приложения, которая имела мало общего с бизнес-логикой. Гигантским шагом в сторону создания служб более высокого уровня стали сервер транзакций корпорации Microsoft (Microsoft Transaction Server, MTS) и COM+. Тем не менее, потребовалась еще одна парадигма разработки приложений. Модель компонентных объектов Microsoft (Component Object Model — COM) сделала возможным настоящее программирование с использованием компонентов. При этом приложение можно было создать достаточно просто с помощью языка Visual Basic. Но такие приложения не обладали достаточной гибкостью. Значительно более мощные приложения можно было создать с помощью языка C++, но при этом нужно было приложить значительные усилия. И это не говоря уже о том, что на языке C++ приходилось постоянно писать (постоянно воссоздавать) повторяющийся каркас (инфраструктуру) приложения. Если бы от этого всего можно было избавиться, скучать за ILJnknown я бы не стал.
CompEbook.ru Железо, дизайн, обучение и другие
Программирование на основе интерфейсов
Предположим, что вы хотите зашифровать ваши данные, и, следовательно, не желаете полагаться на сериализацию (преобразование в последовательную форму), реализованную на основе простого протокола доступа к объектам (Simple Object Access Protocol — SOAP), входящего в состав библиотеки .NET Framework. Ваш класс может наследовать интерфейс ISeriaiizaole и содержать реализацию соответствующего алгоритма (как это сделать, мы обсудим в следующих главах). Тогда при сохранении и восстановлении данных библиотека .NET Framework будет использовать ваши методы.Но как библиотека .NET Framework узнает о том. что вы реализовали интерфейс 15-erializable? Оказывается, она может запросить метаданные соответствующего класса для того, чтобы узнать, наследчет ли он указанный интерфейс! Затем при сериадизации объекта или преобразовании его из последовательной формы в "параллельную" библиотека классов .NET Framework может использовать либо ее собственный алгоритм, либо код соответствующего класса.
Программирование на основе интерфейсов используется платформой .NET для того, чтобы при помощи разработанных программистом объектов дополнить стандартные функционачьные возможности библиотеки классов .NET Framework. Использование интерфейсов позволяет также привести работу с разными объектами к общему знаменателю, не зная точного типа объекта. Например, средства форматирования (скажем, форматер SOAP, который используется в данном примере) наследуют интерфейс I Formatter. Программы могут быть написаны безотносительно к какому бы то ни было конкретному (двоичному, SOAP) форматеру, используемому сейчас, или форматеру, который будет использоваться в будущем, и при этом они будут работать должным образом.
CompEbook.ru Железо, дизайн, обучение и другие
Производительность
Возможно, вам понравилась предложенная модель безопасности и простота использования управляемого кода, но вас волнует вопрос о производительности приложений. В то время, когда появились высокоуровневые языки программирования, программистов, пишущих приложения на самых примитивных языках ассемблера, волновал этот же вопрос.Общеязыковая среда выполнения CLR разрабатывалась с прицелом на высокую производительность приложений. Ведь только при первом вызове метода общеязыковая среда выполнения CLR осуществляет проверку правильности и затем выполняет оперативную компиляцию в собственный (внутренний) код, в который встроены средства безопасности, например проверка границ массивов. Когда метод вызывается в следующий раз, непосредственно выполняется собственный (внутренний) код. Схема управления памятью спроектирована так, чтобы достичь высокой производительности. Распределение памяти происходит почти мгновенно, при этом из управляемой динамически распределяемой области памяти берется следующий доступный участок. Освобождение памяти выполняется сборщиком мусора, который реализован на основе эффективного алгоритма.
За все это вы расплачиваетесь тогда, когда при проверке безопасности приходится просматривать стек.
При создании Web-страниц используется компилируемый, а не интерпретируемый код. В результате, ASP.NET-приложения работают намного быстрее, чем ASP-приложения.
В 95 процентах кода надежность и легкость его создания с лихвой компенсирует незначительные потери в производительности. При создании высокопроизводительных серверных приложений также могут использоваться такие технологии как библиотека ATL Server или неуправляемый C++.
CompEbook.ru Железо, дизайн, обучение и другие
NET решает многие проблемы, которые
Платформа . NET решает многие проблемы, которые в прошлом омрачали процесс разработки Windows-приложений. Теперь существует одна для всех поддерживаемых платформой языков программирования парадигма разработки приложений. Нет больше противоречий между ожидаемыми возможностями приложения и возможностями языка программирования, на котором реализуется приложение. Развертывание приложений стало более рациональным и включает совершенную стратегию управления версиями. Метаданные, система безопасности на основе атрибутов, проверка правильности кода и автономность сборок, удовлетворяющих требованиям типовой безопасности, делают разработку безопасных приложений значительно более легкой. Детальнее это будет обсуждаться дальше. Кроме того, платформа .NET обеспечивает создание каркаса (инфраструктуры) для базовых системных служб. Но в случае необходимости вы можете расширить или даже заменить его.Общеязыковая среда выполнения CLR является прочным базисом, который служит отправной точкой при разработке приложений будущего. Общеязыковая среда выполнения CLR представляет собой основу. Ее элементами являются общая система типов CTS, метаданные, спецификация общего языка CLS, а также система виртуального выполнения (Virtual Execution System— VES), которая выполняет управляемый код12. В следующих главах мы увидим, что платформа .NET облегчает разработку как приложений, предназначенных для поставщиков услуг, так и для клиентских решений. Использование унифицированной платформы .NET позволяет компании Microsoft, а также независимым разработчикам более легко реализовывать нужные расширения.
Все это стало возможным благодаря творческому объединению старых технологий — промежуточного языка, проверки типовой безопасности, и, конечно же, метаданных — в общеязыковой среде выполнения CLR. Изучая различные свойства платформы .NET, вы увидите, что метаданные таятся повсюду.
Более подробно затронутые вопросы будут обсуждаться в данной книге в ходе дальнейшего изложения материала. В следующей главе рассматриваются управляемые расширения языка C++.
CompEbook.ru Железо, дизайн, обучение и другие
Сборки
Еще одной функцией общеязыковой среды выполнения CLR является загрузка и запуск .NET-программ. .NET-программы разворачиваются в виде одной или нескольких сборок. Сборкой является один или несколько исполняемых файлов или файлов динамически подключаемых библиотек (DLL) вместе со связанными с ними метаданными. Метаданные, которые описывают всю сборку целиком, хранятся в декларации (манифесте) сборки. Декларация сборки, или манифест сборки содержит, например, список сборок, от которых зависит данная сборка.В нашем примере Serialize (Сериализация) сборка содержит всего лишь один файл, Serialize . ехе. Этот файл включает и метаданные, и код. Поскольку декларация хранится в самой сборке, а не в отдельном файле (как это имеет место для библиотеки типов или системного реестра), она всегда синхронизирована с этой сборкой. На рис. 2.2 представлены метаданные декларации сборки, соответствующей данному примеру [В дисассемблере откройте файл Serialize . ехе и затем щелкните дважды на элементе MANIFEST.]. Обратите внимание на операторы assembly extern. Они указывают, что данная сборка зависит от сборок библиотеки .NET Framework, которые имеют название mscorlib и System.Runtime.Serialization.Formatters.SOAP (Система.Время выполнения.Преобразование в последовательную форму.Форматеры.ЗОАР). С помощью оператора assembly extern указываются также версии сборок, от которых зависит наш пример Serialize.exe.
Могут существовать несколько версий одной сборки, причем номер версии является частью имени сборки. Если необходимо использовать уникальное имя сборки, вы можете использовать открытый/индивидуальный ключ шифрования, для того чтобы сгенерировать уникальное (строгое) имя.
Сборки могут быть развернуты приватно (privately) либо публично (publicly). В случае приватного (privately) развертывания, все необходимые приложению сборки копируются в тот же каталог, в котором находится само приложение. Как мы уже знаем, версия сборки является частью ее имени. Следовательно, несколько версий сборки могут быть развернуты в одном или в разных каталогах и при этом мешать друг другу они не будут. "Проклятие (ад) динамически подключаемых библиотек (DLL)" больше не действует.
Если сборка должна быть общедоступна для совместного использования, в глобальном кэше сборок (Global Assembly Cache — GAC) делается соответствующая запись, позволяющая другим сборкам отыскать ее. Для сборок, при разворачивании которых применяется глобальный кэш сборок GAC, необходимо использовать строгие имена [Более детально это обсуждается в главе 7 "Сборки и развертывание".]. Возможность развертывания сборок, а также функциональная совместимость языков программирования позволяют создавать компоненты почти автоматически.

Рис. 2.2. Декларации сборки Serialize (Сериализация)
CompEbook.ru Железо, дизайн, обучение и другие
Управляемый код
В примере Serialize (Сериализация) второй экземпляр объекта Customer (Клиент) был присвоен той же переменной, которой раньше был присвоен первый экземпляр. При этом для удаления первого экземпляра класса из памяти деструктор не вызывался. В данном примере ни одна из выделенных областей памяти никогда не освобождалась. Для освобождения памяти от объектов, которые являются экземплярами классов, объявленных с помощью ключевого слова _дс (сборщик мусора), платформа .NET использует автоматическую сборку мусора. Если память, выделенная в управляемой динамически распределяемой области памяти, становится висячей или выходит из области видимости, то она заносится в список участков памяти, подлежащих освобождению. Периодически система инициирует процесс сборки мусора. Освободившаяся в результате этого память возвращается в динамически распределяемую область памяти.Благодаря автоматическому управлению памятью утечка памяти в системе исключается. Утечка памяти является одной из самых распространенных ошибок при программировании на языках С и C++. В большинстве случаев за счет использования автоматической сборки мусора распределение памяти в динамически распределяемой области памяти происходит значительно быстрее по сравнению с классическими схемами. Обратите внимание, что такие переменные KaKpcust и plist являются управляемыми указателями на объекты, а не самостоятельными объектами. Именно вследствие этого и возможна сборка мусора.
Сборка мусора — одна из нескольких служб, предоставляемых общеязыковой средой выполнения CLR программам, выполняющимся на платформе .NET [Формально метаданные, общая система типов CTS (Common Type System), спецификация общего языка CLS и виртуальная система выполнения (Virtual Execution System— VES) также являются частью общеязыковой среды выполнения CLR. Прилагательное "общеязыковая" означает, что среда выполнения является общей для всех языков. Система виртуального выполнения VES загружает и выполняет .NET-программы и поддерживает динамическое связывание. Обратитесь к документам, описывающим общеязыковую инфраструктуру (Common Language Infrastructure — CLI), а именно, к разделу "Partition I: Concepts and Architecture", посвященному концепциям и архитектуре. Эти документы переданы для дальнейшего рассмотрения Европейской Ассоциации производителей ЭВМ (European Computer Manufacturers' Association — ЕСМА). Упомянутые документы загружаются вместе с набором инструментальных средств разработки программного обеспечения .NET Framework SDK.].
Данные, участвующие в процессе сборки мусора, инициируемом общеязыковой средой выполнения CLR, называются управляемыми данными. Управляемый код— это код, который способен использовать службы, предоставляемые общеязыковой средой выполнения CLR. .NET-компиляторы, которые генерируют код на языке MSIL, могут генерировать управляемый код.
Управляемый код не удовлетворяет требованиям типовой безопасности автоматически. Язык C++ может послужить тому классическим примером. Чтобы класс участвовал в сборке мусора (т.е. был управляемым), при его объявлении следует использовать атрибут _дс (сборщик мусора). Компилятор для C++ запрещает в таких классах использовать арифметические операции над указателями. Тем не менее, код на C++ не может быть надежно проверен на типовую безопасность, поскольку используются библиотеки на С и C++.
Проверка кода на типовую безопасность происходит перед его компиляцией. Такая проверка является необязательной и может быть пропущена, если код аттестован. Одно из самых существенных отличий проверяемого кода от непроверяемого состоит в том, что в проверяемом коде не могут использоваться указатели. Код, в котором используются указатели, может разрушить общую систему типов CTS и в результате его трансляции может получиться код, не удовлетворяющий типовой безопасности.
Код, который удовлетворяет типовой безопасности, не может быть легко разрушен. Например, перезапись буфера не может повредить другие структуры данных или программы. К коду, удовлетворяющему требованиям типовой безопасности, можно применить политику безопасности [Более подробно этот вопрос обсуждается в главе 13 "Зашита".]. Например, можно разрешить или запретить доступ к некоторым файлам, или элементам пользовательского интерфейса. Вы можете предотвратить выполнение кода, полученного из неизвестных вам источников. Чтобы предотвратить разрушение системы безопасности платформы .NET, вы можете запретить доступ к неуправляемому коду. Благодаря типовой безопасности можно также изолировать выполняемые ветви кода .NET друг от друга [Области приложений (Application Domains) обсуждаются в главе 8 "Классы каркаса .NET Framework".].
CompEbook.ru Железо, дизайн, обучение и другие
Волшебство метаданных
Чтобы решить все проблемы, связанные с разработкой Windows-приложений, платформа .NET должна обладать базовым набором служб, которые в любой момент доступны в любом языке программирования. Чтобы предоставить такие службы, платформа .NET должна иметь достаточно сведений о приложении.Сериализация (преобразование в последовательную форму) объекта может послужить в качестве простого примера. Перед каждым программистом, рано или поздно, возникает проблема сохранения данных. Но зачем каждому программисту вновь изобретать колесо, решая вопрос о том, как следует сохранять вложенные объекты и сложные структуры данных? Зачем каждому программисту понимать, как эти объекты и данные хранятся в разных информационных хранилищах? Платформа .NET позволяет выполнить сериализацию объекта без вмешательства программиста. При желании разработчик может это сделать и самостоятельно.
Чтобы понять, как происходит сериализация объектов, мы рассмотрим относящийся к данной главе пример Serialize (Сериализация). Не станем акцентировать внимание на применяемых приемах программирования. Они будут рассмотрены позже. Сейчас же мы сосредоточимся на используемых в этом примере понятиях.
//Serialize.cs
>fusing
>fusing
>// <Система. Время выполнения.
>// Преобразование в последовательную форму. Форматеры. Soap.dll>
>using namespace System;
>// использование пространства имен Система;
>using namespace System::Collections;
>// использование пространства имен Система:: Коллекции;
>using namespace System::10;
>// использование пространства имен Система:: Ввод-вывод;
>using namespace
>System::Runtime:Serialization::Formatters::Soap; // использование пространства имен
>// Система:: Время выполнения:: Преобразование в последовательную // форму:: Форматеры:: Soap;
>[Serializable]
>// [Преобразование в последовательную форму]
>_gc class Customer
>// класс сборщика мусора Клиент
>{
>public:
>String *pname; // Строка long id; // идентификатор
>};
>_gc class Test
>// класс сборщика мусора Испытание
>{
>public:
>static void Main()
>{
>ArrayList *plist = new ArrayList;
>Customer *pcust = new Customer; // новый Клиент pcust->pname = "Charles Darwin"; // Чарльз Дарвин pcust->id = 10; // идентификатор plist->Add(pcust); // Добавить
>pcust = new Customer; // новый Клиент pcust->pname = "Isaac Newton"; // Исаак Ньютон pcust->id =20; // идентификатор plist->Add(pcust); // Добавить
>for (int i=0; i < plist->get_Count(); i++)
>{
>Customer *pcust = // Клиент
>dynamic_cast
>//мусора *> (plist->get_Item(i)); Console::WriteLine( "{0}: {!}",
>pcust->pname, _box(pcust->id)); // идентификатор } ~
>Console::WriteLine("Saving Customer List"); // ("Сохранение списка клиентов"); FileStream *ps = new FileStream(
>"cust.txt", FileMode::Create); // Создать SoapFormatter *pf = new SoapFormatter; pf->Serialize(ps, plist);
>// Преобразование в последовательную форму; ps->Close ();
>Console::WriteLine("Restoring to New List");
>// "Восстановить в новом списке");
>ps = new FileStream("cust.txt", FileMode::Open); // Открыть
>pf = new SoapFormatter();
>ArrayList *plist2 =
>dynamic_cast
>(pf->Deserialize(ps)); ps->Close();
>for (int i=0; i < plist->get_Count(); i++) {
>Customer *pcust = // Клиент
>dynamic_cast
>(plist->get_Item(i)); Console::WriteLine( "{0}: {!}",
>pcust->pname, _box(pcust->id)); // идентификатор } } };
>void main(void) {
>Test::Main(); }
Мы определили класс Customer (Клиент) с двумя полями: pname и id (идентификатор). Сначала программа создает экземпляр коллекции, в котором будут храниться экземпляры класса Customer (Клиент). Мы добавляем в коллекцию два объекта Customer (Клиент), а затем распечатываем содержимое коллекции. Потом коллекция сохраняется на диске. Она восстанавливается в новый экземпляр коллекции и выводится на печать. Распечатанные теперь данные будут идентичны данным, которые были распечатаны перед сохранением коллекции [В результате инсталляции примеров программ, которыми сопровождается данная книга, должен быть создан пример, готовый к выполнению. Если он отсутствует, щелкните два раза на том файле решения Visual Studio.NET, который имеет расширение .sin. Когда откроется Visual Studio, нажмите комбинацию клавиш CtrI-F5 для того чтобы построить и выполнить пример.]. Если вы запустите приложение и откроете получившийся в результате файл cust. txt, вы увидите, что он содержит данные в необычном XML-формате, который известен как простой протокол доступа к объектам (Simple Object Access Protocol -r- SOAP). Этот протокол специально разработан для хранения и передачи объектов.
Мы не писали код для того, чтобы указать, как сохраняются или восстанавливаются поля объекта Customer (Клиент). Но мы определили формат (SOAP) и создали среду, в которой затем были сохранены данные. Классы библиотеки .NET Framework сгруппированы таким образом, что каждый выбор — среды, формата и способа загрузки (восстановления) или сохранения объекта — можно сделать независимо друг от друга. Такого типа разделение классов существует в библиотеке .NET Framework повсеместно.
Класс Customer (Клиент) имеет атрибут Serializable (Преобразуемый в последовательную форму, упорядочиваемый). Аналогично поле имени имеет атрибут public (общедоступный). Когда вы не хотите, чтобы объект можно было преобразовывать в последовательную форму, не приписывайте ему соответствующий атрибут. Если будет предпринята попытка сохранения объекта, который не имеет атрибута Serializable (Преобразуемый в последовательную форму, упорядочиваемый), возникнет исключительная ситуация и произойдет отказ в работе программы [Выделите в программе атрибут Serializable (Преобразуемый в последовательную форму, упорядочиваемый) как комментарий и посмотрите, что при этом произойдет. Для того чтобы ввести комментарий в программу, вы можете использовать синтаксис языка С и C++, то есть применять пары символов /* и */ в качестве открывающей и закрывающей цепочек комментария.].
При программировании на платформе .NET атрибуты можно применять повсеместно. Использование атрибутов позволяет описать способ обработки кода и данных библиотекой классов .NET Framework. При помощи атрибутов можно также указать используемую модель безопасности. Атрибуты можно использовать для того, чтобы организовать с помощью каркаса синхронизацию многопоточной обработки. Благодаря использованию атрибутов становится очевидной идея удаленного размещения объектов.
Чтобы указать, что объект может сохраняться и восстанавливаться библиотекой .NET Framework, компилятор добавляет атрибут Serializable (Преобразуемый в последовательную форму, упорядочиваемый) к.метаданным класса Customer (Клиент). Метаданные представляют собой дополнительную информацию о программном коде и данных, которая содержится в самом .NET-приложении. Метаданные, являющиеся характерным свойством общеязыковой среды выполнения CLR, могут содержать также и другую информацию о коде, включая:
Метаданные хранятся вместе с программным кодом, а не в каком-то центральном хранилище наподобие системного реестра в операционной системе Windows. Способ их хранения не зависит от используемого языка программирования. Благодаря всему этому .NET-приложения содержат самоописания. Во время выполнения приложения может быть выдан запрос метаданных с целью получения информации о коде (например, о наличии или отсутствии атрибута Serializacle (Преобразуемый в последовательную форму, упорядочиваемый)). Вы можете расширить метаданные, дополнив их своими собственными атрибутами.
В нашем примере библиотека .NET Framework может запросить метаданные для того, чтобы получить информацию о структуре объекта Customer (Клиент), которая затем используется для сохранения и восстановления объекта.
CompEbook.ru Железо, дизайн, обучение и другие
Возможность взаимодействия языков, или функциональная совместимость
Так как компиляторы всех языков программирования транслируют на один общий промежуточный язык и используют общую библиотеку базовых классов (Base Class Library), то открывается возможность взаимодействия поддерживаемых языков. Иными словами, поддерживаемые языки могут в определенных пределах рассматриваться как функционально совместимые. Но поскольку все части общей системы типов CTS реализованы не во всех языках, не удивительно, что один язык может обладать свойствами, которые отсутствуют в другом.Спецификация общего языка (Common Language Specification — CLS) определяет подмножество общей системы типов CTS, содержащее основные функциональные возможности, которые должны быть реализованы во всех .NET-языках для того, чтобы они могли взаимодействовать друг с другом. Именно согласно этой спецификации класс, написанный на Visual Basic.NET, может быть производным от класса, написанного на управляемом C++ или С#. Следствием использования спецификации общего языка CLS является возможность межъязыковой отладки. Примером соблюдения правил спецификации общего языка CLS является то, что вызовы методов могут не поддерживать переменное число аргументов, хотя такая конструкция и может быть выражена в языке MSIL.
Требование совместимости со спецификацией общего языка CLS предъявляется только к общедоступным свойствам. Например, класс может содержать приватный член, который не совместим со спецификацией общего языка CLS, и при этом являться базовым классом для класса, реализованного на другом языке, поддерживаемом платформой .NET. Например, общедоступные (public) и защищенные (protected) имена классов, код которых написан на языке C++ и С#, не должны отличаться только регистром используемых символов, поскольку в таких языках как VB.NET регистр клавиатуры (регистр прописных и строчных букв) не учитывается. Но имена приватных (private) полей могут различаться именно регистром клавиатуры (т.е. прописными и строчными буквами).
Компанией Microsoft предлагается несколько языков программирования, совместимых со спецификацией общего языка CLS: C#, Visual Basic.NET, и C++ с управляемыми расширениями. Независимые разработчики предлагают и другие языки программирования (их уже больше дюжины). Компания ActiveState занимается реализацией языков Perl и Python. Компания Fujitsu реализует язык COBOL.
CompEbook.ru Железо, дизайн, обучение и другие
Architecture Net
Абстрактные типы
Значение ключевого слова _abstract (абстрактный) очень похоже на значение ключевого слова abstract (абстрактный) в языке Java. Оно также напоминает о сложившейся традиции рассматривать класс C++, содержащий хотя бы одну чистую (pure) виртуальную функцию, как абстрактный. Ключевое слово _abstract (абстрактный) делает это объявление явным. Как и в случае ключевого слова _interface (интерфейс), ключевое слово _abstract (абстрактный) используется для обозначения того, что класс определяет некоторые общие обязательные соглашения между кодом, реализующим методы этого абстрактного класса, и кодом клиентов, вызывающих эти методы. Обратите внимание, что, если абстрактный класс определяется как управляемый, в его описании следует использовать также и ключевое слово _gс (сборщик мусора).Абстрактный класс подобен интерфейсу в том, что он является лишь средством проявления полиморфизма, а создать экземпляр такого класса непосредственно нельзя. Однако, в отличие от интерфейса, абстрактный класс может содержать реализации нескольких, или даже всех своих методов. Абстрактный класс может быть использован как базовый для других классов, экземпляры которых можно инициализировать, причем переменная абстрактного ссылочного типа (т.е. ссылка или указатель, но не тип значения) может использоваться для обращения к экземплярам классов, производных от абстрактного класса.
Обратите внимание на то, что использование ключевого слова _abstract (абстрактный) вместе с _interface (интерфейс) (это слово не является расширением управляемости) является избыточным, так как интерфейсы по определению являются абстрактными. Ключевое слово _abstract (абстрактный) нельзя использовать в комбинации с ключевыми словами _value (значение) или _sealed (конечный). Ключевое слово _value (значение) указывает на то, что объект содержит непосредственно данные, а не ссылки на объекты в динамически распределяемой области памяти. Это значит, что можно создавать экземпляры такого класса, а следовательно, он не может быть абстрактным. Ключевое слово _sealed (конечный) означает, что класс не может быть базовым для других классов, что, очевидно, противоречит концепции абстрактного класса. В следующем фрагменте приведен пример типичного использования ключевого слова _abstract (абстрактный).
//AbstractExample.срр
#using
using namespace System;
// использовать пространство имен Система;
_abstract class AbstractClass
// абстрактный класс AbstractClass
{
public:
virtual void Methodlf) = 0; // виртуальный; не реализован здесь
virtual void Method2() // виртуальный; реализован здесь
{
Console::WriteLine("Method2");
}
};
class DerivedClass : public AbstractClass
{
public:
void Method1() // реализован здесь
{
Console::WriteLine("Method1");
}
};
void main(void) » {
//AbstractClass *pac = new AbstractClass; // ошибка
AbstractClass *pac = new DerivedClass; // указатель
pac->Methodl();
pac->Method2();
AbstractClass &ac = *new DerivedClass; // ссылка
ас.Methodl();
ac.Method2() ; }
Профамма напечатает:
Method1
Method2
Method1
Method2
CompEbook.ru Железо, дизайн, обучение и другие
Атрибуты C++
Visual C++.NET поддерживает использование атрибутов, позволяющих создавать Обычный неуправляемый код, такой, как компоненты модели компонентных объектов Microsoft (COM), даже с меньшими усилиями, чем раньше. Кроме того, Visual C++.NET Поддерживает новые возможности .NET, такие, как Унифицированная модель событий (Unified Event Model). Изначально атрибуты, относящиеся к модели компонентных объектов Microsoft (COM), использовались в отдельном файле IDL (Interface Definition Language — язык описания интерфейсов) для описания информации о типе компонентов Модели компонентных объектов Microsoft (COM). Теперь же эти атрибуты можно использовать непосредственно в исходном коде C++, поэтому необходимость в отдельном файле IDL отпадает. Компилятор использует эти атрибуты для генерации исполняемого Кода и информации о типе. Одно из преимуществ применения атрибутов C++ для программирования с использованием модели компонентных объектов Microsoft (COM) со-СТоит в том, что вам придется возиться только с исходными файлами C++, но не с фай-Дами IDL или RGS (Registry Script — Сценарий системного реестра). Это делает проекты С компонентами на основе модели компонентных объектов Microsoft (COM) более простыми и понятными.Использование атрибутов значительно изменяет язык C++ и расширяет возможности Модульности программ. Источники атрибутов, в качестве которых могут выступать независимые динамически подключаемые библиотеки (DLL), реализуют механизм динамического расширения возможностей компилятора C++. Атрибуты предназначены для по-Ьышения производительности труда программистов, особенно при разработке компонентов на основе модели компонентных объектов Microsoft (COM). Генерируемый при Использовании атрибутов код автоматически вставляется в конечный файл. Атрибуты используются для создания кода таких элементов:
Хотя данная глава называется "Программирование на управляемом C++", а атрибуты используются при создании управляемого кода, в этом разделе мы рассмотрим лишь использование атрибутов для создания неуправляемого кода на основе модели компонентных объектов Microsoft (COM). Информацию об использовании атрибутов при работе с управляемыми событиями можно найти в разделе этой главы "События". Информацию об использовании атрибутов для других целей можно найти в документации по комплексу инструментальных средств разработки программ .NET ( NET SDK). Атрибуты, определяемые разработчиком, рассмотрены в главе 8 "Классы каркаса .NET Framework".
Продемонстрируем необходимость использования атрибутов в C++ на примере описания функции AddEmUp, приведенного в следующей строке кода. Заметим, что в рамках ANSI C++ эта функция не может быть описана полностью. Так, невозможно указать, какие из аргументов являются входными, а какие — выходными Обычно эта дополнительная информация, важная при создании кода на основе модели компонентных объектов Microsoft (COM), использующего маршалинг, описывается с помощью атрибутов языка описания интерфейсов (IDL) в отдельном файле IDL. При этом атрибуты языка описания интерфейсов (IDL) заключаются в квадратные скобки и могут использоваться для описания многих черт компонентов на основе модели компонентных объектов Microsoft (СОМ), в том числе интерфейсов, соклассов и библиотек типов.
// нет важной информации маршалинга
HRESULT AddEmUp (int i, int j, int.* psum) ;
Приведенную функцию C++ можно описать более подробно в файле IDL, как это показано ниже. Здесь используются атрибуты in (входной), out (выходной) и retval.
.HRESULT AddEmUp(
[in]int i, [in]int 3, [out,retval]int *psum);
Для синтаксического разбора файла IDL, создания библиотек типов и исходных файлов, использующих маршалинг (для заместителя (proxy) и заглушки (stub)), для методов интерфейса модели компонентных объектов Microsoft (COM) (поддерживаются также интерфейсы удаленного вызова процедур (RPC)) используется компилятор языка описания интерфейсов IDL, разработанный фирмой Microsoft, — Midi. exe.
Добавлять атрибуты в исходный код можно вручную Однако полезно будет увидеть, как атрибуты вставляются в проект, генерируемый Мастером приложений библиотеки шаблонных классов (ATL) на основе модели компонентных объектов Microsoft (COM) (ATL COM Application Wizard), который является высокопроизводительным инструментальным средством, предназначенным для создания компонентов на основе модели компонентных объектов Microsoft (COM). На рис. 3.4 показано, как разрешить использование атрибутов в Мастере приложений библиотеки шаблонных классов (ATL) на основе модели компонентных объектов Microsoft (COM) (ATL COM Application Wizard).
Рис. 3.4. Окно ATL Project Wizard ( Мастер проектов на основе библиотеки шаблонных классов (ATL)) с установленным флажком Attributed
Следующий фрагмент кода взят из файла MyATLProject, созданного Мастером проектов на основе библиотеки шаблонных классов (ATL) (ATL Project Wizard) при установленном флажке Attributed Обратите внимание, что атрибут module (модуль) применен к проекту в целом, благодаря чему в проекте автоматически генерируются функции DllMain, DllRegisterServer и DllUnregisterServer.
// MyATLProject.срр: Реализация экспорта динамически
// подключаемой библиотеки (DLL).
#include "stdafx.h"
#include "resource.h"
// Атрибут module (модуль) вызывает автоматическую генерацию
// DllMain, DllRegisterServer и DllUnregisterServer
[ module(dll, uuid = "{50434D6D-AAEA-405C-AC49-B9CA769E5D6D}",
// модуль
name = "MyATLProject", // название
helpstring = "MyATLProject 1.0 Type Library",
// Библиотеки Типа
resource_name = "IDR_MYATLPROJECT") ];
На рис 3 5 показано использование Мастера простых объектов на основе библиотеки шаблонных классов (ATL) (ATL Simple Object Wizard) для добавления к проекту простого класса на основе модели компонентных объектов Microsoft (COM), называющегося MyATLClass.
Рис. 3.5. ATL Simple Object Wizard (Мастер простых объектов на основе библиотеки шаблонных классов (ATL))
Мастер простых объектов на основе библиотеки шаблонных классов (ATL) (ATL Simple Object Wizard) добавит приведенный ниже исходный код с атрибутами в файл MyAtlClass .h, а не в файл IDL. Обратите внимание, что атрибуты object (объект), uuid (universally unique identifier— универсальный уникальный идентификатор) и dual (двойной) используются для описания интерфейсов, а атрибуты coclass, progid (программный идентификатор) и version (версия)— для описания классов на основе модели компонентных объектов Microsoft (COM). Эти атрибуты находятся непосредственно в исходном коде C++, поэтому файл IDL не нужен.
// IMyAtlClass
[
object, // объект
uuid("lF9401D8-58BF-469D-845B-A2069CBAFC84") ,
dual, helpstring("IMyAtlClass Interface"), // двойной, Интерфейс
pointer_default(unique) // уникальный
]
_interface IMyAtlClass : IDispatch // интерфейс
{
};
// CMyAtlClass
[
coclass,
threading("apartment") ,
vi_progid("MyATLProject.MyAtlClass"),
progid("MyATLProject.MyAtlClass.1"), // программный идентификатор
version(1.0), // версия
uuid("B3321AD5-3ACE-4820-B134-35FD67120A48"),
helpstring("MyAtIClass Class") // Класс
{
class ATL_NO_VTA5LE CKyAtlClass : // классс
public IMyAtlClass
{
public:
CMyAtlClass()
{
}
DECLARE_PROTECT_FINAL_CONSTRuCT()
HRESULT FinalCor.struct ()
{
return S_OK;
}
void FinalReiease()
{
}
public:
};
В приведенном коде используется атрибут сoсlass, но при этом опущена некоторая информация, присутствующая в коде, сгенерированное прежними версиями АТ!_. Например, отсутствуют карты объектов, карты интерфейсов и сценарии системного реестра. Атрибут coclass обеспечивает все эти, казалось бы, пропущенные возможности. Пропущенный код будет автоматически добавлен компилятором при обработке атрибута coclass.
На рис. 3.6 показан диалог Add Method (Добавление метода), используемый для добавления метода AadEmUp. Обратите внимание на указание атрибутов in (входной), cut (выходной)и retval.
Они обусловливают добавление в файл KyAr iciass . h приведенного ниже кода. Еще раз обратим ваше внимание на то, что атрибуты используются и исходном файле СРР, а не в отдельном файле IDL.
Создание простого проекта на основе библиотеки шаблонных классов ATL (Simple ATL Project)
Создайте проект на основе ATL Server (ATL Server Project):
1. Выберите пункт меню Fiie => New (Файл => Создать). Откроется диалог New Project (Создание проекта).
2. Выберите Visual C++ Projects (Проекты на Visual C++) в окне Project Types (Типы проектов).
3. Выберите ATL Project (Проект на основе библиотеки шаблонных классов ATL) в окне Templates (Шаблоны).
4. Введите MyATLProject в поле Name (Название)
5. Задайте в поле Location (Расположение) папку, в которой будет сохранен проект.
6. Щелкните на кнопке ОК для вызова Мастера проектов на основе библиотеки шаблонных классов (ATL) (ATL Project Wizard).
7. Выберите вкладку Application Settings (Параметры приложения).
8. Убедитесь, что флажок Attributed установлен.
9. Щелкните на кнопке Finish (Готово). Создайте простой объект на основе библиотеки шаблонных классов (ATL):
10. Выберите пункт меню Project => Add Class (Проект => Добавить класс). Откроется диалог Add Class (Добавление класса).
11. Выберите ATL Simple Object (Простой объект на основе библиотеки шаблонных классов (ATL)) в качестве шаблона (Template).
12. Щелкните на кнопке Open (Открыть) для вызова Мастера простых объектов на основе библиотеки шаблонных классов (ATL) (ATL Simple Object Wizard).
13. Введите MyAtlClass в поле Short name (Короткое название).
14. Щелкните на кнопке Finish (Готово) для создания проекта.
Добавьте метод в простой объект на основе библиотеки шаблонных классов (ATL):
15. Щелкните правой кнопкой на интерфейсе IMyAtlClass в окне Class View (Просмотр классов).
16. Выберите пункт меню Add => Add Method (Добавить => Добавить метод). Откроется Мастер добавления методов (Add Method wizard).
17. В качестве названия метода введите AddEmUp в поле Method name (Название метода).
18. Выберите LONG в списке Parameter type (Тип параметра).
19. В качестве имени параметра введите i в поле Parameter name (Имя параметра).
20. Установите флажок in (входной).
21. Щелкните на кнопке Add (Добавить).
22. Выберите LONG в списке Parameter type (Тип параметра).
23. В качестве имени параметра введите j в поле Parameter name (Имя параметра).
24. Установите флажок in (входной).
25. Щелкните на кнопке Add (Добавить).
26. Выберите LONG в списке Parameter type (Тип параметра).
27. В качестве имени параметра введите psum в поле Parameter name (Имя параметра).
28. Установите флажки out (выходной) и retval.
29. Щелкните на кнопке Add (Добавить).
30. Щелкните на кнопке Finish (Готово).
Рис. 3.6. Диалог Мастера добавления методов ( Add Method wizard) позволяет указать атрибуты параметра
// IMyAtlClass
[
object, // объект
uuid("lF9401D8-58BF-469D-845B-A2069CBAFC84") ,
dual, helpstring("IMyAtlClass Interface"), // двойной,
// интерфейс
pointer_default(unique) // уникальный
]
_interface IMyAtlClass : IDispatch // интерфейс
[id(l), helpstring("method AddEmUp")] HRESULT AddEmUp([in]
LONG i, [in] LONG j, [out,retval] LONG* psum);
};
Единственное, что осталось сделать в рассматриваемом примере, — вручную реализовать метод AddEmUp в файле MyAtlClass . cpp и скомпилировать приложение. Реализация этого метода приведена ниже
STDMETHODIMP CMyAtlClass::AddEmUp(LONG i, LONG ], LONG* psum)
{
// TODO: Add your implementation code here
// TODO: Добавьте ваш код реализации здесь
*psum = i+j; // добавлен вручную
return S_OK;
}
Создав приложение, его можно протестировать с помощью программы-клиента на основе моде in компонентных объектов Microsoft (COM) Мы не будем рассматривать здесь как это можно Lделать поскольку создание такой программы предполагает применение обычного стиля программирования на устаревшем Visual Basic или Visual C++ Для полччения более подробной информации оекомендлем обратиться к документации по Visual Studio или к любой из множества отличных книг в этой области (например, Understanding and Programming СОМ+, написанной Робертом Дж Обергом (Robert J. Oberg))
CompEbook.ru Железо, дизайн, обучение и другие
Делегаты
Ключевое слово _delegate (делегат) используется для объявления класса-делегата, основанного на описании сигнатуры метода. Делегаты очень сходны с указателями на функции в обычном C++, с той лишь разницей, что делегат может указывать только на метод управляемого класса. Чаще всего в приложениях .NET Framework делегаты используются для реализации функций обратного вызова или обработки событий. Однако они могут найти применение во всех случаях, когда необходимо вызывать методы динамически.В .NET Framework определены (как абстрактные классы) два типа делегатов — System: : Delegate (Система::Делегат) и System: :MulticastDelegate. Эти два типа делегатов используются как базовые классы для одноадресных (или делегатов единственного приведения — single-cast) и многоадресных (или групповых — multicast) делегатов соответственно. Одноадресный делегат связывает указатель на метод с методом одного управляемого объекта, тогда как многоадресный делегат связывает указатель на метод с одним или несколькими методами управляемого объекта. Вызов одноадресного делегата приводит к вызову только одного метода, а при вызове многоадресного делегата может выполняться неограниченное количество методов. В связи с тем, что многоадресный делегат можно использовать и для вызова одного метода, одноадресная форма делегата является излишней. Обычно в программах используются лишь многоадресные делегаты.
Встретив в программе ключевое слово _delegate (делегат) компилятор создает особый управляемый класс, производный от System: :MulticastDelegate. Конструктор этого класса имеет два аргумента: указатель на экземпляр управляемого класса (который равен нулю, если делегат связывает статический метод), и сам метод, вызываемый с помощью делегата. Этот класс также содержит метод Invoke (Запустить), сигнатура которого совпадает с сигнатурой метода, вызываемого делегатом. Следующий пример демонстрирует использование делегатов:
//DelegateExample.срр
#using
using namespace System;
// использовать пространство имен Система;
// определить управляемые классы для использования
// в качестве делегатов
_delegate int SomeDelegate // делегат
(int i, int j);
_delegate // делегат
void SomeOtherDelegate (int i);
_gc class SomeClass
// класс сборщика мусора SomeClass содержит методы,
// вызываемые делегатами
{
public:
int SomeMethod(int i, int j)
{
Console::WriteLine(
"SomeMethod({0}, {!})", _box(i), _box(j));
return i+j; }
static int SomeStaticMethod(int i, int j) // статический
{
Console::WriteLine(
"SomeStaticMethod({0}, {!})", _box(i), _box(j));
return i+j; }
void SomeOtherMethod(int i) {
Console::WriteLine(
11 SomeOtherMethod ({0}) ", _box(i) ) ;
}
};
Void main ()
{
SomeDelegate *pscd; int sum; // сумма
// связать делегат с нестатическим методом
// требуется экземпляр
SomeClass SomeClass * psc = newSomeClass(); pscd = // создать экземпляр класса делегат sc new SomeDelegate(
psc, SSomeClass::SomeMethod); // нестатический sum = pscd->Invoke(3, 4); // вызвать метод через делегат // сумма = pscd->Bbi3BaTb (3, 4); Console::WriteLine(sum); // сумма
// связать делегат со статическим методом, - нет нужды
// ни в каком экземпляре
pscd = // создать другой экземпляр класса делегата sc new SomeDelegate(
О, SSomeClass::SomeStaticMethod); // статический sum = pscd->Invoke(3, 4); // вызвать метод через делегата // сумма = pscd->Bbi3B3Tb (3, 4); Console::WriteLine(sum); // сумма
// объединить два делегата SomeClass * pscl = new SomeClass(); SomeClass * psc2 = new SomeClass(); SomeOtherDelegate *pmcdl = new SomeOtherDelegate(
pscl, &SomeClass::SomeOtherMethod); SomeOtherDelegate *pmcd2 = new SomeOtherDelegate(
psc2, SSomeClass::SomeOtherMethod); SomeOtherDelegate *pmcd =
static_cast
// Объединение делегатов pmcdl, pmcd2)); pmcd->Invoke(1); // Вызвать }
SomeMethod(3, 4)
7
SomeStaticMethod(3, 4)
7
SomeOtherMethod(I)
SomeOtherMethod(1)
CompEbook.ru Железо, дизайн, обучение и другие
Директива #using и оператор using
Директива fusing делает доступной для компилятора информацию о типах, содержащуюся в сборке. Сборка содержит метаданные (описание информации о типах) и код на промежуточном языке IL. C6opKamscorlib.dll содержит описания многих полезных стандартных классов, определенных в .NET Framework, в том числе класса Console (Консоль), использовавшегося в предыдущем примере, и класса Object (Объект), который является базовым для всех управляемых классов. Добавим, что директива #us_ng совершенно не похожа на директиву #include, вставляющую в компилируемый файл некоторый другой исходный файл. Как отмечено выше, директива fusing скорее напоминает по совершаемым действиям директиву # import.В предыдущем примере System (Системное пространство имен) предсташшет пространство имен C++, прямо соответствующее пространству имен .NET, имеющему то же название. Полное название класса состоит из названия пространства имен, за которым следуют два двоеточия и название класса, например, System: :Console (Система::Консоль) Хотя выражение using namespace, в предыдущем примере не используется, оно позволяет использовать короткие имена классов, например, Console (Консоль). Обратим ваше внимание на то, что выражение using namespace (определенное стандартом ANSI C++) и директива fusing (определенная в Microsoft C++) — совершенно разные вещи. Приведем пример использования выражения using namespace, позволяющего заменить полное имя System: : Console (Система.:Консоль) укороченным Console (Консоль):
//HelloKorld.cpp
fusing
using namespace System;
// использовать пространство имен Система;
// этот оператор позволяет использовать короткие имена классов
void main(void) {
Console::WriteLine("Hello World"); // "Привет, Мир"
// пространство имен опущено
}
CompEbook.ru Железо, дизайн, обучение и другие
Интерфейсы
Ключевое слово _interface (интерфейс) технически не относится к расширению управляемости, так как его можно использовать и в управляемом, и в неуправляемом коде. Однако оно часто используется при создании управляемого кода, поэтому стоит остановиться для его рассмотрения.Интерфейсы используются как обобщенные базовые типы для классов, при реализации которых применяются некоторые общие соглашения (контракты). Эти контракты используются для согласования реализации основной программы и программы-клиента посредством определения общего полиморфного набора методов. Интерфейс можно считать крайней формой абстрактного класса, поскольку цели их существования сходны, но интерфейсы — наименее конкретная его разновидность. Так сложилось, что программисты, работающие с C++, используют термин "интерфейс" для обозначения любого класса, содержащего лишь чистые виртуальные методы. Поэтому новое ключевое слово _interface (интерфейс) лишь делает эту договоренность явной.
Класс, определенный с использованием ключевого слова _interface (интерфейс), может содержать лишь общедоступные (public) чистые виртуальные методы. В частности, ни один из методов класса не должен быть реализован, класс не может содержать статические или нестатические элементы данных, конструкторы, деструкторы, статические методы, и не может перегружать операторы. Интерфейс может быть потомком любого количества базовых интерфейсов, но не потомком какого бы то ни было абстрактного или неабстрактного класса. Обратите внимание, что, хотя интерфейс не может содержать элементы данных, он может содержать свойства (доступ к которым осуществляется методами получения/установки (get/set)). О свойствах будет рассказано ниже. Как и в случае абстрактных классов, создать экземпляр интерфейса нельзя, так что они используются как полиморфные базовые классы.
В описании интерфейса можно использовать только спецификатор общего доступа (public); однако его использование не обязательно, поскольку в качестве спецификатора доступа по умолчанию принимается именно public (общедоступный). Исходя из того, что задача интерфейса — определять базовый контракт для производных классов, несложно сделать вывод, что описывать интерфейс с ключевым словом _sealed (конечный) бессмысленно.
К управляемым интерфейсам (т.е. определенным с ключевым словом _дс (сборщик мусора)) предъявляются некоторые дополнительные требования. Они не могут быть производными от неуправляемых интерфейсов. Однако они могут быть непосредственными потомками произвольного количества управляемых интерфейсов. Следующий фрагмент представляет пример типичного использования ключевого слова _interface (интерфейс):
//InterfaceExample.срр
fusing
using namespace System;
// использовать пространство имен Система;
_interface Somelnterfасе // интерфейс
{
public:
virtual void Methodl() = 0; // чистый виртуальный явный
void Method2(); // чистый виртуальный подразумеваемый
};
class DerivedClass : public Somelnterface
{
public:
void Methodl() // реализован здесь
{
Console::WriteLine("Methodl");
}
void Method2() // реализован здесь
{
Console::WriteLine("Method2");
}
};
void main(void)
{
//Somelnterface *psi = new Somelnterface; // ошибка
Somelnterface *psi = new DerivedClass; // указатель
psi->Methodl();
psi->Method2();
Somelnterface &si = *new DerivedClass; // ссылка
si.Methodl ();
si.Method2 () ;
}
Программа напечатает:
Method1
Method2
Method1
Method2
CompEbook.ru Железо, дизайн, обучение и другие
Использование расширений управляемого C++
При разработке управляемого кода на Visual C++ используются несколько новых ключевых слов, а расширение компилятора C++, позволяющее создавать приложения для .NET, вызывается с помощью параметра /CLR (Компиляция для выполнения в общеязыковой среде). Этот параметр указывает компилятору, что в конечном файле следует применять набор инструкций промежуточного языка IL, а не обычный набор инструкций процессора. Новые ключевые слова используются при создании управляемого кода и не поддерживаются при создании обычного неуправляемого кода. Хотя наличие или отсутствие параметра /CLR (Компиляция для выполнения в общеязыковой среде) полностью определяет, будет ли компилятор генерировать управляемый (на промежуточном языке IL) или неуправляемый код, можно задавать режим компиляции для отдельных частей программы. Это осуществляется с помощью прагм #pragma:#pragma managed
// Последующий код компилируется как управляемый
ttpragma unmanaged
// Последующий код компилируется как неуправляемый
Если задан параметр компилятора /CLR (Компиляция для выполнения в общеязыковой среде), то при отсутствии директив #pragma исходный код по умолчанию компилируется как управляемый. При отсутствии параметра /CLR (Компиляция для выполнения в общеязыковой среде) прагмы #pragma компилятором игнорируются, а код компилируется как неуправляемый.
Для использования возможностей расширения управляемости в исходный файл следует вставить директиву fusing с указанием сборки (assembly) mscorlib.dll, содержащей необходимую для работы управляемого кода информацию о типах. Такие сборки являются расширением для платформы .NET и обычно состоят из файлов DLL (или ЕХЕ). Кроме того, почти всегда определяется, что будет использовано пространство имен System (Системное пространство имен); это, однако, не обязательно для применения управляемого кода. Концепция пространств имен в C++ прямо копирует концепцию пространств имен многоязычной платформы .NET, представляющей собой иерархию имен. Эти два аспекта разработки кода для .NET обусловливают необходимость включения в начало исходного файла следующих двух строк:
fusing
// Требуется для управляемого кода на C++
using namespace System;
// используется пространство имен Система
// Не требуется, но обычно используется
Директива препроцессора fusing похожа на директиву #import в прежних версиях Visual C++ тем, что делает доступной для компилятора информацию о типах. В случае директивы #import информация о типах содержалась в библиотеках типов, обычно являвшихся файлами TLB, DLL, OCX или ЕХЕ. В случае директивы #using информация о типах представлена в форме метаданных, содержащихся в сборке .NET. Сборка mscorlib.dll содержит информацию о типах, необходимую всем приложениям .NET, включая информацию о базовом классе, являющемся предком всех управляемых классов, — классе System: :0bject (Система::Объект). Заметим, что в такой записи System (Системное пространство имен) обозначает пространство имен, a Object (Объект) — имя корневого класса иерархии управляемых типов.
CompEbook.ru Железо, дизайн, обучение и другие
Класс System::Array (Система::МAССИВ)
В отличие от массивов в обычном C++, которые являются простым типом указателя, управляемые массивы являются полноценными управляемыми объектами, расположенными в динамически распределяемой области. System: : Array (Система::Массив) — абстрактный класс, являющийся базовым для всех управляемых массивов. Для определения неуправляемых массивов можно использовать синтаксис обычного C++; для определения же управляемых массивов следует использовать либо ключевое слово _дс (сборщик мусора), либо указывать, что элементы массива относятся к управляемому типу. Далее приведены примеры определения массивов. Ключевое слово _дс (сборщик мусора) и управляемые типы подробнее рассмотрены ниже. Обратите внимание на две закомментированные строки, в которых при определении массива задается его величина. Величину массива можно задавать при определении неуправляемого (располагаемого в стеке) массива, но не при определении управляемого массива (располагаемого в динамически распределяемой области). Причина в том, что, подобно всем остальным управляемым типам, управляемый массив располагается в динамически распределяемой области, а не в стеке.//ArraySyntax.срр
fusing
using namespace System;
// использовать пространство имен Система;
ttpragma warning(disable : 4101)
// уничтожить предупреждение о переменной, на которую нет ссылки:
// предупреждение (отключить: 4101)
void main(void) {
// традиционный синтаксис неуправляемого массива
int *pintUnManagedArrayOnHeap = new int [5];
int intUnManagedArray[5]; // нет ошибки для неуправляемого
// массива
// синтаксис управляемого массива,
// используется ключевое слово _дс (сборщик мусора) int intManagedArrayOnHeap _дс[] = new int _дс[5]; //int intManagedArray _gc[5]; // ошибка для управляемого
// массива
// синтаксис управляемого массива, используется
// управляемый тип элемента
String *strManagedArrayOnHeap[] = new String* [5]; // Строка
//String *strManagedArray[5]; // ошибка для управляемого
// массива }
Управляемые массивы имеют некоторые дополнительные, по сравнению с неуправляемыми массивами, свойства и ограничения.
Следующий пример показывает, как можно использовать обработчик исключений при попытке доступа к несуществующему элементу управляемого массива. Обратите внимание, что массив содержит пять элементов, а в цикле производится попытка установить значение шестого. Программа в обычном C++ выполнила бы такое действие, изменив содержимое памяти за пределами массива. Никто не скажет точно, чем это могло бы закончиться. При проверке корректности адреса выполняются два действия: во-первых, предотвращается изменение содержимого памяти за пределами массива; во-вторых, программе сообщается, что возникла подобная ситуация, тем самым давая возможность исправить ошибку еще на стадии тестирования. В обычном C++ такая ошибка часто не проявляется до тех пор, пока программа, по непонятным причинам, не прекращает работу, обычно в месте кода, далеко отстоящем от самой ошибки. И, разумеется, согласно закону Мэрфи, эта ошибка обнаруживается только тогда, когда программа уже передана заказчику. //IndexOutOfRangeException.срр
#using
// использовать пространство имен Система/void main () {
int intArray _gc[]= new int _gc[5]; // сборщик мусора [5]
for (int i=0; i<6; i++) // больше чем есть!!!
{
try {
intArray[i] = i; }
catch (IndexOutOfRangeException *piore) {
// нужно сделать кое-что более полезное, // чтобы здесь восстановиться Console::WriteLine("Oooops!"); Console::WriteLine(piore->get_Message()); } } }
Программа напечатает:
Oooops!
Exception of type System.IndexOutOfRangeException was thrown.
Перевод такой [Добавлен редактором русского перевода. — Прим. ред.]:
Возникло исключение типа Система.IndexOutOfRangeException.
Как и для неуправляемых массивов, нумерация элементов в управляемых массивах начинается с нуля. Однако, значения элементов управляемых массивов, в отличие от элементов неуправляемых массивов, автоматически инициализируются значением, принятым по умолчанию для каждого типа элемента массива. Переменным примитивных типов, таких, как int, char (символ), float (с плавающей точкой) и double (с удвоенной точностью) присваивается нуль. Элементам, указывающим на управляемые объекты, также по умолчанию присваивается нулевое значение (т.е. нулевой указатель). Элементы значимых типов инициализируются с помощью их принятого по умолчанию конструктора (т.е. конструктора, не имеющего аргументов). Значимые типы подробнее рассмотрены ниже.
В следующем примере иллюстрируется работа с массивами, и сравниваются управляемый двумерный массив и обычный неуправляемый двумерный же массив. Обратите внимание, что при работе с неуправляемым массивом используется старый синтаксис доступа к элементам массива [ ] [ ], тогда как при работе с управляемым массивом, который является истинно двумерным, используется синтаксис [, ]. Хотя в этом примере при использовании синтаксиса [ ] [ ] каждый из подмассивов имеет одинаковое количество элементов, в других случаях они могут иметь разные размеры (т.н. массив с неровным правым краем). Синтаксис [, ] предполагает использование истинно прямоугольного массива.
//Arrayl.срр
fusing
using namespace System;
// использовать пространство имен Система;
void main () {
// управляемый одномерный массив int
// (использующий сборщик мусора)
Console::WriteLine("managed ID array of int");
// ("управляемый одномерный массив int");
int intArray _gc[]= new int _gc[5];
for (int i=0; i
{
intArray[i] = i;
Console::Write(intArray[i]); // Запись
Console::Write("\t"); // Запись } Console::WriteLine();
// управляемый двумерный массив Строк
// (использующий управляемый тип)
Console::WriteLine("managed 2D array of Strings");
// ("управляемый двумерный массив Строк ");
String *str2DArray[,] = new String *[2,3]; // новая Строка
for(int row=0; row
//по строкам
{
for(int col=0; col
//по столбцам
{
str2DArray[row,col] = (row*10 + col).ToString();
// str2DArray [строка, столбец] = (row*10 + столбец)
// .ToString ();
Console::Write(str2DArray[row,col]);
// Запись:: (str2DArray [строка, столбец]);
Console::Write("\t"); // Запись }
Console::WriteLine(); }
// неуправляемый двумерный массив int (для сравнения)
Console::WriteLine("unmanaged 2D array of int");
// ("неуправляемый двумерный массив int");
int int2DArray[2][3];
for(int row=0; row<2; row++) //по строкам
{
for (int col=0; col<3; col++) // по столбцам {
int2DArray[row][col] = row*10 + col;
// int2DArray [строка] [столбец] = row*10 + столбец; Console::Write(int2DArray[row][col]); // Запись:: (int2DArray [строка] [столбец]); Console::Write("\t"); // Запись }
Console::WriteLine(); } )
Результат работы программы приведен ниже. Управляемый прямоугольный двумерный массив содержит элементы типа String*, а неуправляемый — элементы типа int. Однако управляемый массив может также содержать и элементы неуправляемых типов, между тем как неуправляемый массив — лишь элементы неуправляемых типов.
managed ID array of int 01234 managed 2D array of Strings 012 10 11 12
unmanaged 2D array of int
0 1 2
10 11 12
Перевод такой:
управляемый одномерный массив int
01234
управляемый двумерный массив Строк
0 1 2
10 11 12
неуправляемый двумерный массив int
0 1 2
10 11 12
Приведем еще один пример, в котором сравнивается использование массива массивов (синтаксис [ ] [ ]) и прямоугольного двумерного массива (синтаксис [, ]). На этот раз, ради более корректного сравнения, оба массива содержат элементы типа int.
//Array2.срр
#using
using namespace System;
// использовать пространство имен Система;
void main(void) {
Console::WriteLine("Rectangular array using [,]");
// ("Использование прямоугольного массива [,] ");
int rect2DArray [,] = new int _gc [3,41; // сборщик мусора -
// управляемый
for(int row=0; row< rect2DArray ->GetLength(0); row++) // по строкам {
for(int col=0; col< rect2DArray->GetLength(1); col++)
// по столбцам
{
rect2DArray [row,col] = row*10 + col; // rect2DArray [строка, столбец] = row*10 + столбец; Console : -.Write (rect2DArray [row, col] ) ; // Запись:: (rect2DArray [строка, столбец]); Console::Write("\t"); // Запись }
Console::WriteLine(); }
Console::WriteLine("Array of arrays using [][]"); // ("использование массива массивов [] [] "); int arrayOfArray[3][4]; // неуправляемый for(int row=0; row<3 ; row++) // по строкам {
for(int col=0; col<4; col++) // по столбцам {
arrayOfArray[row][col] = row*10 + col;
Добавлен редактором русского перевода. — Прим. ред.
// arrayOfArray [строка] [столбец] = row*10 + столбец; Console::Write(arrayOfArray[row][col]); // Запись:: (arrayOfArray [строка] [столбец]); Console::Write("\t"); // Запись
}
Console::WriteLine(); } >
Программа напечатает:
Rectangular array using [,]
0 1 2 3
10 11 12 13
20 21 22 23
Array of arrays using [][]
0 1 2 3
10 11 12 13
20 21 22 23
Перевод такой:
Использование прямоугольного массива []
0 1 2 3
10 11 12 13
20 21 22 23
Использование массива массивов [] []
0 1 2 3
10 11 12 13
20 21 22 23
На рис. 3.1 и 3.2 показано размещение в памяти элементов массива массивов (объявленного с помощью синтаксиса [ ] [ ]) и прямоугольного двумерного массива (объявленного с помощью синтаксиса [, ]), использовавшихся в предыдущем примере.
CompEbook.ru Железо, дизайн, обучение и другие
Класс System:: string (Система::Строка)
Класс System:: String (Система::Строка) инкапсулирует как управляемый объект строку символов Unicode. Класс String (Строка) определен в пространстве имен System (Системное пространство имен) и является стандартной частью .NET Framework. Тип String (Строка) представляет собой конечный (sealed) класс; это означает, что он не может быть базовым для другого класса. Сам класс String (Строка) — производный от класса System: :Object (Система::Объект), являющегося основой иерархии классов .NET. Объект String (Строка) — неизменяемый, т.е. будучи однажды инициализированным, он не может быть изменен. Класс String (Строка) содержит методы, которые можно использовать для изменения объекта String (Строка), такие, как Insert (Вставка), Replace (Замена) и PadLeft. Однако, в действительности, указанные методы никогда не изменяют исходный объект. Вместо этого они возвращают новый объект String (Строка), содержащий измененный текст. Если вы хотите получить возможность изменять исходные данные, вам следует обратить внимание на класс StringBuilder, а не на сам класс String (Строка). В следующем фрагменте кода показано, что метод Replace (Замена) не влияет на содержимое исходного объекта String (Строка), но изменяет содержимое объекта StringBuilder://StringReplace.срр
#using
using namespace System; // для консоли и строк
// использовать пространство имен Система;
using namespace System::Text; // для StringBuilder
// использовать пространство имен Система::Текст;
void main(void) {
Console::WriteLine("String is immutable:");
// ("Строка является неизменной: ");
String *psl = S"Hello World"; // Строка "Привет, Мир"
String *ps2 = psl->Replace('Н', 'J'); // Замена
Console::WriteLine(psl);
Console::WriteLine(ps2);
Console::WriteLine("StringBuilder can be modified:"); // ("StringBuilder может изменяться: ");
StringBuilder *psbl = new StringBuilder(S"Hello World"); // Привет, Мир
StringBuilder *psb2 = psbl->Replace('H', 'J'); // Замена
Console::WriteLine(psb1);
Console::WriteLine(psb2);
}
Информация, выведенная на экран профаммой, показывает, что действительно, содержимое объекта, на который указывает psl, не изменяется, т.е. метод Replace (Замена) не изменяет исходный объект String (Строка). С другой стороны, объект *psbl изменяется методом Replace (Замена).
String is immutable:
Hello World
Jello World
StringBuilder can be modified:
Jello World
Jello World
Перевод такой [Добавлен редактором русского перевода. — Прим. ред.]:
Строка является неизменной:
Привет, Мир
Jello Мир
StringBuilder может измениться:
Jello Мир
Jello Мир
В приведенном выше фрагменте кода вы можете заметить строковые литералы, определенные с префиксом S и без него. Строковый литерал, определенный с использованием только кавычек, является указателем на char (символ), т.е. указателем на последовательность символов ASCII, заканчивающуюся нулем. Такой указатель не является указателем на объект String (Строка). А строковый литерал, определенный с префиксом S, является указателем на управляемый объект String (Строка). Префикс L, не использовавшийся в предыдущем примере, обозначает строку символов Unicode, которая также не является объектом String (Строка). Следующий фрагмент демонстрирует эти три типа строк:
char *psl = "ASCII string literal"; // неуправляемый
// символ *psl = "строковый литерал ASCII ";
_wchar_t *ps2 = L"Unicode string literal"; // неуправляемый
// L " строковый литерал Уникода ";
String *ps3 = S"String object literal"; // управляемый
// Строка *ps3 = S " строковый литерал - объект String ";
Класс String (Строка) содержит много полезных методов. Так, для сравнения объектов можно использовать метод Equals (Равняется), что продемонстрировано в следующем примере. Подробнее о методах объекта String (Строка) можно узнать из документации по .NET SDK.
//Strings.срр
fusing
using namespace System;
// использовать пространство имен Система;
void main(void) {
String *pstrl = new String ("hello");
// Строка *pstrl = новая Строка ("привет");
String *pstr2 = new String("hello");
// Строка *pstr2 = новая Строка ("привет");
if (pstrl->Equals(pstr2))
// если (pstrl-> Равняется (pstr2))
Console::WriteLine("equal"); // равны - выполняется else
Console::WriteLine("not equal"); // не равный - не
// выполняется if (pstrl==pstr2) // если (pstrl == pstr2)
Console::WriteLine("equal"); // равны - не выполняется else
Console::WriteLine("not equal"); // не равный - выполняется }
Результат работы программы показывает разницу между сравнением объектов String (Строка) с помощью метода Equals (Равняется) и оператора ==. Метод Equals (Равняется) проверяет равенство содержимого объектов, тогда как оператор == проверяет лишь равенство указателей (т.е. равенство адресов объектов в памяти).
Equal
not equal
Вот перевод [Добавлен редактором русского перевода. — Прим. ред.]:
равны
не равны
Метод ToString обеспечивает представление объекта String (Строка) для любого управляемого типа данных. Хотя метод ToString не является автоматически доступным для неуправляемых классов, он доступен для упакованных значимых и упакованных примитивных типов, таких, как int или float (с плавающей точкой). Упаковка и распаковка, также как значимые типы, управляемые и неуправляемые типы, будут рассмотрены ниже в этой главе.
Метод ToString наиболее часто используется для вывода информации, а также при отладке, и создаваемые управляемые классы обычно заменяют ToString так, чтобы он возвращал определенную разработчиком, удобочитаемую информацию об объекте. Метод Obj ect: : ToString просто возвращает полное имя класса данного объекта и его реализация (не особо полезная, впрочем) доступна через наследование любому управляемому типу. Следующий пример демонстрирует некоторые аспекты работы метода ToString:
//ToString.cpp
#using
using namespace System;
// использовать пространство имен Система;
_gc class ClassWithToString
// класс сборщика мусора ClassWithToString
{
public:
String *ToString() // отмена {
return new String("SomeClass - override"); // возвратить новую Строку ("SomeClass - отмена");
}
};
_gc class ClassNoToString
// класс сборщика мусора ClassNoToString
{
//ToString унаследованный, без отмены
};
void main(void)
{
int i = 3;
Console::WriteLine(i.ToString()); // перегрузка String*
Console::WriteLine(i); // перегрузка int
ClassWithToString *psc = new ClassWithToString;
Console::WriteLine(psc->ToString()); // перегрузка String*
Console::WriteLine(psc); // перегрузка Object*
ClassNoToString *psoc = new ClassNoToString;
Console::WriteLine(psoc->ToString()); // перегрузка String*
Console::WriteLine(psoc); // перегрузка Object*
int array _gc[]= new int _gc[5]; // массив сборщика мусора
Console::WriteLine(array->ToString()); // перегрузка String
// (Строка)
Console::WriteLine(array); // перегрузка Object*
}
Результат работы программы приведен ниже. Заметьте, что метод ToString можно вызывать явно как аргумент перегруженного метода WriteLine объекта String (Строка), а можно вызвать перегруженный метод WriteLine объекта String (Строка), который сам вызовет метод ToString. Заметьте также, что даже управляемый массив (который, на самом деле, является управляемым типом) поддерживает метод ToString.
3
3
SomeClass - override SomeClass - override ClassNoToString
ClassNoToString System.Int32[] System.Int32[]
Все идентичные строковые литералы типа String (Строка) автоматически представляются указателями на объекты, являющиеся экземплярами одного класса String (Строка). Это справедливо для объектов, представленных строковыми литералами типа string (Строка), — такие объекты задаются с помощью взятой в кавычки строки. Однако это не справедливо для строковых объектов String (Строка), явно создаваемых с помощью оператора new (создать). Следующий пример подтверждает это. В нем сравниваются два указателя на объект String (Строка), созданных с помощью оператора new (создать). Выведенные на консоль результаты подтверждают, что два идентичных строковых объекта string (Строка), определенных как взятые в кавычки одинаковые последовательности символов, являются одним и тем же объектом (выражение pstrl==pstr2 истинно для строковых объектов String (Строка)). С другой стороны, два одинаковых строковых объекта string (Строка), созданных с помощью оператора new (создать), являются на самом деле разными объектами (выражение pstrl==pstr2 имеет значение false (ложь)).
//StringLiteral.срр
#using
using namespace System;
// использовать пространство имен Система;
void main(void) {
String *pstrl; // Строка
String *pstr2; // Строка
// сравнение объектов - строковых литералов типа String
pstrl = S"hello"; // привет
pstr2 = S"hello"; // привет
if (pstrl->Equals(pstr2)) // если (pstrl-> Равняется (pstr2))
Console::WriteLine("equal"); // равны - выполнен else
Console::WriteLine("not equal"); // не равны - не выполнен if (pstrl==pstr2) // если (pstrl == pstr2)
Console::WriteLine("equal"); // равны - выполнен else
Console::WriteLine("not equal"); // не равны - не выполнен // сравнение новых объектов String (не литералов) pstrl = new String("hello"); // pstrl = новая Строка ("привет"); pstr2 = new String("hello"); // pstr2 = новая Строка ("привет"); if (pstrl->Equals(pstr2) ) // если (pstrl-> Равняется (pstr2))
Console::WriteLine("equal"); // равны - выполнен else
Console::WriteLine("not equal"); // не равны - не выполнен if (pstrl==pstr2) // если (pstrl == pstr2)
Console::WriteLine("equal"); // равны - не выполнен else
Console::WriteLine("not equal"); // не равны - выполнен }
Программа напечатает:
equal equal equal not equal
Вот перевод [Добавлен редактором русского перевода. — Прим. ред.]:
равны
равны
равны
не равны
Управляемые строковые литералы String (Строка) и неуправляемые строковые литералы ASCII и Unicode (благодаря автоматической упаковке) можно использовать в выражениях, в которых ожидается использование управляемого строкового объекта String (Строка). Однако управляемый строковый объект String (Строка) нельзя использовать там, где ожидается появление переменных неуправляемых типов. Следующий пример доказывает это. Обратите внимание на закомментированные строки. Не будучи закомментированными, они привели бы к сообщению об ошибке при компиляции.
//MixingStringTypes.срр
fusing
using namespace System;
// использовать пространство имен Система;
tinclude
void ExpectingManagedString(String *str){} // Строка *str void ExpectingASCIIString(char *str){} // символ *str void ExpectingUnicodeString(wchar_t *str){} void main(void) {
// ожидается управляемый тип
ExpectingManagedString(S"hello"); // полное соответствие
// привет ExpectingManagedString("hello"); // нет ошибки
// привет
ExpectingManagedString(L"hello"); // нет ошибки
// привет
// ожидается неуправляемый тип
ExpectingASCIIString("hello"); // полное соответствие
// привет //ExpectingASCIIString(S"hello"); // ошибка!
// привет ExpectingUnicodeString(L"hello"); // полное соответствие
// привет //ExpectingUnicodeString(S"hello"); // ошибка!
// привет }
CompEbook.ru Железо, дизайн, обучение и другие
Конечные классы
Ключевое слово _sealed (конечный) указывает на то, что класс или структуру нельзя использовать в качестве базового типа. Другими словами, в иерархии наследования этот класс или структура— терминальный тип. Ключевое слово _sealed (конечный) можно также применять к отдельному методу класса. Конечный метод не может быть переопределен в производных классах. В стандарте C++ подобная возможность не предусмотрена; однако в Java такая возможность реализована с помощью ключевого слова final (конечный). Следующий фрагмент кода является некорректным, так как конечный класс не может быть базовым:_sealed class SomeSealedClass
{
};
class SomeDerivedClass : public SomeSealedClass // ошибка
{
};
Одной из причин использования ключевого слова _sealed (конечный) является повышение стабильности работы классов за счет препятствования слишком самоуверенным и/или недостаточно квалифицированным программистам испортить важные и сложные элементы поведения классов в производных от них. Другой аргумент использования _sealed (конечный) — предотвращение попыток изменить возможности, обеспечивающие безопасность. Например, предопределенный класс string (Строка) объявлен как конечный, а вдобавок к тому — еще и как неизменяемый (он не содержит общедоступных методов или элементов данных, что могло бы позволить изменять его содержимое). Это делает его идеальным для использования в защитных целях, например, для хранения паролей. При попытке скомпилировать следующий код будет выдана ошибка, так как класс string (Строка) является конечным:
// не допустимо, потому что Система::Строка - конечный класс
class MyString : public String
// класс MyString: общедоступная Строка
{
};
CompEbook.ru Железо, дизайн, обучение и другие
Место C++ в мире .NET
Одним из достоинств платформы .NET является то, что для разработки приложений, компонентов и сервисов на основе .NET можно использовать любой из широкого круга языков. Можно применять C++ с расширением управляемости, С# и VB.NET, созданные Microsoft, а также еще многие языки, разработанные другими компаниями. Но главное даже не то, что с помощью всех этих языков можно создавать приложения на основе .NET, a то, что во всех вопросах, относящихся к инициализации объектов, вызову методов, наследованию, обработке событий и даже обработке исключений, работа приложения не будет зависеть от языков реализации его составляющих. Это стало возможным благодаря тому, что языки .NET компилируются не на родной язык, а на общий промежуточный язык Intermediate Language (IL).Как уже говорилось в предыду,щих главах, код, выполняющийся под управлением общеязыковой среды выполнения CLR (Common Language Runtime), называется управляемым кодом. Управляемый код отличается от обычного тем, что он компилируется не на родной набор инструкций ЦПУ, а в инструкции промежуточного языка IL, определенного платформой .NET. Промежуточный язык IL подобен обычному набору инструкций ЦПУ, отличаясь от последнего тем, что он изначально разрабатывался с поддержкой объектно-ориентированных и компонентно-ориентированных общих для языков черт, таких, как классы, объекты, методы, события и исключения. Благодаря тому, что исходный код, написанный на языках, поддерживающих .NET, компилируется в инструкции промежуточного языка IL, все эти языки являются полностью совместимыми.
Программы состоят из кода и данных, и общеязыковая среда выполнения CLR обеспечивает поддержку и управляемым данным, и управляемому коду. Как было сказано в предыдущих главах, управляемые данные размещаются в управляемой динамически распределяемой области памяти (куче), которая имеет весьма ценную особенность — автоматическую сборку мусора. Если при создании программ на обычном C++ программист должен сам создавать средства управления динамически распределяемой областью памяти, то общеязыковая среда выполнения CLR реализует этот процесс, отслеживая ссылки на объекты и автоматически освобождая ресурсы, занятые объектами, которые стали недоступны программе.
Используя C++ с расширениями управляемости, можно создавать управляемые код и данные. Однако в настоящее время C++ является единственным из всех языков .NET, с помощью которого можно создавать также и неуправляемые код и данные. Фактически, управляемые и неуправляемые код и данные на C++ могут быть определены в одном и том же исходном файле, и в некоторой степени эти два мира могут взаимодействовать. Хотя использование управляемых кода и данных имеет много преимуществ, оно может привести к снижению производительности и потере гибкости. Поэтому во многих случаях C++ оказывается лучшим выбором для создания программ. Другой причиной выбора из всех языков .NET именно C++ может быть желание совершенствовать ваше знание C++ и существующие наработки на этом языке.
CompEbook.ru Железо, дизайн, обучение и другие
Обработка исключений
Без сомнения, вы уже хорошо знакомы с механизмом исключений в стандартном C++, так что хорошо понимаете, как работают управляемые исключения. Напомним, что платформа .NET (точнее, общеязыковая среда выполнения CLR) поддерживает расширения, совместимые с расширением управляемости C++, и управляемые исключения, возникшие при выполнении кода, созданного на одном из языков .NET, могут быть перехвачены и обработаны кодом, написанным на любом другом языке .NET.Кроме обработки предопределенных исключений, таких, как Invalid-CastException или OverflowException, вы можете определить ваши собственные производные от Exception (Исключение) классы, инкапсулирующие некоторую специфичную для приложения информацию. Рассмотрим следующий пример:
//Exceptions.cpp
#using
using namespace System;
// использовать пространство имен Система;
_gc class MyException : public Exception
// класс сборщика мусора MyException: общедоступное Исключение
{
};
void TemperamentalFunction(int i) // ненавидит нечетные числа
{
Console::WriteLine(
"TemperamentalFunction called with {0}",
i.ToString());
if (i%2 != 0) // если (i%2 != 0), т.е. нечетное
throw new MyException;
Console::WriteLine("No exception thrown"); // Нет исключения
}
void main()
{
try
{
TemperamentalFunction(2); // вызов с четным числом
TemperamentalFunction(3); // вызов с нечетным числом
}
catch (MyException *pe)
{
Console::WriteLine("Exception thrown!"); // Исключение!
Console::WriteLine(pe->get_StackTrace());
}
}
Приведем результат работы программы:
TemperamentalFunction called with 2
No exception thrown
TemperamentalFunction called with 3
Exception thrown!
at TemperamentalFunction(Int32 i) in с:\netcppcode\
chap03\exceptions\exceptions.cpp:line 16
at main() in c:\netcppcode\chap03\exceptions
\exceptions.cpp:line 25
Вот более русифицированная версия этой выдачи .
TemperamentalFunction вызвана с 2
Нет исключения
TemperamentalFunction вызвана с 3
Исключение!
в TemperamentalFunction (Int32 i) в с:\netcppcode\
chap03\exceptions\exceptions.cpp:line 16
в главном () в c:\netcppcode\chap03\exceptions
\exceptions.cpp:line 25
Обратите внимание на метод StackTrace, позволяющий получить текстовую строку, представляющую состояние стека в момент возникновения исключения. Хотя в этом примере ключевое слово _finally (наконец) и не используется, но следует помнить, что такое расширение стандарта ANSI C++ поддерживается в Visual C++. Ключевое слово _finally (наконец) позволяет вставлять в программу код, который выполняется вне зависимости от того, возникло или нет исключение в блоке try. Следует также упомянуть, что ключевое слово _finally (наконец) полностью совместимо с механизмом исключений, поддерживаемым другими языками .NET.
При желании предыдущий пример можно разбить на две части. Первая часть могла быть реализована на С# (в виде динамически подключаемой библиотеки (DLL)) и содержала бы код, при выполнении которого возникало бы исключение. Вторая часть была бы приложением на C++, вызывающим метод TemperamentalFunction. Этим способом можно было бы наглядно продемонстрировать, что исключения действительно являются мостом, соединяющим разные языки .NET.
CompEbook.ru Железо, дизайн, обучение и другие
Определение ключевых слов в качестве идентификаторов
Ключевое слово _identifier (идентификатор) позволяет использовать любое слово, включая и ключевое, в качестве идентификатора. Его можно использовать и для слов, не являющихся ключевыми, но это не дает никаких преимуществ, и потому является бессмысленным. На первый взгляд кажется нелепым, что такая черта может вообще понадобиться; однако, из-за того, чтоплатформа .NET допускает использование в разработке приложений одновременно нескольких языков, может оказаться, что имя класса или переменной, определенное в части программы, написанной на другом языке, совпадет с каким-либо ключевым словом C++. Очевидно, что использование в качестве имен ключевых слов значительно усложнит чтение и понимание исходного кода, так что к этому приему следует прибегать только в крайнем случае. Выглядящий несколько странно код, приведенный ниже, демонстрирует этот прием. В нем описывается класс, называющийся if, элемент данных которого называется while (эксцентричное сочетание). Затем создается экземпляр класса if и вызывается метод while. (О, меня уже тошнит!!!) Удивительно, но это компилируется и работает!
//IdentifierExample.срр
#using
using namespace System;
// использовать пространство имен Система;
_gc class _identifier(if)
// класс сборщика мусора _ идентификатор (если)
{
public:
int _identifier(while) ;
// int _ идентификатор (while);
};
void main(void)
{
_identifier(if)* pif = new _identifier(if);
// _ идентификатор (если)
// * pif = новый _ идентификатор (если);
pif->_identifier(while)= 1;
// pif-> _ идентификатор (while) = 1;
Console::WriteLine(pif->_identifier(while) ) ;
// (pif-> _ идентификатор (while));
}
CompEbook.ru Железо, дизайн, обучение и другие
Программа HelloWorld (Привет, мир)
Чуть ниже приведен пример кода из очень простой управляемой программы, которая выводит на консоль одну-единственную строку. Вы можете открыть сопровождающее решение [Как и для всех других примеров в данной книге, реализация программы HelloWorld доступна читателю в готовом виде. Исходные файлы этого проета находятся в папке C:\OI\NetCpp\Chap3\HelloWorld. Для того чтобы открыть его в Visual Studio, дважды щелкните на файле HelloWorld.sIn в Проводнике.] или создать свой проект и ввести текст программы самостоятельно. Для того чтобы это сделать, необходимо создать пустой проект HelloWorld (Привет, мир), добавить исходный код, а затем скомпилировать и запустить проект.Как создать консольное приложение на управляемом C++
Создайте пустой проект консольного приложения Managed C++, называющийся HelloWorld (Привет, мир):
Директива fusing необходима для всех программ на управляемом С^+. Она делает доступными для компилятора стандартные типы (такие, как Console (Консоль) и Object (Объект)), определенные в библиотеке классов NET. Класс Console (Консоль) находится в пространстве имен System (Системное пространство имен) и его полное имя — System: : Console (Система::Консоль) Данный класс содержит метод WnteLine, выводящий на консоль текст и добавляющий к нему символ новой строки.
//HelloWorld.cpp
fusing
void main(void) {
System: : Console : : WriteLme ( "Hello Wcrla'M ;
// ("Привет, мир"); }
Программа может быть скомпилирована либо в Visual Studio.NET, либо при помощи командной строки с параметром /CLR (Common Language Runtime compilation — компиляция для выполнения в общеязыковой среде). Если вы используете командную строку. вы должны определить соответствующую среду Простейший способ сделать это — открыть командное окно, выбирая пункты меню Start (Пуск) => Programs (Программы) => Microsoft Visual Studio.NET 7.0 => Visual Studio.NET Tools => Visual Studio.NET Command Prompt. В командной строке
cl /CLR HelioWorld.cpp
исходный файл компилируется, а затем автоматически компонуется так, что результатом является ЕХЕ-файл HelloWorld.exe. Позже мы расскажем, как создать управляемую динамически подключаемую библиотеку (DLL).
Полученную управляемую программу можно запустить в Visual Studio.NET или из командной строки, как обычный исполняемый файл. Результатом работы программы будет следующее сообщение:
Hello World
(Привет, мир)
CompEbook.ru Железо, дизайн, обучение и другие
Программа Hotel (Гостиница)
Теперь представим первую версию программы управления системой бронирования Гостиничных номеров, которую мы будем использовать и расширять в следующих главах. Обратите внимание, что класс Hotel (Гостиница) хранится не в сборке ЕХЕ, а в динамически подключаемой библиотеке (DLL).
Рис. 3.1. Размещение в памяти прямоугольного массива
Рис. 3.2. Размещение в памяти массива массивов
Вы можете открыть готовое решение, находящееся в папке HotelRes\Hotel, или создать проект и ввести исходный код сами. Для того чтобы это сделать, необходимо создать проект библиотеки классов на управляемом C++ (Managed C++ Class Library project), называющийся Hotel (Гостиница), добавить исходный код, а затем скомпилировать проект. Заметьте, что поскольку выходной файл —динамически подключаемая библиотека (DLL), его не удастся протестировать до создания исполнимого файла (ЕХЕ) программы-клиента.
Создание библиотеки классов на управляемом C++ (Managed C++ Class Library project)
Создайте проект библиотеки классов на управляемом C++ под названием Hotel (Гостиница):
1. Откройте Visual Studio.NET.
2. Выберите пункт меню File^New^Project (ФайлОСоздаты^Проект) для того чтобы вызвать диалог New Project (Создание проекта).
3. Выберите в списке Project Туре (Тип проекта) Visual C++ Projects (Проекты на Visual C++).
4. Выберите в списке Template (Шаблон) Managed C++ Class Library Project (Проект библиотеки классов на управляемом C++),
5. Введите Hotel (Гостиница) в поле Name (Название).
6. Задайте папку, в которой будет сохранен проект.
7. Щелкните на кнопке ОК. для того чтобы закрыть диалог New Project (Создание проекта) и создать проект.
Добавьте исходный код:
8. Дважды щелкните на файле Hotel.cpp в окне Solution Explorer (Поиск решения).
9. Введите исходный код примера Hotel (Гостиница). Скомпилируйте проект:
10. Выберите пункт меню Build^Build (СборкаОСобрать).
Хотя определение класса уже присутствует в заголовочном файле и используется в качестве типа данных в срр-файлах, мы, ради простоты и наглядности, поместили данное ниже определение класса Hotel (Гостиница) непосредственно в исходный файл Hotel . срр. Это привело также к тому, что указанный файл стал больше похож на исходный файл С#, в котором директива I include отсутствует. Visual Studio создала, конечно, файл Hotel. h, но это не имеет значения, т.к. соответствующая директива #include была удалена из файла Hotel. срр.
//Hotel.cpp
finclude "stdafx.h" // имеет #using
using namespace System;
// использовать пространство имен Система;
public _gc class Hotel
// класс сборщика мусора Гостиница
{
private: // частный
String *pcity; // Строка
String *pname; // Строка
int number;
Decimal rate; // Десятичное
public:
Hotel(String *pcity, String *pname, // Гостиница
int number, double rate)
{
this->pcity = pcity;
this->pname = pname;
this->number = number;
this->rate = rate;
}
Hotel() // Гостиница
{
this->pcity = 0;
this->pname = 0;
this->number = 50; // значение по умолчанию 50
this->rate = 0;
}
String *GetCity() // Строка
{
return pcity;
}
String *GetName() // Строка
{
return pname;
}
int GetNumber()
{
return number;
}
void SetNumber(int val)
{
number = val;
}
Decimal GetRate() // Десятичное число
{
x return rate;
}
void SetRate(Decimal val) // Десятичное число
{
rate = val; } void RaisePrice(Decimal amount) // Десятичное количество
{
rate = rate+1;
}
};
Приведенный код компилируется затем в сборку NET, называющуюся Hotel dll Это можно сделать в Visual Studio NET, а можно — с помощью командной строки Если вы используете командную строку, то должны определить соответствующее окружение Простейший способ сделать это — открыть командное окно, выбрав пункты меню Start (Пуск) => Programs (Программы) => Microsoft Visual Studio NET 7 => Visual Studio NET Tools => Visual Studio NET Command Prompt В командной строке, приведенной ниже, компилируется исходный файл Hotel cpp:
cl /CLR Hotel.cpp /LD
Параметр /LD указывает, что компоновщик должен создать динамически подключаемую библиотеку (DLL), а не ЕХЕ-файл Класс Hotel (Гостиница) содержит частные (private) данные, два конструктора для инициализации данных и несколько общедоступных (public) методов
Для того чтобы продемонстрировать возможность использования в NET разных языков, следующая программа, которая тестирует созданный ранее компонент Hotel (Гостиница), реализована на С# Можно либо самостоятельно ее реализовать с использованием Visual Studio NET, либо просто открыть готовое решение, находящееся в папке HotelRes\TestHotel Для создания программы необходимо создать проект консольного приложения на С# (С# Console Application) TestHotel, добавить исходный код, затем ссылку на сборку Hotel (Гостиница), после чего скомпилировать и запустить программу
Создание консольного приложения на С# (С# Console Application):
Создайте проект консольного приложения С#, называющийся TestHotel
1. Откройте Visual Studio NET
2. Выберите пункт меню Fue => New => Project (Файл => Создать => Проект) для того чтобы вызвать диалог New Project (Создание проекта).
3 Выберите в списке Project Type (Тип проекта) Visual C# Projects (Проекты на Visual C#).
4 Выберите в списке Template (Шаблон) Console Application (Консольное приложение)
5 Введите 'TestHotel" в поле Name (Название)
6. Задайте папку, в которой будет сохранен проект
7. Щелкните на кнопке ОК для того чтобы закрыть диалог New Project (Создание проекта) и создать проект
Добавьте исходный код:
8. Щелкните правой кнопкой на файле Class.cs в окне Solution Explorer (Поиск решения) и выберите в меню пункт Rename (Переименовать)
9. Введите новое имя исходного файла — TestHotel.cs
10. Дважды щелкните на файле TestHotel cs в окне Solution Explorer (Поиск решения) для того чтобы открыть файл для редактирования.
11. Добавьте в файл TestHotel cs соответствующий исходный код
Добавьте ссылку на сборку Hotel (Гостиница):
12. Выберите пуню меню Project => Add Reference (Проект => Добавить ссылку).
13. Щелкните на кнопке Browse (Обзор)
14. Найдите папку, в которой хранится сборка Hotel (Гостиница).
15. Дважды щелкните на сборке Hotel dll.
16. Щелкните на кнопке ОК.
Скомпилируйте и запустите проект:
17. Выберите пункт меню Build => Build (Сборка => Собрать)
18. Нажмите сочетание клавиш Ctrl-F5 для запуска программы без отладчика
//TestHotel.cs
using System;
JI использование Системы;
public class TestHotel
// общедоступный класс TestHotel
public static void Main()
{
Hotel generic = new Hotel (); // универсальная новая
// Гостиница
ShowHotel (generic) ; // универсальная
Hotel ritz = new Hotel("Atlanta", "Ritz", 100, 95);
// Роскошная гостиница = новая Гостиница
// ("Атланта", "Роскошь", 100, 95),
ShowHotel(ritz);
ritz.RaisePrice(50m);
ritz.SetNumber(125);
ShowHotel(ritz);
; }
private static void ShowHotel (Hotel hotel)
// частный статический
ShowHotel (Гостиница гостиница)
{
Console.WriteLine(
"{0} {1}: number = {2}, rate = {3:C}",
hotel.GetCity(), hotel.GetName(), // гостиница
hotel.GetNumber(), hotel.GetRate()); // гостиница
}
}
Обратите внимание, что Visual Studio автоматически копирует Hotel.dll в ту же папку, в которую помещает файл TestHotel. exe Так происходит потому, что в проект С# была добавлена ссылка на данную сборку Это удобно, ведь если запустить программу-клиент, а общеязыковая среда выполнения CLR не найдет соответствующей сборки, возникнет исключение времени выполнения Приведем строки, выведенные на экран программой, созданной на С# с компонентом на C++:
number = 50, rate = $0.00
Atlanta Ritz: number = 100, rate = $95.00
Atlanta Ritz: number = 125, rate = $96.00
CompEbook.ru Железо, дизайн, обучение и другие
Программирование на C++ для платформы .NET
В этом разделе главы мы изучим основные аспекты создания кода на управляемом C++. В частности, будут рассмотрены все ключевые слова расширения управляемости C++, поддерживаемые Visual C++.NET. Заметим, что это далеко не все ключевые слова Visual C++ 7.0, не определенные стандартом ANSI C++, — ведь мы концентрируем ваше внимание именно на расширении управляемости C++. Однако в рассмотрении затрагиваются некоторые аспекты, не относящиеся к управляемому коду. Например, использование ключевого слова _interface (интерфейс) не ограничивается лишь управляемым кодом. И в заключение мы кратко опишем атрибуты, технически не относящиеся к управляемости.Соответствие VC++.NET и ANSI C++
Стоит сказать, что все эти особые ключевые слова, связанные с управляемостью, не Противоречат ANSI C++, так что фактически VC++.NET является более совместимым с ANSI C++, нежели предыдущие версии VC++.
При использовании командной строки следует задавать параметр /CLR (Компиляция для выполнения в общеязыковой среде) компилятора, иначе применение ключевых слов, связанных с управляемостью, не допускается. В Visual Studio корректные установки параметров обеспечиваются при выборе соответствующего шаблона автоматически. Тем не менее, если возникла необходимость установить корректные значения параметров, выполните следующие указания:
1. Щелкните в окне Solution Explorer (Поиск решения) правой кнопкой на узле проекта (но не на узле решения).
2. Выберите пункт меню Properties (Свойства) При этом откроется диалог Project Property Pages (Страницы свойств проекта)
3. Выберите узел General (Общие) под узлом C/C++ и выберите Assembly Support (/clr) для опции Compile As Managed (Компилировать как управляемый).
4. Щелкните на кнопке ОК.
CompEbook.ru Железо, дизайн, обучение и другие
В этой паве рассмотрено использование
В этой паве рассмотрено использование расширений C++ при создании программ и компонентов для платформы NET Изложение основных концепций создания управляемого кода на С-г+ проил пострировано на MHOIHX примерах, таких, как HelloWorld (Привет, мир), ConvertTemp и др. Были рассмотрены классы String (Строка) и Array (Массив) активно используемые почти во всех типах приложений NET Подробно изучены ключевые слова расширения управляемости C++ Рассмотрены также делегаты, события и управление обработкой исключений Напоследок проанализировано использование атрибутов С++ для создания компонентов на основе модели компонентных объектов Miciosott (COM)CompEbook.ru Железо, дизайн, обучение и другие
События представляют
События представляют собой механизм, посредством которого объект имеет возможность получать информацию о происходящем вне него Событие может быть вызвано неким действием пользователя, например, нажатием кнопки мыши, или некими изменениями состояния приложений, например, приостановкой или завершением задачи Объект, генерирующий событие, называется источником или отправителем события, объект, который реагирует на событие, называется приемником или получателем событияВ обычном C++ для работы с событиями реализуют функции обратного вызова, для выполнения которых используются указатели на функции В модели компонентных объектов Microsoft (COM) для работы с событиями используются интерфейсы IConnec-tionPomt и IConnectionPointContainer В NET используются управляемые события Все эти подходы по сути одинаковы, так что для их объединения Microsoft предложила Унифицированную модель событий (Unified Event Model) Для поддержки этой новой Унифицированной модели событий в C++ введены новые ключевые слова _event (событие), _hook (привязать) и _unhook (отцепить), а также атрибуты event_source (источник события) и event_receiver (приемник события)
Ключевое слово _event (событие) используется для описания события, которое может быть сгенерировано источником события Это слово можно использовать не только в управляемых классах, оно может применяться к следующим объявлениям
1. Описание метода класса обычного C++ (обычный обратный вызов)
2. Описание интерфейса модели компонентных объектов Microsoft (COM) (точка стыковки)
3. Описание метода управляемого класса (управляемое событие)
4. Описание элемента данных управляемого класса (управляемое событие с использованием делегата)
Мы рассмотрим только третий случай, т е случай, в котором источником события является метод управляемого класса Для того чтобы объявить обработчиком какого-то события метод класса-получателя этого события, используется ключевое слово _hook (привязать) После того, как это сделано, при каждом возникновении события будет вызываться его обработчик А чтобы такое объявление метода аннулировать, используется ключевое слово _unhook (отцепить) В следующем примере демонстрируется использование ключевых слов _event (событие), _hook (привязать) и _unhook (отцепить), а также атрибутов event_source (источник события) и event_receiver (приемник события) для реализации механизма обратного вызова
//Event.cpp
fusing
using namespace System;
// использовать пространство имен Система,
[event_source(managed)] // управляемый
public _gc class ManagedEventSource
// класс сборщика мусора ManagedEventSource
{ public:
_event void ManagedEvent(} ; // нет реализации
void Fire_ManagedEvent()
{
ManagedEvent();
}
};
[event_receiver(managed)] // управляемый
gc class ManagedEventReceiver // класс сборщика мусора ManagedEventReceiver
{
public:
void HandleManagedEvent() // вызывается через ManagedEvent
{
Console::WriteLine("HandleManagedEvent called");
}
void HookEvent(ManagedEventSource *pEventSource)
{
_hook( // обработчик
SManagedEventSource::ManagedEvent,
pEventSource,
SManagedEventReceiver.:HandleManagedEvent) ,
}
void UnhookEvent(ManagedEventSource* pEventSource)
{
_unhook( // отцепиться
&ManagedEventSourсе::ManagedEvent,
pEventSource,
SManagedEventReceiver:HandleManagedEvent) ; }
};
void main ()
{
ManagedEventSource* pEventSource =
new ManagedEventSource;
ManagedEventReceiver* pReceiver =
new ManagedEventReceiver;
pReceiver->HookEvent(pEventSource) ;
pEventSource->Fire_ManagedEvent(); // вызывается обработчик
pReceiver->UnhookEvent(pEventSource);
}
Профамма напечатает:
HandleManagedEvent called
CompEbook.ru Железо, дизайн, обучение и другие
Стандартный ввод-вывод
Класс System:: Console (Система::Консоль) обеспечивает поддержку стандартного ввода-вывода. Метод ReadLine класса System: : Console (Система::Консоль) считывает введенную с клавиатуры строку как текстовую. С помощью методов Write (Запись) и WriteLine класса System: :Console (Система::Консоль) на консоль выводится текстовая строка, и, говоря о методе WriteLine, также символ новой строки. Проще всего ввод с консоли выполняется путем считывания в объект String (Строка) с последующим преобразованием в необходимый тип данных. Чтобы выполнить это преобразование можно использовать методы ТоХхх класса System: : Convert (Система::Преобразовать).В следующем примере такой метод используется для ввода с консоли температуры в градусах Фаренгейта, преобразования текстовой строки в число, вычисления температуры в градусах Цельсия и вывода на консоль значений температуры в градусах Фаренгейта и Цельсия.
//ConvertTemp.срр
fusing
using namespace System;
// использовать пространство имен Система;
_gc class InputWrapper
// класс сборщика мусора InputWrapper
{
public:
int getlnt(String *pprompt) // Строка
{
Console::Write(pprompt); // Запись
String *pbuf = Console::ReadLine(); // Строка
return Convert::ToInt32(pbuf); // Преобразовать
}
double getDouble(String *pprompt)
{
Console::Write(pprompt); // Запись
String *pbuf = Console::ReadLine(); // Строка
return Convert::ToDouble(pbuf); // Преобразовать
}
Decimal getDecimal(String *pprompt) // Десятичное число
{
Console::Write(pprompt); // Запись
String *pbuf = Console::ReadLine(); // Строка
return Convert::ToDecimal(pbuf); // Преобразовать
}
String *getString(String *pprompt) // Строка
{
Console::Write(pprompt); // Запись
String *pbuf = Console::ReadLine(); // Строка
return pbuf;
}
};
void main(void)
{
InputWrapper *piw = new InputWrapper;
int numTemp = piw->getlnt("How many temp's? "); // Сколько?
for (int i = 0; i < numTemp; i++)
{
int fahr = piw->getlnt("Temp. (Fahrenheit): "); // Фаренгейт
int Celsius = (fahr - 32) * 5 / 9; // Цельсия
Console::WriteLine (
"Fahrenheit = {0}", fahr.ToString()); // Фаренгейт
Console::WriteLine("Celsius = {0}", _box(Celsius)); // Цельсия
}
}
Заметим, что первым аргументом метода WriteLine является форматирующая строка. Например, при первом вызове метода WriteLine форматирующая строка имеет вид "Fahrenheit={0} ", где {0} — заглушка, указывающая, что на это место следует вставить второй аргумент WriteLine. Число, помещенное в фигурные скобки, определяет, какой именно из следующих за форматирующей строкой аргументов следует вывести в указанном месте (естественно, нумерация начинается с нуля). В нашем примере это число — 0, так как за форматирующей строкой следует только один аргумент. Подставляемые аргументы могут быть нескольких типов, включая строки или упакованные значения, что и продемонстрировано в примере. Приведем пример работы программы, в котором преобразование температур производится два раза:
How many temp's? 2
Temp. (Fahrenheit): 212
Fahrenheit = 212
Celsius = 100
Temp. (Fahrenheit): 32
Fahrenheit = 32
Celsius = 0
Перевод такой [Добавлен редактором русского перевода. — Прим. ред.]:
Сколько температур? 2
Фаренгейта: 212
Фаренгейта =212
Цельсия = 100
Фаренгейта: 32
Фаренгейта = 32
Цельсия = О
В следующей программе продемонстрировано, как выводить данные в некоторых форматах с помощью метода WriteLine. Для этого применяются коды форматирования. Чтобы получить более подробную информацию о кодах форматирования, используемых в методе WriteLine (совпадающих, кстати, с кодами для метода string: : Format (Строка::Формат)), обратитесь к документации по .NET SDK.
//FormatString.cpp #using
using namespace System;
// использовать пространство имен Система;
void main(void) {
Console::WriteLine(
"{0:C}, {1:D}, {2:E}, {3:F}, {4:G}, {5:N}, {6:X}",
_box(lOO), // поле валюты (currency)
_box(200), // десятичное число (decimal)
_Ьох(ЗОО), // экспонента (exponent)
_box(400), // с фиксированной точкой (fixed point)
_box(SOO), // общий (general)
_Ьох(бОО), // число (number)
_box(700) // шестнадцатеричное (hexadecimal)
); }
Вот выдача:
$100.00, 200, З.ООООООЕ+002, 400.00, 500, 600.00, 2ВС
CompEbook.ru Железо, дизайн, обучение и другие
используется для указания на то,
Ключевое слово _property (свойство) используется для указания на то, что метод получения и/или установки реализует свойство управляемого класса. В отличие от элемента данных (называемого также полем), который однообразен и негибок, свойство может быть доступно для чтения и записи, либо только для чтения и только для записи, может быть реализовано как обычная переменная или как вычисляемое значение. Например, свойство только для чтения должно быть реализовано методом get_, но не методом set_. Свойство доступно другим программам, написанным на любых языках .NET, посредством использования обычного синтаксиса доступа к элементам данных этого языка, как если бы свойство было обычным элементом данных (точнее псевдоэлементом данных). Это продемонстрировано в следующем фрагменте кода, в котором pmcwp->someProperty используется так, как если бы оно было элементом данных с именем someProperty. Фактически такого элемента данных не существует, но то, что класс содержит методы get_someProperty и set_someProperty, объявленные с ключевым словом _property (свойство), делает возможным такой способ доступа. В действительности класс содержит защищенный (protected) элемент данных m_someProperty, но это уже подробности реализации, инкапсулированные в компоненте. Свойство компонента в .NET похоже на свойства OLE-автоматизации в компонентах ActiveX или свойства компонентов (bean) в JavaBean.Другой интересной чертой управляемых свойств, отсутствующей у элементов данных, является то, что вы можете проверить приемлемость аргумента метода set_ и, в случае, если значение аргумента некорректно, вызвать исключение. Это помогает в создании корректно работающих компонентов.
//PropertyExample.срр
#using
using namespace System;
// использовать пространство имен Система;
_gc class ManagedClassWithProperty
// класс сборщика мусора ManagedClassWithProperty
{
public:
ManagedClassWithProperty() : m_someProperty(0) {}
_property int get_someProperty() // свойство -
// должно быть "get_"
{
return m_someProperty;
}
_property void set_someProperty( // свойство
int propertyValue) // должно быть "set__"
{
m_someProperty = propertyValue;
}
protected: // защищенный
int m_someProperty; // можно реализовать как элемент данных
};
void main() {
ManagedClassWithProperty* pmcwp = new ManagedClassWithProperty;
pmcwp->someProperty = 7; // псевдоэлемент данных
Console::WriteLine(pmcwp->someProperty) ;
}
Вышеприведенная программа выведет:
7
CompEbook.ru Железо, дизайн, обучение и другие
Типовая безопасность
Программы, написанные на C++, не обладают свойством типовой безопасности Программы же на управляемом C++ должны гарантированно обладать указанным свойством Однако, из-за того, что программы C++ могут содержать неуправляемый код, они не обязательно обладают свойством типовой безопасности Нельзя производить арифметические операции с управляемыми указателями Кроме того, нельзя приводить тип управляемого указателя к неуправляемому Поэтому можно доказать безопасность только тех программ на C++, которые содержат лишь управляемые код и данные [Управляемый C++ может генерировать код, гарантированно обладающий свойством типовой безопасности, если избегать использования некоторых особенностей языка, таких, как неуправляемые указатели или приведение типов Для проверки типовой безопасности сборки можно использовать утилиту Pevenfy.exe]. Тем не менее, любая программа на C++, в которой выполняются арифметические действия над неуправляемыми указателями или приводятся типы управляемых указателей к неуправляемым, является потенциально опасной.Следующая программа является примером небезопасного кода на C++, в котором выполняется приведение указателя pumc на неуправляемый объект к указателю на переменную типа j_nt В этом случае подобная операция не является опасной, но в общем случае ее выполнение может представлять опасность Затем выполняется арифметическое действие над указателем на объект, которое уже в этом примере небезопасно, так как получающийся в результате указатель не указывает на какой-либо объект Еще ниже в этом примере, в закомментированных строках, те же действия совершаются над управляемым указателем рте Если бы строки были не закомментированы, компилятор выдал бы сообщение об ошибке
// Unmanaged.срр
# using
class UnmanagedClass
// класс UnmanagedClass
{
public:
int i;
};
_gc class ManagedClass
// класс сборщика мусора ManagedClass
{
public:
int i;
};
void main(void)
{
UnmanagedClass *pumc = new UnmanagedClass;
pumc->i = 10;
int * pi = (int *}pumc; // Опасно приведение указателя
pi = (int *)(pumc+1); // Опасность: арифметика над указателями
ManagedClass *pmc = new ManagedClass; pmc->i = 10;
//pi = (int *)pmc; // Ошибка: приведение _gc //(сборщик мусора) * к *
//pi = (int *)(pmc+1); // Ошибка, арифметика над _gc // (сборщик мусора) *
}
CompEbook.ru Железо, дизайн, обучение и другие
Типы данных C++ и .NET Framework
С другой стороны, некоторым типам C++ соответствуют классы .NET Framework. Для примитивных типов, таких, как int и float (с плавающей точкой), соответствующие классы .NET являются оберточными (wrapping) или, как их еще называют, упаковочными (boxing). Упаковка данных примитивных типов будет рассмотрена в этой главе несколько позже. В следующем примере объявляются переменные разных типов C++ и показываются соответствующие классы .NET Framework, для чего используется метод GetType класса System: :Object (Система::Объект).Таблица 3.1. Типы данных промежуточного языка
//MappingDataTypes.срр
#using
using namespace System;
// использовать пространство имен Система;
void main(void)
{
bool b = false; // логический (булев) b = ложь; Булева переменная
Char ch = '\0'; // Символ
Object *pobj = new Object; //
Объект String *pstr = S""; // Строка
float f = 1.OF; // f с плавающей точкой = l.OF - одинарная
//точность
double d = 1.0; // Двойная точность
char с = '\0'; // символ SByte
unsigned char uc = '\0'; // Байт - символ без знака
short s = 0; //Intl6 (короткий)
unsigned short us = 0; //UIntl6 - короткий без знака
int i = 0; //Int32
unsigned int ui = 0; //UInt32 - int без знака
long l = 0; //Xnt64
unsigned long ul = 0; //UInt64 - длинный без знака
int intManagedArray _gc[] // System.Int32[] - сборщик мусора
= new int _gc[5]; // сборщик мусора
Console::WriteLine(_box(b)->GetType() ) ;
Console::WriteLine(_box(ch)->GetType()) ;
Console::WriteLine(pobj->GetType() ) ;
Console::WriteLine(pstr->GetType()) ;
Console::WriteLine(_box(f)->GetType());
Console::WriteLine(_box(d)->GetType()) ;
Console::WriteLine(_box(c)->GetType());
Console::WriteLine(_box(uc)->GetType());
Console :: WriteLine (_box ( s) ->GetType () ) ;
Console::WriteLine(_box(us)->GetType() ) ;
Console::WriteLine(_box(i)->GetType() ) ;
Console::WriteLine(_box(ui)->GetType<));
Console::WriteLine(_box(1)->GetType()) ;
Console::WriteLine(_box(ul)->GetType());
Console::WriteLine(intManagedArray->GetType());
}
Программа напечатает:
System.Boolean // Система. Булева переменная
System.Char // Система. Символ
System.Object // Система. Объект
System.String // Система. Строка
System.Single // Система. Одинарный
System.Double // Система. Двойной
System.SByte
System.Byte // Система. Байт
System.Intl6
System.UIntl6
System.Int32
System.UInt32
System.Int32
System.UInt32
System.Int32 []
Рис. 3.3. Использование утилиты Ildasm.exe для просмотра типов данных в программе, реализованной на управляемом C++
CompEbook.ru Железо, дизайн, обучение и другие
Типы данных C++ и общеязыковая среда выполнения CLR
Многие типы данных C++ соответствуют типам данных промежуточного языка IL .NET, определенным спецификацией общего (универсального) языка CLS (Common Language Specification). Некоторые из этих типов, совместимые со спецификацией общего (универсального) языка CLS, гарантированно поддерживаются всеми языками .NET. Они определены в рамках общей системы типов CTS (Common Type System). Спецификация общего (универсального) языка CLS и общая система типов CTS обеспечивают возможность взаимодействия языков, и, хотя C++ поддерживает использование многих типов, несовместимых со спецификацией общего (универсального) языка CLS, такие заблудшие типы следует использовать только в реализациях компонентов, и никогда не открывать в общих сборках. Соблюдение этого правила гарантирует, что программы, использующие подобные сборки, можно будет создавать на любом другом языке .NET, не опасаясь проблем с несовместимостью типов. В табл. 3.1 перечислены типы данных промежуточного языка IL, совместимые со спецификацией общего (универсального) языка CLS. Заметим, что это типы данных промежуточного языка IL, а не C++; но в C++ (и во всех других языках .NET) есть типы, эквивалентные приведенным.CompEbook.ru Железо, дизайн, обучение и другие
Типы значений
Ключевое слово _value (значение) похоже на _nogc (без сборки мусора), поскольку оно используется для того, чтобы класс или структура не участвовали в сборке мусора Это полезно для определения объектов в стеке, а не в управляемой динамически распределяемой области памяти Основная цель использования таких типов — возможность создания объектов, не требующих затрат на сборку мусора Использование ключевого слова _value (значение) имеет побочный эффект — класс автоматически становится конечным (ключевое слово _sealed) и не может быть абстрактным (ключевое слово _abstract к нему неприменимо)_value struct ValueStruct {
int i;
};
Может показаться удивительным, что правила позволяют определять тип _value (значение) там, где не позволяется определять тип _дс (сборщик мусора). В следующем фрагменте кода показан пример этого (вместе с несколькими другими конструкциями). Заметьте, что объекты классов Мап-agedClass, NonManagedClass и ValueClass можно создавать в динамически распределяемой области памяти, тогда как в стек можно поместить объекты только классов NonManagedClass и ValueClass. Последний оператор во фрагменте закомментирован, так как иначе компилятор выдал бы сообщение о недопустимости объявления управляемого объекта как переменной, помещаемой в стек.
//ValueType.срр
#using
using namespace System;
// использовать пространство имен Система;
_nogc class NonManagedClass
{
};
_value class ValueClass
// класс значения ValueClass
{
};
_gc class ManagedClass
// класс сборщика мусора ManagedClass
{
NonManagedClass nmc; // Странно! Но для компилятора это не ошибка!
ValueClass vc; // Это не ошибка, здесь допускается тип значения
};
void main(void)
{
NonManagedClass *pnmc = new NonManagedClass; //Нет ошибки
ValueClass *pvc = _nogc new ValueClass; //Нет ошибки
ManagedClass *pmc = new ManagedClass; //Нет ошибки
NonManagedClass; //Нет ошибки в стеке
ValueClass vc; //Нет ошибки в стеке
//ManagedClass me; // ошибка, не может быть размещен в стеке
}
CompEbook.ru Железо, дизайн, обучение и другие
Упаковка и распаковка примитивных типов данных
Упаковка и распаковка — важная концепция программирования в .NET вне зависимости от того, какой именно язык программирования вы используете. Одно из самых важных преимуществ .NET — унифицированная система типов. Каждый тип, в том числе простые упакованные встроенные типы, такие как _box (int), является потомком класса System.Object (Система.Объект). В языках, подобных Smalltalk, все типы являются объектами, но это приводит к неэффективности использования простых типов. В стандартном C++ простые встроенные типы данных и объекты обрабатываются по-разному, — это повышает эффективность использования типов, но исключает возможность унификации системы типов. Управляемый C++ объединяет преимущества обоих подходов, используя прием, называемый упаковкой (boxing). Упаковка — преобразование типов значений, таких, как int или double (с удвоенной точностью), в ссылку на объект, хранимый в динамически распределяемой области памяти. Упаковка производится с помощью ключевого слова _box. Распаковка — преобразование упакованного типа (хранимого в динамически распределяемой области памяти) в неупакованное значение (хранимое в стеке). Распаковка выполняется приведением типов. Проиллюстрируем упаковку и распаковку следующим фрагментом кода:int x = 5; // простой встроенный тип int
_box int *po = _box(x); // упаковка
x = *ро; // распаковывание
Ключевое слово _box создает в управляемой динамически распределяемой области памяти управляемый объект, инкапсулирующий копию выражения, имеющего тип значения. Под выражением, имеющим тип значения, подразумевается примитивный тип данных, такой как int, float (с плавающей точкой), double (с удвоенной точностью), или char (символ), либо тип значения, определенный как класс или структура и описанный с использованием ключевого слова _value (значение). Например, предопределенный управляемый тип _boxed_System_Int32 инкапсулирует упакованный int, a управляемый тип _boxed_ValueStruct — упакованный тип значения ValueStruct. Эти странные названия типов (_boxed_System_Int32 и _boxed_ValueStruct) не обязательно будут встречаться в вашем исходном коде, но они показываются утилитой Ildasm.exe. Обратите внимание, что _box int * — альтернативное имя управляемого типа _boxed_System_Int32, a _box ValueStruct* — альтернативное имя управляемого типа _boxed_ValueStruct.
Если ключевое слово _box используется для создания управляемого объекта, сборщик мусора .NET будет автоматически освобождать память, используемую данным объектом. Это похоже на концепцию использования для примитивных типов интерфейсных классов, однако упаковка имеет более важное значение в среде .NET, чем в программировании на обычном C++. Это происходит из-за того, что объекты в C++ можно использовать и как значения, и как ссылочные типы, тогда как в среде .NET управляемые объекты всегда являются ссылочными типами (т.е. ссылками или указателями на объекты, хранимые в управляемой динамически распределяемой области памяти).
Доступ к типам значений осуществляется так же, как и доступ к неупакованным типам. В приведенном ниже коде это делается в присваивании plntBox = 50. Несмотря на то, что plntBox указывает на управляемый объект, разыменованный указатель используется так, как будто он является просто указателем на неупакованный тип int.
//BoxExample.срр
#using
using namespace System;
// использовать пространство имен Система;
_value struct ValueStruct
{
public:
int i;
};
// функция ожидает получить управляемый указатель на объект
void ExpectManagedObjectPointer(
_box ValueStruct* pManagedObject)
{
pManagedOb]ect->i = 20; // изменяет упакованную копию
Console::WriteLine(pManagedObject->i) ;
}
// функция ожидает получить управляемый указатель на объект
void ExpectBoxedPrimitivePointer(_box int* plntBox)
{
*pIntBox = 50; //изменяет упакованную копию примитивного типа
Console::WriteLine(*рIntBox);
}
void main(void)
{
ValueStruct ValueStruct; // объект типа значение в стеке
ValueStruct.i = 10; // изменяет оригинал распакованной копии
Console::WriteLine(ValueStruct.i);
_box ValueStruct* pManagedObject
= _box(valueStruct); //_boxed_ValueStruct
ExpectManagedObjectPointer(pManagedObject) ;
pManagedObject->i = 30;* // изменяет упакованную копию
Console::WriteLine(pManagedObject->i);
int j; // тип значения - примитивный тип данных
j = 40; // изменяет первоначальный распакованный
// примитивный тип
Console::WriteLine(j);
_box int *p!ntBox = _box(j); // ynaKOBaHHbm_System_Int32
ExpectBoxedPrimitivePointer(plntBox);
}
Приведенная программа напечатает:
10
20
30
40
50
CompEbook.ru Железо, дизайн, обучение и другие
Управление сборкой мусора
В документации по языкам .NET вы могли встречать описание метода Finalize (Завершить), используемого для освобождения ресурсов, не находящихся в управляемой динамически распределяемой области памяти, но созданных управляемыми объектами. Однако в C++ реализовывать данный метод не надо. Если же все-таки сделать это, компилятор выдаст сообщение об ошибке, указав, что вместо метода Finalize (Завершить) для управляемого класса требуется определить деструктор. Сборщик мусора автоматически вызовет деструктор (в отдельном потоке) при освобождении памяти, занятой объектом; но момент вызова деструктора не определен. А это значит: не следует рассчитывать на то, что деструктор будет вызван при удалении ссылки на объект.Если вы реализовали деструктор и удаляете управляемый объект явно, деструктор будет вызван сразу, и сборщик мусора уже не будет его вызывать. Можно также, вызвав статический метод GC: :Collect () (Сборщик мусо-ра::Собрать()), вынудить сборщик мусора попытаться освободить память из-под объекта, а вызов деструктора синхронизировать с завершением работы сборщика мусора при помощи статического метода GC: :WaitForPending-Finalizers. Впрочем, обычно неудобно и неэффективно вызывать сборку мусора принудительно или синхронизовать ее с вызовом деструктора, поэтому, если необходимо выполнять очистку в определенный момент, рекомендуется реализовать это независимо в отдельном методе, а затем вызывать его явным образом. Этот метод рекомендуется называть Dispose (Ликвидировать). Рассмотрим следующую программу.
//ManagingGC.срр
fusing
using namespace System;
// использовать пространство имен Система;
_gc class ManagedClass
// класс сборщика мусора ManagedClass
{
public:
ManagedClass ()
{
Console::WriteLine("c'tor");
}
~ManagedClass ()
{
Console::WriteLine("d'tor");
}
};
void main(void)
{
Console::WriteLine("start"); // начало
ManagedClass *pmc = new ManagedClass;
// Раскомментируйте следующую строку
// для предотвращения вызова деструктора
//GC::SuppressFinalize(pmc); // СБОРЩИК МУСОРА
Console::WriteLine("middle"); // середина
// Раскомментируйте следующую строку
// чтобы вызвать деструктор пораньше
//delete pmc; // удалить
pmc = 0;
// ... или две следующие строки для того,
// чтобы вызвать деструктор пораньше
//GC::Collect(); // СБОРЩИК МУСОРА:: Собрать
//GC:-.WaitForPendingFinalizers () ; // СБОРЩИК МУСОРА
Console::WriteLine("end"); // конец
}
Приведем результат работы программы. Обратите внимание, что деструктор вызывается после того, как программа напечатает end (конец).
start // начало
c'tor
middle // середина
end // конец
d'tor
Однако, если раскомментировать строку, содержащую вызов метода SuppressFi-nalize, деструктор не будет вызван вообще, что доказывается следующей выдачей.
start // начало
с' tor
middle // середина
end // конец
Кроме того, если раскомментировать оператор, в котором используется delete (удалить), деструктор будет вызван до того, как программа напечатает end (конец).
start // начало
c'tor
middle // середина
d'tor
end // конец
Наконец, если раскомментировать только два оператора, содержащих вызовы методов Collect (Собрать) и WaitForPendingFinalizers, деструктор опять будет вызван до того, как программа напечатает end (конец) В этом случае вызов метода Collect (Собрать) приводит к вы зову деструктора, а метод WaitForPendingFinalizers приостанавливает выполнение текущего потока до завершения работы деструктора.
start // начало
c'tor
middle // середина
d'tor
end // конец
CompEbook.ru Железо, дизайн, обучение и другие
Управляемое приведение типов
Ключевое слово _try_cast приводит к возникновению исключения System: : InvalidCastException при попытке выполнить приведение типов, не поддерживаемое общеязыковой средой выполнения CLR. Это похоже на возникновение исключения bad_cast при выполнении оператора dy-namic_cast в C++ и на исключение ClassCastException, возникающее при некорректном приведении типов в Java. Хотя по своему действию оператор _try_cast больше похож на оператор dynamic_cast, чем на оператор static_cast, _try_cast в действительности задуман как временная замена оператора static_cast, применяемая на стадии разработки приложений. После анализа всех возникающих при выполнении _try_cast исключений и внесения соответствующих исправлений в программу, операторы _try_cast обычно заменяются операторами static_cast. В следующем примере продемонстрировано использование операторов _try_cast для выявления некорректных приведений типов.//TryCastExample.cpp
fusing
using namespace System;
// использовать пространство имен Система;
_gc class Mammal
// класс сборщика мусора Млекопитающее
{
};
_gc class Dog : public Mammal
// класс сборщика мусора Собака: общедоступное Млекопитающее
{
};
_gc struct Cat : public Mammal
// сборщик мусора; Кот: общедоступное Млекопитающее
{
};
void main()
{
Mammal *pMammal = new Dog;
// Млекопитающее *pMammal = новая Собака;
try // пробовать
{
Dog *pDog = _try_cast
// Собака
*pDog = _ try_cast <Собака *> (pMammal);
Console::WriteLine("_try_cast
// Собака -
// хорошо
Cat *pCat = _try_cast
// Кот *pCat = _ try_cast <Кот *> (pMammal);
Console::WriteLine("_try_cast
// пропустить
}
catch(InvalidCastException *pe)
{
Console::WriteLine("Ooops: {0}", pe->get_Message());
}
}
Приведенная программа напечатает:
_try_cast
Ooops: Exception of type System.InvalidCastException was
thrown.
CompEbook.ru Железо, дизайн, обучение и другие
Управляемые и неуправляемые типы
Управляемый тип — тип данных, инициализируемый (обычно с помощью оператора new (создать)) в управляемой динамически распределяемой области памяти, но ни в коем случае не в неуправляемой динамически распределяемой области памяти или стеке Попросту говоря, управляемый тип — тип, для которого сборка мусора осуществляется автоматически, потому для освобождения ресурсов, используемых объектами этого типа, нет необходимости использовать оператор delete (удалить). Вместо того чтобы явно удалять объект, можно либо сделать так, чтобы на него не указывал ни один указатель, либо явно приравнять этот указатель нулю. Неуправляемый тип — тип, который игнорируется автоматическим сборщиком мусора, вследствие чего программист должен освобождать занимаемую объектом память с помощью оператора delete (удалить).Объекты неуправляемых типов никогда не создаются в управляемой динамически распределяемой области памяти, а только либо в неуправляемой динамически распределяемой области памяти, либо в каком-нибудь другом месте памяти, как переменные в стеке или элементы данных другого неуправляемого класса. Поэтому именно неуправляемые типы — это то, с чем привыкли иметь дело программисты C++, тогда как управляемые типы больше похожи на ссылочные типы языка Java, для которых применяется автоматическая сборка мусора.
Ключевое слово _дс (сокращение от "garbage collection" — "сборка мусора") используется для объявления управляемых классов, или структур, и может использоваться для указателей и массивов. Ключевое слово _поде (сокращение от "no garbage collection" — "без сборки мусора") является антонимом _дс (сборщик мусора). Надо иметь в виду, что ключевое слово _дс (сборка мусора) можно использовать только в управляемом коде, а значит, при этом следует использовать параметр компилятора /CLR (Компиляция для выполнения в общеязыковой среде), причем прагма Ipragma unman-aged должна быть неактивна. Ключевое слово _nogc (без сборки мусора) можно использовать как в управляемом, так и в неуправляемом коде. Следующий фрагмент демонстрирует типичное использование _дс (сборщик мусора) при определении управляемого класса:
_gc class ManagedClass
// класс сборщика мусора ManagedClass
{
};
Ключевое слово _поде ( без сборки мусора) просто означает, что класс, структура, массив или объект, на который указывает определенный с этим словом указатель, не управляется сборщиком мусора .NET. Данное ключевое слово используется для явного указания, что объект никогда не создается в управляемой динамически распределяемой области памяти. Недопустимо наследование типа, определенного с ключевым словом _дс (сборщик мусора) или _поде (без сборки мусора), от типа, определенного с другим из этих ключевых слов, равно, как не допускается использование _дс (сборщик мусора) в неуправляемом коде.
_nogc class UnmanagedClass
{
};
Заметим, что автоматическая сборка мусора управляемых объектов касается лишь освобождения неиспользуемой управляемой динамически распределяемой области памяти, но не других ресурсов, таких, как дескрипторы файлов или соединения с базами данных.
CompEbook.ru Железо, дизайн, обучение и другие
Ваша первая программа на управляемом C++.NET
Хотя вы, почти наверняка, хорошо знакомы с C++, мы начнем с рассмотрения очень простого, но традиционного примера— программы HelloWorld (Привет, мир). В этом разделе мы расскажем, как написать, скомпилировать и запустить эту и другие программы.CompEbook.ru Железо, дизайн, обучение и другие
Закрепление управляемых объектов
Ключевое слово _pin (закрепить) указывает на то, что указатель на управляемый объект будет оставаться корректным (т.е. общеязыковая среда выполнения CLR не переместит Объект в памяти) на протяжении существования закрепленного указателя. Закрепленный Объект остается на своем месте в памяти до тех пор, пока на него указывает закрепленный указатель. Если изменить указатель так, что он будет указывать на другой объект или присвоить ему нулевое значение, объект может быть перемещен сборщиком мусора. Когда при определении указателя не задано ключевое слово _pin (закрепить), общеязыковая среда Выполнения CLR может в любой момент переместить объект, на который указывает этот указатель. Перемещение объектов происходит вследствие сборки мусора и уплотнения динамически распределяемой области памяти, выполняемых общеязыковой средой выполнения CLR. Эти перемещения не сказываются на управляемом коде, так как общеязыковая среда выполнения CLR автоматически изменяет значения управляемых указателей при перемещении объектов, но могут повлиять на выполнение неуправляемого кода, в котором Используются неуправляемые указатели на управляемые объекты.Ключевое слово _рш (закрепить) следует применять только в тех случаях, когда это Крайне необходимо, так как закрепление объектов расстраивает сборку мусора и снижает ее эффективность. Для примера необходимости закрепления можно упомянуть ситуацию, в которой вы передаете неуправляемой функции в качестве аргумента указатель На управляемый объект (или указатель на элемент данных такого объекта). В данном случае проблема состоит в том, что в процессе выполнения программы управляемый объект Может быть перемещен сборщиком мусора, но неуправляемая функция будет при этом Использовать старый, некорректный указатель. Это приведет к тому, что неуправляемая функция обратится по некорректному адресу и последствия этого могут быть катастрофическими.
Ниже приведен фрагмент, иллюстрирующий использование ключевого слова _pin (закрепить) в описанной ситуации. Обратите внимание, что объект pPinnedObject закреплен в памяти, так что передача указателя на него методам SetGlobalPointerValue и GetGlobalPointerValue в качестве аргумента является допустимой. Реализация этих методов основана на том, что глобальный указатель дх остается корректным, а это может быть верны только в случае, когда общеязыковая среда выполнения CLR не будет перемещать объект класса ManagedClass. Заметим, что компилятор способен предсказать возникновение такой ситуации и выдаст сообщение об ошибке, если из приведенного примера удалить ключевое слово _pin (закрепить).
//PinExample.срр
#using
using namespace System;
// использовать пространство имен Система;
_gc class ManagedClass
// класс сборщика мусора ManagedClass
{
public:
int x; };
ttpragma unmanaged // неуправляемый
int *gx; // глобальный указатель void SetGlobalPointer(int* pi)
{
// установить глобальный указатель,
// чтобы указать на управляемый объект
gx = pi;
}
void SetGlobalPointerValue(int i)
{
// установить управляемый объектный элемент данных
// через глобальный указатель
*gx = i;
}
int GetGlobalPointerValue()
{
// получить управляемый объектный элемент данных
// через глобальный указатель
return *gx;
}
Ipragma managed // управляемый
void main()
{
ManagedClass _pin * pPinnedObject = new ManagedClass;
// обратите внимание на ошибку, генерируемую компилятором
//в следующей инструкции...
// если ключевое слово _pin удалить из предыдущей инструкции
SetGlobalPointer(&pPinnedObject->x); // неуправляемый
SetGlobalPointerValue(1); // неуправляемый
int x = GetGlobalPointerValue();//неуправляемый
}
CompEbook.ru Железо, дизайн, обучение и другие
Architecture Net
Класс Customers (Клиенты)
Нельзя реализовать систему резервирования, не смоделировав клиентов, которые ее используют. Класс Customers (Клиенты), который находится в файле customers.h, поддерживает список объектов типа Customer (Клиент). Этот список также представлен в виде массива. Реализация указанного класса очень похожа на реализацию гостиничных классов, поэтому она будет приведена в общих чертах, а точнее, мы приведем лишь структуры данных и объявления общедоступных методов и свойств.//Customer.h
using namespace System;
// использовать пространство имен Система;
namespace 0I { namespace NetCpp { namespace Acme {
// пространство имен 01 {пространство имен NetCpp
// {пространство имен Acme {
public _gc class Customer
// класс сборщика мусора Клиент
{
public:
int Customerld;
String *FirstName;
String *LastName;
String *EmailAddress;
private: // частный
static int nextCustld = 1; // статический
public:
Customer(String *first, String *last, String *email) // Клиент
{
Customerld = nextCustId++;
FirstName = first;
LastName = last;
EmailAddress = email; // электронная почта
}
};
public _gc class Customers
// класс сборщика мусора Клиенты
{
private: // частный
Customer *customers []; // Клиент
static int nextCust =0; // статический
public:
Customers(int MaxCust) // Клиенты
{
customers = new Customer*[MaxCust]; // клиенты
RegisterCustomer(
"Rocket", //"Ракета"
"Squirrel", "rocky@frosbitefalls.com"); // "Белка"
RegisterCustomer(
"Bullwinkle", "Moose", "moose@wossamotta.edu");
// "Американский лось"
}
_property int get_NumberCustomers()
int RegisterCustomer(
String *firstName,
String *lastName,
String *emailAddress)
void Add(Customer *cust) // Добавить (Клиент)
void ShowCustomers(int customerId)
void ChangeEmailAddress(
int id, String *emailAddress) // идентификатор
};
CompEbook.ru Железо, дизайн, обучение и другие
Класс HotelBroker
Самая важная задача в примере — реализовать класс HotelBroker, который является производным от класса Broker (Брокер). Код этого класса находится в файле hotel-broker.h.public _gc class HotelBroker : public Broker
// класс сборщика мусора - HotelBroker: общедоступный Брокер
{
private: // частный
// статические константы;
static const int MAXDAY = 366;
static const int MAXUNIT = 10;
static const int MAXCITY = 5;
static private int nextCity = 0; // статическая частная
String *cities[];
public:
HotelBroker() : Broker(MAXDAY, MAXUNIT) // Брокер
{
cities = new String*[MAXCITY]; // города
AddHoteK"Atlanta", "Dixie", 100, 115.00); //Атланта,
// Дикси
AddHotel("Atlanta", "Marriott", 500, 70.00); // Атланта,
// Мариот
AddHotel("Boston", "Sheraton", 250, 95.00); // Бостон,
// Шератон
}
};
Для описания массивов вводятся константы, и создается массив, содержащий названия городов. Конструктор определяет массивы с помощью конструктора базового класса, инициализирует массив cities (города) и добавляет несколько гостиниц для тестирования.
Потом определяется свойство NumberCity и метод добавления гостиницы в список гостиниц.
_property int get_NumberCity()
{
return nextCity;
}
String *AddHotel(
String *city,
String *name,
int number, // число
Decimal cost) // Десятичная стоимость
{
if (Findldfcity, name) != -1)
// если (Findld (город, название)! =-1)
return "Hotel is already on the list";
// "Гостиница уже находится в списке";
Hotel *hotel = // Гостиница
new Hotel(city, name, number, cost);
// новая Гостиница (город, название, число, стоимость);
AddUnit(hotel); // гостиница
AddCity(city); // город
return "OK";
}
Частные вспомогательные функции помогают найти идентификатор гостиницы и добавить город в список. Город можно добавить только тогда, когда его в списке еще нет: список не может содержать два одинаковых города.
int Findld(String *city, String *name)
{
for (int i = 0; i < NumberUnits; i++)
{
Hotel *hotel = dynamic_cast
// Гостиница
if ((String::Compare(hotel->City, city) == 0)
// сравнить (гостиница-> Город, город)
&& (String::Compare(
// сравнить (гостиница-> HotelName, название)
hotel->HotelName, name) == 0))
return hotel->Hotel!d; // гостиница
}
return -1;
}
void AddCity(String *city)
{
// проверить, есть ли город уже в списке, добавить, если нет
if ('Contains(city))
// если (! Содержит (город))
cities[nextCity++] = city;
// города [nextCity ++] = город;
}
bool Contains(String *city)
{
for (int 1=0; i < NumberCity; i++)
{
if (String::Compare(cities[i] , city) == 0)
// сравниваются (города [i], город)
return true; // истина
}
return false; // ложь
}
Итак, мы реализовали методы вывода списка всех гостиниц, гостиниц определенного города и списка всех городов. Стоит взглянуть на код, чтобы понять, как реализуется простое форматирование текста.
Наконец, мы подошли к описанию ключевого в классе HotelBroker метода Reserve (Резерв), с помощью которого можно зарезервировать номер.
ReservationResult *Reserve(
int customerld, String *city, String *name,
DateTime dt, int numDays)
{
int id = Findld(city, name);
// int идентификатор = Findld (город, название{имя});
if (id == -1)
// если (идентификатор ==-1)
{
ReservationResult *result =
new ReservationResult;
result->Reservation!d = -1; // результат
result->Comment = "Hotel not found";
// результат-> Комментарий = "Гостиница не найдена";
return result; // результат
}
HotelReservation *res = new HotelReservation;
res->Unit!d = id; // идентификатор
res->CustomerId = customerld;
res->HotelName = name; // название
res->City = city; // Город = город
res->ArrivalDate = dt;
res->DepartureDate =
dc.Add(TimeSpan(numDays, 0, 0, 0)); // Добавить на период
res->NumberDays = numDays;
return Broker::Reserve(res);
}
Реализовать класс HotelBroker оказалось несложно, потому что в его основе лежит логика, реализованная в классе Broker (Брокер). Если гостиницы нет в списке гостиниц, то возвращается сообщение об ошибке. После этого создается объект Но-telReservation, который передается в качестве параметра методу Reserve (Резерв) базового класса. В производном классе мы создаем объект резервирования, так как нам нужны все поля класса HotelReservation, а не только поля, унаследованные от класса Reservation (Резервирование). Для того чтобы подсчитать дату отбытия (прибавить количество дней, на которые зарезервирован номер, к дате прибытия), мы используем структуру TimeSpan вместо ранее использовавшейся для этих целей структуры DateTime. Такое вычисление сделать нетрудно, поскольку в структуре DateTime знак операции + перегружен.
CompEbook.ru Железо, дизайн, обучение и другие
Класс HotelReservation
Класс HotelReservation— это простой класс, который является производным класса Reservation (Резервирование). Его код находится в файле hotelbroker.h. Этот класс включает в себя некоторые дополнительные общедоступные поля и свойство ArrivalDate (Дата прибытия), которое несет больше конкретного смысла, чем поле Date (Дата) базового класса.public _gc class HotelReservation : public Reservation
// класс сборщика мусора - HotelReservation: общедоступное
Резервирование
{
public:
int Customerld;
String *HotelName;
String *City;
DateTime DepartureDate;
_property DateTime get_ArrivalDate()
{
return Date; // Дата
}
_property void set_ArrivalDate(DateTime value) // значение
{
Date = value;
// Дата = значение;
}
};
CompEbook.ru Железо, дизайн, обучение и другие
Класс TestHotel
Класс TestHotel, который находится в файле TestHotel .h, содержит интерактивную программу для испытания классов, связанных с резервированием гостиницы, и классов клиентов, поддерживающих описанные ранее команды. В этом классе имеется цикл, просматривающий команды, — такой цикл считывает команду и затем выполняет ее. Класс содержит большой блок try для всех команд, за которым следует обработчик исключений catch. Обратите внимание, — чтобы получить доступ к пространству имен нужно использовать директиву using.//TestHotel.h
using namespace System;
// использовать пространство имен Система;
using namespace 0I::NetCpp::Acme;
// использовать пространство имен OI::NetCpp::Acme;
public _gc class TestHotel
// класс сборщика мусора TestHotel
{
public:
static void Main()
{
const int MAXCUST = 10; // константа
HotelBroker *hotelBroker = new HotelBroker;
Customers *customers = new Customers(MAXCUST) ;
// новые Клиенты
InputWrapper *iw = new InputWrapper;
String *cmd;
Console::WriteLine("Enter command, quit to exit");
// ("Введите команду, quit для выхода");
cmd = iw->getString("H> ");
while (! cmd->Equals("quit"))
{
try // попытка
{
if (cmd->Equals("hotels")) // если Равняется
// ("гостиницы")
{
String *city = iw->getString("city: ");
// город
hotelBroker->ShowHotels(city); // город
}
else if (cmd->Equals("all")) // если Равняется
// ("все")
hotelBroker->ShowHotels ();
else
hotelhelp() ;
}
catch (Exception *e) // Исключение
{
Console::WriteLine(
"Exception: {0}", e->Message);
// "Исключение: {0} ", e-> Сообщение);
}
cmd = iw->getString("H> ");
}
}
};
CompEbook.ru Железо, дизайн, обучение и другие
Классы
Класс объединяет все объекты с одинаковыми структурой и поведением. Класс дает возможность создавать новые однотипные объекты. Каждый объект является экземпляром какого-то класса. Процесс создания объекта данного класса называется созданием экземпляра. Классы могут быть связаны между собой отношениями наследования и включения.Наследование
Наследование — это ключевая особенность парадигмы объектно-ориентированного программирования. В результате абстрагирования общие свойства некоторого набора классов включаются в базовый класс более высокого уровня. А в производных классах более узкой специализации, которые "наследуют" поведение базового класса, можно изменять свойства или добавлять новые. Именно наследование делает возможным повторное использование кода и позволяет расширить сферу применения имеющихся классов.
Рассмотрим базовый класс Reservable (Резервируемый объект, ресурс) и производные от него классы, Hotel (Гостиница) и Flight (Рейс). У всех резервируемых объектов есть общие свойства, например, объем. У каждого вида резервируемых объектов есть свои уникальные характеристики. Например, гостиница имеет название и находится в определенном городе, а рейс — начало и пункт назначения. На рис. 4.1 проиллюстрированы отношения между разными резервируемыми объектами.
Абстрактные классы
Некоторые классы никогда не используются для создания экземпляров, а служат только шаблонами для создания производных классов. Например, класс Reservable (Резервируемый объект, ресурс) слишком абстрактен для создания экземпляров. На практике приходится создавать экземпляры только производных от него классов, таких как Hotel (Гостиница) и Flight (Рейс). Класс, от которого нельзя создавать экземпляры, называется абстрактным. Примером такого класса может служить класс Reservable (Резервируемый объект, ресурс). Класс, от которого можно создавать экземпляры, называется конкретным.
Рис. 4.1. Отношение наследования для разных классов резервируемых объектов
Отношения между классами
Класс может находиться в следующих отношениях с другим классом:
CompEbook.ru Железо, дизайн, обучение и другие
Логика базовых классов
Базовый класс Broker (Брокер) не только является абстрактным брокером, который резервирует объекты, но также содержит общую логику регистрации резервирований и список резервирований. Если реализовать эту логику в абстрактном (базовом) классе, то это сделает его еще более полезным, так как значительно упростит реализацию резервирования в производных классах.Метод Reserve (Резерв)
Основным методом класса Broker (Брокер) является метод Reserve (Резерв).
ReservationResult *Reserve(Reservation *res) // Резервирование
{
int unitid = res->Unit!d;
DateTime dt = res->Date; // Дата
int numDays = res->NumberDays;
ReservationResult *result = new ReservationResult;
// Проверить, находятся ли даты
// в пределах поддерживаемого диапазона
int day = dt.DayOfYear - 1;
if (day + numDays > MaxDay) // если (день + numDays> MaxDay)
{
result->Reservation!d = -1; // результат
result->Comment = "Dates out of range";
// результат-> Комментарий = "Даты вне диапазона";
return result; // результат }
// Проверить, доступны ли комнаты для всех дат
for (int i = day; i < day + numDays; i++)
{
if (numCust[i, unitid] >= units[unitid]->capacity)
// вместимость
{
result->Reservation!d = -1; // результат
result->Coiranent = "Room not available";
// результат-> Комментарий = "Комната не доступна";
return result; // результат
}
}
// Резервировать комнату для требуемых дат
for (int i = day; i < day + numDays; i++) n
umCust[i, unitid] += 1;
// Добавить резервирование (Add reservation)
// к списку резервирования,
// возвратить результат
AddReservation(res);
result->Reservation!d = res->Reservation!d; // результат
result->ReservationCost = // результат
units[unitid]->cost * numDays; // стоимость
result->Rate = units[unitid]->cost; // результат = стоимость;
result->Comment = "OK"; // результата Комментарий = "хорошо";
return result;
}
Метод Reserve (Резерв) разработан так, чтобы с его помощью можно было зарезервировать разные типы резервируемых объектов. Таким образом, объект Reservation (Резервирование), который хранится в списке резервируемых объектов, создается в классе, производном от Broker (Брокер), и передается в метод Reserve (Резерв) в качестве параметра. Например, объект HotelBroker резервирует объект HotelReservation и т.д. Потом создается объект ReservationResult, который и будет возвращен (поля Unitid, Date (Дата) и NumberDays наследуются из базового класса Reservation (Резервирование)).
ReservationResult *Reserve(Reservation *res) // Резервирование
{
int unitid = res->UnitId;
DateTime dt = res->Date; // Дата
int numDays = res->NumberDays;
ReservationResult *result = new ReservationResult;
Затем мы проверяем, попадают ли даты резервирования в определенный период времени, который поддерживается системой (чтобы облегчить задачу, возьмем период в один год). Для этой цели используем структуру DateTime из пространства имен System (Система). Если какая-то дата не входит в указанный период, будет выдано сообщение об ошибке.
// Проверить, лежат ли даты в пределах поддерживаемого диапазона
int day = dt.DayOfYear - 1;
if (day + numDays > MaxDay) // если (день + numDays> MaxDay)
{
result->Reservation!d = -1; // результат
result->Comment = "Dates out of range";
// результат-> Комментарий = "Даты вне диапазона";
return result; // результат }
Потом нужно для каждого из запрашиваемых дней проверить, доступен ли этот ресурс, то есть, не будет ли число резервирований превышать объем ресурса (гостиницы). Мы сделаем это с помощью массива numCust. Первый индекс этого двумерного массива определяет конкретную дату, а второй — идентификационный код резервируемого объекта (Обратите внимание на то, что поля и методы названы именами, которые описывают их суть, например, HotelBroker).
// Проверить, доступны ли комнаты для всех дат
for (int i = day; i < day + numDays; i++)
{
if (numCust[i, unitid] >= units[unitid]->capacity)
// вместимость
{
result->Reservation!d = -1; // результат
result->Comment = "Room not available";
// результат-> Комментарий = "Комната не доступна";
return result; // результат
}
}
Далее нужно зарезервировать номер на указанный период времени, увеличив количество клиентов в numCust на единицу в каждый из дней, на которые клиент делает запрос.
// Резервировать комнату для требуемых дат
for (int i = day; i < day + numDays; i++)
numCust[i, unitid] += 1;
Наконец, мы добавляем резервирование к списку резервирований и возвращаем результат.
// Добавить резервирование к списку резервирований
// и возвратить результат
AddReservation(res);
result->Reservation!d = res->Reservation!d; // результат
result->ReservationCost = // результат
units[unitid]->cost * numDays; // стоимость
result->Rate = units[unitid]->cost;
// результат = стоимость;
result->Comment = "OK";
// результат-> Комментарий = "хорошо";
return result;
}
Список резервирований и резервируемых объектов
Класс Broker (Брокер) также содержит список резервирований и резервируемых объектов. Для нашей реализации в виде массивов мы реализуем только методы добавления (Add), а в последующих версиях примера рассмотрим, как удалять элементы из списка.
void AddReservation(Reservation *res) // Резервирование
{
reservations[nextReservation++] = res; // резервирование
}
void AddUnit(Reservabie *unit)
{
units[nextUnit++] = unit;
}
CompEbook.ru Железо, дизайн, обучение и другие
Наследование в управляемом C++
В управляемом C++ поддерживается модель единичного наследования. Следовательно, класс может быть порожден не больше чем из одного базового класса. Такая модель проста и дает возможность избежать усложнений и неопределенностей, которые возникают при множественном наследовании в неуправляемом C++. Хотя класс в управляемом C++ может быть производным только от одного базового класса, однако он может быть наследником нескольких интерфейсов, — это мы обсудим в следующей главе. В данном разделе мы рассмотрим наследование в связи с примером резервирования гостиничного номера.CompEbook.ru Железо, дизайн, обучение и другие
Объектно-ориентированное программирование на управляемом C++
В предыдущеи главе были наложены основы программирования на управляемом C++. Учитывая ваш богатый опыт работы с C++ бьпо охвачено много материала В этой паве мы изменим темп вместо того, чтобы осваивать новый мат ерши потратим ботьше времени на углубление понимания объектно ориентированной стороны управляемого С++ в частности, абстракции и наследования Сначала мы повторим основы объектно-ориентированного программирования, потом рассмотрим пример Бюро путешествии Acme. Этот пример будет разрабатываться в последующих павах по мере изучения татформы NET Мы рассмотрим, какие абстракции подходят для того чтобы реализовать систему резервирования ресурсов, и реализуем систему резервирования (бронирования) гостиничных номеров В абстрактные базовые классы мы поместим повторно иелользхемыи код с помощью которого можно легко реализовать и другие системы резервирования Вы увидите что правильное использование абстракции — ключ к такой реализации. Вы также знаете какие особенности мтравллемого C++ особенно полезны для объектно ориентированного программирования Эти особенно сти а именно, контроль за доступом (модификаторы достлпа public (общелрстлпный) private (частный) и protected (защищенный)) и использование свойств дают возможность созывать надежные и удобные в употреблении абстракцииCompEbook.ru Железо, дизайн, обучение и другие
Объекты
Объекты имеют значение не только в мире программ, но и в реальном мире. Объектная модель описывает взаимосвязь между объектами.Объекты в реальном мире
Значение термина объект в реальном мире интуитивно понятно. Существуют конкретные, материальные осязаемые объекты (мяч, автомобиль, самолет), и более абстрактные объекты, которые представляют собой реализацию некоторых понятий (комитет, патент, страховой контракт).
У объектов есть атрибуты (характеристики), причем к объектам можно применять некоторые операции. У мяча есть размер, вес, цвет, и т.д. С мячом можно проделать некоторые действия, например, бросить, поймать и уронить.
Между классами объектов могут существовать разные отношения. Одно из таких отношений— это отношение специализации (конкретизации), например автомобиль — это один конкретный вид из различных средств передвижения. Существуют также отношения "часть—целое"; например, автомобиль состоит из двигателя, шасси, колес и т.д.
Объектные модели
Объекты могут также использоваться в программах. Они служат для реализации программной модели системы из реального мира. Программные объекты являются абстракциями объектов реального мира, они описывают те свойства реальных объектов, которые необходимы для решения данной задачи. Потом с помощью языка программирования можно реализовать программную модель системы из реального мира. Программная модель реализуется таким образом, чтобы наиболее точно моделировать реальную систему и чтобы ее можно было без труда изменить, если изменится реальная система.
Существуют формальные языки для описания объектных моделей. Самый популярный из них — это универсальный язык моделирования UML (Unified Modeling Language), который получился в результате синтеза нескольких более ранних языков моделирования. Описание формальных языков моделирования не является предметом этой книги, и потому мы будем использовать неформальные модели.
Многократно используемые компоненты
Еще одним преимуществом объектов в программировании является то, что с их помощью можно создавать многократно используемые компоненты. При проектировании аппаратных средств ЭВМ уже длительное время извлекается существенная выгода от применения компонентов аппаратных средств ЭВМ многократного использования. Например, компьютер можно собрать из блока питания, печатных плат и других комплектующих. Печатные платы можно собрать из отдельных чипов. Такие же чипы можно использовать и в других компьютерах, и потому новое оборудование не приходится проектировать с самого начала. Подходящая технология позволяет реализовать возможность такого повторного использования и в программном обеспечении. Благодаря объектам можно повторно использовать программы.
Объекты в программах
Объект — это элемент программы, состоящий из членов-данных (состояние) и функций для работы с ними (поведение), который рассматривается как независимый модуль. Например, объект HotelBroker может хранить в себе список гостиниц (состояние) и функции, с помощью которых можно добавить гостиницу в список и зарезервировать гостиничный номер (поведение).
Абстракция
Абстракция передает основные свойства объекта реального мира, опуская ненужные детали. Все экземпляры абстрактного объекта имеют эти общие свойства. Абстракция позволяет уменьшить сложность задачи. Рассмотрим, например, проблему резервирования: можно забронировать гостиничный номер, билет на авиарейс, или зарезервировать зал для совещаний. Резервирование всех этих услуг проводится по-разному, но есть общие свойства, например, объем резервирования (количество мест в гостинице, количество мест в авиарейсе).
Инкапсуляция
Реализация абстракции должна быть скрыта от остальной части системы, т.е. инкапсулирована. Например, для хранения списка гостиниц могут применяться различные структуры данных, например, массив, коллекция или база данных. Остальная часть системы не должна знать детали представления списка.
CompEbook.ru Железо, дизайн, обучение и другие
Обзор основных понятий объектно-ориентированного программирования
Эта часть главы— встлпление в ней мы рассмотрим основы объектно ориентированного программирования Если у вас есть опыт работы с объектно ориентированным С++, можете пропустить вступление и перейти к рассмотрению примера. С другои стороны если вы раньше использовали C++ просто как улучшенныи вариант С++ , вам будет полезно прочитать встуаление, для того чтобы научиться использовать С++ в качестве объектно-ориентированного языка и научиться строить иерархии наследования которые являются моделями реальных системCompEbook.ru Железо, дизайн, обучение и другие
Основные принципы наследования
Если вы используете механизм наследования, учитывайте все абстракции вашей объектной модели и те из них, которые можно будет повторно использовать, поместите в базовые классы как можно более высокого уровня. Добавлять и изменять свойства можно в более специализированных производных классах, которые наследуют стандартное поведение базовых. Благодаря наследованию облегчается повторное использование кода и его расширяемость. Кроме того, производный класс может содержать более подходящий для членов базового класса интерфейс.Давайте рассмотрим базовый класс Reservable (Резервируемый объект, ресурс) и производный от него класс Hotel (Гостиница). У всех резервируемых объектов есть некоторые общие свойства, например, идентификатор объекта (ID), объем (capacity) и стоимость (cost). У каждого отдельного вида резервируемых объектов есть свои уникальные свойства. Например, в классе гостиницы есть атрибуты City (Город) и Hotel-Name (Название гостиницы).
Синтаксис наследования в управляемом C++
Наследование в управляемом C++ реализуется таким образом: в операторе class производного класса через двоеточие указывается имя базового класса. В файле HotelBroker.h в папке CaseStudy показано, как из базового класса Reservable (Резервируемый объект, ресурс) порождается класс Hotel (Гостиница).
public _gc class Hotel : public Reservable
// класс сборщика мусора - Гостиница:Reservable
{
};
Класс Hotel (Гостиница) автоматически наследует все поля класса Reservable (Резервируемый объект, ресурс) и содержит свои собственные поля City (Город) и Но-telName (Название гостиницы).
Внесение изменений в интерфейс существующих членов класса
Члены базового класса, unit id, capacity (объем имеющихся ресурсов), cost (стоимость), предназначены для внутреннего использования и не должны быть общедоступными. В классе Hotel (Гостиница) находятся общедоступные члены Hotel Id, Num-berRooms и Rate (Цена), к которым пользователи имеют доступ "только для чтения". Когда мы реализуем свойство таким образом, можно вместо абстрактного имени, например capacity (объем имеющихся ресурсов), которое используется в базовом классе, выбрать имя, более конкретное, например, NumberRooms.
Вызов конструкторов базового класса
Если в производном классе есть конструктор с параметрами, возможно, вы захотите передать некоторые из этих параметров в конструктор базового класса. В C++ можно вызвать конструктор базового класса, поставив перед ним двоеточие. В управляемом C++, в отличие от обычного неуправляемого C++, можно использовать только один базовый класс и список параметров, так как здесь поддерживается только единичное наследование.
Hotel( // Гостиница
String *city,
String *name,
int number, // число
Decimal cost) // Десятичная стоимость
: Reservable(number, cost) // число, стоимость
{
City = city;
// Город = город;
HotelName = name; // название
}
Обратите внимание на то, что в управляемом C++ можно вызвать только конструктор базового класса, из которого непосредственно был порожден данный класс. Нельзя вызвать конструктор, стоящий в иерархии наследования выше.
CompEbook.ru Железо, дизайн, обучение и другие
Полиморфизм
Давайте рассмотрим задачу создания платежной ведомости для разных категорий служащих. Зарплата для разных категорий служащих может начисляться по-разному. Например, некоторые оплачиваемые служащие получают установленный заработок. Рабочему, получающему зарплату, заработная плата начисляется в зависимости от количества отработанных часов. Коммерческому служащему платят комиссионные, которые он зарабатывает на продажах.Традиционный подход к этой задаче состоит в том, что поле с типом сотрудника, помещается в структуру объекта "сотрудник" и обработка данных происходит с помощью оператора выбора switch, причем для каждой категории служащих предусматривается своя case-фраза. Использование оператора выбора switch часто приводит к ошибкам, — ведь нужно быть внимательным при добавлении новой категории служащих.
Можно поступить по-другому, локализовав логику расчета зарплаты в каждом из классов служащих. Нужно только реализовать для каждого класса служащих свой собственный метод GetPay. В базовом классе можно реализовать функцию GetPay, которая будет содержать общий код для разных категорий служащих, чтобы при добавлении новой категории служащих не пришлось вносить изменения в код. Опишите метод GetPay в базовом классе, и потом в каждом производном классе переопределите его, т.е. опишите метод GetPay, который будет "подменять" метод GetPay базового класса. Вызовите метод GetPay через ссылку на объект базового класса Employee (Служащий). В результате для каждого конкретного класса служащих будет вызван свой метод GetPay.
Способность одного и того же метода выполнять разные действия в зависимости от объекта, к которому он применяется, называется полиморфизмом. Полиморфизм намного уменьшает сложность программных систем и является важной составляющей парадигмы объектного программирования.
Не следует специально менять программную модель, для того чтобы ввести полиморфизм. В примере "Бюро путешествий Acme" мы имеем дело с тремя разными абстрактными базовыми классами, но в этом случае нет необходимости использовать полиморфизм для обобщения поведения. С другой стороны классы .NET Framework часто используют полиморфизм, в чем можно убедиться, прочитав главу 5 "Управляемый C++ в .NET Framework".
CompEbook.ru Железо, дизайн, обучение и другие
Проект: "Бюро путешествий Acme"
Бюро путешествий Acme предоставляет несколько видов услуг: резервирование гостиничного номера, авиабилета и автомобилей напрокат. На простом примере приема заказов на резервирование, который и далее используется в книге, проиллюстрируем характерные черты .NET. В этой главе будет разработана архитектура общей системы приема заказов на резервирование. Мы создадим систему гостиничных брокеров (посредников), в которой реализуются следующие функции:Система будет также содержать список клиентов. При регистрации клиентов, вводятся имя и адрес электронной почты, каждому клиенту присваивается идентификационный номер. В подсистеме управления клиентами реализуются следующие функции:
В этой главе все списки гостиниц, заказов и клиентов реализуются в виде массивов В следующей главе вместо массивов будут использоваться коллекции .NET и реализуется больше возможностей, например, удаление гостиницы из списка и отмена резервирования. В последующих главах пример программы усовершенствуется будет разработан графический пользовательский интерфейс, данные будут храниться в базе данных и т. д.
Код программы примера находится в папке CaseStudy для этой главы
CompEbook.ru Железо, дизайн, обучение и другие
Проектирование абстракций
Поскольку на самом деле нам нужно реализовать не только систему резервирования гостиничных номеров, но и систему резервирования других ресурсов, включая резервирование авиабилетов и автомобилей напрокат, необходимо тщательно продумать механизм абстракции С одной стороны, чем больше функций мы сможем включить в базовый класс, тем меньше останется работы при реализации каждой конкретной системы резервирования. С другой стороны, если базовый класс будет содержать слишком много функций, то множество задач, для решения которых его можно применить, уменьшится При качественном проектировании необходимо определить оптимальное множество функций, включаемых в базовый класс.Еще одним свойством "хороших" абстракций является то, что их не задевают даже серьезные изменения в реализации. Далее в этой книге будет показано, что вносить изменения в абстракции системы резервирования гостиничных номеров не придется даже тогда, когда мы реализуем ее с помощью базы данных SQL Server.
Упомянутые абстракции в C++ представлены с помощью абстрактных классов, описанных в файле Broker.срр, расположенном в папке CaseStudy для этой главы
Класс Reservabie (Резервируемый объект, ресурс)
В качестве первой абстракции выберем объект, который мы будем резервировать. Назовем эту абстракцию просто Reservabie (Резервируемый объект, ресурс). Основной момент в резервировании — это использование ресурса Нельзя, чтобы один ресурс был зарезервирован несколькими пользователями одновременно, поэтому основным атрибутом Reservabie (Резервируемый объект, ресурс) является capacity (объем имеющих-
ся ресурсов). Например, в гостинице — 100 номеров, авиарейс имеет 250 мест Для объекта Reservable (Резервируемый объект, ресурс) также потребуется уникальный идентификатор, который мы обозначим unitid (Вместо более длинного, неудобного имени reservableid, было выбрано более короткое unitid. Позже мы будем использовать слово unit (объект, единица) и в других именах Например, метод для добавления резервируемого объекта назовем AddUnit)
В приложениях мы введем дополнительный атрибут cost (стоимость). Для гостиничного номера — это его стоимость за сутки, для авиабилета — цена и т д Обратите внимание на то, что к некоторым резервируемым объектам этот атрибут может быть неприменим Например, стоимость зала заседаний для компании может и не быть определена, но поскольку наше приложение будет использоваться в коммерческих целях, то атрибут cost (стоимость) нужно включить в модель
Упрощения
Поскольку цель примера состоит в том, чтобы проиллюстрировать основные понятия управляемого C++ и .NET, модель примера довольно упрощена, чтобы не тратить время на программирование деталей. Например, в реальной гостинице есть несколько видов номеров разной стоимости. Точно так же на авиарейс имеются места в разных классах. На практике ситуация еще осложняется тем, что цена билета зависит от того, был ли он зарезервирован, есть ли ограничения на полет и т.д. Чтобы облегчить себе жизнь, предположим, что стоимость всех резервируемых объектов одного типа одинакова.
В управляемом C++ мы представим Reservable (Резервируемый объект, ресурс) в виде абстрактного класса.
public _gc _abstract class Reservable
// сборщик мусора - класс Reservable
{
static private int nextid = 0; // статический частный
protected: // защищенный
int unitid; public:
int capacity; // вместимость
Decimal cost; // Десятичная стоимость;
Reservable(int capacity, Decimal cost)
// вместимость, Десятичная стоимость
{
this->capacity = capacity;
// вместимость
this->cost = cost;
// стоимость
unitid = nextid++;
}
};
В конструкторе можно задать атрибуты capacity (объем имеющихся ресурсов) и cost (стоимость) Значение поля unitid генерируется автоматически с помощью статической переменной. Минимальное значение этого атрибута — ноль, так как мы будем использовать его в качестве одного из индексов двумерного массива, который содержит информацию о количестве клиентов, резервирующих данный объект на конкретную дату.
Роль спецификаторов управления доступом private (частный), internal (внутренний) и protected (защищенный) мы обсудим позже
Резервирование
Ксиди клиент резервирует объект, создается запись о резервировании. Класс Reser-v,u j on (Резервирование) содержит информацию о резервировании.
public _gc _abstract class Reservation
// сборщик мусора - абстрактный класс Резервирование
{
public:
int Reservationld;
int Unitld;
DateTime Date; // Дата
int NumberDays;
static private int nextReservationld = 1; // статический
// частный
Reservation() // Резервирование
{
Reservationld = nextReservation!d++;
}
};
Значение поля Reservationld генерируется автоматически. Поле Unitld идентифицирует зарезервированный объект. Поле Date (Дата) определяет начальную дату резервирования, поле NumberDays определяет количество дней, на которые зарезервирован объект.
Брокер
Третья абстракция, класс Broker (Брокер), — это модель брокера, который отвечает за резервирование разнообразных объектов. Данная абстракция реализована в виде абстрактного класса. Этот класс содержит список резервируемых объектов, представленных в виде массива units, и список резервирований, представленных в виде массива reservations (резервирования). С помощью двумерного массива numCost отслеживается количество клиентов, которые зарезервировали объект на указанный день.
public _gc _abstract class Broker // сборщик мусора - абстрактный класс Брокера ' -{
private: // частный
int MaxDay;
static const int MAXRESERVATION = 10; // статическая константа
static int nextReservation = 0;
static int nextUnit = 0;
int numCust [,];
protected: // защищенный
Reservation *reservations[]; // Резервирование
Reservable *units[];
public:
Broker(int MaxDay, int MaxUnit) // Брокер
{
this->MaxDay = MaxDay;
numCust = new int _gc [MaxDay, MaxUnit];
units = new Reservable*[MaxUnit];
reservations = new Reservation*[MAXRESERVATION];
// резервирование }
Структура ReservationResult
Для возвращения результата резервирования используется простая структура ReservationResult.
public _gc struct ReservationResult
// сборщик мусора - struct ReservationResult
{
public:
int Reservationld;
Decimal ReservationCost; // Десятичное число ReservationCost
Decimal Rate; // Десятичное число - цена
String *Comment;
};
Поле Rate (Цена) определяет стоимость резервируемого объекта за один день. Значение поля ReservationCost определяет полную стоимость резервирования объекта, которая равняется количеству дней, умноженному на стоимость резервирования за один день. Если возникают проблемы, в качестве Reservationld возвращается -1. причем в поле Comment (Примечание) описывается проблема. Структура создана таким образом, что информация о результате будет передаваться в любом случае, например, ее можно использовать в Web-службах, которые не поддерживают обработку исключений.
CompEbook.ru Железо, дизайн, обучение и другие
Проектирование инкапсуляции
В данной реализации класса Broker (Брокер) все списки представлены в виде массивов. Поскольку такая реализация может не быть (и на самом деле не будет) сохранена в последующих версиях, мы не станем рассматривать функции работы с массивами или обращаться к элементам с помощью индексов. Мы реализуем общедоступные свойства NumberUnits и NumberReservations, чтобы обеспечить доступ для просмотра частных переменных nextUnit и nextReservat ion._property int get_NumberUnits()
{
return nextUnit;
}
_property int get_NumberReservations()
{
return nextReservation;
}
Представление простых полей Reservationld, Unit Id, Date (Дата) и NumberDays класса Reservation (Резервирование) не меняется, следовательно, мы не будем инкапсулировать эти поля. Потом, если потребуется, мы можем реализовать некоторые из этих полей как свойства, не изменяя при этом коа клиента. Однако в данный момент, и в дальнейшем, мы просто будем использовать общедоступные поля.
public _gc _abstract class Reservation
// сборщик мусора - абстрактный класс Резервирование
{
public:
int Reservationld;
int Unitld;
DateTirie Date; // Дата
int NumberDays;
static private int nextReservationld = 1;
Reservation() // Резервирование
{
Reservationld = nextReservation!d++;
}
};
CompEbook.ru Железо, дизайн, обучение и другие
Пространство имен
Код примера полностью находится в пространстве имен 01::NetCpp: -.Acme. Все . файлы с описанием классов начинаются с директивы namespace (пространство имен). В файле TestHotel.h помешена соответствующая директива using. Определяется пространство имен 01: : NetCpp: : Acme следующим образом:namespace OI { namespace NetCpp { namespace Acme {
// пространство имен OI {пространство имен NetCpp
// {пространство имен Acme {
}}}
CompEbook.ru Железо, дизайн, обучение и другие
Реализация примера "Бюро путешествий Acme"
С помощью абстрактных классов Reservable (Резервируемый объект, ресурс), Reservation (Резервирование) и Broker (Брокер) можно легко реализовать систему резервирования конкретного ресурса, например гостиничного номера. На рис. 4.2 показана иерархия наследования: класс Hotel (Гостиница) является производным от класса Reservable (Резервируемый объект, ресурс), класс HotelReservation — производным от класса Reservation (Резервирование), класс HotelBroker — производным от класса Broker (Брокер).В этом разделе мы рассмотрим основные моменты реализации примера, которая находится в папке Case Study для этой главы.
Рис. 4.2. Иерархия классов для системы резервирования "Бюро путешествий Acme"
CompEbook.ru Железо, дизайн, обучение и другие
В этой главе сделан обзор
В этой главе сделан обзор принципов объектно-ориентированного программирования на управляемом C++, причем много внимания было уделено изучению наследования. Мы обратились к примеру "Бюро путешествий Acme", который продолжим использовать на протяжении всей книги. Мы также рассмотрели абстракции, наиболее подходящие для того, чтобы реализовать системы резервирования разных объектов, и реализовали систему резервирования гостиничных номеров. Описанные нами абстрактные базовые классы можно использовать и для реализации других систем резервирования.CompEbook.ru Железо, дизайн, обучение и другие
Запуск программы примера
Перед тем, как продолжить просмотр кода, неплохо было бы запустить пример. Программа TestBroker. exe представляет собой консольное приложение. Если после приглашения на ввод команды вы наберете "help" в командной строке, то будет выведен следующий список команд:Enter command, quit to exit
H> help
The following commands are available:
hotels shows all hotels in a city
all shows all hotels
cities shows all cities
add adds a hotel
book book a reservation
bookings show all bookings
register register a customer
email change email address
show show customers
quit exit the program H>
Вот перевод этой выдачи:
Введите команду, quit для выхода
Н> помощь
Доступны следующие команды:
hotels (гостиницы) показывает все гостиницы в городе
all (все) показывает все гостиницы
cities (города) показывает все города
add (добавить) добавляет гостиницу
book (заказать) заказывает резервирование
bookings (заказы) показывает все заказы
register (регистрировать) регистрирует клиента
email (электронная почта) изменяет адрес электронной почты
show (показать) показывает клиентов
quit выход из программы
Н>
Поэкспериментируйте с этой программой, пока полностью не изучите ее свойства.
CompEbook.ru Железо, дизайн, обучение и другие
Architecture Net
Что такое каркасы приложений
Наши примеры предполагают использование некоего каркаса приложений. Такой каркас — это больше, чем просто библиотека. При использовании библиотеки вы можете вызывать в своих программах библиотечные функции. При работе с каркасом приложений не только ваша программа может обращаться к каркасу, но и сам каркас может обращаться к вашей программе. Таким образом, программу можно уподобить среднему слою сандвича:.NET Framework— отличный пример подобной архитектуры. Этот каркас обеспечивает богатый набор возможностей, которые можно использовать напрямую. Кроме того, широкий выбор интерфейсов, которые можно реализовать в программе, позволяет обеспечить задуманное поведение профаммы при обращении к ней каркаса, часто выполняемом по заказу других объектов.
CompEbook.ru Железо, дизайн, обучение и другие
Делегаты
Интерфейсы облегчают написание профамм в том смысле, что ее составляющие могут быть вызваны другими приложениями или системой. Такой стиль профаммирования известен давно как использование функций обратного вызова (callback function). В этом разделе мы рассмотрим использование делегатов (delegate) в управляемом C++. Делегаты можно рассматривать в качестве объектно-ориентированных функций обратного вызова, обеспечивающих типовую безопасность. Делегаты — основа более сложного протокола вызова функций обратного вызова, называемого событиями, которые мы рассмотрим в следующем разделе главы.Функции обратного вызова — это функции, которые ваша программа определяет и некоторым образом "регистрирует", после чего они могут быть вызваны другими приложениями. В С и C++ такие функции реализуются с использованием указателей на функции.
В управляемом C++ указатель на метод можно инкапсулировать в объекте-делегате. Делегат может указывать как на статический метод, так и на экземпляр метода. Если делегат указывает на метод экземпляра класса, он хранит и сам экземпляр класса, и точку входа в метод. Таким образом, метод экземпляра класса можно вызвать, используя сам экземпляр класса. Если делегат указывает на статический метод, в нем хранится только точка входа в этот метод.
Если делегат-объект передается какой-либо части программы, она может вызвать метод, на который указывает делегат. Нередко части программы, использующие делегат, компилируются отдельно от описания самого делегата, так что при их создании даже неизвестно, какие методы они будут использовать в действительности.
Делегат в действительности является управляемым классом, потомком класса System: : Delegate (Система: Делегат). Новый экземпляр делегата создается, как и для любого другого класса, с помощью оператора new (создать). Делегаты являются объектно-ориентированными и безопасными с точки зрения типов; они позволяют полностью использовать все средства безопасности, предусмотренные в среде выполнения управляемого кода.
CompEbook.ru Железо, дизайн, обучение и другие
Динамическое использование интерфейсов
Полезной особенностью интерфейсов является возможность их использования в динамических сценариях, что позволяет по ходу выполнения программы проверять, поддерживается ли интерфейс определенным классом. Если интерфейс поддерживается, мы можем воспользоваться предоставляемыми им возможностями; в противном же случае программа может проигнорировать интерфейс. Фактически такое динамическое поведение реализуется с помощью обработки возникающих исключений, как это уже было продемонстрировано выше. Хотя подобный подход вполне работоспособен, однако он отличается некоторой неуклюжестью и приводит к появлению трудночитаемых программ. C++ поддерживает использование операторов dynamic_cast и typeid, а в .NET Framework есть класс Туре (Тип), облегчающий динамическое использование интерфейсов.В качестве примера рассмотрим интерфейс ICustomer2, имеющий, по сравнению с интерфейсом ICustomer 1, дополнительный метод ShowCustomer.
_gc _interface ICustomer2 : ICustomerl
// сборщик мусора - ICustomer2: ICustomerl
{
public:
void ShowCustomers(int id); // идентификатор
};
Предположим, что класс Customerl поддерживает интерфейс ICustomerl, a класс Customer2 — интерфейс !Customer2. Для консольной программы-клиента удобнее использовать исходный метод ShowCustomer, а не метод Get-Customer, так как последний создает список массивов и копирует данные в него. Поэтому программа-клиент предпочтет работать с интерфейсом ICus-tomer2, если это возможно. В папке TestlnterfaceBeforeCast содержится программа, рассмотренная в следующем разделе.
Проверка поддержки интерфейса перед приведением типов
Проверку поддержки интерфейса можно производить, выполняя динамическое приведение типа указателя и обрабатывая исключение, которое может при этом возникнуть. Однако более изящным решением будет выполнять проверку до приведения типа, избегая при этом возникновения исключений. Если объект поддерживает необходимый интерфейс, можно выполнять приведение типа для получения доступа к интерфейсу. С# поддерживает использование удобного оператора is для проверки того, поддерживает ли объект определенный интерфейс. К сожалению, в C++ с этой целью приходится использовать метод отражения, реализуемый посредством методов GetType и Getlnterf асе. В связи с тем, что это приводит к появлению несколько громоздкого выражения, в следующем примере с помощью директивы #define определяется макрос IS (THIS, THAT_INTERFACE), используемый далее в двух условных операторах if.
//TestlnterfaceBeforeCast.срр
//MACRO: pObj->GetType()->GetInterface("Somelnterface")!=0
// МАКРОС
#define IS(THIS, THAT_INTERFACE) (THIS->GetType()->GetInterface(
THAT_INTERFACE)!=0)
#using
using namespace System;
// использование пространства имен Система;
_gc _interface ICustomer1 {};
// сборщик мусора - ICustomer1;
_gc _interface ICustomer2 : ICustomer1
// сборщик мусора - ICustomer2: ICustomer1
{
public:
void ShowCustomers(int id); // идентификатор
};
_gc class Customer! : public ICustomer1 {};
// класс сборщика мусора Customerl: ICustomerl {};
_gc class Customer2 : public ICustomer2
// класс сборщика мусора Customer2: ICustomer2
{
public:
void ShowCustomers(int id) // идентификатор
{
Console::WriteLine("Customer2::ShowCustomers:
succeeded");
}
};
void main(void) // главный
{
Customerl *pCustl = new Customerl; // не к ICustomer2
Console::WriteLine(pCustl->GetType());
// проверить, относится ли к типу ICustomer2 перед приведением
if (IS(pCustl, "ICustomer2"))
{
ICustomer2 *plcust2 =
dynamic_cast
plcust2->ShowCustomers(-1);
}
else
Console::WriteLine
("pCustl does not support ICustomer2 interface");
// ("pCustl не поддерживает интерфейс ICustomer2");
Customer2 *pCust2 = new Customer2; // да, на ICustomer2
Console::WriteLine(pCust2->GetType());
// проверить, относится ли к типу ICustomer2 перед приведением
if (IS(pCust2, "ICustomer2"))
{
ICustomer2 *plcust2 =
dynamic_cast
pIcust2->ShowCustomers(-1);
}
else
Console::WriteLine
("pCust2 does not support ICustomer2 interface");
// ("pCust2 не поддерживает интерфейс ICustomer2");
}
В этом примере продемонстрировано далеко не оптимальное решение, так как проверка типа производится дважды. Первый раз — при использовании отражения для проверки поддержки интерфейса в макросе IS. А еще раз проверка производится автоматически — при выполнении динамического приведения типа, в этом случае, если интерфейс не поддерживается, возникает исключение. Результат работы программы приведен ниже. Обратите внимание, что при выполнении программы действительно не возникает исключения.
Customer1
pCustl does not support ICustomer2 interface
Customer2
Customer2::ShowCustomers: succeeded
А вот и перевод:
Customer1
pCustl не поддерживает интерфейс ICustomer2
Customer2
Customer2:: ShowCustomers: успешно
Оператор dynamic_cast
Результатом выполнения оператора dynamic_cast является непосредственно указатель на интерфейс. В случае, если интерфейс не поддерживается, значение указателя устанавливается равным нулю. Используем этот факт для создания программы, в которой проверка поддержки интерфейса производится один раз. Приведенный ниже фрагмент взят из примера CastThenTestForNull, отличающегося от предыдущего, Testlnter-f aceBef oreCast, тем, что в нем производится проверка равенства нулю результата динамического приведения типа.
void main(void) // главный
{
Customerl *pCustl = new Customer!; // нет ICustomer2
Console::WriteLine(pCustl->GetType() ) ;
// Использовать оператор С ++ dynamic_cast, чтобы проверить
// наличие ICustomer2
ICustomer2 *plcust2 =
dynamic_cast
if (plcust2 != 0)
p!cust2->ShowCustomers(-1) ;
else
Console::WriteLine
("pCustl does not support ICustomer2 interface");
// ("pCustl не поддерживает интерфейс ICustomer2");
Customer2 *pCust2 = new Customer2; // да, есть ICustomer2
Console::WriteLine(pCust2->GetType()) ;
// Использовать оператор С ++ dynamic_cast, чтобы проверить
// наличие ICustomer2
plcust2 =
dynamic_cast
if (plcust2 != 0)
p!cust2->ShowCustomers(-1) ;
else
Console::WriteLine
("pCust2 does not support ICustomer2 interface");
// ("pCust2 не поддерживает интерфейс ICustomer2");
}
Результат выполнения программы CastThenTestForNull показывает, что действительно, исключения не возникает, но проверка поддержки интерфейса при этом производится всего один раз для каждого из объектов.
Customer1
pCustl does not support ICustomer2 interface
Customer2
ICustomer2::ShowCustomers: succeeded
Вот перевод этой выдачи:
Customer1
pCustl не поддерживает интерфейс ICustomer2
Customer2
ICustomer2:: ShowCustomers: успешно
Если вы знакомы с моделью компонентных объектов Microsoft (COM), проверка поддержки классом интерфейса вам должна быть хорошо знакома.
CompEbook.ru Железо, дизайн, обучение и другие
Интерфейсы
Концепция интерфейсов — одна из основных в современном программировании. Большие системы неизбежно разделяются на части, и существенным становится вопрос о взаимодействии этих частей друг с другом. Правила такого взаимодействия должны быть строго определены и постоянны, так как их изменение может повлиять на несколько частей системы. Однако сама реализация взаимодействия может быть изменена и это не потребует изменения кода других частей системы. В Visual C++ .NET ключевое слово _interface (интерфейс) имеет четко определенное значение. Управляемый (managed) интерфейс — ссылочный тип данных, подобный абстрактному классу, задающий поведение с помощью набора методов с определенными сигнатурами. Интерфейс — это просто контракт. Когда класс реализует интерфейс, он, таким образом, должен придерживаться контракта.Использование интерфейсов — удобный способ разделения функциональных возможностей. Сначала определяются интерфейсы, а затем разрабатываются классы, реализующие эти интерфейсы. Методы класса могут быть сгруппированы в разных интерфейсах. Хотя в управляемом C++ класс является непосредственным потомком только одного базового класса, он может реализовывать несколько интерфейсов.
Использование интерфейсов помогает в создании динамических, гибких и легко изменяемых программ. CLR и BCL (Base Class Library— библиотека базовых классов) обеспечивают удобную возможность во время выполнения программы послать классу запрос для определения, реализует ли он некоторый интерфейс. Интерфейсы в .NET концептуально очень похожи на интерфейсы в модели компонентных объектов Microsoft (СОМ), но работать с ними намного легче.
Далее мы подробно изучим преимущества и использование интерфейсов. Затем мы рассмотрим несколько важных родовых интерфейсов библиотеки .NET, что поможет нам понять, каким образом управляемый C++ и .NET могут использовать друг друга для того, чтобы способствовать разработчикам в создании мощных и полезных программ.
CompEbook.ru Железо, дизайн, обучение и другие
Интерфейсы коллекций
Теперь, достаточно подробно обсудив концепцию интерфейсов, мы можем обратить более пристальный взгляд на коллекции, в частности, на класс ArrayList (Список массивов), который мы активно использовали в программе Бюро путешествий Acme (Acme Travel Agency). Присмотревшись к определению класса ArrayList (Список массивов), можно увидеть, что он реализует четыре стандартных интерфейса.// класс сборщика мусора ArrayList
_gс class ArrayList : public IList, ICollection,
lEnumerable, ICloneable
Первые три образуют несложную иерархическую структуру, показанную на рис. 5.1. При продвижении по структуре в интерфейсах появляются дополнительные методы, и, наконец, IList имеет наиболее полный их набор.
Рис. 5.1. Иерархия интерфейсов для списков
Четвертый интерфейс из реализованных в ArrayList (Список массивов), ICloneable, является независимым от первых трех и предназначен для осуществления детального копирования объектов. Для знакомства с интерфейсами, обеспечивающими работу с коллекциями, рассмотрим программу StringList. Просмотрите главный метод Main программы StringList, а вспомогательные методы будут подробно рассматриваться по мере нашего знакомства с разными интерфейсами, предназначенными для работы с коллекциями.
//StringList.h
_gc class StringList
// класс сборщика мусора StringList
{
private: // частный
static ArrayList *pList; // статический
public:
static void Main() // статический Главный
{
// Инициализировать строки и показать начальное состояние
pList = new ArrayList(4);
ShowCount();
AddStringC'Amy") ; // Эми
AddStringC'Bob"); // Боб
AddString("Charlie"}; // Чарли
ShowEnum(pList);// счетчик
ShowCount ();
// Добавить еще две строки и снова показать состояние
AddString("David"); //Дэвид
AddString("Ellen"); // Эллен
ShowList(pList);// моделировать
foreach ShowCount (};
// Удалить две строки из списка и показать состояние
RemoveString("David"); // Дэвид RemoveAt(0);
ShowArray(pList);// запись индекса ShowCount();
// Попытка удалить две строки, которых нет в списке
RemoveString("Amy"); // Эми
RemoveAt(3);
}
private: // частный
static void ShowEnum(ArrayList *pArray) // статическая функция
{
lEnumerator *plter = pArray->GetEnumerator();
bool more = p!ter->MoveNext();
while (more)
{
String *pStr =
dynamic_cast
Console::WriteLine(pStr);
more = p!ter->MoveNext();
}
}
static void ShowList(ArrayList *pArray) // статическая функция
{
lEnumerator *pEnum =
pArray->GetEnumerator() ;
while (pEnum->MoveNext() )
{
String *pStr =
dynamic_cast
Console::WriteLine(pStr};
}
}
static void ShowArray(ArrayList *pArray) // статическая функция
{
for (int i = 0; i < pArray->Count; i++) f
Console::WriteLine(
"pArray->get_Item({0}) = {!}", _box (i) ,
pArray->get_Item(i)); } }
static void ShowCount() // статическая функция
{
Console::WriteLine(
"pList->Count = {0}", _box(pList->Count));
Console::WriteLine(
"pList->Capacity = {0}", _box(pList->Capacity));
// Вместимость
}
static void AddString (String *pStr) // статическая функция
{
if (pList->Contains(pStr)) // если содержит throw new Exception!
// новое Исключение
String::Format("list contains {0}", pStr)); // Формат: список содержит
pList->Add(pStr); // Добавить
}
static void RemoveString(String *pStr) // статическая функция
{
if (pList->Contains(pStr)) // если содержит
pList->Remove(pStr); // Удалить else
Console::WriteLine(
"List does not contain {0}", pStr); // Список
//не содержит
}
static void RemoveAt(int nlndex) // статическая функция
{
try // попытка
{
pList->RemoveAt(nlndex) ;
}
catch (ArgumentOutOfRangeException *)
{
Console::WriteLine(
"No element at index {0}", _box(nlndex));
// Нет элемента с таким индексом
}
}
};
Результат работы программы будет таким:
pList->Count = О
pList->Capacity = 4 // Вместимость
Amy // Эми
Bob // Боб
Charlie // Чарли
pList->Count = 3
pList->Capacity =4 // Вместимость
Amy // Эми
Bob // Боб
Charlie // Чарли
David // Дэвид
Ellen // Эллен
pList->Count = 5
pList->Capacity =8 // Вместимость
pArray->get_Item(0) = Bob // Боб
pArray->get_Item(1) = Charlie // Чарли
pArray->get_Item(2) = Ellen // Эллен
pList->Count = 3
pList->Capacity =8 // Вместимость
List does not contain Amy // Список не содержит Эми
No element at index 3 // Нет элемента с индексом 3
lEnumerableИ JEnumerator
Исходным для всех интерфейсов, предназначенных для работы с коллекциями, является интерфейс lEnumerable, имеющий один метод — GetEnumerator.
_gc _interface lEnumerable
// сборщик мусора - lEnumerable
{
lEnumerator* GetEnumerator();
};
GetEnumerator возвращает указатель на интерфейс lEnumerator, который используется для последовательного доступа к элементам коллекции. Этот интерфейс имеет свойство Current (Текущая запись) и методы MoveNext и Reset (Сброс).
_gc _interface lEnumerator
// сборщик мусора - lEnumerator
{
_property Object* get__Current () ;
bool MoveNext(); // логический (булев)
void Reset();
};
Сразу после инициализации нумератор указывает на позицию перед первым элементом коллекции и, следовательно, для получения доступа даже к первому ее элементу его следует продвинуть. Использование нумератора для последовательного доступа к элементам списка проиллюстрировано в методе ShowEnum.
static void ShowEnum(ArrayList *pArray) // статическая функция
{
lEnumerator *plter =
pArray->GetEnumerator();
bool more = piter->MoveNext(); // логическое значение while (more)
{
String *pStr =
dynamic_cast
Console::WriteLine (pStr) ;
more = p!ter->MoveNext ();
}
}
Интерфейс ICollection
Интерфейс ICollection является производным от lEnumerable и в нем добавляются свойство Count (Количество) и метод СоруТо.
_gc _interface ICollection : public lEnumerable
// сборщик мусора - ICollection: lEnumerable
{
_property int get_Count();
_property bool get_IsSynchronized(); // логический
_property Object* get_SyncRoot();
void CopyTo(Array* array, int index); // массив, индекс
};
Кроме того, в данном интерфейсе появляются свойства, предназначенные для обеспечения синхронизации при использовании потоков. "Является ли безопасным использование потоков?" — вот один из часто задаваемых вопросов о любой библиотеке. Что касается среды .NET Framework, то ответ на этот вопрос короток и ясен — нет. Это не значит, что разработчики .NET Framework не задумывались о безопасности использования потоков. Наоборот, они реализовали множество механизмов, которые могут помочь вам при работе с потоками. Причина же, по которой использование потоков при работе с коллекциями не является безопасным автоматически, — в том, что обеспечение безопасности приводит к понижению производительности, а если ее обеспечить автоматически, то и при работе в однопотоковом режиме производительность окажется заниженной. Если же вам необходимо обеспечить безопасность при работе с потоками, вы можете использовать свойства интерфейса, предназначенные для синхронизации. Более подробно механизмы синхронизации потоков в .NET Framework будут рассмотрены в главе 8 "Классы каркаса .NET Framework".
Программа StringList иллюстрирует использование свойства Capacity (Объем) класса ArrayList (Список массивов), а также свойства Count (Количество), унаследованного классом ArrayList (Список массивов) от интерфейса ICollection.
static void ShowCount() // статическая функция
{
Console::WriteLine(
"pList->Count = {0}", _box(pList->Count)); // Счет
Console::WriteLine(
"pList->Capacity = {0}", _box(pList->Capacity));
// Вместимость
}
Интерфейс IList
Интерфейс IList является производным от интерфейса iCollection и в нем введены методы для добавления элемента в список, удаления его из списка и т.д.
_gc _interface IList : public ICollection
// сборщик мусора - IList: ICollection
{
_property bool get_IsFixedSize(); // логический
_property bool get_IsReadOnly(); // логический
_property Object* get_Item(int index); // индекс
_property void set_Item(int index, Object*); // индекс,
// Объект *
int Add(0bject* value); // Добавить значение
void Clear();
bool Contains(Object* value); // Содержит ли значение
int IndexOf(Object* value); // значение
void Insert(int index, Object* value); // Вставка (int индекс,
// Object* значение);
void Remove(Object* value); // Удалить значение
void RemoveAt(int index); // индекс };
В программе stringList продемонстрировано использование индексатора get_Item и методов Contains (Содержит), Add (Добавить), Remove (Удалить) и RemoveAt.
static void ShowArray(ArrayList *pArray)
{
for (int i = 0; i < pArray->Count; i++)
{
Console::WriteLine(
"pArray->get_Item({0}) = {!}", _box (i) ,
pArray->get_Item(i));
}
}
static void AddString(String *pStr)
{
if (pList->Contains(pStr))
// если содержит throw new Exception(
// новое Исключение
String::Format("list contains {0}", pStr));
// Формат:: ("список содержит")
pList->Add(pStr); // Добавить
}
i static void RemoveString(String *pStr)
{
if (pList->Contains(pStr)) // если содержит
pList->Remove(pStr); // Удалить
else
Console::WriteLine(
"List does not contain {0}", pStr); // Список
// не содержит
}
static void RemoveAtfint nlndex)
{
try // попытка
{
pList->RemoveAt(nIndex);
}
catch (ArgumentOutOfRangeException *)
{
Console::WriteLine(
"No element at index {0}", _box(nlndex)); // Нет элемента
//с индексом
}
}
CompEbook.ru Железо, дизайн, обучение и другие
Использование методов класса object (Объект) в классе Customer (Клиент)
Для иллюстрации описанных методов рассмотрим класс Customer (Клиент) до и после перегрузки методов Equals (Равняется), ToString и GetHashCode.Стандартные методы класса object (Объект)
Если не произвести подмены виртуальных методов объекта Object (Объект), наш класс будет наследовать стандартное поведение. Это поведение продемонстрировано в CustomsrObjectXStepl.
//Customer.срр
#using
using namespace System;
// использование пространства имен Система;
#include "Customer.h"
//Customer.h
_gc class Customer // сборщик мусора - класс Клиент
{
public:
int nCustomerld;
String *pFirstName;
String *pLastName;
String *pEmailAddress;
Customer(int id. String *pFirst, // Клиент (int идентификатор,
// Строка *pFirst, String *pLast, String *eMail)
{
nCustomerld = id; // идентификатор
pFirstName = pFirst;
pLastName = pLast;
pEmailAddress = eMail; // электронная почта
}
};
//TestCustomer.срр
ftusing
using namespace System;
// использование пространства имен Система;
#include "Customer.h"
#include "TestCustomer.h"
void main(void)
{
TestCustomer::Main(); // Главный }
//TestCustomer.h
_gc class TestCustomer
// класс сборщика мусора TestCustomer
{
public:
static void Main() // статическая Главная
{
Customer *pCustl, *pCust2; // Клиент
pCustl = new Customer ( // новый Клиент
99, "John", "Doe", "john@rocky.com"); // "Джон", "Доу"
pCust2 = new Customer( // новый Клиент
99, "John", "Doe", "john@rocky.com"); // "Джон", "Доу"
ShowCustomerObject("pCustl", pCustl);
ShowCustomerObject("pCust2", pCust2);
CompareCustomerObjects(pCustl, pCust2);
}
private: // частный
static void ShowCustomerObject( String *pLabel, Customer *pCust)
{
Console::WriteLine("——— {0} ——", pLabel);
Console::WriteLine(
"ToStringO = {0}", pCust->ToString());
Console::WriteLine(
"GetHashCodeO = {0}", _box(pCust->GetHashCode() ) ) ;
Console::WriteLine(
"GetTypeO = {0}", pCust->GetType () ) ;
}
static void CompareCustomerObjects( Customer *pCustl, Customer *pCust2) // Клиенты
{
Console::WriteLine(
"Equals() = {0}", // Равняется
_box(pCustl->Equals( // Равняется
dynamic_cast<0b]ect _gc *>(pCust2)))); // сборщик
// мусора
}
};
Запустите испытательную программу, и вы получите следующий результат:
——— custl ——— // ———custl ———
ToString () = Customer // ToString () = Клиент
GetHashCode () = 4 // GetHashCode () = 4
GetType () = Customer // GetType () = Клиент
——— cust2 ——— // ———cust2 ———
ToString () = Customer // ToString () = Клиент
GetHashCode () = 6 // GetHashCode () = 6
GetType () = Customer // GetType () = Клиент
Equals() = False // Равняется () = Ложь
Понятно, что реализация, принятая по умолчанию, — это совсем не то, что желательно иметь для объекта Customer (Клиент). Метод ToString возвращает имя класса, а не информацию о конкретном клиенте. Метод Equals (Равняется) сравнивает только указатели на объекты, а не сами объекты. В приведенном примере два разных указателя указывают на одинаковые (по содержащейся информации) объекты класса Customer (Клиент), но метод Equals (Равняется) при этом возвращает значение false (ложь).
Подмена методов класса object (Объект)
Версия проекта, содержащаяся в папке CustomerOb;ject\Step2, демонстрирует подмену виртуальных методов Ob] ect (Объект)
//Customer.h
_gc class Customer
// сборщик мусора - класс Клиент
{
public:
int nCustomerld;
String *pFirstName,
String *pLastName;
String *pEmailAddress;
Customer(int id, String *pFirst, // Клиент int идентификатор,
// Строка *pFirst, String *pLast, String *eMail)
{
nCustomerId = id; // идентификатор
pFirstName = pFirst;
pLastName = pLast;
pEmailAddress = eMail; // электронная почта
}
public:
bool Equals(Object *pobj)
// логический (булев) Равняется (Объект *pobj)
{
Customer *pCust = // Клиент
dynamic_cast
return (pCust->nCustomerId == nCustomerld),
}
int GetHashCode()
{
return nCustomerld,
}
String *ToStnng()
{
return String::Format( // Формат
"{0} {l}", pFirstName, pLastName);
}
};
Остальная часть программы не изменена Теперь результат выполнения программы будет таким:
--- custl --- // ---custl---
ToStnng () = John Doe // ToString () = Джон Доу
GetHashCode () = 99 // GetHashCode () = 99
GetTypeO = Customer // GetType () = Клиент
--- cust2 --- // --- cust2 ---
ToString () = John Doe // ToString () = Джон Доу
GetHashCode () = 99 // GetHashCode () = 99
GetType () = Customer // GetType () = Клиент
Equals () = True // Равняется () = Истина
CompEbook.ru Железо, дизайн, обучение и другие
Явное определение интерфейсов
При использовании интерфейсов может возникать неопределенность в случае, если в двух реализованных классом интерфейсах есть методы с одинаковыми именами и сигнатурами. Просмотрим, например, следующие версии интерфейсов lAccount и IState-ment. Обратите внимание, что в каждом из них есть метод Show (Показать)._gc _interface lAccount
// сборщик мусора - IAccount
{
void Deposit(Decimal amount); // Депозит (Десятичное
// количество)
void Withdraw(Decimal amount); // Снять (Десятичное количество)
_property Decimal get_Balance(); // Десятичное число
void Show(); // Показать
};
_gc _interface IStatement // сборщик мусора - IStatement
{
_property int get_Transactions();
void Show(); // Показать
};
Как в подобном случае указать классу нужную реализацию метода? Такая задача решается благодаря использованию имени интерфейса вместе с именем реализуемого метода, как это продемонстрировано на примере программы Ambiguous (Неоднозначная программа). Версия метода Show (Показать), относящаяся к интерфейсу lAccount, выводит на экран информацию только о состоянии счета, а метод IStatement: : Show (Показать) выводит число сделок и баланс.
//Account.h
_gc class Account : public lAccount, public IStatement
// класс сборщика мусора Счет
{
private: // частный
Decimal decBalance; // Десятичное число
int nNumXact; public:
Account(Decimal decBalance) : nNumXact(O)
// Счет (Десятичное число decBalance)
{
this->decBalance = decBalance;
}
void Deposit(Decimal decAmount)
// Депозит (Десятичное число decAmount)
{
decBalance = decBalance + decAmount;
++nNumXact; } void Withdraw(Decimal decAmount) // Снять (Десятичное
// число decAmount)
{
decBalance = decBalance - decAmount;
++nNumXact;
}
_property Decimal get_Balance() // Десятичное число
{
return decBalance;
}
void lAccount::Show() // Показать
{
Console::WriteLine(
"balance = {0}", _box(decBalance)); // баланс
}
_property int get_Transactions()
{
return nNumXact;
}
void IStatement::Show() // Показать
{
Console::WriteLine(
"{0} transactions, balance = {!}", // сделки, баланс
_box(nNumXact),
_box(decBalance)) ;
}
};
Доступ к методам lAccount::Show (Показать) и IStatement:: Show (Показать) нельзя получить с использованием указателя на экземпляр класса. Доступ к этим методам возможен только с помощью указателя на интерфейс того типа, который явно указан в объявлениях методов. Приведенная программа демонстрирует, что метод lAccountShow можно вызвать только при использовании указателя на интерфейс lAccount, но не с помощью указателя на экземпляр класса Account (Счет). Попытка вызвать такой метод с помощью указателя на экземпляр класса является ошибкой и будет пресечена компилятором. Получая указатель на интерфейс IStatement, можно вызвать метод IStatement :: Show (Показать). Результат выполнения программы будет следующим:
О transactions, balance = 100
О transactions, balance = 100
balance = 115
2 transactions, balance = 115
Вот перевод:
О сделок, баланс = 100
О сделок, баланс = 100
баланс = 115
2 сделки, баланс = 115
Иногда и при отсутствии неопределенности желательно использовать явную реализацию интерфейсов для того, чтобы вынудить программу-клиента вызывать методы интерфейса с помощью указателя на интерфейс. Подобный подход дает уверенность в том, что в исходном коде программы-клиента явно указана принадлежность метода определенному интерфейсу, а не большому набору методов класса. Такой код легко адаптировать, чтобы применять для реализации других классов, использующих тот же интерфейс.
CompEbook.ru Железо, дизайн, обучение и другие
Коллекции
Библиотека классов NET Framework предлагает широкий выбор классов для работы с коллекциями объектов Все эти классы находятся в пространстве имен System: Collections (Система Коллекции) и реализуют ряд различного типа коллекций, в том числе списки, очереди, массивы, стеки и хэш-таблицы В коллекциях содержатся экземпляры класса Object (Объект) Так как все управляемые типы происходят исключительно от Object (Объект), в коллекции может храниться экземпляр любого встроенного илирпределяемого пользователем типаВ этом разделе мы рассмотрим типичный представитель данного пространства имен — класс ArrayList (Список массивов), и научимся на практике использовать списки массивов. В частности, мы используем их для подходящей реализации нашего класса, экземпляры которого предполагается хранить в коллекции Мы увидим, что метод Equals (Равняется) нашего класса должен быть подменен, так как реализация любого из классов коллекций требует реализации метода Equals (Равняется)
CompEbook.ru Железо, дизайн, обучение и другие
Комната для дискуссий: пример чат-программы
Пример чат-программы EventOeTO иллюстрирует архитектуру как сервера, так и клиента В сервере реализованы следующие методыКогда в программе регистрируется новый участник или уходит зарегистрированный, сервер посылает сообщение об этом клиенту Обработчик соответствующего события выводит на экран надлежащее сообщение Приведем пример выдачи программы
sender = 01 Chat Room, Michael has joined the chat
sender = 01 Chat Room, BOD has -oned the chat
sender = 01 Chat Room, Sam nas ]oinea tne chat
--- After 3 nave joined---
Michael
Bob
Sam
sender = 01 Chat Room, BOD has qait the chat
--- After 1 has quit---
Michael
Sam
А вот и перевод:
отправитель = Комчата для дискуссий 01, Майкл присоединился к чату
отправитель = Комната для дискуссий 01, Боб присоединился к чату
отправителе = Коуната для дискуссий 01, Сэм присоединился к чату
---После того, как 3 присоединились---
Майкл
Боб
Сэм
отправитель = Комната дгя дискуссий 01, Боб оставил чат
---После того, как 1 покинул---
Майкл
Сэм
Исходный код клиента
В клиенте реализованы обработчики событий. Прежде всего, клиент создает экземпляр серверного объекта, а затем ставит в соответствие каждому событию обработчик события Затем клиент вызывает методы сервера Эти вызовы приводят к генерации сервером событий, обрабатываемых соответствующими обработчиками событии клиента
//ChatClient.h
_gc class ChatClient
// класс сборщика мусора ChatClient
{
public:
static void OnJoinChat(Object *pSender, ChatEventArg *pe)
{
Console::WrxteLine
{
"sender = {0}, {1} has joined the chat",
// "отправитель = {0}, {1} присоединился к чату ",
pSender,
pe->pName ;
}
static void OnQuitChat(Object *pSender, ChatEventArg *pe)
{
Console::WriteLine(
"sender = 40}, {1} has quit the chat", // "отправитель = {0}, {1} покинул чат ",
pSender, pe->pName);
}
static void Main()
{
// создать сервер чата
ChatServer *pChat = new ChatServer("01 Chat Room");
// "Комната для дискуссий 01"
// Регистрация обработчиков сообщений от сервера
pChat->pJoin += new JoinHandler(pChat, OnJoinChat);
pChat->pQuit += new QuitHandler(pChat, OnQuitChat);
// вызвать методы сервера
pChat->JoinChat("Michael"); // Майкл
pChat->JoinChat/'Bob"); // Боб
pChat->JoinChat("Sam"); // Сэм
pChat->ShowMembers("After 3 have joined");
// "После того, как 3 присоединились"
pChat->QuitChat("Bob"); // Боб
pChat->ShowMembers("After 1 has quit");
// "После того, как 1 ушел"
}
};
Копирование объектов и интерфейс icioneable
Иногда бывает необходимо сделать копию объекта. Если при этом копируются объекты, которые содержат другие объекты или указатели на них, то для корректной реализации этого процесса необходимо понимать его особенности. Ниже мы сравним копирование указателя, поверхностное почленное копирование и детальное копирование. Мы увидим, что в зависимости от реализации интерфейса ICloneable, используемого для копирования объектов, существует возможность как поверхностного, так и детального копирования.Напомним, что в .NET Framework есть значащие типы данных и ссылочные типы. Значащие типы данных представляют собой непосредственно данные, а ссылочные типы всего лишь указывают местонахождение данных. Если значение одной переменной-указателя скопировать в другую переменную того же типа, обе переменные будут указывать на один и тот же объект. Если с помощью первого указателя этот объект будет изменен, то изменится и объект, на который ссылается второй указатель. Иногда требуется именно такое поведение, а иногда другое.
Интерфейс ICloneable
Интерфейс ICloneable является исходным и имеет единственный метод— Clone (Клон). Данный метод может быть реализован для выполнения как поверхностного, так и детального копирования, но указатель на Object (Объект), возвращаемый методом, должен указывать на объект того же (или совместимого) типа, для которого реализован интерфейс ICloneable. Обычно метод Clone (Клон) реализуют так, чтобы он создавал новый объект. Однако бывает, например в случае класса String (Строка), что метод Clone (Клон) просто возвращает указатель на исходный объект.
_gc _interface ICloneable
// сборщик мусора - ICloneable
{
Object* Clone(); // Клон
};
Поверхностная и детальная копии
Неуправляемые структуры и классы в C++ автоматически реализуют почленное копирование содержимого, которое будет актуальным до тех пор, пока не будет произведена подмена конструктора копирования. Почленное копирование называют также поверхностным копированием. В базовом классе Object (Объект) также есть защищенный (protected) метод, MemberwiseClone, выполняющий почленное копирование управляемых структур или классов.
Если среди членов структуры или класса есть указатели, такое почленное копирование может быть не тем, что вам требуется. Результатом этого копирования будет то, что указатели разных объектов будут ссылаться на одни и те же данные, а не на их независимые копии. Для фактического копирования данных следует сделать детальную копию. Детальное копирование может быть обеспечено на уровне языка или на уровне библиотек. В обычном C++ детальное копирование осуществляется на уровне языка с использованием конструктора копирования. В управляемом C++ оно осуществляется .NET Framework с помощью интерфейса Idoneable, который можно реализовывать в создаваемых классах специально для того, чтобы эти классы могли выполнять детальное копирование.
Следующими тремя вариантами исчерпываются возможные виды копирования, как для управляемых классов и структур, с использованием интерфейса Idoneable или без него, так и для неуправляемых классов и структур.
Пример программы
Проиллюстрируем изложенный материал программой CopyDemo. Эта программа делает копию экземпляра класса Course (Курс). В классе Course (Курс) содержится название курса и список (коллекция) студентов.
//Course .h
_gc class Course : public Idoneable
// класс сборщика мусора Курс: Idoneable
{
public:
String *pTitle;
ArrayList *pRoster;
Course(String *pTitle) // Курс
{
this->pTitle = pTitle; pRoster = new ArrayList;
}
void AddStudent(String *pName)
{
pRoster->Add(pName); // Добавить
}
void Show(String *pCaption) // Показать
{
Console::WriteLine("————{0}————", pCaption) ;
Console::WriteLine(
"Course : {0} with {1} students", // Курс: студенты
pTitle,
_box(pRoster->Count)); // Счет
lEnumerator *pEnum = pRoster->GetEnumerator();
while (pEnum->MoveNext())
{
String *pName =
dynamic_cast
Console::WriteLine(pName);
}
}
Course *ShallowCopy() // Курс - поверхностная копия
{
return dynamic_cast
}
Object *Clone()
{
Course *pCourse = new Course(pTitle); // новый Курс
pCourse->pRoster =
dynamic_cast
return pCourse;
}
};
Тестовая программа создает экземпляр класса Course (Курс), называющийся pCl, a затем разными способами создает его копии с именем рС2.
//CopyDemo.h
_gc class CopyDemo
// класс сборщика мусора CopyDemo
{
private: // частный
static Course *pCl, *pC2; // статический Курс
public:
static void Main()
{
Console::WriteLine("Copy is done via pC2 = pCl");
// Копия, сделанная через рС2 = pCl
InitializeCourse ();
pCl->Show("original"); // Показать ("оригинал");
pC2 = pCl;
pC2->Show("copy"); // Показать ("копия");
pC2->pTitle = ".NET Programming"; // Программирование на .NET
pC2->AddStudent("Charlie"); // Чарли
pC2->Show("copy with changed title and new student");
// Показать ("копия с измененным названием
//и новым студентом");
pCl->Show("original"); // Показать ("оригинал");
Console::WriteLine(
"\nCopy is done via pC2 = pCl->ShallowCopy()") ;
InitializeCourse ();
pC2 = pCl->ShallowCopy();
pC2->pTitle = ".NET Programming"; // Программирование на .NET
pC2->AddStudent("Charlie"); // Чарли
pC2->Show("copy with changed title and new student"};
// Показать ("копия с измененным названием и новым
// студентом");
pCl->Show("original"); // Показать ("оригинал");
Console::WriteLine(
"\nCopy is done via pC2 = pCl->Clone()");
InitializeCourse ();
pC2 = dynamic_oast
pC2->pTitle = ".NET Programming"; // Программирование
//на .NET
pC2->AddStudent("Charlie"); // Чарли
pC2->Show(" copy with changed title and new student");
// Показать ("копия с измененным названием
// новым студентом");
pCl->Show("original"); // Показать ("оригинал");
}
private: // частный
static void InitializeCourse()
{
pCl = new Course("Intro to Managed C++");
// новый Курс ("Введение в Управляемый С ++ ");
pCl->AddStudent("John"); // Джон
pCl->AddStudent("Mary"); // Мэри
}
};
Вот выдача программы:
Copy is done via pC2 = pCl
————original-----
Course : Intro to Managed C++ with 2 students
John
Mary
————copy———
Course : Intro to Managed C++ with 2 students
John
Mary
—----copy with changed title and new student-----
Course : .NET Programming with 3 students
John
Mary
Charlie
——--original-----
Course : .NET Programming with 3 students
John
Mary
Charlie
Copy is done via pC2 = pCl->ShallowCopy()
---—copy with changed title and new student--—-
Course : .NET Programming with 3 students
John
Mary
Charlie
-----original-----
Course : Intro to Managed C++ with 3 students
John
Mary
Charlie
Copy is done via pC2 = pCl->Clone()
--—-copy with changed title and new student——--
Course : .NET Programming with 3 students
John
Mary
Charlie
- -—original- —— -
Course : Intro to Managed C++ with 2 students
John
Mary
А вот и перевод выдачи1**:
Копия сделана через рС2 = рС1
-----оригинал-----
Курс: Введение в Управляемый С ++ с 2 студентами
Джон
Мэри
-----копия————
Курс: Введение в Управляемый С ++ с 2 студентами
Джон
Мэри
-----копия с измененным названием и новым студентом-----
Курс: Программирование на .NET с 3 студентами
Джон
Мэри
Чарли
- ——оригинал—- —
Курс: Программирование на .NET с 3 студентами
Джон
Мэри
Чарли
Копия сделана через рС2 = рС1-> ShallowCopy ()
-----копия с измененным названием и новым студентом-----
Курс: Программирование на .NET с 3 студентами
Джон
Мэри
Чарли
-—-оригинал---—
Добавлен, естественно, редактором русского перевода. — Прим. ред.
Курс: Введение в Управляемый С ++ с 3 студентами
Джон
Мэри
Чарли
Копия сделана через рС2 = рС1-> Clone ()
-——копия с измененным названием и новым студентом—---
Курс: Программирование на .NET с 3 студентами
Джон
Мэри
Чарли
-----оригинал-----
Курс: Введение в Управляемый С ++ с 2 студентами
Джон
Мэри
Копирование указателей с помощью присваивания
Первым способом копирования является просто присваивание рС2=рС1. В этом случае мы получаем два указателя на один и тот же объект, и изменения, произведенные с использованием первого указателя, можно будет обнаружить в данных, адресуемых вторым указателем.
_gc class CopyDemo
// класс сборщика мусора CopyDemo
{
public:
static void Main()
{
Console::WriteLine("Copy is done via pC2 = pCl");
// Копия сделана через рС2 = pCl
InitializeCourse();
pCl->Show("original"); // Показать ("оригинал");
pC2 = pCl;
pC2->Show("copy"); // Показать ("копия");
pC2->pTitle = ".NET Programming"; // Программирование на .NET
pC2->AddStudent("Charlie"); // Чарли
pC2->Show("copy with changed title and new student"};
// Показать ("копия с измененным названием
//и новым студентом");
pCl->Show("original"); // Показать ("оригинал");
Экземпляр класса Course (Курс) инициализируется методом InitializeCourse, причем в качестве названия курса выбирается "Intro to Managed C++" (Введение в управляемый C++) и на курс записаны два студента. Затем выполняется присваивание рС2=рС1, и с помощью указателя рС2 изменяется заголовок и добавляется новый студент. Затем демонстрируется, что произведенные изменения видны с помощью обоих указателей. Приведем результат работы первой части программы: Copy is done via pC2 = pCl
--original-----
Course : Intro to Managed C++ with 2 students
John
Mary
-copy-——-
Course : Intro to Managed C++ with 2 students
John
Mary
-copy with changed title and new student-----
Course : .NET Programming with 3 students
John
Mary
Charlie
-----original-----
Course : .NET Programming with 3 students
John
Mary
Charlie
А вот и перевод выдачи:
Копия сделана через рС2 = рС1
-----оригинал--——
Курс: Введение в Управляемый С ++ с 2 студентами
Джон
Мэри
-----копия-----
Курс: Введение в Управляемый С ++ с 2 студентами
Джон
Мэри
-----копия с измененным названием и новым студентом-----
Курс: Программирование на .NET с 3 студентами
Джон
Мэри
Чарли
-----оригинал-----
Курс: Программирование на .NET с 3 студентами
Джон
Мэри
Чарли
Почленное копирование
Теперь проиллюстрируем почленное копирование, выполненное с помощью метода MemberwiseClone класса Object (Объект). Так как этот метод защищен (имеет спецификатор доступа protected), он может быть вызван непосредственно только из экземпляра класса Course (Курс). Поэтому в классе Course (Курс) мы определили метод ShallowCopy, реализованный через метод MemberwiseClone.
_gc class Course : public ICloneable
// класс сборщика мусора Курс: ICloneable
{
Course *ShallowCopy()
{
return dynamic_cast
}
};
Приведем вторую часть тестовой программы, в которой происходит вызов метода ShallowCopy. Так же, как и раньше, изменим с помощью второго указателя заголовок и добавим в список нового студента.
_gc class CopyDemo
// класс сборщика мусора CopyDemo
{
public:
static void Main() {
Console::WriteLine(
"\nCopy is done via pC2 = pCl->ShallowCopy()");
InitializeCourse();
pC2 = pCl->ShallowCopy();
pC2->pTitle = ".NET Programming"; // Программирование на .NET
pC2->AddStudent("Charlie"); // Чарли
pC2->Show("copy with changed title and new student");
// Показать ("копия с измененным названием
// и новым студентом");
pCl->Show("original");
// Показать ("оригинал");
Ниже приведен результат работы второй части программы. Видно, что поле Title (Название), существует после копирования в двух независимых экземплярах, но коллекция Roster (Список), представляющая собой список студентов, была скопирована через указатель. Поэтому она одна для обеих копий, и изменения, внесенные с использованием одного указателя, видны через другой.
Copy is done via pC2 = pCl->ShallowCopy()
———-copy with changed title and new student—-—
Course : .NET Programming with 3 students
John
Mary
Charlie
—---original-----
Course : Intro to Managed C++ with 3 students
John
Mary
Charlie
А вот и перевод выдачи:
Копия сделана через рС2 = рС1-> ShallowCopy ()
—---копия с измененным названием и новым студентом-----
— Курс: Программирование на .МЕТ с 3 студентами Джон
Мэри
Чарли
-----оригинал-----
Курс: Введение в Управляемый С ++ с 3 студентами
Джон
Мэри
Чарли
Использование ICloneable
Последний способ копирования использует тот факт, что класс Course (Курс) поддерживает интерфейс ICloneable и реализует метод Clone (Клон). Для копирования коллекции Roster (Список) используется также то, что ArrayList (Список массивов) реализует интерфейс ICloneable, как было указано в этой главе ранее. Заметим, что метод Clone (Клон) возвращает значение типа Object*, так что перед тем, как присвоить его полю pRoster, возвращаемое значение необходимо привести к типу ArrayList*.
_gc class Course : public ICloneable
// класс сборщика мусора Курс: ICloneable
{
public:
Object *Clone()
{
Course *pCourse = new Course(pTitle); // новый Курс
pCourse->pRoster =
dynamic_cast
return pCourse;
}
};
Приведем третью часть программы, в которой вызывается метод Clone (Клон). Здесь мы тоже с помощью второго указателя изменяем название и добавляем в список нового студента.
_gc class CopyDemo
// класс сборщика мусора CopyDemo
{
public:
static void Main()
{
Console::WriteLine(
"\nCopy is done via pC2 = pCl->Clone()");
InitializeCourse ();
pC2 = dynamic_cast
pC2->pTitle = ".NET Programming"; // Программирование на .NET
pC2->AddStudent("Charlie"); // Чарли
pC2->Show("copy with changed title and new student");
// Показать ("копия с измененным названием
//и новым студентом");
pCl->Shcw("01iginal");
// Показать ("оригинал");
Приведем выдачу третьей части программы. Теперь видно, что в результате копирования мы получили два независимых экземпляра класса Course (Курс), каждый из которых имеет свой заголовок и список студентов.
Copy is done via рС2 = pCl->Clone()
—---copy with changed title and new student-—--
Course : .NET Programming with 3 students
John
Mary
Charlie
—---original--——
Course : Intro to Managed C++ with 2 students
John
Mary
А вот и перевод выдачи:
Копия сделана через рС2 = рС1-> Clone О
—---копия с измененным названием и новым студентом-—--
Курс: Программирование на .NET с 3 студентами
Джон
Мэри
Чарли
—----оригинал-----
Курс: Введение в Управляемый С ++ с 2 студентами
Джон
Мэри
Последний подход иллюстрирует природу интерфейсов .NET. Вы можете копировать объекты, не особо задумываясь и не беспокоясь об их типах.
CompEbook.ru Железо, дизайн, обучение и другие
Моделирование фондовой биржи
Для дальнейшего знакомства с использованием делегатов рассмотрим пример моделирования фондовой биржи, реализованный в папке stockMarket. Модель состоит из двух модулей:На рис. 5.2 показана общая архитектура модели.
Модель допускает выполнение следующих операций:
Модель содержит следующие параметры:
Рис. 5.2. Архитектура эмулятора фондовой биржи
Запуск моделирования
Скомпонуйте и запустите программу StockMarket. Начните с принятой по умолчанию конфигурации: информация о текущем шаге не выводится, информация о сделках выводится, количество шагов— 100. (Заметим, что в программе используется генератор случайных чисел, так что результаты будут разными при каждом запуске программы.)
Ticks are OFF
Trades are ON
Run count = 100
Enter command, quit to exit
: run
2 ACME 23 600
27 MSFT 63 400
27 IBM 114 600
38 MSFT 69 400
53 MSFT 75 900
62 INTC 27 800
64 MSFT 82 200
68 MSFT 90 300
81 MSFT 81 600
83 INTC 30 800
91 MSFT 73 700
99 IBM 119 400
Список доступных команд выводится по команде help (помощь). Вот что выводится по этой команде:
count set run count
ticks toggle ticks
trades toggle trades
config show configuration
run run the simulation
quit exit the program
При выполнении программы выводится информация о шаге, акциях, цене и количестве проданных акций.
Определение делегата
В файле Engine . h объявлены два делегата.
_delegate void TickCallback(int ticks);
_delegate void TradeCallback(
int ticks, String *pStock, int price, int volume);
Как мы уже знаем, делегат подобен классу, так что экземпляр делегата создается с помощью оператора new (создать).
TickCallback *pTickDlg =
new TickCallback(0, PrintTick); //0 для статического
TradeCallback *pTradeDlg =
new TradeCallback(0, PrintTrade); //0 для статического
Имя метода передается делегату в качестве аргумента его конструктора. Сигнатура метода должна совпадать с сигнатурой делегата.
static void PrintTick(int ticks)
{
Console::Write("{0} ", _box(ticks));
if (++printcount == LINECOUNT)
{
Console::WriteLine(); printcount = 0;
}
}
static void PrintTrade(
int ticks, String *pStock, int price, int volume)
{
if (printcount != 0)
{
Console::WriteLine();
}
printcount = 0;
Console::WriteLine("{0,4} {l,-4} {2,4} {3,4}",
_box(ticks), pStock,
_box(price), _box(volume));
}
Передача информации о делегате эмулятору
Класс Admin (Управляющий модуль) передает информацию об используемом делегате классу Engine (Машинный модуль) при вызове конструктора класса Engine (Машинный модуль).
Engine *pEngine = new Engine(pTickDlg, pTradeDlg);
Генерация случайных чисел
Основой эмулятора является метод Run (Запуск) класса Engine (Машинный модуль). Основная работа метода Run (Запуск) состоит в присвоении данных, полученных в результате генерации случайных чисел. Для генерации случайных чисел используется класс System: :Random (Система::Случайный), рассмотренный нами в главе 3 "Программирование на управляемом C++".
double г = pRangen->NextDouble();
if (r < tradeProbti])
{
int delta = // дельта
(int) (price[i] * volatility[i]); // цена * изменение
if (pRangen->NextDouble() < .5)
{
delta = -delta; // дельта = - дельта
}
price[i] += delta; // цена + = дельта
int volume = pRangen->Next(
minVolume, maxVolume) * 100;
pTradeOp(
tick, stocks[i], price [i], volume); // шаг, акции,
//цена, объем
Использование делегатов
Указатели на экземпляры делегатов объявляются в классе Engine (Машинный модуль):
TickCallback *pTickOp;
TradeCallback *pTradeOp;
Указатели на делегаты инициализируются в конструкторе класса Engine (Машинный модуль):
Engine(TickCallback *pTickOp, TradeCallback *pTradeOp)
{
this->pTickOp = pTickOp; this->pTradeOp = pTradeOp;
}
Метод, связанный с экземпляром делегата, можно вызвать, используя указатель на делегат:
pTickOp(tick);
pTradeOp(
tick, stocks[i], price [i], volume); // шаг, акции, цена, объем
CompEbook.ru Железо, дизайн, обучение и другие
Объединение экземпляров делегатов
Несколько делегатов можно объединить в один так, чтобы результирующий делегат имел список вызываемых методов. При вызове подобного делегата будут вызваны по очереди все методы, содержащиеся в списке вызываемых методов этого делегата. Полезным свойством делегатов является возможность объединять списки вызываемых делегатом методов и удалять методы из таких списков. Для этого используются статические методы Delegate: :Combine (Делегат::Объединение) и Delegate: :Remove (Делегат::Удалить). Кроме того, для класса Delegate (Делегат) операторы += и -= перегружены так, чтобы обеспечить сокращенный синтаксис добавления и удаления методов.// псевдокод: pCurrDlg = pCustDlg + pBankDlg
pCurrDlg = static_cast
Delegate::Combine(pCustDlg, pBankDlg)); // Делегат::Объединение
// дополнительный код: pCurrDlg - = pBankDlg
pCurrDlg = static_cast
Delegate::Remove(pCurrDlg, pBankDlg)); // Делегат::Удалить
// дополнительный код: pCurrDlg + = plnstDlg
pCurrDlg = static_cast
Delegate::Combine(pCurrDlg, plnstDlg)); // Делегат: Объединение
В этом примере мы создаем два экземпляра делегатов для статических методов и один — для метода экземпляра класса. Пример демонстрирует некоторые возможности добавления методов в список и удаления их из него, а также вызов методов, связанных с делегатом. Этот пример, с подробным описанием важных аспектов использования делегатов, приведен в следующем разделе главы.
CompEbook.ru Железо, дизайн, обучение и другие
Объект системы: System::Object
Как мы уже знаем, каждый управляемый (managed) тип (объявленный с префиксом _дс (сборщик мусора)) в управляемом C++ в конце концов является потомком корневого класса System::Object (Система::Объект). Даже такие традиционные примитивные типы, как System:: Int32, System::SByte и System:: Double являются потомками System::ValueType, а тот, в свою очередь— потомок System: :Object (Система-Объект). Кроме упомянутых, в .NET Framework имеется свыше 2500 классов, и все они — потомки Ob j ect (Объект).CompEbook.ru Железо, дизайн, обучение и другие
Объявление делегата
В управляемом C++ делегат объявляется с помощью особого обозначения — ключевого слова_delegate (делегат) — и сигнатуры инкапсулированного метода. В соответствии с соглашением об именовании, имя делегата должно заканчиваться буквосочетанием "Callback". Приведем пример объявления делегата:_delegate void NotifyCallback(Decimal balance);
// делегировать NotifyCallback (Десятичный баланс);
CompEbook.ru Железо, дизайн, обучение и другие
Общедоступные методы экземпляров класса Object (Объект)
Есть четыре общедоступных метода экземпляров класса Object (Объект), три из которых являются виртуальными и часто подменяются в управляемых классах.Метод Equals (Равняется)
public: virtual bool Equals(Object*);
// виртуальный логический (булев) Равняется (Объект *);
Этот метод сравнивает объект, указатель на который передается методу в качестве аргумента, с текущим объектом. В случае равенства объектов метод возвращает true (истина). В классе Object (Объект) данный метод сравнивает только указатели на объекты. В классе ValueType этот метод подменен и производит сравнение содержимого объектов. Многие классы, в свою очередь, подменяют этот метод для того, чтобы приспособить условия сравнения объектов к своим нуждам. Существует также и статическая версия метода Equals (Равняется), сравнивающая два объекта, указатели на которые передаются ему в качестве аргументов.
Метод ToString
public: virtual String* ToString(); // виртуальный
Этот метод возвращает строку, содержащую удобочитаемое для человека описание объекта. Принятая по умолчанию версия, реализованная в объекте Object (Объект), возвращает просто полное имя типа. Производные классы часто перегружают данный метод, чтобы возвращаемое описание объекта было более содержательным.
Метод GetHashCode
public: virtual int GetHashCode(); // виртуальный
Этот метод возвращает значение хеш-функции объекта, который может использоваться в алгоритмах хэширования и хэш-таблицах. Классы, подменяющие данный метод, должны также подменять метод Equals (Равняется) для того, чтобы равные объекты возвращали одинаковые значения хеш-функции. Если этого не сделать, класс Hashtable (Хэш-таблица) не сможет корректно работать с объектами используемого класса.
Метод CetType
public: Type* GetType();
Этот метод возвращает информацию о типе объекта. Такая информация может быть использована для получения связанных метаданных посредством отражения (reflection), которое мы рассмотрим в главе 8 "Классы каркаса .NET Framework". Это метод не виртуальный, поэтому подменять его обычно не приходится.
CompEbook.ru Железо, дизайн, обучение и другие
Описание клиента
В этой части программы реализованы функции-обработчики событий._gc class ChatClient
// класс сборщика мусора ChatClient
{
public:
static void OnJoinChat(Object *pSender, ChatEventArg *pe)
{
Console::WriteLine(
"sender = {0}, {1} has joined the chat",
// "отправитель = {0}, {1} присоединился к чату ",
pSender,
pe->pName);
}
static void OnQuitChat(Object *pSender, ChatEventArg *pe)
{
Console::WriteLine(
"sender = {0}, {1} has guit the chat",
// "отправитель = {0}, {1} оставил чат ",
pSender,
pe->pName);
}
Клиент связывает программу обработки с событиями с помощью оператора +=
static void Main()
{
// создать сервер чата
ChatServer *pChat = new CratServer("01 Chat Room"); // Комната
// для дискуссий 01
// зарегистрировать, чтобы получать уведомления
// о событиях от сервера
pChat->pJoin += new Jo_n4andler(pChat, OnJoinChat);
pChat->pQuit += new QuitHanaler(pCnat, OnQuitChat);
Изначально событие представлено пустым указателем (null), т е не связано с каким-либо обработчиком событий, а добавление обработчиков событий происходит в процессе выполнения программы с помощью оператора += При вызове делегата, соответствующего событию, будут вызваны все зарегистрированные таким образом обработчики событий Отменить регистрацию обработчика событий можно оператором -=.
CompEbook.ru Железо, дизайн, обучение и другие
Описание сервера
Начнем рассмотрение с реализации сервера, находящейся в файле ChatServer. h. В архитектуре событий .NET используются делегаты с особой сигнатурой:_delegate void JoinHandler(
Object *pSender, ChatEventArg *pe);
_delegate void QuitHandler(
Object *pSender, ChatEventArg *pe);
Первый параметр определяет объект, посылающий сообщение о событии. Второй параметр используется для передачи данных одновременно с сообщением о событии. Обычно для хранения таких данных используется класс, производный от EventArg.
Рис. 5.3. Сервер с входящим и исходящим интерфейсами
_gc class ChatEventArg : public EventArgs
// класс сборщика мусора ChatEventArg: EventArgs
{
public:
String *pName;
ChatEventArg(String *pName)
{
pName = pName;
} };
Указатель на экземпляр делегата объявляется с использованием ключевого слова _event (событие).
_gc class ChatServer // класс сборщика мусора ChatServer
{
public:
_event JoinHandler *pJoin;
_event QuitHandler *pQuit;
Обычно для упрощения вызова делегатов, связанных с обработчиком некоторого события, используют вспомогательный метод. О вызове делегата часто говорят, как о "запуске" события.
_gc class ChatServer
// класс сборщика мусора ChatServer
{
protected: // защищенный
void OnJoin(ChatEventArg *pe)
{
if (pJoin != 0)
{
pJoin(this, pe); // запуск события
}
}
void OnQuit(ChatEventArg *pe)
{
if (pQuit != 0)
{
pQuitfthis, pe); // запуск события
}
}
Приведенный здесь вспомогательный метод проверяет, обрабатывается ли событие каким-либо экземпляром делегата. (Проверка проводится сравнением с 0.) Обычно такие вспомогательные методы объявляются как защищенные (protected), так что доступ к ним имеют только производные классы.
Теперь с помощью вызова вспомогательных методов можно запускать события.
_gc class ChatServer
// класс сборщика мусора ChatServer
{
public:
void JoinChat(String *pName)
{
pMembers->Add(pName); // Добавить
OnJoin(new ChatEventArg(pName));
}
void QuitChat(String *pName)
{
pMembers->Remove(pName); // Удалить
OnQuitfnew ChatEventArg(pName));
}
CompEbook.ru Железо, дизайн, обучение и другие
Определение метода
После инициализации делегата следует определить метод обратного вызова, сигнатура которого соответствует сигнатуре, описанной в объявлении делегата. Метод может быть как статическим, так и методом экземпляра класса. Приведем несколько примеров методов, которые могут использоваться с объявленным выше делегатом Not i f yCalIback:static void NotifyCustomer(Decimal balance) // Десятичный баланс
{
Console::WriteLine("Dear customer,"); // Дорогой клиент
Console::WriteLine(
" Account overdrawn, balance = {0}", // баланс на счете
_box(balance)); // баланс
}
static void NotifyBank(Decimal balance) // Десятичный баланс
{
Console::WriteLine("Dear bank,"); // Дорогой банк
Console::WriteLine(
" Account overdrawn, balance = {0}", // баланс на счете
_box(balance));
}
void Notifylnstance(Decimal balance) // Десятичный баланс
{
Console::WriteLine("Dear instance,"); // Дорогой представитель
Console::WriteLine(
" Account overdrawn, balance = {0}", // баланс на счете
_box(balance)); // баланс
}
CompEbook.ru Железо, дизайн, обучение и другие
Основные сведения об интерфейсах
Объектно-ориентированное программирование — мощная методология, помогающая в проектировании и реализации больших программных систем. Использование классов позволяет применять обобщение (абстракцию) и инкапсуляцию. С помощью классов большая система может быть естественным образом разбита на удобные в обращении части. Наследование является еще одним удобным средством структурирования системы, которое позволяет поместить общие черты разных частей в один базовый класс, благодаря чему достигается возможность повторного использования программного кода.Основной задачей интерфейсов является определить контракт, не зависящий от реализации. Каждый интерфейс имеет набор методов, не реализованных непосредственно. Для каждого метода определена сигнатура, описывающая количество и тип аргументов, а также тип возвращаемого методом значения.
Интерфейсы в управляемом C++
В Visual C++ .NET для определения интерфейсов используется ключевое слово _interface (интерфейс). Само же определение подобно определению класса. Так же, как и классы, интерфейсы являются ссылочными типами, а для определения их как управляемых используется ключевое слово _дс (сборщик мусора). Наибольшее отличие интерфейсов от классов (как управляемых, так и неуправляемых) — отсутствие конкретной реализации для интерфейсов; они представляют собой чистые спецификации. Тем не менее, подобно классам, интерфейсы могут иметь свойства (property), индексаторы (indexer) и, конечно же, методы.
На примере интерфейса ICustomer продемонстрируем определение методов, используемых клиентами системы Бюро путешествий Acme (Acme Travel Agency).
_gc _interface Icustomer
// сборщик мусора - ICustomer
{
public:
int RegisterCustomer;
String *pFirstName,
String *pLastName,
String *pEmailAddress) ;
void UnregisterCustomer(int id); // идентификатор
ArrayList *GetCustomer(int id); // идентификатор
void ChangeEmailAddress(int id, String *pEmailAddress) ;
// (int идентификатор, Строка *pEmailAddress);
};
Описания методов RegisterCustomer, UnregisterCustomer и ChangeEmailAd-dress полностью совпадают с сигнатурами одноименных методов, реализованных нами в классе Customer (Клиент). Метод GetCustomer является новым. Ранее мы использовали метод ShowCustomer, выводящий на экран список клиентов. Этот метод использовался временно. Однако лучше возвращать по запросу сами данные и предоставить пользователю самому решать, что с ними делать. Метод GetCustomer возвращает информацию об одном или нескольких клиентах в списке массивов. Когда в качестве идентификатора клиента задается значение -1, возвращается информация обо всех зарегистрированных клиентах. В противном случае возвращаемый список содержит информацию о клиенте с заданным идентификатором. Если среди клиентов нет клиента с таким идентификатором, возвращаемый список будет пустым.
Наследование для интерфейсов
Интерфейс может быть потомком другого интерфейса (однако управляемый интерфейс не может быть потомком неуправляемого, и наоборот). В отличие от классов, для которых допустимо лишь единичное наследование, допускается множественное наследование интерфейсов, т.е. интерфейс может иметь несколько непосредственных родителей. Например, интерфейс ICustomer может быть определен как производный от двух более мелких интерфейсов IBasicCustomer и ICustomerlnfo. Заметим, что в описании указанных трех интерфейсов не задается спецификатор открытого доступа. Это потому, что интерфейсы общедоступны по умолчанию.
_gc _interface IBasicCustomer
// сборщик мусора - IBasicCustomer
{
int RegisterCustomer(
String *pFirstName,
String *pLastName,
String *pEmailAddress) ;
void UnregisterCustomer(int id); // идентификатор
void ChangeEmailAddress(int id, String *pEmailAddress) ;
// (int идентификатор, Строка *pEmailAddress) ;
};
_gc _interface ICustomerlnfo
// сборщик мусора - ICustomerlnfo
{
ArrayList *GetCustomer(int id); // идентификатор
};
_gc _interface ICustomer : IBasicCustomer, ICustomerlnfo
// сборщик мусора - ICustomer: IBasicCustomer, ICustomerlnfo
{
};
При таком объявлении интерфейса можно также определить новые методы, как это сделано ниже для интерфейса ICustomer2.
_gc _interface ICustomer2 : IBasicCustomer, ICustomerlnfo
// сборщик мусора - ICustomer2: IBasicCustomer, ICustomerlnfo
{
void NewMethod();
};
CompEbook.ru Железо, дизайн, обучение и другие
Полный пример
Программа DelegateAccount иллюстрирует использование делегатов на примере банковского счета. В файле DelegateAccount. cpp объявляется делегат NotifyCallback. Методы, соответствующие по сигнатурам методам, описанным в объявлении делегата, реализованы в классе DelegateAccount. Метод Мат (Главный) создает экземпляры делегатов и объединяет их различными способами. Указатель на экземпляр делегата передается экземпляру класса Account (Счет), который использует инкапсулированные делегатом методы для выдачи соответствующего ситуации сообщения об овецдрафте.Обратим внимание на динамичную и гибкую связь между этими объектами. Класс Account (Счет) не знает, какой из методов будет использоваться для выдачи сообщения в случае овердрафта. Он просто вызывает делегат, который по очереди вызывает все методы из списка, причем адаптацию списка можно производить и в процессе выполнения программы.
Приведем исходный код класса Account (Счет):
//Account.h
_delegate void NotifyCallback(Decimal balance); // делегировать NotifyCallback (Десятичный баланс);
_gc class Account
// класс сборщика мусора Счет
{
private: // частный
Decimal balance; // Десятичный баланс
NotifyCallback *pNotifyDlg; public:
Account(Decimal bal, NotifyCallback *pDlg) // Счет
{
balance = bal; // баланс
pNotifyDlg = pDlg;
}
void SetDelegate(NotifyCallback *pDlg)
{
pNotifyDlg = pDlg;
}
void Deposit(Decimal amount) // Депозит (Десятичное количество)
{
balance = balance + amount;
// баланс = баланс + количество;
}
void Withdraw(Decimal amount) // Десятичное количество
{
balance = balance - amount;
// баланс = баланс - количество;
if (balance < 0) // если (баланс <0) ситуация кредита
//по текущему счету
pNotifyDlg(balance); // баланс - обратный вызов
}
_property Decimal get_Balance() // Десятичное число
}
return balance; // баланс
}
_property void set_Balance(Decimal balance) // Десятичный
// баланс
{
this->balance = balance; // баланс
}
};
Приведем исходный код объявления и тестирования делегата:
//DelegateAccount.h
_gc class DelegateAccount
// класс сборщика мусора DelegateAccount
{
public:
static void Main() // Главный
{
// создать делегат для статического метода NotifyCustomer
NotifyCallback *pCustDlg = new NotifyCallback(
О, // ноль для статического метода NotifyCustomer
NotifyCustomer);
// создать делегат для статического метода NotifyBank
NotifyCallback *pBankDlg = new NotifyCallback(
О, // ноль для статического метода NotifyBank
NotifyBank);
// объявить, что делегат-объект используется // объектом Account NotifyCallback *pCurrDlg;
// псевдокод: pCurrDlg = pCustDlg + pBankDlg pCurrDlg = static_cast
Delegate::Combine(pCustDlg, pBankDlg)); // Делегат:
// Объединение
// создать объект Account и установить
// делегат для использования
Account *рАсс = new Account(100, pCurrDlg); // новый Счет
Console::WriteLine(
"balance = {0}", _box(pAcc->get_Balance())); // баланс
// вызвать делегат два раза
pAcc->Withdraw(125); // обратный вызов через делегат!
Console::WriteLine(
"balance = {0}", _box(pAcc->get_Balance())); // баланс
pAcc->Deposit(200);// Депозит pAcc->Withdraw(125);
// кредит по текущему счету не нужен, // так что не вызывать обратно
Console::WriteLine(
"balance = {0}", _box(pAcc->get_Balance())); // баланс
// альтернатива: pCurrDlg - = pBankDlg
pCurrDlg = static_cast
Delegate::Remove(pCurrDlg, pBankDlg)); // Делегат::
// Удалить
// установить новый делегат, который используется
// объектом Account
pAcc->SetDelegate(pCurrDlg);
// вызвать делегат
pAcc->Withdraw(125); // обратный вызов через делегат!
// создать экземпляр, требуемый для экземпляра делегата DelegateAccount *pda = new DelegateAccount();
// создать делегат для экземпляра метода Notifylnstance NotifyCallback *p!nstDlg = new NotifyCallback(
pda, // отличный от нуля для метода Notifylnstance
Notifylnstance);
// дополнительный код: pCurrDlg + = plnstDlg
pCurrDlg = static_cast
Delegate::Combine(pCurrDlg, plnstDlg)); // Делегат:
// Объединение
// установить новый делегат, который используется
// объектом Account pAcc->SetDelegate(pCurrDlg);
pAcc->Withdraw(125); // обратный вызов через делегат!
}
private: // частный
static void NotifyCustomer(Decimal balance) // Десятичный
// баланс
{
Console::WriteLine("Dear customer,"); // Дорогой клиент,
Console::WriteLine(
" Account overdrawn, balance = {0}", // Счет превышен,
// баланс
_box(balance)); // баланс
}
static void NotifyBank(Decimal balance) // Десятичный баланс
{
Console::WriteLine("Dear bank,"); // Дорогой банк,
Console::WriteLine(
" Account overdrawn, balance = {0}", // Счет превышен,
// баланс
_box(balance)); // баланс
}
void Notifylnstance(Decimal balance) // Десятичный баланс
{
Console::WriteLine("Dear instance,"); // Дорогой
// представитель
Console::WriteLine(
" Account overdrawn, balance = {0}", // Счет превышен,
// баланс
_box(balance)); // баланс
}
};
Ниже приведен результат работы программы. Обратите внимание на то, какие методы вызываются в зависимости от операций, выполняемых делегатом на каждом этапе.
balance = 100
Dear customer,
Account overdrawn, balance = -25
Dear bank,
Account overdrawn, balance = -25
balance = -25
balance = 50
Dear customer,
Account overdrawn, balance = -75
Dear customer,
Account overdrawn, balance = -200
Dear instance,
Account overdrawn, balance = -200
А вот и перевод выдачи:
баланс = 100 Дорогой клиент,
Овердрафт по счету, баланс =-25
Дорогой банк,
Овердрафт по счету, баланс =-25
баланс =-25
баланс = 50
Дорогой клиент,
Овердрафт по счету, баланс =-75
Дорогой клиент,
Овердрафт по счету, баланс =-200
Дорогой представитель,
Овердрафт по счету, баланс =-200
CompEbook.ru Железо, дизайн, обучение и другие
Пример класса ArrayList (Список массивов)
Для начала приведем простой пример использования класса ArrayList (Список массивов) Как понятно из названия (Array List — Список массивов), ArrayList — это список объектов, хранимый подобно массиву Размер списка массивов может динамически изменяться, и может расти при добавлении новых элементовКлассы коллекций содержат экземпляры класса ОЬц ect (Объект) Мы создадим и будем иметь дело с коллекцией объектов Customer (Клиент) Использовать любой другой встроенный или определяемый пользователем управляемый тип ничуть не сложнее При использовании простого типа, такого, как int, экземпляр данного типа для сохранения в коллекции должен быть упакован (boxed), а перед его использованием — распакован обратно в int
Взятая для примера программа называется CustomerCollection В ней инициализируется список клиентов, после чего пользователь может просмотреть данный список, зарегистрировать нового клиента, отменить регистрацию клиента или изменить адрес его электронной почты Вызов простого метода help (помощь) отображает список доступных команд
Enter command, quit to exit
H> help
The following commands are available:
register register a customer
unregister unregister a customer
email change email address
show show customers
quit exit the program
Вот перевод:
Введите команду, quit для выхода из программы
Н> help
Доступны следующие команды:
register (регистрировать) регистрирует клиента
unregister (отменить регистрацию) отменяет регистрацию клиента
email (электронная почта) изменяет адрес электронной почты
show (показать) показывает клиентов
quit выход из программы
До того, как ознакомиться с исходным кодом, было бы неплохо запустить программу, зарегистрировать нового клиента, просмотреть список клиентов, изменить адрес электронной почты клиента, отменить регистрацию клиента и снова просмотреть список клиентов. Приведем пример выдачи программы:
Н> show // показать
id (-1 for all): -1 // идентификатор (-1 для всех):-1
1 Rocket Squirrel rocky@frosbitefalls.com
2 Bullwinkle Moose moose@wossamotta.edu
H> register // регистрировать
first name: Bob // имя: Боб
last name: Oberg // фамилия: Оберг
email address: oberg@objectinnovations.com // адрес электронной
// почты:
id = 3 // идентификатор = 3
H> email // электронная почта
customer id: 1 // идентификатор клиента
email address: rocky@objectinnovations.com // адрес электронной
// почты
Н> unregister
id: 2 // идентификатор: 2
Н> show // показать
id (-1 for all): -1 // идентификатор (-1 для всех)
1 Rocket Squirrel rocky@objectinnovations.com
3 Bob Oberg oberg@objectinnovations.com
Класс Customer (Клиент)
Все файлы с исходными кодами программы-примера находятся в папке Customer-Collection. В файле customer. h находится реализация классов Customer (Клиент) и Customers (Клиенты). Исходный код для класса Customer (Клиент) почти такой же, как приведенный ранее. Единственное добавление — специальный конструктор, инициализирующий объект Customer (Клиент) заданным идентификатором. Этот конструктор используется классом Customers (Клиенты) при удалении элемента (UnregisterCustomer) и при проверке того, присутствует ли в коллекции некоторый элемент (Checkld).
_gc class Customer
// сборщик мусора - класс Клиент
{
pmblic:
Customer(int id) // Клиент (int-идентификатор)
{
nCustomerld = id; // идентификатор
pFirstName = "";
pLastName = "";
pEmailAddress = "";
}
};
Класс Customers (Клиенты) содержит список клиентов, хранимый в ArrayList
{Список массивов).
_gc class Customers // сборщик мусора - класс Клиенты
{
private: // частный
ArrayList *pCustomers;
public:
Customers() // Клиенты
{
pCustomers = new ArrayList;
RegisterCustomer(
"Rocket", // Ракета "Squirrel",
// Белка "rocky@frosbitefalls.com");
RegisterCustomer( "Bullwinkle",
"Moose", // Американский лось "moose@wossamotta.edu");
}
int RegisterCustomer( String *pFirstName, String *pLastName, String *pEmailAddress)
{
Customer *pCust = new Customer( // Клиент
*pCust = новый Клиент
{
pFirstName, pLastName, pEmailAddress); pCustomers->Add(pCust); // Добавить return pCust->nCustomer!d; }
void UnregisterCustomer(int id) // идентификатор {
Customer *pCust = new Customer(id);
// Клиент *pCust = новый Клиент (идентификатор);
pCustomers->Remove(pCust);
}
void ChangeEmailAddress(int id, String *pEmailAddress)
// (int идентификатор, Строка *pEmailAddress)
{
lEnumerator *pEnum =
pCustomers-XSetEntimerator () ;
while (pEnum->MoveNext())
{
Customer *pCust = // Клиент
dynamic_cast
if (pCust->nCustomer!d == id)
// если (pCust-> nCustomerld == идентификатор)
{
pCust->pEmailAddress = pEmailAddress;
return;
}
}
String *pStr = String::Format( // Формат
"id {0} {!}", _box(id), S"not found");
// "идентификатор {0} {!}", (идентификатор),
// " не найден");
throw new Exception(pStr); // новое Исключение
}
void ShowCustomers(int id) // идентификатор
{
if ('Checkld(id) && id '= -1)
// если (! Checkld (идентификатор) && идентификатор! =-1)
return; lEnumerator *pEnum =
pCustomers-XSetEnumerator();
while (pEnum->MoveNext())
{
Customer *pCust = // Клиент
dynamic_cast
if (id == -1 || id == pCust->nCustomer!d)
// если (идентификатор == -1 ||
// идентификатор == pCust-> nCustomerld)
{
String *pSid =
pCust->nCustomerId.ToStnng() ->PadLeft (4) ;
String *pFirst =
pCust->pFirstName->PadRight(12) ;
String *pLast =
pCust->pLastName->PadRight(12);
String *pEmail =
pCust->pEmailAddress->PadRight(20) ;
Console::Write("{0} ", pSid); // Пульт:: Записать
Console::Write("{0} ", pFirst); // Пульт:: Записать
Console::Write("{0} ", pLast) ; // Пульт:: Записать
Console::WriteLine("{0}", pEmail); // Пульт:: Записать
}
}
}
bool Checkld(int id)
// логический{булев} Checkld (int идентификатор)
{
Customer *pCust = new Customer(id);
// Клиент *pCust = новый Клиент (идентификатор);
return pCustomers->Contains(pCust); // Содержит ?
}
};
Жирным шрифтом в листинге выделены строки, в которых используются особенности класса коллекции. Для перемещения по коллекции используется интерфейс lEnumerator. Он может быть использован в данном случае, так как ArrayList (Список массивов) поддерживает интерфейс lEnumerable. Этот интерфейс обеспечивает поддержку особой семантики С# — семантики foreach. Ключевое слово foreach не поддерживается в C++. Однако из приведенного примера видно, что в управляемом C++ для перебора элементов ArrayList (Список массивов) можно применять интерфейс lEnumerable. Интерфейсы, предназначенные для работы с коллекциями, включая lenumerable, будут рассмотрены в этой главе позже.
Методы Add (Добавить) и Remove (Удалить), как и следует предположить по их названиям, используются для добавления и удаления элементов коллекции, соответственно. Метод Remove (Удалить) просматривает коллекцию в поисках элемента, равного элементу, переданному методу в качестве аргумента, и удаляет найденный элемент. Равенство элементов устанавливается вызовом метода Equals (Равняется). Методу Remove (Удалить) в качестве аргумента передается элемент, создаваемый реализованным нами специальным конструктором, причем для создания элемента используется идентификатор. Поскольку мы подменили метод Equals (Равняется) так, что равенство элементов устанавливается только по атрибуту CustomerlD, этот конструктор имеет единственный аргумент — идентификатор клиента.
Метод Contains (Содержит), применяемый нами во вспомогательном методе Checkld, использует подмененный метод Equals (Равняется) аналогичным образом.
Благодаря использованию коллекций облегчается добавление и удаление элементов. При использовании с той же целью массивов вместо коллекций требуется написать немало кода, необходимого для вставки и перемещения элементов массива, а также для заполнения пустого места, остающегося после удаления элемента. Кроме того, коллекции не имеют определенного размера, и при необходимости могут быть динамически увеличены.
CompEbook.ru Железо, дизайн, обучение и другие
Программа Бюро путешествий Acme (Acme Travel Agency)
Попытаемся применить полученные знания об интерфейсах для небольшой переделки программы Бюро путешествий Acme (Acme Travel Agency). Одним из наибольших достоинств интерфейсов является то, что благодаря им повышается уровень абстракции, — это позволяет понять и ощутить систему на уровне взаимодействия ее отдельных частей, абстрагируясь от конкретной их реализации.Файлы с исходным кодом находятся в папке CaseStudy.
Интерфейсы в управляемом C++ и .NET
.NET и модель компонентных объектов Microsoft (COM) имеют много сходного. В обеих этих технологиях фундаментальную роль играет концепция интерфейсов. Их удобно использовать для определения контрактов. Интерфейсы обеспечивают очень динамичный стиль программирования.
В модели компонентных объектов Microsoft (COM) разработчику приходится самому заботиться о тщательном создании инфраструктуры, необходимой для реализации СОМ-компонентов. Для создания СОМ-объектов требуется реализовать фабрику класса (class factory). Для динамической проверки поддержки интерфейса разработчик (Должен реализовать метод Querylnterfасе интерфейса Unknown. Кроме того, для соответствующего управления памятью следует реализовать методы AddRef и Release (Освободить).
При использовании же языков .NET все эти действия осуществляются автоматически виртуальной машиной, реализующей общий язык времени выполнения CLR (Common Language Runtime). Для создания объекта достаточно воспользоваться " оператором new (создать). Проверку поддержки классом интерфейса и получение указателя на интерфейс несложно провести с помощью оператора dynamic_cast. Управление памятью берет на себя сборщик мусора.
Контракт
Ранее мы уже рассмотрели интерфейс ICustomer класса Customers (Клиенты). Теперь обратим внимание на класс HotelBroker. Его методы естественным образом разделяются на три группы.
1. Информация о гостинице, такая, как названия городов, в которых есть гостиницы, и названия гостиниц, которые есть в определенном городе
2. Управление информацией о гостиницах, в частности добавление или удаление гостиницы из базы данных либо изменение количества комнат, доступных в некоторой гостинице.
3. Операции резервирования гостиниц, например, бронирование номеров, отмена заказа или просмотр списка резервирования.
В свете изложенного логично будет создать для класса HotelBroker три интерфейса. Эти интерфейсы определены в файле AcmeDef initions.h.
__gc _interface IHotellnfo
// сборщик мусора - IHotellnfo
{
ArrayList *GetCities();
ArrayList *GetHotels();
ArrayList *GetHotels(String *pCity);
};
_gc _interface IHotelAdmin
// сборщик мусора - IHotelAdmin
{
String *AddHotel (
String *pCity,
String *pName,
int numberRooms,
Decimal rate); // Десятичная цена
String *DeleteHotel (String *pCity, String *pName);
String *ChangeRooms(
String *pCity,
String *pName,
int numberRooms,
Decimal rate); // Десятичная цена
};
_gc _interface IHotelReservation
// сборщик мусора - IHotelReservation
{
ReservationResult MakeReservation(
int customerld,
String *pCity,
String *pHotel,
DateTime checkinDate,
int numberDays);
void CancelReservation(int id); // идентификатор
ArrayList *FindReservationsForCustomer(
int nCustomerld);
};
Реализация
Далее реализуем систему управления гостиницами, используя коллекции вместо массивов. При этом мы будем возвращать программе-клиенту запрошенную ею информацию в методе TestHotel: -.Main вместо того, чтобы делать это непосредственно в классе HotelBroker. Ранее в этой же главе мы рассмотрели новую реализацию класса Customers (Клиенты). Принципы, применявшиеся при тех переделках, будут использованы и для обновления класса HotelBroker.
Структуры
Прежде всего следует разобраться со структурой данных, передаваемых клиенту по его запросу. Мы используем класс ArrayList (Список массивов). А что же будет храниться в указанном списке массивов? В нашем случае это могут быть объекты Customer (Клиент) и Hotel (Гостиница). Проблема применимости такого подхода состоит в том, что кроме данных, которые клиенту могут понадобиться, оба этих класса содержат также данные, необходимые для реализации класса, но не нужные программе-клиенту вовсе. Для того чтобы решить эту проблему, определим несколько структур.
В файле Customers . h определим структуру CustomerListltem, предназначенную для возврата информации о клиенте.
_value struct CustomerListltem
{
public:
int nCustomerld;
String *pFirstName;
String *pLastName;
String *pEmailAddress;
};
В файле AcmeDef initions. h определим структуры для хранения данных о гостиницах и заказах, а также результатов резервирования.
_value struct HotelListltem
{
public:
String *pCity;
String *pHotelName;
int nNumberRooms;
Decimal decRate; // Десятичное число
};
_value struct ReservationListltem
{
public:
int nCustomerld;
int nReservationld;
String *pHotelName;
String *pCity;
DateTime dtArrivalDate;
DateTime dtDepartureDate;
int nNumberDays;
};
_value struct ReservationResult
{
public:
int nReservationld;
Decimal decReservationCost; // Десятичное число
Decimal decRate; // Десятичное число
String *pComment;
};
ReservationResult возвращает значение Reservationld или -1 при возникновении проблем (в этом случае в поле pComment содержится более подробное описание возникших проблем; если же никаких проблем нет, там находится строка "ОК.").
А теперь вам стоит изучить файлы исходного кода, находящиеся в папке CaseStudy, откомпилировать и скомпоновать приложение, и запустить его.
CompEbook.ru Железо, дизайн, обучение и другие
Программирование с использованием интерфейсов
Использование интерфейсов облегчает программирование на управляемом C++. Интерфейсы реализуются через классы, и для получения указателя на интерфейс можно выполнить приведение указателя на класс. Методы интерфейсов можно вызывать, используя и указатели на класс, и указатели на интерфейс; однако для того, чтобы полностью воспользоваться достоинствами полиморфизма, предпочтительно везде, где только возможно, использовать указатели на интерфейсы.Реализация интерфейсов
В C++ указание того, что класс реализует интерфейс, осуществляется с помощью двоеточия, используемого также для указания наследования класса. Управляемый класс может наследовать от одного управляемого класса и, кроме этого, от одного или нескольких управляемых интерфейсов. В этом случае базовый класс должен указываться в списке первым, сразу после двоеточия. Заметим, что, в отличие от управляемых интерфейсов, наследование управляемых классов может быть только общедоступным.
_gc class HotelBroker : public Broker, public IHotellnfo,
// класс сборщика мусора - HotelBroker: общедоступный Брокер,
public IHotelAdmin, public IHotelReservation
{
...
};
В этом примере класс HotelBroker является производным от класса Broker (Брокер) и реализует интерфейсы IHotellnfo, IHotelAdmin и IHotelReservation. В HotelBroker должны быть реализованы все методы этих интерфейсов, либо непосредственно, либо используя реализацию, унаследованную от базового класса Broker (Брокер).
Подробно пример использования интерфейсов будет рассмотрен в этой главе несколько позже, когда мы возьмемся за реализацию второго шага создаваемой системы.
А сейчас в качестве небольшого примера вышеизложенного, рассмотрим программу Smalllnterface. Класс Account (Счет) реализует интерфейс IBasicAccount. В описании этого интерфейса демонстрируется синтаксис объявления свойства интерфейса.
//Account.h
_gc _interface IBasicAccount
// сборщик мусора - IBasicAccount
{
void Deposit(Decimal amount); // Депозит (Десятичное
// количество);
void Withdraw(Decimal amount); // Снять (Десятичное
// количество);
_property Decimal get_Balance(); // Десятичное число };
_gc class Account : public IBasicAccount
// сборщик мусора - класс Счет: IBasicAccount
{
private: // частный
Decimal balance; // Десятичный баланс public:
Account(Decimal balance) // Счет (Десятичный баланс)
{
this->balance = balance; // баланс
}
void Deposit(Decimal amount) // Депозит (Десятичное количество)
//не поддерживается
Console::WriteLine(pe->Message); // Сообщение
}
}
};
В приведенном примере сначала мы имеем дело с классом Account (Счет), который поддерживает интерфейс IBasicAccount. В этом случае попытки вызвать методы интерфейса как с помощью указателя на класс, так и указателя на интерфейс, полученного в результате приведения, заканчиваются успешно. Далее мы имеем дело с классом NoAccount. Несмотря на то, что набор методов этого класса идентичен набору методов класса Account (Счет), в его описании не указано, что он реализует интерфейс IBasicAccount.
//NoAccount.h
_gc class NoAccount
// класс сборщика мусора NoAccount
{
При запуске этой программы возникает исключение NullReferenceException. Это происходит при попытке использовать указатель на интерфейс IBasicAccount, полученный в результате динамического приведения указателя на класс NoAccount. (Иными словами, исключение возникает при попытке приведения типа NoAccount * к данным типа указателя на интерфейс IBasicAccount *.) Если бы мы использовали обычное приведение типа в стиле С, то при подобной попытке возникло бы исключение InvalidCastException. Однако уже при компиляции такой программы было бы выдано предупреждение, что использование приведения типов в стиле С не рекомендуется.
balance = 100
balance = 125
balance = 150
balance = 500
balance = 525
IBasicAccount is not supported
Value null was found where an instance of an object was
required.
Вот перевод выдачи:
баланс = 100
баланс =125
баланс = 150
баланс = 500
баланс = 525
IBasicAccount не поддерживается
Пустой указатель там, где требуется указатель на объект.
CompEbook.ru Железо, дизайн, обучение и другие
В этой главе рассмотрены некоторые
В этой главе рассмотрены некоторые важные связи управляемого C++ и .NET Framework, причем начали мы с базового класса Object (Объект). Мы рассмотрели использование коллекций, в частности, те методы класса Object (Объект), которые следует переопределять для работы с коллекциями. Очень подробно мы обсудили концепцию интерфейсов, позволяющую разработчику строго определять свойства, которые должны быть реализованы в классе. Хотя класс в управляемом C++ может иметь только один базовый класс, он может реализовывать несколько интерфейсов. Другим достоинством интерфейсов является то, что они значительно облегчают создание динамичных программ. Управляемый C++ обеспечивает возможность во время выполнения программы послать запрос классу для выяснения, поддерживает ли он определенный интерфейс.Мы подробно рассмотрети интерфейсы используемые для работы с коллекциями, и виды копирования объектов В обычном C++ для копирования объектов используются специальные языковые средства — конструкторы копирования, а в управляемом C++ те же возможности обеспечиваются реализацией особого интерфейса ICloneable В итоге мы пришли к изучению ропи родовых интерфейсов в методологии программирования NET Framework и сравнению использования компонентов NET и СОМ Использование родовых интерфейсов также проиллюстрировано на примере сортировки коллекций с помощью интерфейса. Соответствующие примеры позволили полнее ощутить отличие каркаса притожении от простои библиотеки классов При использовании каркаса притожении программа может вызывать методы каркаса, а те могут вызывать методы программы Поэтому создаваемый код можно уподобить среднему слою сандвича Этот пример помошет понять для чего необходима платформа NET
А в конце главы рассмотрено использование делегатов и событий. С этой целью были представлены два простых примера моделирование фондовой биржи и комната для дискуссии (чат-программа).
CompEbook.ru Железо, дизайн, обучение и другие
Родовые интерфейсы и обычное поведение
Если вы знакомы с языком Smalltalk или ему подобными, набор возможностей, реализованных непосредственно в классе Object (Объект), может показаться вам весьма ограниченным. В языке Smalltalk, в котором использована концепция иерархии классов, являющихся потомками одного базового класса, набор методов, реализованных в Object (Объект), весьма широк. Я насчитал 38 методов! Эти методы осуществляют различные действия, такие, как сравнение и копирование объектов. Библиотека классов .NET Framework содержит и подобные методы, и еще множество других. Но вместо того, чтобы вводить их в базовый класс, .NET определяет набор стандартных интерфейсов, которые при желании может реализовывать класс. Такая организация, используемая также в технологии COM (Component Object Model — модель компонентных объектов Microsoft) от Microsoft и в языке Java, очень гибка. В этой главе мы рассмотрим некоторые родовые интерфейсы .NET Framework.CompEbook.ru Железо, дизайн, обучение и другие
Родовые интерфейсы в .NET
Большая часть возможностей, предоставляемых .NET Framework, осуществлена в родовых интерфейсах, которые реализованы в разных вариантах классами самой среды или могут быть реализованы создаваемыми классами с тем, чтобы изменить или дополнить стандартные возможности, определенные средой. В данном разделе мы рассмотрим несколько категорий операций, выполняемых стандартными, родовыми интерфейсами. ЭтоНаш обзор ни в коем случае не может считаться исчерпывающим, но он даст вам достаточно информации для понимания, каким образом работают родовые интерфейсы в среде .NET Framework.
CompEbook.ru Железо, дизайн, обучение и другие
основа более сложного протокола обратного
Делегаты — основа более сложного протокола обратного вызова, называемого событиями. Согласно замыслу, сервер реализует входящие интерфейсы, которые могут быть вызваны клиентом. На диаграммах подобный интерфейс можно обозначить с помощью маленького кружка (обозначение, использующееся в модели компонентных объектов Microsoft (COM)). Иногда клиенту может понадобиться получать от сервера сообщения при возникновении некоторых "событий". Для подобных ситуаций сервером определяется исходящий интерфейс. Ключевой идеей механизма событий является то, что сервер определяет интерфейс, но реализует этот интерфейс клиент. На диаграммах такой интерфейс обозначается стрелкой (опять же, в обозначениях модели компонентных объектов Microsoft (COM)). На рис. 5.3 представлена диаграмма, изображающая сервер с одним входящим и одним исходящим интерфейсами. Для использования исходящего (для сервера) интерфейса клиент реализует входящий (для клиента) интерфейс, обратный вызов которого осуществляется сервером.CompEbook.ru Железо, дизайн, обучение и другие
События в управляемом C++ и .NET
.NET Framework позволяет использовать простую реализацию идеи событий, основанную на применении делегатов. В управляемом C++ работа с событиями .NET основана на использовании ключевого слова _event (событие) и операторов, предназначенных для добавления и удаления обработчиков событий. Мы рассмотрим концепцию событий на примере чат-программы EventDemo (Диалоговая комната для дискуссий).CompEbook.ru Железо, дизайн, обучение и другие
Создание экземпляра делегата
Экземпляр делегата инициализируется с помощью оператора new (создать), так же, как и для любого другого класса. Ниже приведен код, демонстрирующий создание двух экземпляров делегатов. Первый из них связан со статическим методом, второй — с методом экземпляра класса. Второй экземпляр делегата хранит как точку входа в метод, так и экземпляр класса, который используется для вызова метода.// создать делегат для статического метода NotifyCustomer
NotifyCallback *pCustDlg = new NotifyCallback(
0, // ноль для статического метода NotifyCustomer
NotifyCustomer);
// создать делегат для экземпляра метода Notifylnstance
NotifyCallback *p!nstDlg = new NotifyCallback(
pda, // отличный от нуля для экземпляра метода Notifylnstance
Notifylnstance);
CompEbook.ru Железо, дизайн, обучение и другие
Сравнение объектов
Итак, мы подробно рассмотрели копирование объектов. Теперь рассмотрим сравнение объектов. Для сравнения объектов в .NET Framework используется интерфейс ICompa-rable. В этом разделе мы в качестве примера воспользуемся интерфейсом ICompara-Ые для сортировки массива.Сортировка массива
В классе System::Array (Система::Массив) статический метод Sort (Сортировка) предназначен для сортировки массива. Программа ArrayName иллюстрирует применение этого метода к сортировке массива объектов Name (Имя), где класс Name (Имя) содержит просто строку. Приведем листинг основной программы:
//ArrayName.срр
_gc class ArrayName
// класс сборщика мусора ArrayName
{
public:
static void Main() {
Name *array[] = new Name*[5]; // Имя
array[0] = new Name("Michael"); // новое Имя ("Майкл")
array[l] = new Name("Charlie"); // новое Имя ("Чарли")
array[2] = new Name("Peter"); // новое Имя ("Питер")
array[3] = new Name("Dana"); // новое Имя ("Дана")
array[4] = new Name("Bob"); // новое Имя ("Боб")
if (dynamic_cast
Array::Sort(array); else
Console::WriteLine(
"Name does not implement IComparable");
// (" Name (Имя) не реализует IComparable");
lEnumerator *pEnum = array->GetEnumerator();
while (pEnum->MoveNext())
{
Name *pName = // Имя
dynamic_cast
if (pName != 0)
Console::WriteLine(pName);
}
}
};
Реализация IComparable
Для того чтобы можно было произвести сортировку, необходимо определить правила сравнения сортируемых объектов. Такие правила реализуются в методе CompareTo интерфейса IComparable. Поэтому, для того, чтобы иметь возможность сортировать массивы, содержащие данные определенного вами типа, необходимо реализовать интерфейс IComparable для этого типа данных.
_gc _interface IComparable
// сборщик мусора - IComparable
{
int CompareTo(Object* obj);
};
Приведем листинг реализации класса Name (Имя), включая и реализацию интерфейса
IComparable:
_gc class Name : public IComparable
{
private: // частный
String *pText;
public:
Name(String *pText) // Имя
{
this->pText = pText;
}
_property String* get_Item()
{
return pText;
}
_property void set_Itern(String* pText)
{
this->pText = pText;
}
int CompareTo(Object *pOb]) // Объект
{
String *pSl = this->pText;
String *pS2 =
(dynamic_cast
return String::Compare(pSl, pS2); // сравниваются имена
}
String *ToString()
{
return pText;
}
};
Результатом работы профаммы является упорядоченный по алфавиту список имен:
Bob // Боб
Charlie // Чарли
Dana // Дана
Michael // Майкл
Peter // Питер
CompEbook.ru Железо, дизайн, обучение и другие
Управляемый C++ в .NET Framework
Язык C++ — мощный инструмент разработки программ, оказавший огромное влияние на развитие вычислительной науки. Управляемые (managed) расширения от Microsoft добавили в язык C++ целый новый мир — мир .NET. Для того чтобы полностью использовать возможности Visual C++ .NET, необходимо понимать, как он работает с .NET Framework. Мы начнем рассмотрение с базового класса Object (Объект) из пространства имен System. Затем рассмотрим коллекции, а также методы класса Object (Объект), которые следует перегрузить для использования возможностей, предоставляемых .NET Framework. Далее познакомимся с интерфейсами, позволяющими строго определить свойства реализуемых классов. В управляемом C++ класс может реализовывать несколько интерфейсов, даже при том, что он может быть потомком только одного суперкласса. Интерфейсы позволяют применять динамическое программирование; во время выполнения программы можно послать классу запрос для того, чтобы узнать, поддерживает ли он определенный интерфейс.Будут подробно рассмотрены интерфейсы, поддерживающие использование коллекций. Потом остановимся на видах копирования. Вместо применения конструкторов копирования, как это делается в обычном C++, в управляемом C++ для реализации копирования используется интерфейс ICloneable. Мы рассмотрим родовые интерфейсы в методологии программирования .NET Framework и сравним использование компонентов .NET и СОМ. Более полно использование родовых интерфейсов иллюстрируется на примерах различных сортировок с помощью интерфейса IComparable. Этот пример позволяет также почувствовать удобство работы с каркасом приложений, определяющим архитектуру программ, а не являющимся просто библиотекой классов, в которой имеются некие полезные функции. При использовании каркаса приложений программа может вызывать методы каркаса, а те могут вызывать методы программы. Поэтому создаваемый код можно уподобить сандвичу. Этот пример помогает понять, для чего необходима платформа .NET. Функции обратного вызова применяются в программировании уже много лет. Управляемый C++ использует эту концепцию в работе с делегатами и событиями. Здесь представлены два простых и понятных примера: моделирование фондовой биржи и диалоговая комната для дискуссий (чат-программа).
CompEbook.ru Железо, дизайн, обучение и другие
Вызов делегата
Синтаксис "вызова" делегата совпадает с синтаксисом вызова метода. Делегат не является сам по себе методом, но он инкапсулирует метод. Делегат "передает" вызов инкапсулированному методу, потому и называется делегатом (от англ, delegate — поручать, уполномочивать). В приведенном ниже фрагменте кода делегат notif yDlg вызывается в случае, если при выплате со счета получается отрицательный баланс. В этом примере экземпляр notif yDlg инициализируется в методе SetDelegate._gc class Account
// класс сборщика мусора Счет
{
private: // частный
Decimal balance; // Десятичный баланс
NotifyCallback *pNotifyDlg;
void SetDelegate(NotifyCallback *pDlg)
{
pNotifyDlg = pDlg; }
void Withdraw(Decimal amount) // Десятичное количество
{
balance = balance - amount;
// баланс = баланс - количество;
if (balance < 0) // если баланс <0, ситуация овердрафта
pNotifyDlg(balance); callback // баланс, обратный вызов
}
CompEbook.ru Железо, дизайн, обучение и другие
Защищенные методы экземпляров класса object (Объект)
Защищенными являются два метода класса Object (Объект). Эти методы могут использоваться только производными классами.Метод MemberwiseClone
protected: Object* MemberwiseClone(); // защищенный
Данный метод создает поверхностную (shallow) копию объекта. Это метод не виртуальный, поэтому подменять его обычно не приходится. Для того чтобы сделать детальную (deep) копию, следует использовать интерфейс ICloneable. Разница между поверхностной и детальной копией будет рассмотрена в этой главе несколько позже.
Метод Finalize (Завершить)
-Object();
Этот метод позволяет освободить используемые объектом неуправляемые ресурсы и выпочнить другие операции, необходимые при сборке мусора (утилизации неиспользуемой памяти и других ресурсов). В управляемом C++ метод Finalize (Завершить) имеет такой же синтаксис, как и деструктор в обычном C++. Но при этом семантика данного метода качественно отличается от семантики деструктора в обычном C++. В обычном C++ деструктор вызывается детерминированно и синхронно. В управляемом C++ для сборщика мусора создается независимый поток.
CompEbook.ru Железо, дизайн, обучение и другие
Architecture Net
Демонстрация диалогового окна
Мы продемонстрируем детали реализации диалогового окна на примере создания диалога для изменения информации о гостинице в упрощенной версии нашего примера Код системы запуска нашего примера имеется в папке HotelAdminNStepl, которая находится в главной папке этой главы Окончательная версия программы находится в папке HotelAdmin\Step3 Можно запустить первую версию примера, а можно сразу запустить решение Step 3 и посмотреть, как должно выглядеть диалоговое окно в окончательной версии В главной форме выберите гостиницу, щелкнув на соответствующем элементе списка гостиниц Затем щелкните на кнопке Change (Изменить ) В результате появится диалоговое окно Change Hotel Information (Изменение информации о гостинице), показанное на рис. 6.24 Обратите внимание на то, что поля City (Город) и Hotel Name (Название гостиницы) недоступны Эти поля доступны только для чтения, и их значения изменить нельзя Пользователь может изменить только значения полей Rooms (Количество номеров) и Rate (Стоимость)Создание модального диалога
В первой части нашей демонстрации мы научим вас создавать модальное диалоговое окно Мы покажем, как устанавливать свойства диалога и как возвращать результаты после щелчка на кнопке (Ж или Cancel (Отмена)
1. Скомпонуйте и запустите стартовую систему приложения Кнопки Add (Добавить ) и Delete (Удалить) работают, но для кнопки Change (Изменить ) имеется только заглушка, которая по щелчку на этой кнопке отображает пустую форму Это обычная форма Ее размер можно изменять, у нее есть системное меню, кнопки свертывания Minimize (Свернуть) и развертывания Maximize (Развернуть)
2. Откройте файл ChangeHotelDialog. cs в режиме Design (Конструктор) В окне Properties (Свойства) установите значение свойства FormBorderStyle равным FixedDialog
3. Установите значение свойств ControlBox, MinimizeBox и MaximizeBox равным False (Ложь) Сейчас можно скомпоновать и запустить приложение Теперь размер диалогового окна будет фиксированным, у него не будет системного меню, а в правом верхнем углу окна не будет кнопки "X", с помощью которой можно закрыть окно.

Рис. 6.24. Диалоговое окно для изменения информации о гостинице
4. Теперь необходимо определить надписи и текстовые поля, содержащие информацию о гостинице Кроме того, необходимо добавить кнопки ОК и Cancel (Отмена) Когда вы будете добавлять эти управляющие элементы, вы можете еще раз попрактиковаться в работе с панелью инструментов Toolbox (Инструментальная панель) Вы можете выбрать и другой подход скопировать эти свойства из файла NewHotelDialog.cs и затем вставить их, для этого нужно открыть оба файла в режиме Design (Конструктор)
5. Если вы использовали копирование и вставку, то у управляющих элементов свойства Name (Имя) и Text (Текст) уже определены правильно. В противном случае установите значения в соответствии с табл. 6.2.
6. Значение свойства Readonly для txtCity и txtHotelName установите равным true (истина).
7. Измените размер формы так, чтобы на ней помещались все добавленные управляющие элементы.
8. Установите значение свойства DialogResult кнопки ОК равным ОК. Точно так же установите значение этого свойства кнопки Cancel (Отмена) равным Cancel (Отмена). Сохраните изменения, сделанные в файле ChangeHotelDialog. cs.
9. В файле MainAdminForm.cs временно добавьте к обработчику cmdChange_Click код, который отвечает за отображение в текстовом поле Messages (Сообщения) строк "ОК" и "Cancel" ("Отмена") в зависимости оттого, как был закрыт диалог: с помощью кнопки ОК или Cancel (Отмена). Обратите внимание на то, что диалоговое окно отображается с помощью метода ShowDialog, а не метода Show (Показать), который используется для обычных форм. В качестве результата метод ShowDialog возвращает перечисление типа DialogResult.
private void cmdChange_Click(object sender,
System.EventArgs e)
{
ChangeHotelDialog dig = new ChangeHotelDialog();
DialogResult status = dig.ShowDialog(); // состояние
if (status == DialogResult.OK)
// если (состояние == DialogResult. OK)
{
txtMessages.Text = "OK"; // Текст
}
else
{
txtMessages.Text = "Cancel";
// txtMessages. Текст = "Отмена";
}
}
Таблица 6.2. Значения свойств текстовых полей и кнопок диалога ChangeHotelDialog.cs
10. Скомпонуйте и запустите пример. Теперь диалоговое окно уже можно открыть с помощью меню, а закрыть— с помощью любой из кнопок ОК или Cancel (Отмена), причем на экран будет выведено соответствующее сообщение. Можно проверить, что диалоговое окно является модальным, пощелкав мышью где-нибудь еще в приложении. Программа сейчас находится на шаге 2 разработки.
Передача информации между родительской формой и диалогом
Во второй части нашей демонстрации будет показано, как передавать информацию из родительской формы диалогу и как получать информацию от диалога. Для этих целей в классах .NET Framework нет встроенного механизма, но все-таки существует некоторый шаблон проектирования, которому можно следовать. В классе диалога для каждого сообщения (порции информации), которое может передаваться между родительской формой и диалогом, нужно определить некоторое свойство.
В нашем примере мы сделаем свойства City (Город) и HotelName (Название гостиницы) доступными только для записи, а свойства Rate (Стоимость) и NumberRooms — доступными для чтения и записи.
1. Для того чтобы реализовать указанные свойства, добавьте к классу Chan-geHotelDialog в файле ChangeHotelDialog. cs приведенный ниже код.
public string City
// общедоступный строковый - Город
{
set
{
txtCity.Text = value; // txtCity. Текст = значение;
}
}
public string HotelName // общедоступная строка HotelName
{
set
{
txtHotelName.Text = value; // txtHotelName. Текст = значение;
}
}
public int NumberRooms
{
get
{
return Convert.ToInt32 (txtNumberRooms.Text);
}
set
{
}
}
public decimal Rate // общедоступная десятичная Цена
{
get
{
return Convert.ToDecimal(txtRate.Text);
}
set
{
txtRate.Text = value.ToString(); // Текст = значение
}
}
2. Теперь, чтобы установить эти свойства перед вызовом диалогового окна, и для того, чтобы использовать эти свойства перед закрытием диалогового окна с помощью кнопки ОК., добавьте код в главную форму MainAdminForm.cs. Удалите или закомментируйте временно вставленный ранее код, который отображает строки "ОК." или "Cancel" ("Отмена") в поле Messages (Сообщения).
private void cmdChange_Click(object sender,
System.EventArgs e)
{
ChangeHotelDialog dig = new ChangeHotelDialog();
if (currHotel.HotelName != "")
{
dig.City = currHotel.City; // Город
dig.HotelName = currHotel.HotelName;
dig.NumberRooms = currHotel.NumberRooms; d
ig.Rate = currHotel.Rate;
}
else
{
MessageBox.Show("Please select a hotel",
// "Пожалуйста, выберите гостиницу ",
"Hotel Broker Administration",
MessageBoxButtons.OK,
MessageBoxIcon.Exclamation // Восклицательный знак
) ;
return;
}
DialogResult status = dig.ShowDialog(); // состояние
if (status == DialogResult.OK)
// если (состояние == DialogResult. OK)
{
string comment = hotelBroker.ChangeRooms( // строковый
// комментарий
currHotel.City, // Город
currHotel.HotelName,
dig.NumberRooms,
dig.Rate);
if (comment == "OK")
// если (комментарий == "OK")
{
ShowHotelList(hotelBroker.GetHotels(;) ;
txtMessages.Text = "Hotel " + currHotel.HotelName
// txtMessages. Текст = "Гостиница"
// + currHotel. HotelName
+ " has been changed";
// + " была изменена";
}
else
txtMessages.Text = comment; // Текст = комментарий
}
}
Структура currHotel содержит поля гостиницы, выбранной в данный момент в качестве элемента списка. В следующем разделе вы научитесь извлекать информацию из элемента списка и заполнять элемент списка.
3. Скомпонуйте и испытайте программу. Все функции диалога должны работать корректно. Полученный проект соответствует проекту HotelAdmin\Step3.
CompEbook.ru Железо, дизайн, обучение и другие
Демонстрация Windows Forms (Формы Windows)
Лучший способ научиться создавать приложения Windows с помощью Visual Studio.NET — самостоятельно с самого начала создать небольшое приложение на С#. Для примера мы создадим Windows-приложение, которое позволит вносить деньги на счет и снимать деньги со счета в банке.1. Создайте на СП новый проект Windows Application (Windows-приложение), как на рис. 6.11, и назовите его BankGui.

Рис. 6.11. Создание проекта Windows Application (Windows-приложение)
2. Раскройте панель инструментов Toolbox, перетянув указатель мыши на вертикальную вкладку Toolbox в левой части главного окна Visual Studio. Если вкладки нет, инструментальную панель Toolbox можно открыть из меню ViewOToolbox (Вид1^ Панель инструментов). Чтобы панель инструментов Toolbox оставалась открытой, щелкните на "канцелярской кнопке", которая находится в заголовке панели инструментов Toolbox рядом с X. Если курсор мыши навести на "канцелярскую кнопку", появится подсказка с надписью "Auto Hide" (Автоматическое свертывание).
3. Перетащите из панели инструментов Toolbox две надписи (Label), два поля (TextBox) и две кнопки (Button) на форму (рис. 6.12).

Рис. 6.12. Перетаскивание управляющих элементов с панели инструментов Toolbox() на форму
4. В конструкторе форм (Forms Designer) щелкните на надписи labell. Тем самым вы выделите этот управляющий элемент в окне Properties (Свойства), которое находится под Solution Explorer (Поиск решения). Окно Properties (Свойства) позволяет изменять свойства управляющих элементов. В поле свойства Text (Текст) объекта labell введите Amount (Сумма). После того, как вы ввели значение, нажмите возврат каретки. Вы увидите, что текст появится на форме. На рис. 6.13 показано окно Properties (Свойства) после изменения свойства Text (Текст) первой надписи.

Рис. 6.13. Изменение значений свойств в окне Properties (Свойства)
5. Точно так же измените текст надписи 1аЬе12 на Balance (Баланс).
6. Введите значения свойств полей и кнопок в соответствии с табл. 6.1.
7. С помощью маркеров размера, которые находятся посредине каждой стороны формы, измените ее размер. При желании, перетащите управляющие элементы на выбранные места, и измените их размер. Если внешний вид формы вас удовлетворяет, сохраните изменения, сделанные в проекте. Ваша форма должна выглядеть примерно так, как на рис. 6.14.
8. Добавьте обработчики событий кнопок, дважды щелкнув на каждой кнопке.
9. Добавьте необходимый код к коду, сгенерированному мастером:
Таблица 6.1. Значения свойств полей (Textbox) и кнопок (Button)

Рис. 6.14 Форма приложения BankGui
public class Forml : System.Windows.Forms.Form
// общедоступный класс Forml:Система.Windows.Формы.Форма
{
public Forml()
{
//
// Требуется для поддержки Windows Form Designer
//
InitializeComponent();
//
// TODO: Добавьте любой код конструктора после
// вызова InitializeComponent
//
txtAmount.Text = "25";
// Текст txtBalance.Text = "100";
// Текст
}
///
/// The main entry point for the application.
/// Основная точка входа для приложения.
///
[STAThread]
static void Main()
{
Application.Run(new Forml{));
}
private void cmdDeposit_Click(object sender,
System.EventArgs e)
{
int amount = Convert.Tolnt32(txtAmount.Text);
int balance = Convert.Tolnt32(txtBalance.Text); // баланс
balance += amount;
// баланс + = количество;
txtBalance.Text = Convert.ToString(balance); // Текст
}
private void cmdWithdraw_Click(object sender,
System.EventArgs e)
{
int amount = Convert.Tolnt32(txtAmount.Text);
int balance = Convert.Tolnt32(txtBalance.Text); // баланс
balance = amount;
txtBalance.Text = Convert.ToString(balance); // Текст
}
10. Откомпилируйте и выполните приложение. Оно должно вести себя как стандартное приложение Windows. Вы должны без проблем вносить деньги на счет и снимать деньги со счета. На рис. 6.15 показано выполняющееся приложение BankGui.

Рис. 6.15. Windows-приложение BankGui
В данный момент проект реализован на С#. И хотя этого, как правило, не делают, мы перенесем этот проект в C++ для того, чтобы показать, как это нужно делать. Сначала с помощью шаблона Managed C++ Empty Project (Пустой проект на управляемом C++) создадим новый проект C++, который назовем BankGuiPort.
Теперь создадим исходный файл Forml.cpp в проекте BankGuiPort и перенесем (с помощью команд сору (копировать) и paste (вставить)) код С# из исходного файла Forml. cs проекта BankGui.
Перенесите все строки кода из файла Forml.cs проекта BankGui в файл Forml.cpp проекта BankGuiPort. При таком переносе кода могут возникнуть проблемы и непредвиденные ситуации. Эти проблемы не будут рассмотрены в нашей книге и вам придется ознакомиться с ними самостоятельно, если вы и в дальнейшем захотите выполнять подобный перенос кода. Итак, откройте оба проекта — BankGui на С# и BankGuiPort на C++ — в двух копиях Visual Studio.NET и визуально сравните исходные файлы Forml.cs и Forml.cpp, чтобы получить представление о подробностях переноса кода.
//Form1.cpp
#using
#using
#using
#using
using namespace System;
// использование пространства имен Система;
namespace BankGui
// пространство имен BankGui
{
_gc class Forml : public System::Windows::Forms::Form
// класс сборщика мусора Forml: общедоступная Система::
// Windows:: Формы:: Форма
{
private: // частный
System::Windows: Forms::Label *label1;
System::Windows: Forms::Label *labe!2;
System::Windows: Forms::TextBox *txtAmount;
System::Windows: Forms::TextBox *txtBalance;
System::Windows: Forms::Button *cmdDeposit; // Кнопка
System::Windows: Forms::Button *cmdWithdraw; // Кнопка
System::ComponentModel::Container *components; // Контейнер
public:
Form1 ()
{
components =0; // компоненты
InitializeComponent();
txtAmount->Text = "25"; // Текст
txtBalance->Text = "100"; // Текст
}
private: // частный
void InitializeComponent()
{
cmdWithdraw = new System: :Windows::Forms::Button;
// Кнопка
cmdDeposit = new System::Windows::Forms::Button;
// Кнопка
txtBalance = new System::Windows::Forms::TextBox;
txtAmount = new System::Windows::Forms::TextBox;
labell = new System::Windows::Forms::Label; // Надпись
Iabel2 = new System: : Windows :: Forms :-.Label; // Надпись
SuspendLayout();
//
// cmdWithdraw
//
cmdWithdraw->Location = // Местоположение
* _nogc new System::Drawing::Point(152, 144);
// Точка
cmdWithdraw->Name = "cmdWithdraw"; // Имя
cmdWithdraw->TabIndex = 2;
cmdWithdraw->Text = "Withdraw"; // Текст = "Снять"
cmdWithdraw->Click += // Щелчок
new System::EventHandler(this, cmdWithdraw_Click);
//
// Form1
//
AutoScaleBaseSize =
* _nogc new System::Drawing::Size(5, 13);
// Размер
ClientSize =
* _nogc new System::Drawing::Size(280, 189);
// Размер
System: :Windows::Forms::Control* pltems[] = {
cmdDeposit,
txtAmount,
label1,
label2,
txtBalance,
cmdWithdraw};
Controls->AddRange(pltems);
"Name = "Forml"; // Имя
Text = "Forml"; // Текст
Load += new System::EventHandler(this, Forml_Load);
ResumeLayout(false); // ложь } void Forml_Load(
Object *sender, System::EventArgs *e)
{
}
void cmdWithdraw_Click(
Object *sender, System::EventArgs *e)
{
int amount = Convert::ToInt32(txtAmount->Text);
// преобразование текста
int balance = Convert::ToInt32(txtBalance->Text);
// преобразование текста
balance -= amount;
// -количество
txtBalance->Text = Convert::ToString(balance);
// преобразование в текст }
public:
[STAThread] static void Main()
{
System::Windows::Forms::Application::Run(new Forml);
// Приложение:: Выполнить (новая Forml);
}
};
}
CompEbook.ru Железо, дизайн, обучение и другие
Диалоговые окна
Использование диалоговых окон облегчает процесс взаимодействия пользователя с приложением Windows Диалоговое окно — это набор управляющих элементов, с помощью которых упрощается процесс ввода данных В предыдущем примере было описано, как создать простое диалоговое окно, которое позволяло бы пользователю на вопрос дать ответ "Да" или "Нет" Для создания таких диалоговых окон используется класс MessageBox (Окно сообщения) Более сложные диалоговые окна создаются на основе форм.Мы проиллюстрируем создание диалоговых окон на примере графического пользовательского интерфейса для бюро путешествий Acme (Acme Travel Agency) Код примера, как обычно, находится в папке CaseStuay для этой главы Давайте рассмотрим простой диалог, с помощью которого можно добавить гостиницу в список гостиниц. Скомпонуйте и запустите пример В главной форме щелкните на кнопке Add... (Добавить ) После этого появится диалоговое окно New Hotel (Новая гостиница), которое показано на рис. 6.21.

Рис. 6.21. Диалоговое окно для добавления новой гостиницы
Теперь пользователь может вводить данные. Щелчок на кнопке ОК. приведет к тому, что информация будет запомнена Щелчок на кнопке Cancel (Отмена) приведет к тому, что данные будут проигнорированы. Это диалоговое окно, так же как и окно сообщения, является модальным Если модальное диалоговое окно открыто, то пользователь может взаимодействовать с приложением только через это окно, — ни с какими иными средствами приложения он взаимодействовать не может. Если вы попытаетесь сделать что-нибудь на главной форме, когда открыто диалоговое окно New Hotel (Новая гостиница), то услышите гудок. Существуют также и немодальные диалоговые окна, которые позволяют взаимодействовать с иными средствами приложения даже тогда, когда немодальное диалоговое окно открыто.
Если рассматривать диалоговое окно как форму, то обычно у него есть свои специфические характеристики. Как правило, у диалоговых окон нет системного меню, нет кнопок свертывания Minimize (Свернуть) и развертывания Maximize (Развернуть), причем размер окна фиксирован Вы можете наблюдать эти свойства на примере диалога New Hotel (Новая гостиница).
Для того чтобы продолжить испытание программы, введите данные о новой гостинице и щелкните на кнопке ОК. Программа вернет вас в главную форму, где новая гостиница будет отображена в списке гостиниц (рис. 6 22). На главной форме реализованы также и другие управляющие элементы графического интерфейса пользователя, например, окно, в котором отображается список гостиниц, а также многострочное текстовое поле для отображения текста, который не помещается в одну строку.

Рис. 6.22. Главная форма для управления гостиницами
CompEbook.ru Железо, дизайн, обучение и другие
Добавление события
1. Скомпонуйте и выполните программы (стартовые системы) на С# и C++, находящиеся в папках VsForm\Stepl и VsFormPortXStepl, и убедитесь, что они работают одинаково Это полностью статические приложения, — они просто отображают строку приветствия в фиксированной позиции2. Откройте форму проекта VsForm\Stepl в окне конструктора (Design window) и щелкните на кнопке Events (События) в окне Properties (Свойства)
3. Найдите событие MouseDown (Кнопка мыши нажата), как на рис. 6.18.
4. В окне Properties (Свойства) дважды щелкните на событии MouseDown (Кнопка мыши нажата) Автоматически будет сгенерирован код, который зарегистрирует делегата для события и образует скелет метода, связанного с делегатом

Рис. 6.17. Окно кода (Code window) в проекте Windows Forms (Формы Windows)
private void InitializeComponent ()
{
this.MouseDown =
new System.WinForms.MouseEventHandler
(this.Forml_MouseDown);
}
protected void Forml_MouseDown (object sender,
System.WinForms.MouseEventArgs e)
{
}

Рис. 6.18. Добавление события с помощью кнопки Events (События)
CompEbook.ru Железо, дизайн, обучение и другие
Документация по диалогам .NET
Диалоговые окна очень хорошо описаны в документации по комплексу инструментальных средств разработки программ .NET Framework SDK Информацию по диалоговым окнам следует искать в подразделе "Dialog Boxes in Windows Forms" ("Диалоговые окна в Формах Windows") раздела "Introduction to Windows Forms" ("Введение в Формы Windows") Следует заметить, что во всех языках .NET принципы работы диалоговых окон одни и те же Это является серьезным отличием программирования на NET от традиционного программирования на Visual Basic и программирования с применением библиотеки базовых классов Microsoft (Microsoft Foundation Classes, MFC), где принципы создания диалоговых окон совершенно разные На рис. 6.23. показано, где искать документацию по диалоговым окнам
Рис. 6.23. Документация по диалоговым окнам в NET Framework
CompEbook.ru Железо, дизайн, обучение и другие
Документация по обработке событий
Документацию, касающуюся событий и их обработки, можно найти в справочнике по NET Framework ( NET Framework Reference) На рис 6 6 показаны предопредепенные события, связанные с классом Form (Форма)CompEbook.ru Железо, дизайн, обучение и другие
Иерархия Windows Forms (Формы Windows)
Windows Forms (Формы Windows) — это та часть каркаса .NET Framework, которая поддерживает создание приложений со стандартным графическим пользовательским интерфейсом (GUI) на платформе Windows. Среди классов Windows Forms (Формы Windows) есть обширный набор классов для создания сложных графических пользовательских интерфейсов. Эти классы можно использовать в приложениях, написанных на любом языке .NET
Рис. 6.1. Упрощенная схема иерархии классов Windows Forms (Формы Windows)
Как правило, ваше приложение будет содержать главное окно, которое реализовано с помощью некоторого класса MyForm, производного от класса Form (Форма). На рис 6.1 изображено, какое место ваш класс MyForm занимает в иерархии классов Windows Forms (Формы Windows).
CompEbook.ru Железо, дизайн, обучение и другие
Использование управляющего элемента Menu (Меню)
3. Откройте панель инструментов Toolbox, если она до сих пор еще не открыта (щелкните на ярлыке панели инструментов Toolbox в вертикальной линейке) и перетащите управляющий элемент MainMenu (Главное меню) на форму приложения.4. Для создания выпадающего меню File (Файл) с пунктом Exit (Выход), введите File (Файл) и Exit (Выход), как на рис. 6.19.

Рис. 6.19. Использование управляющего элемента Menu (Меню) для добавления в форму меню
5. В окне Properties (Свойства) измените названия этих пунктов меню на menuFile и menuExit.
6. Дважды щелкните на Exit (Выход), чтобы добавить код в обработчик события File => Exit (Файл => Выход).
7. Добавьте в обработчик код, закрывающий приложение.
protected void menuExit_Click (object sender,
System.EventArgs e)
{
Application.Exit(); // Приложение.Выход
}
8. Скомпонуйте и выполните приложение. Меню должно полностью работать. Полученный проект соответствует проекту, который находится в папке VsForm\Step3.
И снова, вместо того, чтобы переносить каждую строчку кода, созданного на С#, из файла Forml.cpp проекта VsForm\step3 в проект на C++, просто сделайте копию проекта VsFormPort\Step2, созданного ранее с помощью переноса кода. Потом перенесите те несколько строчек кода, которые связаны с новыми функциями меню, из VsForm\Step3.
// VSForm - Step3
_gc class Forml : public System::Windows::Forms::Form
{
private: // частный
float x, у; // с плавающей точкой
Brush *pStdBrush; // Кисть
System: -.Windows: : Forms : :MainMenu *mainMenul;
System::Windows::Forms::MenuItem *menuFile;
System::Windows::Forms::MenuItem *menuExit;
private: // частный
void InitializeComponent()
{
menuFile =
new System: -.Windows: : Forms: :MenuItem() ; menuExit =
new System::Windows::Forms::MenuItem(); mainMenul =
new System::Windows::Forms::MainMenu() ;
//
// menuFile
//
menuFile->Index =0; // Индекс
System::Windows::Forms::MenuItem *pltems[] =
{menuExit};
menuFile->MenuItems->AddRange(pltems) ;
menuFile->Text = "File"; // menuFile-> Текст = "Файл";
//
// menuExit
//
menuExit->Index = 0;
// Индекс menuExit->Text = "Exit";
// menuExit-> Текст = "Выход";
menuExit->Click += // Щелчок
new System::EventHandler
(this, menuExit_Click);
//
// mainMenul
// System::Windows::Forms::MenuItem *pMenus[] =
{menuFile};
mainMenul->Menu!tems->AddRange(pMenus);
//
// Forml
// AutoScaleBaseSize =
* _nogc new System::Drawing::Size(5, 13);
// Размер ClientsTze =
* _nogc new System::Drawing::Size(248, 181);
// Размер Menu = mainMenul;
}
private: // частный
void menuExit_Click(
Object *sender, System::EventArgs *pe)
{
Application::Exit(); // Приложение:: Выход
}
};
CompEbook.ru Железо, дизайн, обучение и другие
Код меню
//SimpleForm.срр - Шаг 4_gc class Forml : public Form
// класс сборщика мусора Forml: общедоступная Форма
{
private: // частный
void InitializeComponent()
{
pMainMenul = new MainMenu ();
pMenuFile = new Menultem ();
pMenuExit = new Menultem ();
// mainMenul
Menultem* pMainMenulItems[] = {pMenuFile};
pMainMenul->get_MenuItems()
->AddRange(pMainMenulItems); // Меню File
pMenuFile->set_Index(0);
Menultem* pMainFileltems[] = {pMenuExit};
pMenuFile->get_MenuItems()
->AddRange(pMainFileltems);
pMenuFile->set_Text("File"); // Файл
// Меню Exit
pMenuExit->set_Index{0);
pMenuExit->set_Text("Exit"); // Выход
pMenuExit->Click += new System::EventHandler // Щелчок (this, MenuExit_Click);
Menu = pMainMenul; // Меню
MouseDown += new MouseEventHandler
(this, Forml_MouseDown);
KeyPress += new KeyPressEventHandler
(this, Forml_KeyPress); }
float x, у; // с плавающей точкой
Brush *pStdBrush; // Кисть
StringBuilder *pStr;
Menultem *pMenuExit;
Menultem *pMenuFile;
MainMenu *pMainMenul;
public:
private: // частный
void MenuExit_Cliok(
Object *pSender, EventArgs *pea)
{
Application::Exit(); // Приложение:: Выход
}
В методе initializeComponent создается иерархическая структура меню, представленная экземпляром класса MainMenu (Главное меню). Меню состоит из объектов Menultem, каждый из которых является отдельной командой меню Каждый объект Menultem является командой приложения или командой родительского меню для других пунктов подменю В нашем приложении мы связываем объект MainMenu (Главное меню) с объектом Form (Форма), присваивая свойству Menu (Меню) объекта Form (Форма) значение MainMenu (Главное меню)
Когда в этой главе мы позже обсудим конструктор форм (Forms Designer), вы увидите, что меню можно создать и так нужно просто перетянуть элемент управления MainMenu (Главное меню) с панели инструментов на форму. Конструктор форм (Forms Designer) позаботится о генерации нужного шаблонного кода.
CompEbook.ru Железо, дизайн, обучение и другие
Код обработчика события
1. Чтобы установить координаты строки приветствия, добавьте код в обработчик события мыши (нажатие кнопки мыши) Не забудьте после этого вызвать метод Invalidate (Считать недействительным)protected void Forml_MouseDown (object sender,
System WinForms.MouseEventArgs e)
{
x = e X;
у = e Y;
Invalidate() ;
}
2. Скомпонуйте и выполните проект Теперь по щелчку мыши (любой кнопкой) приветствие должно перемещаться Проект сейчас находится на шаге 2 разработки и соответствует проекту, хранящемуся в папке VsForm\Step2
Вместо того, чтобы переносить каждую строчку кода, созданного на СП, в файл Forml cpp проекта VsForm\Step2, просто сделайте копию проекта VsFormPortXStepl, который уже получен с помощью переноса кода Потом перенесите несколько строчек кода, связанных с событием MouseDown (Кнопка мыши нажата) из VsForm\Step2
void InitializeComponent()
{
MouseDown +=
new System::Windows::Forms:-MouseEventHandler
(this, Forml_MouseDown);
}
void Forml_MouseDown (Object *sender,
System::Windows::Forms::MouseEventArgs *e)
{
x = (float)e->X; // с плавающей точкой
у = (float)e->Y; // с плавающей точкой
Invalidate();
}
CompEbook.ru Железо, дизайн, обучение и другие
Код события Menu (Меню)
Как и в случае других событий Windows Forms (Формы Windows), с событием связывается его делегат Щелчок на пункте меню приводит к выполнению соответствующей командыvoid InitializeComponent()
{
pMenuExit->Click += new System::EventHandler // Щелчок
(this, MenuExit_Click);
}
void MenuExit_Click(
Object *pSender, EventArgs *pea)
{
Application::Exit(); // Приложение::Выход
}
CompEbook.ru Железо, дизайн, обучение и другие
Меню
Все пользователи Windows-приложений хорошо знакомы с меню, которые представляют собой простой механизм выбора команд. В языках .NET меню реализуется в самой программе. Иными словами, для меню файл ресурсов не нужен.CompEbook.ru Железо, дизайн, обучение и другие
Начальная загрузка списка элементов
При запуске программы HotelAdmin в процессе инициализации конструктор формы MainAdminForm осуществляет начальную загрузку списка элементов listHotels, — в него загружается список гостиниц.public MainAdminForm()
{
//
// Требуется для поддержки конструктора форм Windows
// (Windows Form Designer)
//
InitializeComponent();
//
// TODO: Добавьте любой код конструктора после
// вызова InitializeComponent
//
hotelBroker = new HotelBroker();
ShowHotelList(hotelBroker.GetHotels()
};
}
Метод ShowHotelList отображает в списке элементов список массивов, в которых хранится информация о гостиницах. Чтобы получить список массивов, вызывается метод HotelBroker . GetHotels. Ниже приведен метод ShowHotelList.
private void ShowHotelList(ArrayList array) // массив
{
listHotels.Iterns.Clear();
if (array == null)
// если (массив == пустой указатель)
{
return; }
foreach(HotelListltem hotel in array) // гостиница в массиве
{
string city = hotel.City.Trim();
// строковый город = гостиница.Город.Вырезка();
string name = hotel.HotelName.Trim();
// строковое название = гостиница.HotelName.Вырезка();
string rooms = hotel.NumberRooms.ToString(); // гостиница
string rate = hotel.Rate.ToString();
string str = city + "," + name + ","
// строка str = город +,"" + название +,""
+ rooms + "," + rate;
listHotels.Items.Add(str); // Добавить элементы
}
}
Управляющий элемент ListBox (Список элементов) содержит свойство Items (Элементы), которое поддерживает коллекцию объектных ссылок. Сначала мы вызываем метод Items.Clear (Элементы.Очистить), чтобы очистить список элементов от тех элементов, которые отображаются в нем в момент вызова метода. Потом мы с помощью цикла перебираем гостиницы в списке массивов и создаем строку, которая состоит из полей структуры гостиницы, разделенных запятыми. Чтобы добавить эту строку в список элементов, вызывается метод Items .Add (Элементы.Добавить).
CompEbook.ru Железо, дизайн, обучение и другие
Обработка событий в Windows Forms (Формы Windows)
Графический пользовательский интерфейс (GUI) управляется событиями приложение выполняет действия в ответ на события, вызванные пользователем, например, на щелчок кнопкой мыши или выбор пункта меню Каждая форма или элемент управления имеет заранее определенный набор событий Например, у каждой формы есть код, обрабатывающий событие MouseDown (Кнопка мыши нажата)В Windows Forms (Формы Windows) применяется модель обработки событий NET, в которой делегаты используются для того, чтобы связать события с обрабатывающими их методами В классах Windows Forms (Формы Windows) используются групповые делегаты Групповой делегат содержит список связанных с ним методов Когда в приложении происходит событие, управляющий элемент возбуждает событие, вызвав делегат для этого события Потом делегат вызывает связанные с ним методы
Для того чтобы добанить де iciar к событию, в C++ используется перегруженный оператор += Мы добавляем метод Forml_MouseDowr к событию MouseDown (Кнопка мыши нажата)
MouseDown += new MouseEventHandler
(this, Forml_MoaseDown),
Вскоре мы увидим этот код в программе
CompEbook.ru Железо, дизайн, обучение и другие
Окно конструктора (Design window) и окно кода (Code window)
Для работы с проектами Windows Forms (Формы Windows) в Visual Studio очень важно научиться переключаться между окном конструктора (Design window), где вы работаете с управляющими этементами на форме, и окном кода (Code window), где вы работаете с кодом программы Мы можем показать это на примере двух окон проекта VsForm на С#, код стартовой системы этого проекта находится в папке VsForm\Stepl главной папки данной главы Версия этого проекта, перенесенная из С# на C++, находится в папке VsFormPortXStepl Это первая версия проекта стартовой системы, которая отображает одну и ту же строку приветствия Проекты, отвечающие разным стадиям разработки, последовательно пронумерованы, и каждой версии проекта на С# (они содержатся в папке VsForm), созданной с помощью конструктора форм (Forms Designer), соответствует перенесенная версия проекта на C++, которая содержится в папке VsFormPortЕсли дважды щелкнуть на файле Forml cs проекта VsForml\Stepl в окне Solution Explorer (Поиск решения), то файл будет открыт в окне конструктора (Design window), как на рис 6 16
Для того чтобы появилось окно кода (Code window), щелкните на кнопке View Code (Просмотреть код), находящейся на инструментальной панепи Таким образом вы откроете исходный код, и вверху главной области окна вы увидите горизонтально расположенные ярлыки, с помощью которых можно выбирать нужные окна В данный момент для этой формы открыты оба окна, — и окно конструктора (Design window), и окно кода (Code window) Можно без труда вернуться в окно конструктора (Design window), щелкнув на кнопке View Designer (Открыть окно Design), находящейся на инструментальной панели На рис 6 17 вы можете увидеть внешний вид окна кода (Code window)

Рис. 6.16. Окно конструктора (Design window) в проекте Windows Forms (Формы Windows)
CompEbook.ru Железо, дизайн, обучение и другие
Acme Travel Agency) был представлен
Пример бюро путешествий Acme ( Acme Travel Agency) был представлен в главе 4 "Объектно-ориентированное программирование на управляемом C++", где в качестве структур данных для хранения списков гостиниц, клиентов и резервирований мы использовали массивы. В главе 5 "Управляемый C++ в .NET Framework" мы внесли изменения в реализацию примера, и для хранения информации вместо массивов использовали коллекции. Мы также определили множество интерфейсов и передавали списки в качестве объектных ссылок ArrayList. В предыдущей главе был реализован интерфейс пользователя в виде командной строки. В папке CaseStudy этой главы реализован графический интерфейс пользователя, созданный с помощью Windows Forms (Формы Windows).Вы уже видели главное окно приложения (рис. 6.22), которое ничем не отличается от главного окна упрощенного варианта программы HotelAdmin, служившей для демонстрации использования диалоговых окон. Кнопка Add... (Добавить...) позволяет добавлять новую гостиницу (рис. 6.21), а кнопка Change... (Изменить...) (рис. 6.24) позволяет изменять число номеров в гостинице и их цену. Кнопка Delete (Удалить) удаляет выбранную в настоящее время гостиницу.
По щелчку на кнопке Customers (Клиенты ) появляется форма Customer Management (Управление клиентами), на которой отображается список клиентов, зарегистрированных на текущий момент Клиента можно выбрать, щелкнув на его имени в списке На рис. 6.25 показана форма после того, как клиент был выбран.
В текстовом окне Id отображается идентификатор клиента Можно отменить регистрацию этого клиента, щелкнув на кнопке Unregister (Отменить регистрацию) Изменить адрес электронной почты клиента можно, щелкнув на кнопке Change Email (Изменить электронную почту (email)), — по щелчку появится диалоговое окно Щелкнув на кнопке One Customer (Этот клиент) можно сделать так, чтобы была отображена информация только об одном этом клиенте Если после этого щелкнуть на кнопке All Customers (Все клиенты), то в списке снова будут отображены все клиенты С помощью кнопки Register (Зарегистрировать) можно добавить нового клиента

Рис. 6.25. Форма управления клиентами Customer Management (Управление клиентами)
Третья основная форма нашего интерфейса пользователя — это форма HotelReserva-tions (Бронирование мест в гостинице), которая вызывается по щелчку на кнопке Reservations . (Резервирование ) главной управляющей формы Для того чтобы сделать резервирование, заполните поля Customer Id (Идентификатор клиента), Checkin Date (Дата прибытия), Number of Days (Количество дней) Поля City (Город) и HotelName (Название гостиницы) можно заполнить автоматически (при выборе гостиницы в списке элементов) И чтобы резервирование было выполнено, просто щелкните на кнопке Make Reservation (Сделать резервирование) Для того чтобы вывести список резервирований клиента по его идентификационному коду (Customer Id), необходимо щелкнуть на кнопке Show Reservations (Отобразить список резервирований) На рис. 6.26. показана эта форма после того, как клиент, идентификатор которого равен 1, сделал резервирование, и оно было отображено
Список резервировании можно очистить, щелкнув на кнопке Clear Reservations (Очистить список резервировании) Кнопка Cancel Reservation (Отмена резервирования) служит для того, чтобы отменить резервирование для клиента с определенным идентификационным кодом, который можно ввести вручную или выбрать в списке элементов Reservations (Резервирования)

Рис. 6.26. Форма Hotel Reservation для бронирования мест в гостинице
Пример бюро путешествии Acme (Acme Travel Agency) рассматривается также в последующих главах, и стоит поэкспериментировать с этим приложением уже сейчас Графический пользовательский интерфейс значительно удобнее, чем пользовательский интерфейс командной строки С другой стороны, в интерфейсе командной строки реализован простой глобальный блок try для цикла по всем командам, облегчающий обнаружение всех искчючений Но этот подход не годится для графических пользовательских интерфейсов В промышленных приложениях необходимо делать проверку исключений везде, где они могут произойти Наш пример упрощен в учебных целях, и мы не пытались тщательно обрабатывать все исключительные ситуации (исключения) Кроме того, мы допустили еще одно упрощение мы не проверяем, является ли идентификатор клиента (Customer ID), который используется при резервировании, кодом реапьно существующего, зарегистрированного клиента Эта проверка реализована в главе 9 "Программирование в ADO NET', где пример реализуется на основе базы данных
CompEbook.ru Железо, дизайн, обучение и другие
В этой главе мы рассмотрели
В этой главе мы рассмотрели создание графических пользовательских интерфейсов с помощью классов Windows Forms (Формы Windows) из .NET Framework. Сперва мы рассмотрели основные принципы, и научились создавать с помощью .NET Framework простые Windows-приложения с самого начала, без использования каких-либо специальных средств. Рисование выполняется в переопределенном методе OnPaint с помощью шрифта и кисти. Для управления взаимодействием пользователя с программой используется механизм обработки событий .NET. Он применяется, например, для обработки событий мыши и клавиатуры. Управляющие элементы намного упрощают программирование Windows-приложений. Управляющий элемент меню упрощает процесс добавления меню к Windows-программе. Visual Studio.NET также намного упрощает процесс программирования Windows-приложений. К сожалению, в C++ не поддерживается конструктор форм (Forms Designer). Зато с помощью конструктора форм (Forms Designer) можно очень быстро создать проект на С#, который, при желании, можно перенести в проект на C++. Конструктор форм (Forms Designer) позволяет перетаскивать управляющие элементы с инструментальной панели (Toolbox) на форму и потом в режиме Design (Конструктор) определять свойства этих управляющих элементов. Можно также без труда добавить в приложение обработчики событий. Диалоговое окно — это особый вид формы, с помощью свойств которого можно реализовать обмен информацией между родительской формой и диалоговым окном. Управляющий элемент ListBox (Список элементов) упрощает процесс отображения списков. В заключение мы разработали графический пользовательский интерфейс для программы Acme Travel Agency (бюро путешествий Acme).CompEbook.ru Железо, дизайн, обучение и другие
Создание простой формы
Приложение SimpleForm (Простая форма) — скелет стандартного приложения Windows. Вот код приложения SimpleForm (Простая форма), созданный на шаге 0://SimpleForm.срр - Шаг 0
// Эта версия отображает простую форму (simple form)
fusing
fusing
fusing
fusing
using namespace System;
// использование пространства имен Система;
using namespace System::Windows::Forms;
// использование пространства имен Система::Windows::Формы;
_gc class Forml : public Form
// класс сборщика мусора Forml: общедоступная Форма
{
public:
Forml()
{
Size = // Размер
*_nogc new System::Drawing::Size(300,200); // Размер
Text = "Simple Form - Step 0";
// Текст = "Простая Форма - Шаг О";
}
static void Main()
{
Application::Run(new Forml);
// Приложение:: Выполнить (новая Forml);
}
};
int _stdcall WinMain(
long hlnstance, // дескриптор текущего экземпляра
long hPrevInstance, // дескриптор предыдущего экземпляра
long IpCmdLine, // командная строка
int nCmdShow // состояние отображения )
{
Forml::Main();
return 0;
}
Класс Forml является производным от System:: Windows:: Forms:: Form (Cистема::Windows::Формы::Форма). В классе System::Windows::Forms::Application (Система::Windows::Формы::Приложение) есть статические методы для управления приложением, например Run (Выполнить) и Exit (Выход). Метод WinMain создает новую форму и запускает ее в качестве главного окна приложения.
Обратите внимание, что в примерах этой главы, написанных на C++, вместо имени функции main (главная) в качестве точки входа используется WinMain. В принципе можно в функции main (главная) в рамках консольного приложения реализовать все возможности графического интерфейса пользователя. Но при этом подходе придется создать бездействующее консольное окно, что в приложениях с графическим пользовательским интерфейсом совсем ни к чему. Если же использовать WinMain вместо main (главная), то в программе не создаются консольные окна, а сразу создается главное окно.
Конструктор формы инициализирует форму. Значение в поле Size (Размер) определяет размер формы в пикселях. Поле Text (Текст) определяет заголовок, который отображается в области заголовка окна новой формы.
Ключевым классом Windows Forms (Формы Windows) является базовый класс Form (Форма). Этот класс содержит обширный набор функций, которые наследуются разрабатываемыми нами классами форм, производными от класса Form (Форма).
Чтобы создать приложение, нужно выполнить из командной строки командный файл build.bat. А чтобы запустить командный файл, откройте окно DOS, перейдите в папку SimpleFormXStep(), и введите в командной строке build (компоновка). Помните, что . перед этим необходимо правильно установить значения переменных среды. Для этого достаточно выполнить Visual Studio.NET Command Prompt.
cl /CLR SimpleForm.cpp
По умолчанию будет откомпилирован исполняемый файл Windows. В исходном коде приложения находятся директивы fusing, в которых указаны используемые библиотеки .NET: System.dll,System.Drawing.dllи System.Windows.Forms.dll.
После того, как вы откомпилировали приложение с помощью командного файла, можете запустить его, введя в командной строке SimpleForm (Простая форма). Вы также можете запустить приложение в проводнике Windows, дважды щелкнув на файле SimpleForm.exe. На рис. 6.2 изображен внешний вид этого простого приложения. И хотя приложение SimpleForm (Простая форма) совсем примитивное, в нем заложено множество возможностей, унаследованных созданным нами классом, который является производным от класса Form (Форма). Окно приложения можно перетаскивать по экрану, изменять его размер, свертывать, развертывать, в нем можно открывать системное меню (щелкнув кнопкой мыши в верхнем левом углу окна) и т.д.
Сообщения о работе окна
В Visual Studio.NET есть инструментальное средство под названием Spy++ (Шпион++). Эта программа "шпионит" за окнами, чтобы иметь представление о том, что происходит внутри окна. Чтобы в Visual Studio запустить Spy++ (Шпион++), нужно воспользоваться меню Tools (Сервис). Запустите версию приложения SimpxeForm.exe, полученную на шаге 0, а затем запустите Spy++ (Шпион++). Выберите Spy (Шпион) Find Window (Найти окно) — появится диалоговое окно Find Window (Найти окно). В этом диалоговом окне установите переключатель Messages (Сообщения), как на рис. 6.3

Рис. 6.2. Скелет приложения \Vindowb Forms (Формы Windows) (Шаг 0)

Рис. 6.3. Инструмент Finder Tool (Средство поиска) служит для поиска окна — объекта шпионажа
Левой кнопкой мыши перетащите инструмент Finder Tool (Средство поиска) (в диалоговом окне Find window (Найти окно) этот инструмент отображается в виде специальной пиктограммы — перекрестия) на окно приложения SimpleForm (Простая форма), а потом щелкните на кнопке ОК. Теперь в окно программы-шпиона Spy++ будут выводиться сообщения, информирующие обо всех взаимодействиях с окном. Окно программы-шпиона Spy++ показано на рис. 6.4.
Чтобы обрабатывать события, приложения Windows должны иметь специальную структуру Операционная система Windows в ответ на действие пользователя, например щелчок кнопкой мыши, выбор меню или ввод символов с клавиатуры, посылает приложению сообщение Приложения Windows должны иметь такую структуру, которая позволяет реагировать на эти сообщения
Удобство создания Windows-программ с помощью классов NET Framework состоит в том, что программировать можно на очень высоком уровне абстракции На шаге 0 вы уже убедились, насколько просто создать приложение В последующих разделах мы будем добавлять в приложение новые основные свойства графических пользовательских интерфейсов, и таким образом проиллюстрируем основы создания графических пользовательских интерфейсов с помощью классов Windows Forms (Формы Windows)
CompEbook.ru Железо, дизайн, обучение и другие
Отображение текста на форме
В приложении, созданном на первом шаге, будет показано, как отобразить на форме текст На рис. 6.5. можно увидеть, как выглядит это приложение при выполнении
Рис. 6.4. Окно Messages (Сообщения) программы Spy++(Шпион++)

Рис. 6.5. Отображение текста на простой форме (Шаг 1)
Вывод данных в программах Windows сильно отличается от вывода данных в аналогичных консольных приложениях, где для этого используется метод Console: :WriteLine Вычерчивание результата в окне часто называют закрашиванием или закраской Закрашивание выполняется в ответ на сообщение "раскрасить", WM_PAINT Такой способ закрашивания по требованию гарантирует, что если окно будет накрыто каким-либо другим окном, а затем открыто снова, то содержимое окна будет отображено корректно
Еще одно отличие выполнения вывода в Windows-программах от выполнения вывода в консольных приложениях состоит в том, что необходимо определить все детали Например, нужно указать координаты области рисования, "кисть", которой будет выполняться рисование, шрифт текста, и так далее Вот код приложения, созданного на шаге 1
//SimpleForm.cpp - Шаг 1
// Эта версия отображает приветствие
fusing
#using
#using
#using
using namespace System;
// использование пространства имен Система;
using namespace System::Windows:.Forms;
// использование пространства имен Система.:Windows::Формы;
using namespace System::Drawing;
// использование пространства имен Система::Рисование;
_gc class Forml : public Form
// класс сборщика мусора Forml: общедоступная Форма
{
private: // частный
float x, у; // с плавающей точкой Brush *pSto
Size = // Размер
*_nogc new System::Drawing::Size(300,200);
// Размер
Text = "Simple Form - Step 1";
// Текст = "Простая Форма - Шаг 1";
x = у = 10; pStdBrush = new SolidBrush(Color::Black);
// Красить::
// Черным } protected: // защищенный
virtual void OnPaint(PaintEventArgs * ppea)
{
ppea->get_Graphics()->Graphics::DrawString // Графика
("Hello, Window Forms", Font,
// "Привет, Формы Window ", Шрифт,
pStdBrush, x, у);
}
public:
static void Main() {
Application.:Ran(new Forml);
// Приложение:: Выполнить (новая Форма),
}
};
int _stdcall WinMain(
long hlnstance, // дескриптор текущего экземпляра
long hPrevInstance, // дескриптор предыдущего экземпляра
long IpCmdLine, // командная строка
int nCmdShow // состояние отображения )
{
Forml::Main();
return 0;
}
Для того чтобы рисовать с помощью Windows Forms (Формы Windows), нужно переопределить виртуальный метод OnPaint Класс PaintEventArgs содержит объект Graphics в качестве свойства, доступного только для чтения Класс Graphics, который принадлежит пространству имен System: -Drawing (Система Рисунок), содержит методы рисования
Параметры метода Drawstring
В качестве стандартной кисти используется черная кисть SolidBrush
CompEbook.ru Железо, дизайн, обучение и другие
Обработка событий мыши
На шаге 2 мы внесем изменения в приложение, чтобы при щелчке любой кнопкой мыши строка с приветствием перемещалась на место щелчка На рис. 6.7 можно увидеть, что после щелчка кнопкой мыши строка действительно переместилась
Рис. 6.7. Перемещение текста по щелчку кнопкой мыши (Шаг 2)
//SimpleForm.cpp - Шаг 2
// SimpleForm.срр - 2
// Эта версия отображает приветствие, которое может быть перемещено
// щелчком кнопки мыши
fusing
fusing
fusing
fusing
using namespace System;
// использование пространства имен Система;
using namespace System::Windows. Forms;
// использование пространства имен Система::Windows::Формы;
using namespace System::Drawing;
// использование пространства имен Система:: Рисунок;
_go class Forml : public Form
// класс сборщика мусора Forml: общедоступная Форма
{
private: // частный
void InitializeComponent()
{
MouseDown += new MouseEventHandler // новый обработчик
// события
(this, Forml_MouseDown);
}
float x, у; // с плавающей точкой
Brush *pStdBrush; // Кисть public:
Forml()
{
InitializeComponent(); Size = // Размер
*_nogc new System::Drawing::Size(300,200); // Размер
Text = "Simple Form - Step 2"; // Текст = "Простая Форма - Шаг 2"; x = у = 10;
pStdBrush = new SolidBrush(Color::Black) ; // Красить::Черным
}
protected- // защищенный void Forml_MouseDown
(Object *pSender, MouseEventArgs *pmea)
{
x = pmea->X; у = pmea->Y;
Invalidate(); I
}
Во время инициализации программа связывает метод Forml_MouseDown с событием MouseDown (Кнопка мыши нажата) Этот метод устанавливает координаты текста, х и у, равными координатам точки, в которой находился указатель в момент щелчка Для того чтобы понять роль метода Invalidate (Считать недействительным), закомментируйте его и снова скомпонуйте код Щелкните мышью, чтобы перенести текст Что произойдет в результате9 Приветствие останется на том же месте Потом накройте окно приложения каким-либо другим окном, а потом снова откройте его Теперь вы увидите, что строка перемещена
Метод Invalidate (Считать недействительным) определен в базовом классе Control (Элемент управления) Существует несколько переопределенных версий этого метода Каждая из них заставляет считать недействительной определенную область управляющего элемента и посылает элементу управления сообщение о перерисовывании Метод, который не содержит параметров, заставляет считать недействительным весь управляющий элемент Для того чтобы максимально уменьшить объем перерисовывания, в более сложном приложении можно ограничиться тем, что недействительным будет считаться прямоугольник
CompEbook.ru Железо, дизайн, обучение и другие
М: Несколько обработчиков для события
На шаге 2М создания нашего приложения мы реализуем два разных обработчика события MouseDown (Кнопка мыши нажата). Второй обработчик по щелчку кнопкой мыши просто отображает окно сообщения.//SimpleForm.срр - Шаг 2М
// Эта версия имеет два обработчика событий для MouseDown
_gc class Forml : public Form
// класс сборщика мусора Forml: общедоступная Форма
{
private: // частный
void InitializeComponent()
{
MouseDown += new MouseEventHandler
(this, Forml__MouseDown) ;
MouseDown += new MouseEventHandler
(this, ShowClick);
}
void Forml_MouseDown
(Object *pSender, MouseEventArgs *pmea)
{
x = pmea->X;
у = pmea->Y;
Invalidate(); }
void ShowClick (Object *pSender, MouseEventArgs *pmea)
{
MessageBox::Show("Mouse clicked!!'");
// "Мышь щелкнула!!! }
CompEbook.ru Железо, дизайн, обучение и другие
События MouseDown (Кнопка мыши нажата) и Keypress (Нажатие клавиши)
На шаге 3 мы введем в наш пример обработку еще одного события, а именно, события KeyPress (Нажатие клавиши), а также покажем, как в событии MouseDown (Кнопка мыши нажата) различать, какая кнопка была нажата, левая или правая.Обработка событий, вызванных правой и левой кнопкой мыши
Для того чтобы определить, какая кнопка мыши была нажата, нужно использовать свойство Button (Кнопка) параметра MouseEventArgs. Правую кнопку мыши будем использовать для удаления строки с приветствием, которая хранится в элементе данных str класса StringBuilder.
void Forml_MouseDown
(Object *pSender, MouseEventArgs *pmea)
{
if (pmea->Button == МоиseButtons::Left) // если левая кнопка
{
x = pmea->X;
у = pmea->Y;
}
else if (pmea->Button == MouseButtons::Right) // если правая
// кнопка
{
pStr = new StringBuilder();
}
Invalidate() ;
}
Событие Keypress (Нажатие клавиши)
На шаге 3 мы научимся обрабатывать событие KeyPress (Нажатие клавиши). Каждый раз, когда пользователь нажмет клавишу, в конец строки приветствия будет добавлен соответствующий символ. Обратите внимание, что вместо класса String (Строка) используется класс StringBuilder, который более эффективен в этой ситуации. Объект String (Строка) — стационарный (неизменяемый), то есть, для того, чтобы реализовать добавление символов в конец строки, нужно постоянно удалять одни объекты String (Строка) и создавать другие.
StringBuilder *pStr;
void Forml_KeyPress
(Object *pSender, KeyPressEventArgs *pmea)
{
pStr—>Append(pmea->KeyChar) ; // Добавляем в конец
Invalidate() ;
}
Также, как и на шаге 2, необходимо вызвать метод Invalidate (Считать недействительным), для того, чтобы принудительно перерисовать окно приложения после сделанных изменений. На рис. 6.8 показано окно приложения SimpleForm (Простая форма), после удаления исходного текста и ввода нового.

Рис. 6.8. Испытываем события мыши и нажатия клавиши на клавиатуре (Шаг 3)
CompEbook.ru Железо, дизайн, обучение и другие
Меню для выхода из программы
На шаге 4 мы добавим в наше приложение Simple Form простое меню Для того чтобы выйти из программы, пользователь должен выбрать File => Exit (Файл => Выход), как на рис. 6.9.
Рис. 6.9. Шаг 4 Добавление в форму меню File => Exit (Файл => Выход)
CompEbook.ru Железо, дизайн, обучение и другие
Использование управляющего элемента TextBox (Поле)
На шаге 5 создания приложения SimpleForm (Простая форма) мы используем управляющий элемент TextBox (Поле) для отображения строки с приветствием. В более ранних версиях приложения строку можно было переместить щелчком левой кнопки мыши и удалить щелчком правой кнопки мыши. Можно было также ввести свою собственную строку с приветствием. Теперь, применив управляющий элемент TextBox (Поле), вы получите полноценные возможности редактирования. Управляющий элемент TextBox (Поле) позволяет в любом месте строки вставить символы, вырезать и вставить текст (с помощью комбинаций клавиш Ctrl+X и Ctrl+V соответственно) и так далее. Все возможности редактирования поддерживаются управляющим элементом TextBox (Поле). На рис. 6.10 изображено окно приложения после того, как текст приветствия был перемещен, и мы ввели некий собственный текст.Это новая версия программы. Обратите внимание на то, что она значительно проще предыдущей, хотя и имеет гораздо более богатые функциональные возможности. Нет больше необходимости использовать переменные экземпляра для координат и текста строки приветствия (теперь эта информация хранится в управляющем элементе pTxtGreeting типа TextBox (Поле)). Не нужен больше метод OnPaint, так как управляющий элемент TextBox (Поле) знает, как нарисовать себя. Можно также избавиться от кисти. Теперь не нужно обрабатывать событие KeyPress (Нажатие клавиши), потому что оно автоматически обрабатывается управляющим элементом TextBox (Поле), притом весьма аккуратно.

Рис. 6.10. Шаг 4: текст приветствия отображается с помощью управляющего элемента Text Box (Поле)
//SimpleForm.cpp - Шаг 5
_gc class Forml : public Form
// класс сборщика мусора Forml: общедоступная Форма
{
private: // частный
void InitializeComponent()
{
// текст приветствия (text greeting) pTxtGreeting = new TextBox; pTxtGreeting->Location = // Местоположение
* _nogc new Point(10, 10); // новая точка pTxtGreeting->Size = // Размер
(*_nogc new struct Size(150, 20)); // новый Размер pTxtGreeting->Text = "Hello, Windows Forms"; // Текст = "Привет, Формы Windows";
Controls->Add(pTxtGreeting); // Добавить
}
float x, у; // с плавающей точкой
Brush *pStdBrush; // Кисть
Menultem *pMenuExit;
Menultem *pMenuFile;
MainMenu *pMainMenul;
TextBox *pTxtGreeting;
protected: // защищенный void Forml_MouseDown
(Object *pSender, MouseEventArgs *pmea) {
if (pmea->Button == MouseButtons::Left)
// если кнопка левая
{
pTxtGreeting->Location = // Местоположение
*_nogc new Point(pmea->X, pmea->Y); // новая точка }
else if (pmea->Button == MouseButtons::Right) // если кнопка правая {
pTxtGreeting->Text = ""; // Текст } }
};
Управляющий элемент TextBox (Поле) удобен в использовании. В инициализирующей части программы мы создаем объект TextBox (Поле) и определяем значения его свойств Location (Местоположение), Size (Размер) и Text (Текст). Мы добавляем новый управляющий элемент к коллекции управляющих элементов Controls (Управляющие элементы) этой формы. В обработчике событий мыши мы перемещаем управляющий элемент, изменив значение свойства Location (Местоположение). С помощью свойства Text (Текст) управляющего элемента TextBox (Поле) можно удалить строку с приветствием.
CompEbook.ru Железо, дизайн, обучение и другие
Событие MouseDown (Кнопка мыши нажата)
Событие MouseDown (Кнопка мыши нажата) является одним из предопределенных событий класса Control (Элемент управления), от которого порожден класс Form (Форма)public: _event MouseEventHandler* MouseDown;
А вот и объявление обработчика этого события, MouseEventHandler
public _gc _delegate void MouseEventHandler(
Object* sender, // отправитель
MouseEventArgs* e
) ;

Рис. 6.6. Документация по событиям класса Form (Форма)
В качестве параметра обработчик событий получает объект класса Мои seEventArgs (производный от класса EventArgs) Свойства этого объекта доступны только для чтения и содержат информацию, связанную с данным событием
CompEbook.ru Железо, дизайн, обучение и другие
Создание графических пользовательских интерфейсов
Ксожалению, конструктор форм (Forms Designer) не поддерживается в C++ Тем не менее, вы можете использовать конструктор форм (Forms Designer) в С#, и потом перенести полученный с помощью С# код графического интерфейса пользователя в программу на C++ Для переноса графического интерфейса пользователя в программу на C++ необходимы дополнительные усилия, и, в большинстве случаев, такой перенос особой пользы не дает Как правило, C++ не используют для разработки пользовательских графических интерфейсов, вместо этого применяется подход смешения языков, в котором для создания почьзовательского интерфейса используется С#, а в других аспектах разработки проекта из разных соображений используется C++ Поскольку вам, скорее всего, придется часто создавать графический интерфейс пользователя для многих приложений, в этой паве мы преследуем две цели Во-первых, мы хотим научить вас совместно использовать код на С# и C++ Даже если вы в основном программируете на C++, стоит ознакомиться с С# и конструктором форм (Forms Designer), — у вас появится возможность использовать те мощные инструментальные программные средства, которые не поддерживаются в C++ Во-вторых, мы приведем в пример один из немногих случаев, когда перенос кода графического интерфейса пользователя из С# в программу на C++ является целесообразным В главе представлено несколько пар примеров кода графических пользовательских интерфейсов до и после переносаКлючевым средством взаимодействия пользователя с компьютером является графический пользовательский интерфейс (Graphical User Interface, GUI) Из этой главы вы узнаете, как создавать графический пользовательский интерфейс с помощью классов Windows Forms (Формы Windows), которые находятся в NET Framework На практике программирование Windows-приложений предполагает экстенсивное использование различных инструментальных средств и мастеров, которые намного упрощают этот процесс Однако все указанные средства автоматизации заслоняют то, что лежит в основе создания графического пользовательского интерфейса Поэтому сначала мы рассмотрим основы создания графических пользовательских интерфейсов Иными словами, мы научимся создавать простые приложения Windows с самого начала, пользуясь только комплексом инструментальных средств разработки программ NET Framework SDK Это значит, что вначале мы будем создавать простые приложения Windows без применения каких-либо специальных сервисных программ Будут рассмотрены основы рисования с помощью Windows Forms (Формы Windows) с применением шрифтов и кистей, а также необходимые обработчики событий Мы объясним принципы обработки событий в Windows Forms (Формы Windows) и реализуем обработчики событий мыши. С помощью Windows Forms (Формы Windows) мы также реализуем меню и соответствующие обработчики событий. Кроме того, мы рассмотрим управляющие элементы, а после этого изучим среду Visual Studio.NET, посредством которой можно без труда создать простой графический пользовательский интерфейс на С#. С помощью конструктора форм (Forms Designer) добавим в форму управляющие элементы, создадим меню, добавим обработчики событий и другие полезные функциональные возможности. При желании полученный в результате проект на С# можно потом перенести на C++. В заключение будут рассмотрены диалоговые окна и такой элемент управления, как список.
CompEbook.ru Железо, дизайн, обучение и другие
Создание простых форм с помощью комплекса инструментальных средств разработки программ .NET SDK
Для ознакомления с классами Windows Forms (Формы Windows) полезно будет создать простое приложение SimpleForm (Простая форма) в несколько шагов. Ни на одном из этих шагов мы не будем использовать средства проектирования Visual Studio. Используя интерфейс командной строки, необходимо запустить командный файл build. bat.CompEbook.ru Железо, дизайн, обучение и другие
Управляющие элементы
В программе, которую мы только что рассматривали, объект pMainMenul является управляющим элементом. Данный объект — указатель на экземпляр класса MainMenu (Главное меню). Управляющий элемент — это объект на форме, который придает форме новые функциональные возможности. Управляющий элемент автоматически выполняет много заданий "по поручению" формы. Использование управляющих элементов упрощает программирование, так как программисту не надо беспокоиться о рисовании, о том, следует ли считать представление формы недействительным, не нужно думать о графических элементах и так далее. Для того чтобы реализовать простое меню с самого начала, нам пришлось написать довольно много кода. Управляющие элементы, которые реализуются с помощью объектов, содержат богатый, вполне пригодный для повторного использования код.CompEbook.ru Железо, дизайн, обучение и другие
Управляющий элемент ListBox (Список элементов)
В .NET Framework поддерживается несколько управляющих элементов, с помощью которых можно отобразить списки элементов. Эти управляющие элементы позволяют пользователю выбрать элемент списка; обычно для выбора элемента требуется щелкнуть на нем. В этом разделе мы рассмотрим управляющий элемент ListBox (Список элементов).Пример программы находится в папке HotelAdmm\Step3. Главная форма в файле MainAdminForm. cs содержит список элементов listHotels, в котором хранится список гостиниц. Каждая гостиница представлена в виде строки, а значения атрибутов гостиницы разделены запятыми.
CompEbook.ru Железо, дизайн, обучение и другие
Visual Studio.NET и формы
И хотя вполне реально создать приложение Windows Forms (Формы Windows), используя в командной строке только комплекс инструментальных средств разработки программ .NET Framework SDK, на практике подобную работу намного проще выполнить с помощью Visual Studio.NET. К сожалению, в Visual Studio.NET нет средств для генерирования проекта пусковой системы на управляемом C++ на основе Form (Форма), и управляемый C++ не поддерживает конструктор форм (Forms Designer). Однако для начала можно создать проект Windows-приложения на С# (Windows Application). При этом будет сгенерирован код пусковой системы и будут установлены ссылки на необходимые библиотеки .NET. Затем можно в конструкторе форм (Forms Designer) перетащить управляющие элементы с инструментальной панели на вашу форму. Конструктор форм (Forms Designer) вставит необходимый шаблон кода на С#, который поддерживает функционирование этих управляющих элементов в форме. В окне Properties (Свойства) несложно определить свойства управляющего элемента в процессе проектирования. Можно, конечно, определить эти свойства и во время запуска приложения, как мы это сделали для поля pTxtGreeting в предыдущем примере. После этого можно перенести код С# в программу на управляемом C++, но этого обычно не рекомендуется делать.CompEbook.ru Железо, дизайн, обучение и другие
Выбор элемента в списке элементов ListBox
Чтобы в списке элементов выбрать какой-нибудь элемент, нужно щелкнуть на нем. Выбор элемента вызовет событие SelectedlndexChanged. Доступ к выбранному элементу можно получить с помощью свойств Selectedlndex и Selectedltem. Если никакой элемент не выбран, значение Selectedltem будет равно -1. Ниже приведен код обработчика события SelectedlndexChanged.private void listHotels_Selected!ndexChanged(object sender,
System.EventArgs e) {
if (listHotels.Selectedlndex != -1)
{
string selected - (string) listHotels.Selectedltem;
// выбранная строка
char [ ] sep = new char[] {','};
// символ
string[] fields;
// строка [] поля;
fields = selected.Split(sep);
// поля = выбранное, разбить;
currHotel = new HotelListltem();
currHotel.City = fields[0];
// Город = поля [О];
currHotel.HotelName = fields[1];
// поля [1]
currHotel.NumberRooms = Convert.Tolnt32(fields[2]);
// поля [2]
currHotel.Rate = Convert.ToDecimal(fields[3]);
// поля [3]
}
else
{
currHotel.HotelName = "";
}
}
Поскольку в списке элементов элементы хранятся в виде объектных ссылок, мы выполняем приведение типа выбранного элемента к типу string (Строка). Для того чтобы выделить значения полей, разделенные запятыми, используется метод String. Split (Строка.Разбиение), затем значения сохраняются в массиве строк fields (поля). Из массива эти значения переносятся в currHotel, где они и хранятся. В предыдущем разделе мы использовали currHotel для инициализации диалоговых окон New Hotel (Новая гостиница) и Change Hotel Information (Изменение информации о гостинице).
CompEbook.ru Железо, дизайн, обучение и другие
Закрытие формы (Выход из формы)
Давайте сделаем так, что каждый раз, когда пользователь попытается закрыть приложение, приложение будет его спрашивать, действительно ли он хочет выйти Существует несколько способов закрыть окно-Когда закрывается форма, вызывается событие Closing (Процедура завершения) Можно остановить процедуру завершения, установив в обработчике этого события свойство Cancel (Отмена). (Сначала нужно, как обычно, добавить обработчик события Closing (Процедура завершения) ) Просто напечатайте код MessageBox (Окно сообщения), приведенный ниже
protected void Forml_Closing (object sender.
System.ComponentModel.CancelEventArgs e)
{
DialogResult status = MessageBox.Show( // состояние
"Do you want to close",
// "Хотите закрыть?",
"Simple Form (VS)", MessageBoxButtons.YesNo);
if (status == DialogResult.No)
// если (состояние == DialogResult. Нет)
{
e.Cancel = true; // Отмена = истина
}
}
Для того чтобы получить нужное нам поведение приложения, обработчик меню FileOExit (ФайлОВыход) должен не выходить из приложения, а закрыть главное окно, вызвав метод С lose (Закрыть)
protected void menuExit_Click (object sender,
System.EventArgs e)
{
//Application.Exit();
// Приложение.Выход ();
Close () ;
}
Теперь проект соответствует проекту, находящемуся в папке VSForm\Step4 Запустите программу, и попытайтесь закрыть окно приложения разными способами В любом случае должно появиться диалоговое окно, показанное на рис. 6.20.

Рис. 6.20. Диалоговое окно, которое спрашивает пользователя, действительно ли он хочет закрыть приложение
И, наконец, как обычно, перенесите код проекта, подготовленного на С#, в проект на C++ VSFormPort\Step4. Скомпонуйте и выполните программу на C++. Убедитесь в том, что она работает так же, как и программа на С#
// VSForm - Step4
_gc class Forml : public System:-.Windows :: Forms :: Form
{
private: // частный
void InitializeComponent()
{
Closing += // Закрытие
new CancelEventHandler(this, Forml_Closing) ;
}
void menuExit_Click(
Object *sender, System::EventArgs *pe)
{
//Application::Exit();
// Приложение:: Выход ()
; Close();
}
void Form1_Closing(
Object *sender, CancelEventArgs *e)
{
int status = MessageBox::Show( // состояние
"Do you want to close",
// "Хотите закрыть",
"Simple Form (VS)", // Простая Форма
MessageBoxButtons::YesNo);
if (status == DialogResult::No)
// если (состояние == DialogResult::No)
{
e->Cancel = true; // Отмена = истина
}
}
};
Все оставшиеся примеры в этой главе написаны исключительно на С#!
Далее в этой главе мы будем рассматривать только программы на С#, поскольку даже в программах, которые в основном написаны на C++, при разработке графических пользовательских интерфейсов, как правило, используется С#. Причина этого проста— в C++ не поддерживается конструктор форм (Forms Designer). Всегда помните о том, что вы можете сначала создать графический пользовательский интерфейс на С#, а потом, если понадобится, перенести этот код в C++. В предыдущих примерах мы показали, как это сделать.
CompEbook.ru Железо, дизайн, обучение и другие
Architecture Net
CAB Project (Проект CAB)
Проект CAB (CAB project) создает САВ-файл, содержащий любое количество других файлов, которые могут использоваться для традиционных целей развертывания Файлы CAB использовались для упаковки старых компонентов, построенных на основе модели компонентных объектов Microsoft (COM), для развертывания их по Internet Кроме того, они использовались в традиционных инсталляционных программах, которые выполняли установку с компакт-дисков Теперь файлы CAB также могут использоваться для того, чтобы упаковать управляемый код, однако при развертывании в NET файл CAB может содержать только одну сборку, причем он должен иметь то же самое имя, что и содержащаяся в нем сборка, но с расширением cab Например, сборка SomeComponent.dll может содержаться в файле CAB с именем SomeComponent. cab
Рис. 7.12. Шаблоны установки и развертывания проектов (Setup and Deployment Projects)
CompEbook.ru Железо, дизайн, обучение и другие
Частное развертывание сборки
Частное развертывание сборки просто означает, что конечный пользователь копирует сборку в тот же самый каталог, что и клиентская программа, использующая ее. Не нужна никакая регистрация, и не требуется никакая причудливая инсталляционная программа. Кроме того, не требуется никакая очистка системного реестра, не нужна также и никакая программа деинсталляции для удаления компонента. Чтобы деинсталлировать сборку, просто удалите ее с жесткого диска.Конечно, ни один программист из самоуважения никогда не поставит коммерческий компонент, который конечный пользователь должен вручную копировать или удалять какие-либо файлы подобным способом, даже если сделать это очень просто. Пользователи привыкли использовать формальную инсталляционную программу, так что она должна поставляться, даже если ее работа тривиальна. Однако, ручное копирование и удаление сборки — идеальный способ быстрого и безболезненного управления развертыванием реализаций при разработке, тестировании, отладке и испытании. Как вы помните, развертывание компонентов, построенных на основе модели компонентных объектов Microsoft (COM), никогда не было настолько просто. Ведь для развертывания требовался как минимум файл сценария, чтобы занести в системный реестр информацию о компоненте к клиентским программам и о среде времени выполнения модели компонентных объектов Microsoft (СОМ). С появлением сборок не нужно при инсталляции конфигурировать системный реестр, а, значит, позже, когда вы захотите отказаться от компонента, не нужно заботиться о том, чтобы тщательно стереть информацию из системного реестра.
Далее мы рассмотрим, как выполнить частное развертывание сборки для простого NET-компонента. В следующем разделе будет показано, как развернуть общедоступную сборку в глобальном кэше сборок.
Скомпоновав компонент сборки, можно создать клиентскую программу, которая вызывает общедоступные методы компонента Следующий код показывает пример такой клиентской программы, вызывающей общедоступный метод AddEmUp, приведенный ранее. Конечно, в случае необходимости, путь к сборке SimpleComponent.dll в инструкции fusing должен быть откорректирован, чтобы компилятор смог найти нужные данные. Кроме того, сборку нужно развернуть, чтобы общеязыковая среда выполнения CLR могла определить местонахождение сборки и загрузить ее во время выполнения. В этом подразделе развертывание достигается простым копированием сборки в каталог клиентской программы.
//SimpleComponentClient.срр
finclude "stdafx.h"
fusing
using namespace System;
// использование пространства имен Система;
fusing fusing
// использование пространства имен SimpleComponent;
void main()
{
SomeClass *psc = new SomeClass;
int sum = psc->AddEmUp(3, 4); // суммировать
Console::WriteLine(sum); // сумма
}
Как найти SimpleComponent?
Обратите внимание, что программа SimpleComponentClient имеет инструкцию fusing, указывающую компилятору, где найти сборку SimpleComponent, метаданные которой компилятор использует для контроля соответствия типов. Однако, если вы попробуете выполнить клиентскую программу, то возникнет исключение System. IQ.FileNotFoundException. Так получится потому, что загрузчик класса общеязыковой среды выполнения CLR неспособен найти сборку SimpleComponent. Чтобы выполнить клиент, нужно только скопировать сборку SimpleComponent в тот же самый каталог, где находится программа SimpleComponentClient.ехе.
Теперь можно рассмотреть и сравнить декларацию сборки этой клиентской программы, чтобы увидеть, как она взаимодействует с декларацией сборки SimpleComponent, приведенной ранее. Чтобы рассмотреть декларацию клиентской программы, используйте команду
Ildasm SimpleComponentClient.exe
Можно заметить, что декларация клиентской программы содержит следующую внешнюю зависимость от сборки SimpleComponent.
.assembly extern SimpleComponent
{
.hash = (2A 1C 2D D7 CA 9E 7E D5 08 5B DO 75 23 D3 50 76
5E 28 EA 31 )
.ver 1:0:584:39032
}
Из этого следует, что клиентская программа ожидает использовать сборку SimpleComponent с номером версии 1:0:584:39032. Однако, поскольку сборка развернута частным образом, на самом деле, когда клиент загружает эту сборку, принятая по умолчанию в общеязыковой среде выполнения CLR политика проверки значения версии проверку номера не выполняет. Просто ответственность за то, что в данном конкретном каталоге развернута нужная версия, возлагается на администратора или инсталляционную программу. Эту заданную по умолчанию политику проверки версии можно отменить, используя файл конфигурации. Хотя в клиентской декларации имеется хэш-код компонента, он фактически не используется общеязыковой средой выполнения CLR для того, чтобы проверить подлинность двоичного кода. И снова это происходит потому, что сборка развернута частным образом. В следующем разделе мы увидим, что номер версии и хэш-код используются автоматически для проверки содержимого кода общедоступных сборок, развернутых в глобальном кэше сборок.
CompEbook.ru Железо, дизайн, обучение и другие
Цифровая подпись и развертывание общедоступной сборки
Чтобы развернуть сборку в глобальном кэше сборок, надо, чтобы она имела цифровую подпись. Разработчики могут разместить сборку в глобальном кэше сборок, используя утилиту Gacutil .exe (Global Assembly Cache utility), Проводник Windows (Windows Explorer) с расширением оболочки Windows посредством просмотра кэша сборок, или.Инструмент администрирования NET (.NET Admin Tool). Инсталляция общедоступных сборок на конкретной машине конечного пользователя должна быть сделана с помощью программы установки компонентов (системы) по выбору пользователя.
Процесс цифрового подписания сборки включает генерацию криптографической пары открытого и секретного ключа, вычисление хэш-кода сборки, шифровку хэш-кода с помощью секретного ключа, и запись в сборку зашифрованного хэш-кода вместе с открытым ключом.
Зашифрованный хэш-код и открытый ключ вместе составляют полную цифровую сигнатуру (подпись). Обратите внимание, что цифровая подпись (сигнатура) записана в зарезервированную область сборки, не участвующую в вычислении хэш-кода. Когда все это сделано, сборка может быть развернута в глобальном кэше сборок (GAC). Все указанные шаги выполняются тремя простыми инструментальными средствами: утилитой Strong Name (Strong Name utility — Sn. exe), компоновщиком сборок (Assembly Linker — Al.exe) и утилитой Global Assembly Cache (Global Assembly Cache utility — Gacutil.exe). Чтобы скомпоновать, подписать в цифровой форме и развертывать общедоступную сборку, необходимо выполнить следующие шаги:
1. Разработать и скомпоновать компонент.
2. Сгенерировать пару открытого и секретного ключей.
3. Вычислить хэш-код содержимого сборки.
4. Зашифровать хэш-код, используя секретный ключ.
5. Поместить зашифрованный хэш-код в декларацию.
6. Поместить открытый ключ в декларацию.
7. Поместить сборку в глобальный кэш сборок.
Конечно, шаг 1 обычно выполняется с помощью Visual Studio.NET. Шаги 2 — 6 представляют собой цифровое подписание. Шаг 2 выполняется с помощью утилиты Strong Name— Sn.exe. Шаги 3—6 выполняются с помощью Visual Studio.NET или компоновщика сборок — утилиты Assembly Linking — Al. exe ("1" — это "эль", а не "один"). Чтобы на шаге 7 поместить сборку в глобальный кэш сборок, применяется утилита Global Assembly Cache— Gacutil.exe, Проводник Windows (Windows Explorer), или Инструмент администрирования .NET (.NET Admin Tool).
Сейчас мы опишем первый шаг— создание компонента. Для целей демонстрации мы используем пример, подобный предыдущему примеру сборки SimpleComponent, но он называется SharedComponent, и будет развернут в глобальном кэше сборок. Сначала должен быть создан новый проект SharedComponent; для этого используется шаблон Managed C++ Class Library (Управляемый C++ на основе Библиотеки классов), причем нужно добавить следующий код:
//SharedComponent.срр
#include "stdafx.h" // имеет fusing
#include "SharedComponent.h"
//SharedComponent.h
using namespace System;
// использование пространства имен Система;
namespace SharedComponent
// пространство имен SharedComponent
{
public _gc class SomeClass
// класс сборщика мусора SomeClass
{
public:
int AddEmUpdnt i, int j)
{
return i+j;
}
};
}
На следующем шаге нужно сгенерировать криптографическую пару. Генерацию криптографической пары можно выполнить с помощью утилиты Sn.exe. Эта утилита известна как утилита Strong Name, но иногда также называется и утилитой Shared Name, однако, этот последний термин теперь осуждается. Данный инструмент генерирует криптографически стойкое имя сборки. Вы генерируете криптографическую пару ключей (открытого и секретного) и размещаете ее в файле KeyPair.snk, как показано в следующей команде.
sn -k KeyPair.snk
Получающийся двоичный файл KeyPair. snk не предназначен для чтения пользователю. Но если вы любопытны, можете записать эти ключи в текстовый файл, в котором поля разделяются запятыми. Это делается с помощью следующей команды:
sn -о KeyPair.snk KeyPair.txt
Теперь можете рассмотреть полученный файл с помощью Блокнота (Notepad.exe); однако это не обязательно.
На следующем шаге нужно применить секретный ключ к сборке. Это может быть сделано при компиляции, что, конечно, является полезным для разработки и тестирования; однако, когда придет время промышленного выпуска сборки, нужно будет поместить "строку с названием компании, да и вообще придется использовать более формальный подход. Для защиты корпоративной цифровой подписи предприятия официальный секретный ключ держится в секрете и потому не может быть сообщен программисту. Вместо него программист может при разработке и проверке (испытании) использовать заменяющую криптографическую пару, которая применяется во время компиляции автоматически. Тогда перед выпуском сборки уполномоченный сотрудник ставит официальную корпоративную цифровую сигнатуру (подпись), используя секретный ключ, строго хранимый в тайне. Это делается после компиляции с помощью инструментального средства А1. ехе. Указанный инструмент будет описан в этом подразделе позже. Однако, чтобы во время компиляции применить цифровую сигнатуру (подпись) автоматически, вы просто используете определенный атрибут C++, как показано в следующем коде. В частности, обратите внимание, что файл KeyPair.snk, сгенерированный предварительно инструментом Sn. ехе, определен в атрибуте AssemblyKeyFileAttribute.
//Assemblylnfо.срр
#include "stdafx.h" // имеет fusing
using namespace System::Reflection;
// использование пространства имен Система::Отражение;
using namespace System::Runtime::CompilerServices;
// использование пространства имен
// Система::Время выполнения:: CompilerServices;
[assembly:AssemblyTitleAttribute(""}];
[assembly:AssemblyDescriptionAttribute("")];
[assembly:AssemblyConfigurationAttribute("")];
[assembly:AssemblyCompanyAttribute("")];
[assembly:AssemblyProductAttribute("")];
[assembly:AssemblyCopyrightAttribute("")];
[assembly:AssemblyTrademarkAttribute("")];
[assembly:AssemblyCultureAttribute("")];
[assembly:AssemblyVersionAttribute("1.0.*") ];
[assembly:AssemblyDelaysignAttribute(false)] ;
[assembly:AssemblyKeyFileAttribute("KeyPair.snk") ];
[assembly:AssemblyKeyNameAttribute("")];
После добавления файла KeyPair. snk к AssemblyKeyFileAttribute, сборку нужно перетранслировать. Тогда при следующем запуске Ildasm. ехе покажет полученную в результате информацию, которая была включена в декларацию сборки для динамически подключаемой библиотеки (DLL) SharedComponent. Обратите внимание, что новая запись называется .publickey. В этой записи содержится открытый ключ создателя, который находится в файле KeyPair. snk. Именно этот открытый ключ может использоваться для расшифровки профиля сообщения, чтобы найти первоначальный хэш-код. Когда сборка развернута в глобальном кэше сборок, этот расшифрованный хэш-код сравнивается с новым, вычисленным по фактическому содержимому сборки, хэш-кодом. Такое сравнение позволяет определить, законна ли сборка (т.е. идентична оригиналу), или незаконна (т.е. разрушена или подделана). Конечно, когда вы используете Sn.exe, эта утилита сгенерирует другую криптографическую пару, и открытый ключ, приведенный ниже, будет отличаться от вашего.
.assembly SharedComponent
{
.publickey = (00 24 00 00 04 80 00 00 94 00 00 00 ...
... 56 5А Bl 97 D5 FF 39 5F 42 DF OF 90 7D D4 )
.hash algorithm 0x00008004
.ver 1:0:584:42238
}
Чтобы проверить разработанную нами сборку SharedComponent, мы должны создать испытательную клиентскую программу, а затем, вместо того, чтобы копировать SharedComponent.dll в каталог клиента, мы применим утилиту Gacutil.exe (Global Assembly Cache utility), или Проводник Windows (Windows Explorer), или Инструмент администрирования .NET (.NET Admin Tool), чтобы развернуть сборку в глобальном кэше сборок. Следующий код представляет собой испытательную клиентскую программу.
//SharedComponentClient.срр
linclude "stdafx.h"
#using
using namespace Systeir;
// использование пространства имен Система;
#using
using namespace SharedComponent;
// использование пространства имен SharedComponent;
using namespace System;
// использование пространства имен Система;
void main()
{
SomeClass *psc = new SomeClass;
int sum = psc->AddEmUp(3, 4); // суммируем
Console::WriteLine(sum); // сумма
}
Если бы вышеупомянутая испытательная клиентская программа была выполнена до инсталляции компонента-сборки, во время выполнения было бы запущено исключение FileNotFoundException, потому что сборку не удалось бы найти. Но на сей раз мы развертываем сборку в глобальном кэше сборок с помощью утилиты Gacutil.exe (Global Assembly Cache utility) Напомним, это только один из нескольких методов.
Gacutil -1 SharedComponent.dll
Затем можно выполнить клиент, причем теперь он должен работать надлежащим образом.

Рис. 7.4. Проводник Windows (Windows Explorer), показывает глобальный кэш сборок
После этого вы должны увидеть на консоли сообщение Assembly successfully added to the cache (Сборка успешно добавлена в кэш). В результате выполнения приведенной выше команды в каталоге \WINNT\Assembly был создан новый глобальный узел кэша сборки, имя этого узла — SharedComponent. Как видно на рис. 7.4, номер версии и создатель (т.е. лексема открытого ключа) сборки отображаются в Проводнике Windows (Windows Explorer)
Чтобы установить сборку в глобальный кэш сборок (GAC), можно также использовать Проводник Windows (Windows Explorer) — перетащите и опустите компонент в каталог Assembly. Кроме того, чтобы установить сборку в глобальный кэш сборок (GAC), можно использовать Инструмент администрирования .NET (.NET Admin Tool). Инструмент администрирования .NET (.NET Admin Tool) встроен в ММС и расположен в \WINNT\Microsoft.NET\Framework\vl.0.2914\mscorcfg.msc. Номер версии в каталоге будет отличен в более позднем выпуске .NET Framework. Хотя наличие третьего инструмента может показаться избыточным, эта встроенная в ММС утилита очень полезна, потому что упрощает решение многих задач На рис. 7.5 показано окно верхнего уровня данного инструмента. Чтобы с его помощью добавить сборку в глобальный кэш сборок (GAC), достаточно всего лишь выбрать Assembly Cache в левой области окна, щелкнуть правой кнопкой мыши, и выбрать Add (Добавить). В появившемся диалоговом окне переместитесь к нужному файлу, выберите сборку, которую хотите добавить, и щелкните на кнопке Open (Открыть).
Теперь, когда общедоступная сборка развернута в глобальном кэше сборок, клиентская программа SharedComponentClient может использовать нашу сборку. После того, как вы установили сборку в глобальном кэше сборок (GAC), вы можете переместить клиентскую программу в другой каталог. Вы можете выполнять клиента, причем нет необходимости перемещать сборку, установленную в глобальном кэше сборок (GAC).

Рис. 7.5. Инструмент администрирования .NET(. NETAdmin Tool) позволяет выполнять много административных функции .NET
Но что случается тогда, когда мы изменяем главную или младшую версию SharedComponent, или подписываем ее другим секретным ключом, а клиентскую программу SharedComponentClient не перекомпоновываем? Если сделать это со сборкой SimpleComponent, развернутой в частном порядке, то общеязыковая среда выполнения CLR не предпринимает никаких попыток обнаружить такое несоответствие (когда действует заданная по умолчанию политика) Однако, если сделать это с общедоступной сборкой, общеязыковая среда выполнения CLR запустит FileNotFoundException, когда обнаружит, что главная или младшая версия либо лексема открытого ключа отличается от того, что было установлено в соответствующем поле при компоновке клиента. Обратите внимание, что это исключение не будет вызвано, если реализация изменена, но главный и младший номер версии не изменены, а сборка подписана тем же самым секретным ключом.
CompEbook.ru Железо, дизайн, обучение и другие
Цифровые сигнатуры (подписи)
Если сборка должна быть развернута в глобальном кэше сборок, то необходимо, чтобы она имела цифровую подпись. Цифровая подпись (сигнатура) не требуется и не особенно полезна для сборки, развернутой частным образом, так как частная сборка развертывается пользователем для того, чтобы работать со своей определенной клиентской программой и потому согласована с ней. Если даже развернутая частным образом сборка имеет цифровую подпись, общеязыковая среда выполнения CLR по умолчанию не проверяет этого, когда загружает сборку для клиентской программы. Поэтому ответственность за предотвращение неправомочной модификации или подтасовки развернутых частным образом сборок полностью возложена на администратора. С другой стороны, очень выгодно использовать общедоступно развернутые (т.е. общедоступные) сборки, так как они должны быть подписаны в цифровой форме, и потому обычно их используют многие клиенты, причем рядом могут существовать несколько версий одной сборки.Цифровые подписи (сигнатуры) основаны на криптографических методах, в которых применяются открытые ключи. В мире криптографии применяется два основных криптографических метода — симметричные шифры (общий ключ) и асимметричные шифры (открытый ключ). Симметричные шифры совместно используют секретный ключ как для кодирования, так и для расшифровки. Стандарт шифрования данных DES (Data Encryption Standard), Triple DES (Тройной стандарт шифрования данных) и RC2 — примеры симметричных алгоритмов шифрования. Симметричные шифры могут быть очень эффективными и мощными в обеспечении секретности сообщения между двумя доверяющими друг другу сотрудничающими лицами, но они не совсем подходят для ситуаций, где трудно совместно использовать секретный ключ. По этой причине симметричные шифры считаются неподходящими для цифровых подписей (сигнатур). Именно потому цифровые подписи (сигнатуры) используются не для секретности, а лишь для идентификации и опознавания, что является более открытым делом. Если бы вы совместно использовали ваш симметричный ключ с каждым, кто потенциально хотел бы убедиться в подлинности ваших сборок, то по неосторожности могли доверить его людям, которым захотелось исполнить вашу роль. Для использования в цифровых подписях (сигнатурах) намного лучше подходят асимметричные шифры.
Асимметричные шифры, которые также называются шифрами с открытым ключом, используют криптографическую пару открытого и секретного ключа. Ключи в паре математически связаны, и генерируются они вместе; однако чрезвычайно трудно вычислить один ключ по другому. Обычно открытый ключ выдается каждому, кто хотел бы убедиться в подлинности владельца сборки С другой стороны, владелец сохраняет соответствующий секретный ключ подписи в тайне, чтобы никто не мог подделать его подпись. Метод шифрования по схеме открытого ключа RSA (RSA-кодирование) — пример системы шифрования с открытым ключом.
Шифрование с открытым ключом основано на очень интересной математической схеме, которая позволяет зашифровать обычный текст одним ключом, а расшифровать, только зная ключ, соответствующий исходному Например, когда открытый ключ используется, чтобы зашифровать первоначальные данные (называемые открытым текстом), только соответствующий секретный ключ может помочь в расшифровке этого текста. Даже ключ, который использовался для шифрования, не поможет в расшифровке зашифрованного текста! Этот сценарий полезен тогда, когда секретные сообщения посылаются только человеку, который знает секретный ключ.
Рассмотрим теперь противоположный сценарий. Человек, который знает секретный ключ, использует его для того, чтобы зашифровать открытый текст. Получающийся зашифрованный текст не является тайной, так как каждый заинтересованный может получить открытый ключ, чтобы расшифровать данный текст. Этот сценарий бесполезен для сохранения тайны, но очень эффективен для опознавательных целей. Нет необходимости шифровать исходные данные полностью, поэтому, чтобы повысить эффективность, вместо них шифруется компактный хэш-код, который с высокой вероятностью характерен для исходных данных. Если вы получаете файл, который содержит зашифрованную версию его собственного хэш-кода, и расшифровываете его с помощью соответствующего открытого ключа, то фактически вы повторно вычислите хэш-код исходных данных. И если теперь вы обнаружите, что он совпадает с хэш-кодом, который был зашифрован, можете быть совершенно уверены, что именно владелец секретного ключа приложил цифровую подпись и данные не были изменены другими лицами Если предположить, что владелец сумел сохранить тайну секретного ключа, тогда совпадение результатов вычисления доказывает, что никто не смог бы исказить файл после того, как он был подписан в цифровой форме. На рис. 7.3 показано, как работает цифровая подпись (сигнатура).

Рис. 7.3. Вот так работает цифровая подпись (сигнатура)
Цифровое подписание методом шифрования по схеме открытого ключа RSA и SHA1
Чтобы подписать сборку, производитель вычисляет с помощью алгоритма SHA1 ее хэш-код (причем байты, зарезервированные для подписи (сигнатуры), предварительно обнуляются), и затем зашифровывает значение хэш-функции с помощью секретного ключа, используя метод шифрования по схеме открытого ключа RSA (RSA-кодирование). Открытый ключ и зашифрованный хэш-код сохраняются в метаданных сборки.
CompEbook.ru Железо, дизайн, обучение и другие
Файлы конфигурации политики управления версиями
Заданная по умолчанию политика управления версиями часто оказывает желательное действие, причем она действует автоматически, если не существует никакого файла конфигурации. Однако иногда требуется более тонкое управление процессом связывания с общедоступной сборкой. Как упоминалось ранее, чтобы сделать это для выполняемых клиентских программ, нужно создать файл конфигурации, содержащий XML-документ с тем же самым именем, что и у клиента, но с расширением . conf ig, и поместить его в тот же самый каталог, где находится клиентская выполняемая программа. Это может быть полезно в тех случаях, когда вы хотите отключить заданную по умолчанию автоматическую политику QFE, или если вы хотите использовать версию компонента-сборки, отличную от той, которая была указана при компиляции клиентской программы. Важно обратить внимание, что файл конфигурации приложения может затронуть поведение связывания только единственного приложения-клиента. Он не может быть применен ко всем клиентским программам, использующим данную сборку.Для .NET файлов конфигурации тэгом верхнего уровня является
publicKeyToken="8bOe612d60bdeOca" />
Правила, определяющие политику управления версиями, находятся в разделе
Для установки файла конфигурации можно использовать Инструмент администрирования .NET (.NET Admin Tool). Чтобы запустить этот инструмент, дважды щелкните на \WINNT\Microsoft.NET\Framework\vl.0.2914\mscorcfg.msc в Проводнике Windows (Windows Explorer). Затем добавьте приложение к инструменту, щелкнув правой кнопкой мыши на Applications (Приложения) в левой области окна, и выберите Add (Добавить) из контекстного меню. Переместитесь к приложению, которое вы хотите конфигурировать.
Выберите его и щелкните на кнопке Open (Открыть). На рис. 7.7 показан Инструмент администрирования (Admin Tool) после того, как выполнены все указания.

Рис. 7.7. Обычно для конфигурирования политики управления версиями используется Инструмент администрирования .NET (.NET Admin Tool)
CompEbook.ru Железо, дизайн, обучение и другие
Инсталляция примера программной системы
Возможно, вы помните, что в предыдущей главе пример программы был реализован на С#, а не на управляемом C++, так как в нем было много кода графического интерфейса пользователя, который намного лучше реализуется на С#, чем на C++. Для целей данной главы все это вполне бы пригодилось, так как процесс развертывания одинаков для сборок, написанных на любом языке .NET, включая С# и управляемый C++. Однако проект был частично преобразован (конвертирован) из С# в управляемый C++. На данном этапе изучения примера мы разбили нашу программу администратора гостиницы (Hotel Administrator) на три сборки. В каталоге CaseStudy для этой главы имеется прикладная программа AcmeGui (файл с расширением ЕХЕ), написанная на С#, и два компонента (динамически подключаемые библиотеки (DLL)) сборки — Customer (Клиент) и Hotel (Гостиница), которые были реализованы на управляемом C++. Часть, написанная на С#, посвящена формам (т.е. функциональным возможностям графического интерфейса пользователя (GUI)). Код неграфического интерфейса пользователя, связанный с классами Customer (Клиент) и Hotel (Гостиница), а также связанные с ним классы, интерфейсы и структуры, в предыдущей версии примера программы были перемещены в новые сборки, представляющие собой динамически подключаемые библиотеки (DLL), написанные на управляемом C++.На рис. 7.9 видно, как Solution Explorer (Поиск решения, Проводник решения) показывает, что проект AcmeGui имеет ссылки на сборки (динамически подключаемые библиотеки (DLL)) Customer (Клиент) и Hotel (Гостиница). Эти ссылки дают возможность компилятору найти типы Customer (Клиент) и Hotel (Гостиница), используемые в AcmeGui и затем скомпоновать приложение. Они не диктуют, куда нужно поместить динамически подключаемые библиотеки (DLL) при развертывании проекта. Обратите также внимание на ссылки на системные сборки, такие как System.dll. Посмотрев на свойства ссылок, вы узнаете, где расположена сборка.
Однако в пределах существующего решения AcmeGui довольно просто создать проект на основе шаблона Managed C++ Class Library (Управляемый C++ на основе Библиотеки классов). Щелкните правой кнопкой мыши на узле Solution AcmeGui (Решение AcmeGui) в Solution Explorer (Поиск решения, Проводник решения), затем выберите Add => New Project (Добавить => Новый проект). Выберите тип Visual C++ Project (Проект на Visual C++), потом выберите шаблон Managed Class Library (Библиотека Управляемых классов), найдите CaseStudy, и назовите проект Customer (Клиент). В Visual Studio.NET выберите Class Library (Библиотека классов) в New Project Wizard (Мастер создания проектов), затем укажите местоположение и имя, после чего щелкните на ОК. Проект Customer (Клиент) находится в папке C:\OI\NetCpp\Chap7\CaseStudy. То же самое сделайте для другой сборки — Hotel (Гостиница), которая представляет собой динамически подключаемую библиотеку (DLL). Чтобы установить в вашем проекте ссылку на каждую из сборок, которые в данном примере представляют собой динамически подключаемые библиотеки (DLL), используйте оператор ttusing. Конечно, библиотеки должны быть созданы прежде, чем на них можно будет сослаться.
На рис. 7.10 показан верхний уровень, который вы увидите, когда откроете сборку Customer.dll в ILDASM и выполните двойной щелчок на пространстве имен 01 .NetCpp.Acme. Вы видите точки входа для декларации MANIFEST (МАНИФЕСТ, ДЕКЛАРАЦИЯ), классов Customers (Клиенты) и Customer (Клиент), интерфейса ICustomer и типа CustomerListltem. Если щелкнуть на кнопке плюс (+), точка входа будет развернута.
Чтобы рассмотреть декларацию, дважды щелкните на узле MANIFEST (МАНИФЕСТ, ДЕКЛАРАЦИЯ), показанном на рис. 7.10, и в результате будет отображена информация, относящаяся к декларации (рис. 7.11). Некоторые числа будут другими, если вы заново создали какой-либо из примеров, или имеете более позднюю версию .NET.
Декларация содержит информацию о зависимостях и содержимом сборки. Видно, что декларация для Customer (Клиент) содержит, среди других, следующую внешнюю зависимость:
.assembly extern mscorlib
{
.publickeytoken = ( B7 7A 5C 56 19 34 EO 89 )
.hash = (09 BB ВС 09 EF 6D 9B F4 F2 CC IB 55 ... EF 77 )
.ver 1:0:2411:0
}

Рис. 7.9. Ссылки, показываемые Solution Explorer (Поиск решения, Проводник решения) для AcmeGui
Если вы заново создали какой-нибудь из компонентов, то, конечно, номера компоновки и пересмотра будут отличаться.

Рис. 7.10. Верхний уровень представления (вида), отображаемого утилитой ILDASM для компонента Customer (Клиент)

Рис. 7.11. ILDASM показывает декларацию Customer .dll
Инструкция метаданных .assembly extern mscorlib указывает, что сборка Customer (Клиент) использует (и поэтому зависит от нее) стандартную сборку mccorlib. dll, которая нужна для всего управляемого кода. Когда в сборке встречается ссылка на другую сборку, вы увидите инструкцию метаданных . assembly extern. Если открыть AcmeGui в ILADASM, то в декларации вы увидите зависимости от нескольких других сборок, включая зависимости от сборок Customer (Клиент) и Hotel (Гостиница), а также зависимости от сборки System. Drawing (Система.Рисунок).
.assembly extern Customer
{
.publickeytoken = (8В ОЕ 61 2D 60 BD EO CA )
.ver 1:0:641:33530
}
.assembly extern Hotel
{
.publickeytoken = (CF OB C2 2F 8E 2C 15 22 )
.ver 1:0:641:33536
}
.assembly extern System.Drawing
{
.publickeytoken = (BO 3F 5F 7F 11 D5 OA ЗА )
.ver 1:0:2411:0
}
Сборка System. Drawing (Система.Рисунок) — общедоступная сборка, которую можно легко найти в каталоге \WINNT\Assembly с помощью Проводника Windows (Windows Explorer). Общедоступная сборка mscorlib не развернута в кэше сборки. Microsoft сделала единственное исключение для этой сборки, потому что mscorlib так близко связана с механизмом общеязыковой среды выполнения CLR (mscorwks), что она устанавливается в соответствующем каталоге установки (\WINNT\Microsoft.NET\Framework) для текущей версии .NET.
В общедоступной сборке System. Drawing (Система.Рисунок) инструкция метаданных .publickeytoken = (ВО 3F 5F 7F 11 D5 ОА ЗА) содержит лексему открытого ключа, представляющую собой самые младшие 8 байтов хэш-кода открытого ключа, который соответствует своему секретному ключу, принадлежащему автору сборки System. Drawing (Система.Рисунок). Эта лексема открытого ключа не может фактически непосредственно использоваться для того, чтобы подтвердить подлинность автора сборки System. Drawing (Система.Рисунок). Однако первоначальный открытый ключ, указанный в декларации System. Drawing (Система.Рисунок) может использоваться, чтобы математически проверить, что для того, чтобы подписать сборку System. Drawing (Система.Рисунок) в цифровой форме на самом деле использовался соответствующий секретный ключ. Поскольку Microsoft создала System.Drawing.dll, лексема открытого ключа, приведенная выше, принадлежит, несомненно, Microsoft.
CompEbook.ru Железо, дизайн, обучение и другие
Конфигурация сборки
Когда во время выполнения встречается статическая или динамическая ссылка на сборку, общеязыковая среда выполнения CLR осуществляет связывание со сборкой. Статическая ссылка определяется постоянным (статическим) образом в клиентской декларации сборки еще при ее компиляции. Динамическая ссылка генерируется программой во время выполнения, например, при вызове метода System.Reflection. Assembly.Load (Система.Отражение.Сборка.Загрузка). В любом случае связывание выполняется тем же самым способом для статической или динамической ссылки на сборку со строгим именем. Кроме простого имени, версия, открытый ключ (если он есть) и культура (если она есть) также отличают общедоступную сборку. Для связывания необходимо, как минимум, простое имя.Процесс связывания с общедоступной сборкой часто называется зондированием или исследованием, которое является не чем иным, как набором эвристических правил, используемых общеязыковой средой выполнения CLR для того, чтобы определить местонахождение сборки и загрузить ее по требованию. Эти правила применяются только к ссылке на сборку со строгим именем, а ссылки на неподписанные (т.е. без цифровой подписи) частные сборки разрешаются непосредственно, т.е. без исследования или проверки версии (мы предполагаем, что действует заданная по умолчанию политика).
Однако можно использовать сборку со строгим именем, чтобы вынудить клиента связываться с определенной версией сборки. Предположим, что вы хотите допустить соответствие для нескольких обратно совместимых сборок (т.е. чтобы несколько сборок удовлетворяли условиям загрузки сборки общеязыковой среды выполнения CLR). Вы можете использовать XML-файл конфигурации, чтобы определить правила, используемые общеязыковой средой выполнения CLR для нахождения соответствующей сборки. Инструмент Администрирования .NET (.NET Admin Tool) позволяет создавать и поддерживать такие файлы с помощью графического интерфейса пользователя (GUI).
Имя файла конфигурации представляет собой имя клиентской программы, в конец которого добавлено расширение . conf ig. Файл конфигурации затем помещается в тот же самый каталог, где находится выполняемая клиентская программа.
В дополнение к файлу конфигурации приложения, существует административный файл конфигурации Machine .config. Он находится в подкаталоге Config того каталога, где установлена среда времени выполнения .NET (\WINNT\Microsoft.NET\ FrameworkA vl. О . 2914\, где номер версии отражает текущую компоновку .NET). Политика администрирования версии определяется теми же дескрипторами XML, что используются и в файле конфигурации приложения. Однако файл конфигурации администратора отменяет любые соответствующие параметры в файле конфигурации приложения.
CompEbook.ru Железо, дизайн, обучение и другие
Merge Module Project (Проект модуля слияния)
Проект модуля слияния упаковывает многократно используемую информацию об установке; она может храниться независимо, а затем объединяется в качестве общедоступного инсталляционного пакета с другими инсталляционными пакетами. Проект модуля слияния генерирует модуль слияния— файл .msm, который может быть объединен в файлы .msi. Это позволяет совместно использовать общие сборки, связанные (входящие в состав) файлы, значения системного реестра и функциональные возможности установки для нескольких приложений.Чтобы запустить Merge Module Project Wizard (Мастер проектов модуля слияния), выберите File => New (Файл => Создать), затем выберите Project (Проект). В диалоговом окне New Project (Новый проект) в качестве типа проекта (Project Type) выберите Setup and Deployment Projects (Установка и Развертывание проектов, Проекты Setup и Deployment). Наконец, выберите в качестве шаблона (Template) Merge Module Project " Wizard (Мастер проектов модуля слияния), укажите имя и местоположение, а затем щелкните на ОК.
Как правило, файл MSI предназначен для использования конечным пользователем с целью установить законченное решение за один простой сеанс развертывания. Файл MSM, напротив, обычно предназначается для других разработчиков, которые хотят использовать разработанные вами компоненты в их собственных проектах установки. Эти разработчики могут объединить ваш файл MSM в их собственный файл MSI для того, чтобы развернуть ваши компоненты в их средах испытания и разработки, а также для их окончательного конечного пользователя. Конечные пользователи не должны получать какие-либо файлы MSM, так как Windows Installer (Инсталлятор Windows) не может использовать их непосредственно, а сами они не являются очень дружественными по отношению к пользователю.
Чтобы добавить существующий проект модуля слияния к проекту установки, создайте или откройте проект установки и выберите Select FileOAdd Project (Выбрать файл^Добавить проект), а затем выберите Existing Project. В диалоговом окне Add Existing Project (Добавить существующий проект) найдите местоположение нужного проекта модуля слияния, выберите связанный (т.е. входящий в состав) файл . vdp проекта развертывания, а потом щелкните на Open (Открыть).
Мы только добавили проект модуля слияния к решению. Теперь мы должны добавить его к самому проекту установки. Выберите проект установки и вызовите Add: Project Output (Добавить: Вывод проекта), а затем в появившемся диалоге выберите проект модуля слияния.
CompEbook.ru Железо, дизайн, обучение и другие
Многомодульные, или мультимодульные сборки
Сборка может быть составлена из нескольких модулей. Модуль — динамически подключаемая библиотека (DLL) (или файл с расширением ЕХЕ), который содержит управляемый код плюс метаданные, а также, возможно (но не обязательно'), декларацию. Однако сборка должна иметь одну и только одну декларацию, так что сборка может содержать несколько модулей, но только один модуль содержит декларацию, в которой хранится информация о содержимом всех модулей в сборке Модуль с декларацией может иметь или только декларацию, или содержать также и код, и ресурсыОсновное преимущество разбиения большой сборки на модули состоит в том, что каждый модуль содержится в отдельном файле динамически подключаемой библиотеки (DLL). Благодаря этому по сети можно загружать только требуемые модули. Это позволяет оптимизировать потребление памяти и ускорить работу (выполнение программ). Даже в локальном сценарии, чтобы повысить эффективность, общеязыковая среда выполнения CLR загружает классы на локальной машине со степенью детализации модуля Другая причина создания сборок с несколькими модулями состоит в том, что каждую часть такой сборки можно написать на своем языке NET. Чтобы скомпоновать сборку, которая содержит несколько модулей, необходимо скомпоновать каждый модуль отдельно, а затем объединить их утилитой А1. ехе.
Есть два способа создания мультимодульной сборки. Один способ состоит в том, чтобы создать все модули без деклараций, а затем создать один дополнительный модуль, который содержит только декларацию для всей сборки, но фактически не содержит никакого кода. Другая методика полагается на то, что только один модуль в сборке должен содержать и код, и декларацию для всей сборки, а все другие модули содержат только код без декларации. Мы опишем первую из этих альтернатив, так как она более симметрична и ее проще визуализировать. Вторая альтернатива здесь не описана; однако реализовать ее можно подобным способом, теми же самыми инструментальными средствами.
Сначала мы создаем два модуля без декларации, используя Visual C++ с управляемыми расширениями. Затем создаем третий модуль с декларацией, содержащей информацию о двух других модулях, используя инструмент Assembly Linker (Компоновщик сборки) — А1. ехе. Вместе эти три модуля образуют сборку. На рис. 7.8 показана структура полученной таким образом сборки.

Рис. 7.8. Мультимодульная сборка с явно выделенным модулем
Следующие исходные файлы составляют первый модуль без декларации. Этот модуль содержит общедоступный класс ClassInModuleWithoutManifestl, который предоставляет для использования общедоступный метод SomeMethod. Вы можете создать его с помощью шаблона Managed C++ Class Library (Управляемый C++ на основе Библиотеки классов).
//ModuleWithoutManifestl.h
using namespace System;
// использование пространства имен Система;
namespace ModuleWithoutManifestl
// пространство имен ModuleWithoutManifestl
{
public _gc class ClassInModuleWithoutManifestl
// класс сборщика мусора
ClassInModuleWithoutManifestl
{
public:
void SomeMethod();
};
}
//ModuleWithoutManifestl.cpp
finclude "stdafx.h"
// имеет
fusing
using namespace System;
// использование пространства имен Система;
tinclude "ModuleWithoutManifestl.h"
namespace ModuleWithoutManifestl
// пространство имен
ModuleWithoutManifestl
{
void CiassInModuleWithoutManifestl::SomeMethod()
{
Console::WriteLine(
"CiassInModuleWithoutManifestl::SomeMethod");
}
}
Опция компилятора /CLR:noAssembly указывает, что генерируется управляемый код, но декларация для сборки не генерируется. Опция компилятора /с подавляет автоматическое связывание, так как мы выполним его отдельной командой. Опция связывания /NOASSEMBLY также запрещает генерацию информации, относящейся к декларации. Поэтому чтобы сгенерировать первый модуль (динамически подключаемую библиотеку (DLL)) без декларации, в приглашении к вводу команды используются следующие две команды:
cl /CLR:noAssembly /с ModuleWithoutManifestl.срр
link /NOASSEMBLY /DLL ModuleWithoutManifestl.obj
Если вы предпочитаете создавать модуль в Visual Studio.NET, сначала для проекта нужно изменить опции компилятора и компоновщика, чтобы не генерировать информацию, относящуюся к декларации, и не добавлять ее к модулю. Для установки опции компилятора /CLR: noAssembly в Visual Studio.NET сделайте следующее:
1. Откройте диалоговое окно Property Pages (Страницы свойств) проекта. Чтобы сделать это, щелкните правой кнопкой мыши на узле ModuleWithoutManifestl, находящемся под узлом решения в окне Class View.
2. Откройте папку Configuration Properties (Свойства конфигурации).
3. Откройте папку C/C++.
4. Щелкните на странице General (Общая), находящейся под папкой C/C++.
5. Установите MetaData Only (/CLR: noAssembly) (Только метаданные) в качестве значения свойства Compile as managed (Компилировать как управляемый).
Объектный модуль, полученный в результате компиляции с такими параметрами, теперь нужно скомпоновать в Visual Studio.NET с опцией компоновщика /NOASSEMBLY. Эта опция компоновщика требует, чтобы вы установили также опцию компоновщика /NOENTRY. Чтобы внести эти изменения в проект, выполните следующее:
6. Откройте диалоговое окно Property Pages (Страницы свойств) проекта, если только оно не открыто.
7. Откройте папку Configuration Properties (Свойства конфигурации), если она не открыта.
8. Откройте папку Linker (Компоновщик).
9. Щелкните на Advanced page (Дополнительная страница) под папкой Linker (Компоновщик).
10. Установите Yes (/NOASSEMBLY) (Да) в качестве значения свойства Turn Off Assembly Generation (Выключить генерацию сборки).
11. Оставаясь на странице Advanced page (Дополнительная страница), установите Yes (/NOENTRY) (Да) в качестве значения свойства Resource Only DLL (Динамически подключаемая библиотека (DLL) только с ресурсами).
Теперь можно создать проект обычным способом.
Второй модуль— динамически подключаемая библиотека (DLL), названная ClassInModuleWithManifest2 .dll, — также будет создан без декларации. Следующие исходные файлы, которые очень похожи на приведенные выше исходные файлы первого модуля, составляют второй модуль без декларации. Он содержит общедоступный класс ClassInModuleWithoutManifest2, который предоставляет для использования общедоступный метод Some-OtherMethod. Этот проект также может быть создан в Visual Studio.NET с помощью шаблона Managed C++ Class Library (Управляемый C++ на основе Библиотеки классов).
//ModuleWithoutManifest2.h
using namespace System;
// использование пространства имен Система;
namespace ModuleWithoutManifest2
// пространство имен
ModuleWithoutManifest2
{
public _gc class ClassInModuleWithoutManifest2
// класс сборщика мусора ClassInModuleWithoutManifest2
{
public:
void SomeOtherMethod();
};
}
//ModuleWithoutManifest2.cpp
#include "stdafx.h" // имеет #using
using namespace System;
// использование пространства имен Система;
#include "ModuleWithoutManifest2.h"
namespace ModuleWithoutManifest2
// пространство имен ModuleWithoutManifest2
{
void ClassInModuleWithoutManifest2::SomeOtherMethod()
{
Console::WriteLine(
"ClassInModuleWithoutManifest2::SomeOtherMethod");
}
}
Этот модуль также не будет иметь декларации, и он создается с помощью следующих команд, которые фактически совпадают с теми, что использовались для создания первого модуля:
cl /CLFUnoAssembly /с ModuleWithoutManifest2.срр
link /NOASSEMBLY /DLL ModuleWithoutManifest2.obj
И снова можно выполнить те же самые шаги, что и для предыдущего модуля, если вы хотите создать этот модуль с помощью Visual Studio.NET. Только не забудьте установить MetaData Only (/CLR: noAssembly) (Только метаданные) в качестве значения свойства Compile as managed (Компилировать как управляемый), a Yes (/NOASSEMBLY) (Да)— в качестве значения свойства Turn Off Assembly Generation (Выключить генерацию сборки) и Yes (/NOENTRY) (Да)— в качестве значения свойства Resource Only DLL (Динамически подключаемая библиотека (DLL) только с ресурсами).
Наконец, эти два модуля должны быть помещены в ту же самую сборку. Обратите внимание, что физически они не связаны вместе. Они остаются различными файлами динамически подключаемых библиотек (DLL), но помещаются в объединенную логическую сборку. Для этого используется утилита А1. exe (Assembly Linker), которая включает информацию, содержащуюся в метаданных обоих модулей, в одну декларацию, содержащуюся в третьей динамически подключаемой библиотеке (DLL) — Combined-Assembly . dll. Чтобы сделать это, скопируйте два существующих модуля в общий каталог CombinedAssernbly, и введите следующую команду:
Аl ModuleWithoutManifestl.dll, ModuleWithoutManifest2.dll /
out:CombinedAssembly.dll
Теперь мы закончили создание мультимодульной сборки. Декларация сборки содержится в CombinedAssembly.dll. Чтобы это проверить, мы должны создать испытательную клиентскую программу. Обратите внимание, что все модули должны быть доступны для клиента. Этого можно достичь с помощью локального или общедоступного развертывания. Чтобы сделать этот пример простым, мы развернем сборку локальным образом, т.е. только скопируем три модуля (три динамически подключаемые библиотеки (DLL)) в ту же самую папку, в которой находится испытательная клиентская программа. Следующий код реализует испытательную клиентскую программу. Обратите внимание, что клиент ссылается только на одну динамически подключаемую библиотеку (DLL) сборки — на CombinedAssembly. dll, так как только она содержит декларацию. Если теперь вы создадите эту клиентскую программу и скопируете все три динамически подключаемые библиотеки (DLL), то увидите, что созданная клиентская программа успешно вызывает методы, определенные в классах, которые определены в отдельных модулях (динамически подключаемых библиотеках (DLL)). Однако ссылки есть только на одну сборку — на CombinedAssembly. dll. Это доказывает, что все три модуля работают как единая логическая сборка.
//MultiModuleAssemblyTestClient.срр
#include "stdafx.h"
#using
#using
using namespace ModuleWithoutManifestl;
// использование пространства имен
ModuleWithoutManifestl;
using namespace ModuleWithoutManifest2;
// использование пространства имен
ModuleWithoutManifest2;
void main()
{
ClassInModuleWithoutManifestl *pl =
new ClassInModuleWithoutManifestl;
pl->SomeMethod();
ClassInModuleWithoutManifest2 *p2 =
new ClassInModuleWithoutManifest2 ;
p2->SomeOtherMethod();
}
CompEbook.ru Железо, дизайн, обучение и другие
Обнаружение физического местоположения сборки
Мы выяснили, откуда общеязыковая среда выполнения CLR знает, какие версии сборки удовлетворят условиям разрешения ссылки. Но откуда общеязыковая среда выполнения CLR узнает, где сборка постоянно хранится на диске? Если сборка с нужной версией была загружена предварительно потому, что ранее в программе уже встретилась другая ссылка на эту сборку, то эта сборка уже используется. Если сборка имеет строгое имя, то проверяется кэш сборки; если в нем находится правильная версия, то используется найденная там сборка.В файле конфигурации есть несколько элементов, которые можно определить для того, чтобы указать общеязыковой среде выполнения CLR, где нужно попробовать найти сборку.
Если сборка еще не найдена, выполняются динамические проверки, чтобы увидеть, была ли определена в файле конфигурации кодовая страница. В разделе
Чтобы использовать элемент Codebase (Кодовая страница) вне каталога приложения или подкаталогов, требуется строгое имя. Если это условие не удовлетворяется, независимо от того, действительно ли найдена требуемая сборка, процесс связывания останавливается. Если сборка не найдена, генерируется исключение.
Если элемент Codebase (Кодовая страница) не был найден в файле конфигурации, среда времени выполнения продолжает исследовать сборку Теперь весь поиск ведется относительно каталога, в котором выполняется приложение. Такой каталог называется базой (ядром, основанием или уровнем отсчета) приложения (application base)
Сначала среда времени выполнения ищет в ядре приложения. Затем она смотрит в любых подкаталогах ядра приложения, которые имеют то же самое имя, что и сборка. Если в запросе определена культура, то среда времени выполнения ищет только подкаталог сборки в подкаталоге с названием (именем) требуемой культуры.
Наконец, в разделе assemblyBinding файла конфигурации можно определить атрибут privatePath. Этот атрибут представляет собой список подкаталогов ядра приложения. Подкаталоги в этом списке отделяются точкой с запятой, именно в них и осуществляется поиск.
Атрибут privatePath можно также установить с помощью Инструмента администрирования .NET (.NET Admin Tool) на вкладке свойств приложения.
CompEbook.ru Железо, дизайн, обучение и другие
Общедоступное развертывание сборки
Кэш сборки — средство параллельного ("бок о бок") развертывания (инсталляции) компонентов на всей машине. Термин "бок о бок" означает, что множество версий того же самого компонента могут постоянно находиться в кэше сборок рядом друг с другом. Глобальный кэш сборок содержит общедоступные сборки, которые являются глобально доступными для всех .NET-приложений на машине. Есть также кэш загружаемых сборок, доступный для приложений типа Internet Explorer, которые автоматически загружают сборки по сети. Чтобы развернуть сборку в глобальном кэше сборок, нужно создать для нее строгое имя.CompEbook.ru Железо, дизайн, обучение и другие
Подписание в цифровой форме после компиляции
С развертыванием общедоступных сборок связана еще одна проблема. Шаги, описанные выше, годились для цифрового подписания сборки во время компиляции, так как использовался атрибут, определяющий файл ключей, который содержал открытый и секретный ключи Но часто случается так, что ни один из программистов, разрабатывающих компоненты, которые будут упакованы в сборки, не знает секретного ключа всей организации. В этом случае при разработке и испытании программисты должны использовать временный секретный ключ, но когда придет время отправить сборку клиентам, назначенный представитель компании выполняет окончательное цифровое подписание, используя официальный секретный ключ организации, хранимый в строгой тайне. Это делается при помощи инструмента Sn.exe, с использованием опции -R, где "R" означает "resign" ("подписать заново"). Как правило, предварительно с помощью опции -k утилиты Sn.exe генерируется официальная криптографическая пара. Затем, используя опцию -R утилиты Sn. ехе. ставится подпись на каждой новой сборке.sn -k TrueKeyPair.snk
sn -R SharedComponent.dll TrueKeyPair.snk
Хотя здесь две команды показаны вместе, обычно не нужно генерировать новую криптографическую пару каждый раз, когда вы заново подписываете очередную новую сборку. После второй команды на пульте появится сообщение Assembly 'SharedComponent.dll' successfully re-signed (Сборка 'SharedComponent.dll' успешно подписана заново).
Впрочем, есть и другой метод: вы можете задержать цифровое подписание. При создании сборки открытый ключ делается доступным компилятору, чтобы он мог поместить его в поле PublicKey декларации сборки. В файле резервируется место для подписи (сигнатуры), но сама подпись (сигнатура) не генерируется. Когда в конечном счете будет сгенерирована фактическая подпись (сигнатура), она помешается в файл с помощью опции -R утилиты Strong Name (sn.exe).
Чтобы указать компилятору, что вы хотите использовать отсроченное цифровое подписание, вы указываете значение true (истина) для атрибута AssemblyDelaySignAttribute в исходном тексте. Вы также должны включить открытый ключ с помощью атрибута
AssemblyKeyFileAttribute.
Предположим, что вы сгенерировали криптографическую пару открытого и секретного ключа так, как описано выше. Тогда вы с помощью опции -р утилиты Strong Name можете получить только открытый ключ, все еще сохраняя в тайне секретный ключ.
sn -p TrueKeyPair.snk PublicKey.snk
Затем вы добавляете следующие два атрибута к вашему коду:
[assembly:AssemblyDelaySignAttribute(true)];
[assembly:AssemblyKeyFileAttribute("PublicKey.snk")];
Сборка все еще не имеет правильной подписи (сигнатуры). Вы не сможете установить ее в глобальном кэше сборок или загрузить ее из каталога приложения. Вы можете отключить проверку подписи (сигнатуры) конкретной сборки, используя опцию -Vr утилиты Strong Name.
sn -Vr SharedComponent.dll
Перед поставкой сборку надо снабдить правильной сигнатурой (подписью), чтобы она могла быть развернута как общедоступная сборка. Для этого используйте опцию -R утилиты Strong Name и запишите криптографическую пару открытого и секретного ключей.
sn -R SharedComponent.dll TrueKeyPair.snk
CompEbook.ru Железо, дизайн, обучение и другие
Проект установки (Setup Project)
Шаблон Setup Project (Проект установки) создает файл msi, используемый инсталлятором Windows (Windows Installer) для настольного или распределенного приложения Проект установки (Setup Project) не предназначен для развертывания приложений, основанных на технологии WWW, так как для этой цели используется специализированный проект сетевой установки (Web setup project) Проект установки (Setup Project) генерирует программу, которая устанавливает приложение на целевую машину Вы можете создать проекты установки в пределах того же самого решения, которое содержит другие развертываемые проекты В решении с несколькими уровнями вы можете создать один проект установки для каждого проекта, который должен быть развернут на конкретной целевой вычислительной машине Например, в простом решении с тремя звеньями, вероятно, будет три проекта развертывания Два простых проекта развертывания предназначены для установки клиента и сервера Третий проект развертывания будет заботиться о более сложной бизнес-логике среднего звена Кроме того, могли бы потребоваться дополнительные проекты развертывания, если бы решение было очень сложным или в стратегию развертывания были включены модули слиянияЧтобы создать проект установки, выберите FileONew (Файл<> Создать), затем выберите Project (Проект) В диалоговом окне New Project (Новый проект) в качестве типа проекта (Project Type) выберите Setup and Deployment Projects (Проекты установки и развертывания) Наконец, выберите в качестве шаблона (Template) Setup Project (Проект установки), укажите имя и местоположение, а затем щелкните на ОК Результат показан на рис. 7.13. — вы видите открытые Solution Explorer (Поиск решения, Проводник решения) и File System Editor (Редактор файловой системы)

Рис. 7.13. Применение Solution Explorer (Поиск решения, Проводник решения) и File System Editor (Редактор файловой системы) в проекте установки
Создав начальный проект установки с помощью шаблона Setup Project (Проект установки), его можно разрабатывать далее, используя File System Editor (Редактор файловой системы). Редактор файловой системы (File System Editor) позволяет перетащить и опустить или скопировать и вставить файлы, которые будут развернуты проектом развертывания установки, и управлять их адресатами на целевой машине. Сразу после запуска редактор файловой системы (File System Editor) показывает начальный список папок-получателей (результирующих папок), в которые будут развернуты компоненты, причем к этому списку вы можете добавить ваши собственные папки.
Создав файл MSI, вы можете использовать инсталлятор Windows (Windows Installer), как показано в следующей командной строке:
Msiexec /i SomeSetup.msi
Затем запускается программа Windows Installer (Инсталлятор Windows). Эта программа отображает ряд инсталляционных диалогов. После того, как инсталлятор Windows (Windows Installer) завершит развертывание, вы можете попробовать выполнить установленное приложение, чтобы проверить, что инсталляция была завершена успешно. Если выполнить ту же самую команду, Msiexec /i SomeSetup.msi, то она обнаружит, что приложение уже существует, и предложит вам восстановить инсталляцию или деинсталлировать приложение.
CompEbook.ru Железо, дизайн, обучение и другие
Проводимая по умолчанию политика управления версиями
При связывании с общедоступно развернутыми сборками, в отсутствие любого файла конфигурации политики управления версиями, связывание просто использует информацию в декларации сборки для того, чтобы во время выполнения определить местонахождение других сборок и загрузить их по требованию. По умолчанию используются только поля главного и младшего номера версии, причем главный и младший номера должны совпадать точно, а затем берутся наиболее поздняя компоновка и пересмотр из доступных. Именно это чаще всего и требуется, так как клиентская сборка определяет зависимости от других сборок на основании главных и младших номеров версии, которые были установлены при компиляции клиентской сборки. Поскольку неизменные главные и младшие значения традиционно указывают совместимость вниз, а измененная компоновка и пересмотр указывают обратно совместимые исправления и изменения, то часто имеет смысл именно эта заданная по умолчанию политика управления версиями. Эта заданная по умолчанию политика управления версиями называется также Автоматической политикой QFE (Automatic QFE policy). При необходимости в файле конфигурации политики управления версиями это поведение может быть отменено, например для того, чтобы номера компоновки и пересмотра совпадали точно. Как это сделать, описано в следующем подразделе.CompEbook.ru Железо, дизайн, обучение и другие
Развертывание по сети
При развертывании по сети у клиента используется Internet Explorer для того, чтобы по требованию от Web-сервера .NET автоматически загрузить сборки, упакованные как ЕХЕ-файлы, динамически подключаемые библиотеки (DLL), или файлы CAB. Чтобы управлять процессом связывания, файлы HTML могут динамически развернуть сборки, а также файлы конфигурации. При развертывании по сети затребованные сборки загружаются в глобальный кэш загрузки сборок клиента.Тэг