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 предоставляет:
  • устойчивую общеязыковую среду выполнения CLR (Common Language Runtime), которая входит в состав данной платформы;
  • средства разработки приложений на любом из многих языков программирования, поддерживаемых платформой .NET;
  • лежащую в основе открытой модели программирования огромную библиотеку классов .NET Framework. Эти классы содержат многократно используемый код. Они доступны в любом языке программирования, поддерживаемом платформой .NET;
  • поддержку сетевой инфраструктуры, построенной на верхнем слое стандартов Internet, вследствие чего обеспечивается высокий уровень взаимодействия между приложениями;
  • поддержку нового промышленного стандарта, а именно технологии Web-служб. Технология Web-служб предоставляет новый механизм создания распределенных приложений. По сути, она является распространением технологии создания приложений на базе компонентов и на сферу Internet;
  • модель безопасности, которую программисты могут легко использовать в своих приложениях;
  • мощные инструментальные средства разработки.

  • CompEbook.ru Железо, дизайн, обучение и другие

    Факторы, определяющие успех Web-служб

    Перспектива Internet-приложений, как ее видит компания Microsoft, стала достоянием общественности. Окончательный успех инициативы, с которой выступила Microsoft, зависит от двух внешних факторов, которые не связаны со сферой программного обеспечения А именно, от степени развития инфраструктуры сети Internet и успеха предложенной модели предприятия. Вопрос о том, приобретет ли технология Web-служб широкое распространение, прямо зависит от наличия сетей с высокой пропускной способностью. Такие сети уже сейчас широкодоступны. И пропускная способность их в последующие несколько лет существенно увеличится. А вот что касается перспектив предложенной модели предприятия, то они нам пока еще неизвестны!

    Важно отдавать себе отчет в том, что технология .NET обладает гораздо более широкими возможностями, чем громко рекламируемые возможности Internet. Более устойчивая платформа, предназначенная для создания Windows-приложений, чрезвычайно мощная библиотека классов .NET Framework, а также инструментальные средства разработки — это именно те особенности технологии .NET, благодаря которым она выдержит испытание временем.
    CompEbook.ru Железо, дизайн, обучение и другие



    Глава 1. Что такое Microsoft .NET?

  • Что такое Microsoft .NET?

  • Microsoft и Web

  • Приложения в эпоху Internet
  • Web-службы

  • ASP.NET

  • Открытые стандарты и возможность взаимодействия (функциональная совместимость)

  • Протоколы обмена

  • Windows на рабочем столе

  • Проблемы с Windows

  • Стеклянный дом и тонкие клиенты
  • Устойчивая Windows

  • Новая платформа программирования

  • Каркас NET Framework

  • Общеязыковая среда выполнения CLR (Common Language Runtime)

  • Разработка приложений на разных языках

  • Инструментальные средства разработки

  • Важность инструментальных средств разработки
  • Роль языка XML

  • Факторы, определяющие успех Web-служб

  • Резюме


  • CompEbook.ru Железо, дизайн, обучение и другие

    Инструментальные средства разработки

    Настоящим ключом к успеху в разработке программного обеспечения является наличие набора эффективных инструментальных средств разработки. Компания Microsoft уже давно предлагает замечательные инструментальные средства разработки, к числу которых принадлежат Visual C++ и Visual Basic. Платформа .NET объединяет средства разработки в единую интегрированную среду, которая имеет название Visual Studio.NET.
  • Среда VS.NET обладает широкими функциональными возможностями, которые могут быть использованы при создании приложения на любом языке, поддерживаемом платформой .NET.

  • Платформа .NET позволяет использовать несколько языков программирования для написания приложений и имеет необходимые средства отладки.

  • Среда VS.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 позволяет реализовать проверку типовой безопасности и проверку надежности. Следствием этого является более устойчивое функционирование приложений.
  • Процесс создания приложении на платформе .NET значительно облегчился по сравнению с созданием приложении на основе интерфейса 32-разрядных Windows-приложений (Win32 API) или модели компонентных объектов Microsoft (COM)
  • Платформа целиком, как и некоторые ее части, может быть реализована на многих различных типах компьютеров (аналогично Java-машине).
  • Имеется единая библиотека классов, используемая всеми языками, которые поддерживает платформа .NET.
  • Приложения, написанные на различных языках программирования платформы .NET, могут быть легко интегрированы друг с другом.

  • Платформа NET имеет также несколько важных характерных особенностей, а именно:
  • каркас .NET Framework;
  • общеязыковую среду выполнения CLR (Common Language Runtime);
  • возможность разработки приложения на многих языках программирования, поддерживаемых платформой .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:
  • библиотека базовых классов. — содержит основные функциональные возмож ности, такие как строки, массивы и элементы форматирования;

  • передача данных посети;

  • система безопасности;

  • удаленная обработка;

  • диагностика;

  • ввод/вывод;

  • базы данных;

  • язык ХМ L;

  • Web-службы, которые позволяют использовать интерфейсы компонентов в любом месте Internet;

  • Web-программирование;

  • пользовательский интерфейс операционной системы Windows.

  • 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 и изучить его возможности.
    ILDASM — дисассемблер промежуточного языка Microsoft

    Рис. 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). Генерируемый при Использовании атрибутов код автоматически вставляется в конечный файл. Атрибуты используются для создания кода таких элементов:
  • интерфейсы и компоненты на основе модели компонентных объектов Microsoft (СОМ);

  • события модели компонентных объектов Microsoft (COM) (точки стыковки);

  • события унифицированной модели событий (управляемые события);


  • код ATL Server;


  • код пользователя OLE для баз данных;


  • код со счетчиками производительности;


  • входные точки модулей.


  • Хотя данная глава называется "Программирование на управляемом 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).


    Атрибуты C++

    Рис. 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.

    Атрибуты C++

    Рис. 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 (Готово).


    Атрибуты C++

    Рис. 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(Delegate:.Combine(
    // Объединение делегатов 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]; // ошибка для управляемого
    // массива }
    Управляемые массивы имеют некоторые дополнительные, по сравнению с неуправляемыми массивами, свойства и ограничения.

  • Управляемый массив можно определить только в управляемой динамически распределяемой области памяти. Его нельзя поместить вне кучи (т.е. он не может быть расположен в стеке).


  • Описание управляемого массива не должно содержать размер массива, так как это предполагало бы его размещение вне динамически распределяемой области памяти. Вместо этого размер массива указывается при использовании оператора new (создать), создающего объект-массив, содержащийся в управляемой динамически распределяемой области памяти.


  • Все управляемые массивы являются потомками класса System: :Array (Система::Массив), так что методы этого класса, как, например, Сору (Копировать), GetLength и GetType, также как и методы класса System: :Object (Система::Объект), наподобие ToString и Equals (Равняется), могут использоваться в любом управляемом массиве.


  • При попытке доступа к элементу управляемого массива производится проверка принадлежности к диапазону, т.е. контроль границ. Одна из самых распространенных ошибок — обращение к несуществующему объекту по адресу, указывающему за пределы массива. При попытке обратиться к элементу, номер которого не попадает в диапазон индексов элементов, возникает исключение IndexOutOfRangeException.


  • Следующий пример показывает, как можно использовать обработчик исключений при попытке доступа к несуществующему элементу управляемого массива. Обратите внимание, что массив содержит пять элементов, а в цикле производится попытка установить значение шестого. Программа в обычном C++ выполнила бы такое действие, изменив содержимое памяти за пределами массива. Никто не скажет точно, чем это могло бы закончиться. При проверке корректности адреса выполняются два действия: во-первых, предотвращается изменение содержимого памяти за пределами массива; во-вторых, программе сообщается, что возникла подобная ситуация, тем самым давая возможность исправить ошибку еще на стадии тестирования. В обычном C++ такая ошибка часто не проявляется до тех пор, пока программа, по непонятным причинам, не прекращает работу, обычно в месте кода, далеко отстоящем от самой ошибки. И, разумеется, согласно закону Мэрфи, эта ошибка обнаруживается только тогда, когда программа уже передана заказчику. //IndexOutOfRangeException.срр


    #using using namespace System;
    // использовать пространство имен Система/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; iLength; 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; rowGetLength(0); row++)
    //по строкам
    {
    for(int col=0; colGetLength(l) ; 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 // для wchar_t
    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 (Привет, мир):
  • Откройте Visual Studio.NET. Выберите пункт меню File => New => Project (Файл => Создать => Проект) для того чтобы открыть диалог New Project (Создание проекта).
  • Выберите пункт Visual C++ Projects (Проекты Visual C++) в списке Project Types (Типы проектов).
  • Выберите пункт Managed C++ Empty Project (Пустой проект на управляемом C++) в списке Templates (Шаблоны).
  • Введите HelloWorld (Привет, мир) в качестве названия проекта.
  • Задайте папку, в которой будет храниться проект.
  • Щелкните на ОК для того чтобы закрыть диалог New Project (Создание проекта) и завершить создание нового проекта. Добавьте исходный код:
  • Щелкните правой кнопкой на папке Source Files (Исходные файлы) в окне Solution Explorer (Поиск решений).Выберите пункт меню Add => Add New Item (Добавить => Добавить новый элемент) для того, чтобы открыть диалог Add New Item dialog (Добавить новый элемент).
  • Выберите в списке Templates (Шаблоны) пункт C++ File (Файл C++).
  • Укажите HelloWorld (Привет, мир) в качестве названия проекта.
  • Не изменяйте значение расположения (Location), принятое по умолчанию.
  • Щелкните на кнопке Open (Открыть) для того, чтобы закрыть диалог Add New Item dialog (Добавить новый элемент) и открыть Source Editor (Редактор текстов программ).
  • Введите код примера HelloWorld (Привет, мир). Скомпилируйте и запустите проект:
  • Выберите пункт меню Build => Build (Создать => Создать).
  • Используйте сочетание клавиш Ctrl-F5 для запуска программы без отладчика.


  • Директива 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).
    Программа Hotel (Гостиница)

    Рис. 3.1. Размещение в памяти прямоугольного массива
    Программа Hotel (Гостиница)

    Рис. 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 []
    Типы данных C++ и .NET Framework

    Рис. 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 (pMammal); // хорошо
    // Собака
    *pDog = _ try_cast <Собака *> (pMammal);
    Console::WriteLine("_try_cast ");
    // Собака -
    // хорошо
    Cat *pCat = _try_cast (pMammal); // плохо!
    // Кот *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(units[i]);
    // Гостиница
    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. Отношение наследования для разных классов резервируемых объектов
    Отношения между классами

    Класс может находиться в следующих отношениях с другим классом:

  • Отношение наследования (IS-A, является) определяет, что один из классов является частным случаем другого. Класс Hotel (Гостиница) (подкласс, или производный класс) является одним из видов класса Reservable (Резервируемый объект, ресурс) т.е. надклассом, или базовым классом.


  • Отношение включения (HAS-A, имеет) определяет, что один класс (целое) состоит из других классов (частей). Объект HotelBroker (целое) включает в себя список объектов типа Hotel (Гостиница).


  • Отношение использования (USES-A, использует), — более слабое отношение между классами, чем включения (HAS-A, имеет). Это отношение можно определить следующим образом. Класс X находится в отношении использования (USES-A, использует) с классом У, если при выполнении обязанностей класса А" используется класс Y, т.е. в описании класса X используется класс Y.


  • 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(pCustl);
    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(pCust2);
    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(pCustl);
    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(pCust2);
    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((p!ter->Current));
    Console::WriteLine(pStr);
    more = p!ter->MoveNext();
    }
    }
    static void ShowList(ArrayList *pArray) // статическая функция

    {
    lEnumerator *pEnum =
    pArray->GetEnumerator() ;
    while (pEnum->MoveNext() )
    {
    String *pStr =
    dynamic_cast(pEnum->Current);
    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((p!ter->Current));
    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(pob]);
    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 иллюстрирует архитектуру как сервера, так и клиента В сервере реализованы следующие методы
  • JomChat,

  • QuitChat;

  • ShowMembers

  • Когда в программе регистрируется новый участник или уходит зарегистрированный, сервер посылает сообщение об этом клиенту Обработчик соответствующего события выводит на экран надлежащее сообщение Приведем пример выдачи программы
    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 или без него, так и для неуправляемых классов и структур.

  • Присваивание значения одного указателя другому (будь то указатели на управляемые или неуправляемые классы или структуры) приводит к простому копированию указателей. Это не поверхностное, и не детальное копирование, а простое присваивание указателей.


  • Преобразование одного нессылочного (неуказательного) типа в другой, причем типы эти могут быть неуправляемыми классами, неуправляемыми структурами или значащими типами данных, является поверхностным копированием, автоматически осуществляемым C++.


  • Присваивание новому указателю значения, возвращаемого методом Clone (Клон) (конечно, это значение должно быть также указателем) и являющегося управляемым классом или структурой с реализованным интерфейсом IClone-able, представляет собой поверхностное либо детальное копирование, в зависимости от реализации метода Clone (Клон).


  • Пример программы

    Проиллюстрируем изложенный материал программой 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(pEnum->Current);
    Console::WriteLine(pName);
    }
    }
    Course *ShallowCopy() // Курс - поверхностная копия
    {
    return dynamic_cast(MemberwiseClone());
    }
    Object *Clone()
    {
    Course *pCourse = new Course(pTitle); // новый Курс
    pCourse->pRoster =
    dynamic_cast(pRoster->Clone()); // Клон

    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(pCl->Clone());
    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(MemberwiseClone());
    }
    };

    Приведем вторую часть тестовой программы, в которой происходит вызов метода 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(pRoster->Clone());
    return pCourse;
    }
    };

    Приведем третью часть программы, в которой вызывается метод Clone (Клон). Здесь мы тоже с помощью второго указателя изменяем название и добавляем в список нового студента.

    _gc class CopyDemo
    // класс сборщика мусора CopyDemo
    {
    public:
    static void Main()
    {
    Console::WriteLine(
    "\nCopy is done via pC2 = pCl->Clone()");

    InitializeCourse ();
    pC2 = dynamic_cast(pCl->Clone());
    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. Модель состоит из двух модулей:
  • Модуль Admin (Управляющий модуль) предоставляет пользовательский интерфейс для конфигурирования и запуска модели. Кроме того, в нем реализованы операции, вызываемые моделирующей машиной.

  • Модуль Engine (Машинный модуль) — это и есть моделирующая машина. В данном модуле есть внутренние часы; модуль случайным образом, в соответствии с параметрами конфигурации, генерирует информацию о совершаемых сделках.

  • На рис. 5.2 показана общая архитектура модели.
    Модель допускает выполнение следующих операций:
  • PrintTick: показывать ход часов (номер текущего шага);

  • PrintTrade: показывать все совершаемые сделки.

  • Модель содержит следующие параметры:
  • включить/выключить вывод информации о текущем шаге;

  • включить/выключить вывод информации о совершаемых сделках;

  • установить количество шагов моделирования.

  • Моделирование фондовой биржи

    Рис. 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(pEnum->Current); // Клиент
    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(pEnum->Current); // Клиент

    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[0]) != 0)
    Array::Sort(array); else
    Console::WriteLine(
    "Name does not implement IComparable");
    // (" Name (Имя) не реализует IComparable");

    lEnumerator *pEnum = array->GetEnumerator();
    while (pEnum->MoveNext())
    {
    Name *pName = // Имя
    dynamic_cast(pEnum->Current);
    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(pObj))->pText;
    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.
    Демонстрация Windows Forms (Формы Windows)

    Рис. 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).
    Демонстрация Windows Forms (Формы Windows)

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

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

    Демонстрация Windows Forms (Формы Windows)

    Рис. 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.

    Демонстрация Windows Forms (Формы Windows)

    Рис. 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. показано, где искать документацию по диалоговым окнам
    Документация по диалогам .NET

    Рис. 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
    Иерархия Windows Forms (Формы Windows)

    Рис. 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.
    Использование управляющего элемента Menu (Меню)

    Рис. 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)
    Окно конструктора (Design window) и окно кода (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 (Зарегистрировать) можно добавить нового клиента


    Acme Travel Agency) был представлен

    Рис. 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 (Резервирования)
    Acme Travel Agency) был представлен

    Рис. 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 *pStoForml() {
    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
  • выводимая строка,

  • шрифт (Font (Шрифт) — свойство класса Form (Форма), которое определяет шрифт, по умолчанию применяемый для вывода текста в форму),

  • используемая кисть,

  • координаты в пикселях (числа типа float (с плавающей точкой))

  • В качестве стандартной кисти используется черная кисть 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 (Простая форма), после удаления исходного текста и ввода нового.
    События MouseDown (Кнопка мыши нажата) и Keypress (Нажатие клавиши)

    Рис. 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 (Поле), притом весьма аккуратно.
    Использование управляющего элемента 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
    ) ;
    Событие MouseDown (Кнопка мыши нажата)

    Рис. 6.6. Документация по событиям класса Form (Форма)
    В качестве параметра обработчик событий получает объект класса Мои seEventArgs (производный от класса EventArgs) Свойства этого объекта доступны только для чтения и содержат информацию, связанную с данным событием
  • Button (Кнопка) определяет, какая кнопка была нажата,

  • Clicks (Щелчки) определяет, сколько раз была нажата и отпущена кнопка,

  • Свойство Delta (Дельта) является счетчиком оборотов колесика мыши;

  • X и Y — координаты точки, в которой находился указатель в момент нажатия кнопки мыши

  • 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 Железо, дизайн, обучение и другие

    Закрытие формы (Выход из формы)

    Давайте сделаем так, что каждый раз, когда пользователь попытается закрыть приложение, приложение будет его спрашивать, действительно ли он хочет выйти Существует несколько способов закрыть окно-
  • щелкнуть на кнопке "X" (Закрыть) в правом верхнем углу окна;

  • закрыть окно из системного меню в левом верхнем углу окна,

  • закрыть окно с помощью комбинации клавиш AU+F4,

  • выйти из приложения с помощью меню FileOExit (ФайлОВыход)

  • Когда закрывается форма, вызывается событие 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
    CAB Project (Проект 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.dll> using namespace SimpleComponent;
    // использование пространства имен 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 SharedComponent.dll>
    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" />
    newVersion="l.1.0.0" />






    Правила, определяющие политику управления версиями, находятся в разделе . XML-спецификация пространства имен необходима. Каждая сборка, политику управления версиями которой мы хотим установить, помещается в ее собственный раздел . Элемент assemblyldentity имеет атрибуты, определяющие сборку, к которой этот раздел относится. Атрибут name (имя) обязателен; атрибуты culture (культура) и publicKeyToken являются необязательными. Атрибуты элемента bindingRedirect определяют то, что версии могут отобразить на другую версию. Атрибутом oldVersion может быть диапазон; атрибут newVersion может быть установлен только на одну версию. В вышеупомянутом примере любые ссылки на версии от 1.0.0.0 до 1.1.0.0 могут быть разрешены с помощью версии 1.1.0.0. Другими словами, версия 1.1.0.0 обратно совместима со всеми указанными версиями. Можно определить несколько элементов bindingRedirect.
    Для установки файла конфигурации можно использовать Инструмент администрирования .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 CombinedAssembly.dll>
    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, где нужно попробовать найти сборку.
    Если сборка еще не найдена, выполняются динамические проверки, чтобы увидеть, была ли определена в файле конфигурации кодовая страница. В разделе можно определить элемент (<кодовая страница>). Этот элемент имеет два атрибута, — version (версия) и URI (Uniform Resource Identifier — универсальный идентификатор ресурса (вариант унифицированного указателя информационного ресурса (URL))), — используемых с целью проверки сборки. Для установки этих элементов в файле конфигурации может использоваться вкладка Codebases (Кодовые страницы) диалога свойств сборки Инструмента администрирования .NET (.NET Admin Tool). Примеры этого элемента:


    Чтобы использовать элемент 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 (Редактор файловой системы)
    Проект установки (Setup Project)

    Рис. 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 могут динамически развернуть сборки, а также файлы конфигурации. При развертывании по сети затребованные сборки загружаются в глобальный кэш загрузки сборок клиента.
    Тэг (<объект>) используется для загрузки и установки сборок по относительному или абсолютному унифицированному указателю информационного ресурса (URL). В приведенном ниже примере используется относительный унифицированный указатель информационного ресурса (URL), поэтому сборка располагается относительно того каталога Web-сервера, в котором содержится файл HTML:
    id="SomeComponent"
    classid="./SomeDirectory/MyComponent.dll#SomeClass">


    В приведенном ниже примере используется абсолютный унифицированный указатель информационного ресурса (URL), поэтому сборка располагается на указанном Web-сервере:
    id="SomeComponent"
    classid="http://www.acme.com/MyComponent.dll#SomeClass">


    По умолчанию IE создает отдельную прикладную область для каждого встреченного им Web-узла. Прикладная область— средство .NET, напоминающее масштабируемый упрощенный процесс. Прикладная область эффективно обеспечивает изоляцию ошибки без накладных расходов, неизбежных при выполнении множества настоящих процессов. Каждая из этих прикладных областей может при необходимости иметь свой собственный файл конфигурации, чтобы управлять связыванием и защитой. Кроме того, в файле конфигурации можно определить изолированную прикладную область для индивидуальных приложений на том же самом Web-сервере. Каждый файл HTML, который определяет тот же самый файл конфигурации, будет помещен в ту же самую прикладную область.
    CompEbook.ru Железо, дизайн, обучение и другие

    и составляет одну из главных

    Развертывание очень важно, и составляет одну из главных стадий в разработке программного обеспечения. Если оно не выполнено должным образом, все усилия, потраченные на разработку, становятся пустой тратой времени и денег. К счастью, благодаря сборкам, которые в .NET являются основными единицами развертывания, легко решаются многие сложные проблемы, связанные с развертыванием. Просто создавая динамически загружаемые библиотеки, вы можете получить многократно используемые компоненты без всяческих трудностей, связанных с созданием и установкой компонентов, построенных на основе модели компонентных объектов Microsoft (COM).
    Частное развертывание сборки представляет собой не что иное, как просто копирование сборки в тот же самый каталог, в котором расположено приложение-клиент. Для общедоступного развертывания сборки нужно создать строгое имя сборки, а затем развернуть ее в глобальном кэше сборок. Утилита Strong Name (Sn. ехе) создает строгое имя сборки. Потом утилита Gacutil.exe (Global Assembly Cache utility) или .NET Admin Tool (Инструмент администрирования .NET) может использоваться для того, чтобы развернуть общедоступную сборку в глобальном кэше сборок. Кроме того, можно объединить несколько модулей в единую логическую сборку. Чтобы это сделать, необходимо создать отдельные модули без информации, относящейся к декларации, а затем можно сгенерировать модуль декларации для всех модулей, которые включаются в сборку. Мастера CAB и установки, имеющиеся в среде разработки Visual Studio.NET, очень полезны для того, чтобы создать установку стартера и проекты развертывания, — это позволяет сэкономить много времени, которого всегда не хватает на разработку большой системы.
    CompEbook.ru Железо, дизайн, обучение и другие

    Сборки

    Сборки положены в основу технологии компонентов .NET. Сборка — основная единица развертывания и управления разрешениями защиты, версиями, а также повторным использованием двоичного кода. Сборка содержит двоичный выполнимый код, составленный из команд управляемого промежуточного языка 1L (Intermediate Language), a также метаданных, которые полностью описывают содержимое сборки. Сборки могут также содержать данные ресурса и быть упакованы в виде динамически подключаемой библиотеки (DLL) или ЕХЕ-файлов. Сборка может быть составлена из одного или нескольких физических файлов на диске, но это — все равно одна логическая единица развертывания. Сборки, как говорят, описывают себя сами, так как они содержат в метаданных информацию о себе, и поэтому для их установки не требуется никакой внешней информации, например, в системном реестре. Это делает .NET-компоненты намного более простыми и менее подверженными ошибкам при установке и деинсталляции, чем традиционные компоненты, построенные на основе модели компонентных объектов Microsoft (СОМ), которые имели обширные требования к информации в системном реестре. Мало того, что сборки описывают себя сами, они также содержат и хэш-код, представляющий двоичное содержимое сборки. Этот хэш-код может использоваться для того, чтобы подтвердить подлинность и обнаружить тайные изменения или искажение сборок с цифровой подписью.
    Прежде чем развернуть сборку в глобальном кэше сборок, ее сначала необходимо подписать в цифровой форме. Сборка с цифровой подписью имеет криптографически сгенерированную информацию, которую общеязыковая среда выполнения CLR может использовать для проверки, чтобы выполнить важные правила зависимости при размещении и загрузке сборок. Простая проверка версии гарантирует, что клиентская программа правильно использует общедоступные сборки с учетом тех их версий, которые были определены при первоначальной компиляции и тестировании клиента. Благодаря этому можно на самом деле устранить жуткую проблему "ада динамически подключаемых библиотек (DLL)", где клиенты и компоненты, построенные на основе модели компонентных объектов Microsoft (COM), могли легко "рассинхронизироваться" друг с другом, если старая версия заменялась более новой, — это нарушало работу существующих клиентов. Сборка с цифровой подписью может также проверить, что никакой неправомочный человек не изменил общедоступное содержимое сборки после того, как была поставлена цифровая подпись. Такая проверка гарантирует не только то, что вы не сможете случайно использовать неправильную версию, но также, что вы не будете злонамеренно обмануты в результате использования компонента, который мог бы причинить серьезный вред.
    Хотя часто имеется взаимнооднозначное соответствие между пространством имен и сборкой, сборка может содержать множественные пространства имен, причем одно пространство имен может использоваться несколькими сборками. Точно так же часто существует взаимнооднозначное соответствие между сборкой и файлом двоичного кода (т.е. динамически подключаемой библиотекой (DLL) или исполняемым файлом). Однако одна сборка может содержать несколько файлов двоичного кода. Помните, что сборка — единица развертывания, пространства имен поддерживают иерархическую систему именования, а динамически подключаемая библиотека (DLL), или исполняемый файл— единица упаковки функциональных возможностей в пределах файловой системы. Важно также понимать разницу между сборкой, которая является единицей развертывания, и приложением, которое является единицей конфигурации.
    Сборка без подписи идентифицируется просто удобочитаемым названием (именем), а также номером версии. Сборка с цифровой подписью идентифицируется также по имени ее создателя, однозначно определяемому по криптографической паре (ключей). При необходимости сборка может также включать код культуры для того, чтобы поддерживать специфические для данной культуры наборы символов и форматы строк.
    CompEbook.ru Железо, дизайн, обучение и другие

    Сборки и развертывание

    Именно благодаря операции развертывания тяжелая работа программиста становится доступной пользователю. Сборки .NET делают развертывание намного более простым и намного более надежным, чем традиционное развертывание Windows. Частное развертывание сборки столь же просто, как копирование компонента сборки в тот же самый каталог, в котором расположена клиентская программа. А общедоступное развертывание сборки регистрирует компонент с уникальным именем (известным как строгое имя) в глобальном кэше сборок, благодаря чему сборка становится доступной для общего использования.
    Эта глава начинается с обсуждения понятий сборки, которая является основной единицей развертывания при частном развертывании сборки в .NET. Затем описано и общедоступное развертывание сборки. Управление версиями и цифровое подписание сборок обсуждаются в контексте общедоступного развертывания. После этого на примере программы Hotel (Гостиница) демонстрируется инсталляция и приемы развертывания сборок. Наконец, представлены развертывание Visual Studio.NET и мастера установки. По ходу обсуждения мы иллюстрируем множество полезных инструментальных средств, которые являются частью комплекса инструментальных средств разработки программ .NET Framework SDK.
    CompEbook.ru Железо, дизайн, обучение и другие

    Содержимое сборки

    Утилита lldasm.exe может использоваться для просмотра содержимого сборки, чтобы лучше понять, как работают управление версиями, цифровое подписание и развертывание. Сначала нам понадобится сборка для экспериментирования. Здесь поможет компонент SimpleComponent, созданный с помощью Visual Studio.NET. Для его создания мы в диалоге New Project (Новый проект) выбрали шаблон managed C++ Class Library (Управляемый C++ на основе Библиотеки классов). Название проекта — SimpleComponent, а в следующих исходных файлах приведена его реализация, которая только немного изменяется в зависимости от кода, сгенерированного мастером. Вспомните, что важно объявить метод AddEmUp общедоступным, чтобы он был видим для кода вне данной сборки.
    //Assemblylnfо.cpp
    #include "stdafx.h" // имеет #using
    using namespace System::Reflection;
    // использование пространства имен Система::Отражение;
    using namespace System::Runtime::CompilerServices;
    // использование пространства имен
    // Система::Время выполнения::CompilerServices;
    [_assembly::AssemblyTitleAttribute("")];
    [_assembly: : AssemblyDescriptionAttnbute ( "" ) ] ;
    [_assembly::AssemblyConfigurationAttribute("")];
    [_assembly: : AssernblyCornpanyAttribute ( "") ] ;
    [_assembly::AssemblyProductAttribute("")];
    [_assembly::AssemblyCopyrightAttribute("")];
    [_assembly::AssemblyTrademarkAttribute("") ] ;
    [_assembly::AssemblyCultureAttribute("")];
    [_assembly::AssemblyVersionAttribute("1.0.*")];
    [_assembly::AssemblyDelaySignAttribute(false) ] ;
    [_assembly::AssemblyKeуFileAttribute("")];
    t_assembly::AssemblyKeyNameAttribute("")];
    //SimpleComponent.cpp
    #include "stdafx.h" // имеет #using
    #include "SimpleComponent.h"
    // SimpleComponent.h
    using namespace System;
    // использование пространства имен Система;
    namespace SimpleComponent
    // пространство имен SimpleComponent
    {
    public _gc class SomeClass
    // класс сборщика мусора SomeClass
    {
    public: // должен быть общедоступен, чтобы сборка могла
    // предоставлять его для использования
    int AddEmUp(int i, int j)
    {
    return i+j;
    }
    };
    }

    Как только вы создали сборку компонентов .NET, содержащую вышеупомянутый код и скомпоновали ее, вы можете с помощью Ildasm.exe просмотреть ее содержимое, включая декларацию. Декларация содержит следующую информацию о сборке:

  • сборка идентифицируется по имени, версии, культуре, и, при необходимости, по цифровой сигнатуре (подписи);


  • список файлов, которые составляют содержимое сборки;


  • список других сборок, от которых зависит данная сборка;


  • список разрешений, требуемых сборкой для ее выполнения.


  • Все это можно увидеть с помощью утилиты Ildasm Чтобы ее запустить, выполните следующую команду Результат выполнения показан на рис. 7.1.

    Ildasm SimpleComponent.dll

    Чтобы увидеть декларацию сборки SimpleComponent, выполните двойной щелчок на узле MANIFEST (МАНИФЕСТ, ДЕКЛАРАЦИЯ), который виден на рис. 7.1. Информация из манифеста показана на рис. 7.2.
    Декларация содержит информацию о зависимостях и содержимом сборки Видно, что декларация сборки SimpleComponent содержит, среди других, следующую внешнюю зависимость

    .assembly extern mscorlib
    {
    .publickeytoken = (B7 7A 5C 56 19 34 EO 89 )
    .hash = (09 BB ВС 09 EF 6D 9B F4 F2 CC IB 55 76 A7 02 91
    22 88 EF 77 )
    .ver 1:0:2411:0
    }

    Инструкция метаданных .assembly extern mscorlib указывает, что сборка SimpleComponent использует (и поэтому зависит от нее) стандартную сборку mccorlib.dll, необходимую для всего управляемого кода Сборка mscorlib — общедоступная сборка, которую можно найти в каталоге \WINNT\Assembly с помощью Проводника Windows (Windows Explorer) Эта зависимость появляется в метаданных SimpleComponent.dll благодаря оператору fusing в первоначальном исходном тексте (Stdafxh) Если бы другой оператор #using добавлялся для другой сборки, например fusing , то декларация содержала бы также соответствующую инструкцию зависимости .assembly extern System. WinForms

    Содержимое сборки

    Рис. 7.1. Ildasm вле показывает содержимое SimpleComponent.dll

    Инструкция метаданных .publickeytoken = (В7 7А 5С 56 19 34 ЕО 89 ) указывает общедоступную лексему (маркер) открытого ключа, являющуюся хэш-кодом открытого ключа, который ставится в соответствие своему секретному ключу, принадлежащему автору сборки mscorlib Эта лексема открытого ключа на самом деле не может использоваться непосредственно, чтобы подтвердить подлинность автора mscorlib Однако первоначальный открытый ключ, указанный в декларации mscorlib может использоваться для того, чтобы математически проверить, что секретный ключ на самом деле совпадает с тем, который действительно применялся при цифровом подписании сборки mscorlib Поскольку mscorlib.dll создала Microsoft, лексема открытого ключа, приведенная выше, принадлежит Microsoft Конечно, соответствующий секретный ключ — тщательно охраняемая корпоративная тайна, и, как полагает большинство экспертов в области защиты, такой секретный ключ практически очень трудно определить по открытому ключу Однако нет никакой гарантии, что некий математический гений не найдет когда-нибудь хитроумный способ делать это проще.
    Как мы вскоре увидим, инструкция . publickeytoken присутствует в декларации клиентской сборки только в случае, когда сборка, на которую есть ссылка, имеет цифровую подпись (На самом деле все сборки, предназначенные для общедоступного развертывания, должны иметь цифровую подпись) Microsoft подписала в цифровой форме стандартные сборки NET, такие KaKmscorlib.dll и System.WinForms.dll принадлежащими ей секретными ключами Именно поэтому лексема открытого ключа для многих общедоступных сборок, содержащихся в каталоге \WlNNT\Assembly, имеет то же самое повторяющееся значение Создаваемые другими производителями сборки с цифровой подписью подписаны их собственными, отличными от приведенного выше, секретными ключами, и они будут иметь отличную от приведенной выше лексему открытого ключа в декларациях их клиентской сборки Позже вы научитесь создавать ваши собственные криптографические пары секретного и открытого ключа и сможете подписывать собственные сборки цифровой подписью для их развертывания через глобальный кэш сборок.


    Содержимое сборки

    Рис. 7.2. Ildasm. ехе показывает декларацию SimpleComponent.dll

    Декларация .publickeytoken

    Чтобы сэкономить память, декларация . publickeytoken содержит только самые младшие 8 байтов кэш-кода открытого ключа производителя (он состоит из 128 байтов), вычисленного с помощью алгоритма SHA1. Однако, несмотря на это, она все же может использоваться для довольно надежной проверки А вот декларация .publickey содержит полный открытый ключ. Конечно, она занимает больше места, но именно поэтому злодеям труднее найти секретный ключ, который соответствует полному открытому ключу.
    Важно обратить внимание, что, хотя цифровой ключ уникален, он сам по себе не может идентифицировать фактического автора конкретного модуля Однако разработчик сборки может использовать утилиту signcode, чтобы добавить цифровое свидетельство, которое может идентифицировать издателя сборки А если зарегистрировать цифровое свидетельство у Certificate Authority (Полномочного свидетеля), например у VenSign, то пользователи смогут установить надежность источника.
    Инструкция метаданных .hash = (09 ВВ ВС 09 ... 77 ) обеспечивает фиксированный размер представления хэш-кода двоичного содержимого mscor lib. dll Если бы содержимое изменилось, в результате изменился бы и этот хэш-код Несмотря на то, что хэш-код имеет компактное представление, он с высокой вероятностью характеризует сборку Поэтому вычисленный на основе первоначальных данных, он может использоваться для многих целей, включая обнаружение ошибок и проверку Хэш-код для сборки mscorlib, показанной выше, с высокой вероятностью характеризует двоичные данные сборки mscorlib Это означает, что, если бы содержимое mscor I ib. dll было изменено случайно, преднамеренно, или даже злонамеренно, то, с астрономически высокой вероятностью, новый хэш-код не совпадал бы со старым, и изменение было бы обнаружено по хэш-коду Как описано далее в разделе по цифровому подписанию сборок, секретный ключ используется для того, чтобы гарантировать, что только уполномоченный человек может зашифровать хэш-код, и это используется для подтверждения (проверки) подлинности всей сборки
    Инструкция метаданных .ver 1:0:2411:0 указывает версию сборки mscorlib Формат спецификации этой версии— Major .Minor :Build:Revision (Главный Младший Компоновка Пересмотр) Через какое-то время, когда будут выпущены новые версии этой сборки, существующие клиенты, которые были скомпонованы так, чтобы использовать данную версию, продолжат использовать именно данную версию, по крайней мере те версии, у которых совпадают значения главного и младшего номера версий Более новые клиентские программы, конечно, смогут обратиться к более новым версиям этой сборки, поскольку именно для них станут доступными новые версии Старые и новые версии могут быть развернуты буквально рядом посредством кэша глобальных сборок, и быть одновременно доступны старым и новым клиентским программам Обратите внимание, что версия 1:0:2411:0, появляющаяся в клиентской декларации, принадлежит текущей версии сборки mscorlib и не связана с атрибутом версии 1.0.*, указанным в исходном тексте SimpleComponent Вскоре мы более подробно рассмотрим четыре поля, которые составляют номер версии, а также управление версиями сборки
    До сих пор мы сосредотачивались на зависимостях, которые определены в декларации сборки SimpleComponent Теперь давайте подробнее рассмотрим информацию в декларации, описывающую компонент SimpleComponent, содержащийся в сборке. Обратите внимание, что эта сборка не имеет цифровой подписи, и поэтому не содержит информации о ее создателе (т е , открытый ключ она в себе не содержит)


    .assembly SimpleComponent
    {
    .hash algorithm 0x00008004
    .ver 1:0:584:39032
    }

    Директива .assembly

    Директива . assembly объявляет декларацию и определяет, какой сборке принадлежит текущий модуль. В данном примере директива . assembly определяет SimpleComponent (в качестве имени сборки. Именно это имя (вместе с номером версии и, возможно, открытым ключом), а не имя динамически подключаемой библиотеки (DLL) или (.исполняемого файла, используется во время выполнения для определения принадлежности сборки. Обратите также внимание, что, если сборка подписана, то в |директиве .assembly будет определен параметр .publickey. Директива .assembly декже указывает, добавлялись ли какие-либо пользовательские атрибуты к метаданным. Инструкция метаданных .assembly SimpleComponent указывает, что имя ысборки — SimpleComponent. Имейте в виду, что это — не имя класса компонента в рЬборке, а само имя сборки.

    Алгоритмы хэширования

    Алгоритм хэширования— математическая функция, которая берет первоначальные Ввходные данные произвольной длины и генерирует хэш-код, также известный как щрофиль сообщения, который представляет собой двоичный результат установленной |длины Эффективная хэш-функция — односторонняя (однонаправленная) функция, |При использовании которой коллизии возникают очень редко, а ее результат имеет рЬтносительно маленькую установленную длину. Идеальная хэш-функция также легко вычислима. Односторонняя функция— функция, не имеющая такой обратной, с помощью которой можно было бы фактически быстро вычислить первоначальные. Данные по значению хэш-кода. Фраза "коллизии возникают очень редко" означает, что вероятность того, что по двум первоначально различным входным данным будет сгенерирован тот же самый хэш-код, является очень маленькой, и мала вероятность вычисления двух отличающихся входных данных, которые приводят к тому же самому рначению хэш-кода Известные алгоритмы хэширования MD5 и SHA1, как полагают, являются превосходным выбором для использования в цифровом подписании, и оба они поддерживаются в .NET.
    Инструкция .ver 1:0:584:39032 указывает окончательную версию сборки SimpleComponent, которая определена частично атрибутом AssemblyVersionAttribute в исходном тексте компонента. Управление версиями более подробно описано в следующем подразделе.


    Управление версиями сборки

    Как мы только что видели, декларация сборки содержит версию сборки, а также версии каждой из сборок, от которых она зависит. Детальный набор правил, используемых общеязыковой средой выполнения CLR для того, чтобы определить зависимости версии, называют политикой управления версиями. Заданная по умолчанию политика управления версиями определена зависимостями, указанными в декларациях сборки, но при необходимости ее можно изменить в файле конфигурации приложения или в общесистемном файле конфигурации. Автоматическая проверка версии выполняется общеязыковой средой выполнения CLR только на сборках со строгими именами (то есть, на сборках с цифровой подписью). Однако, каждой сборке, независимо от того, как она развернута, должен быть назначен номер версии. Номер версии сборки состоит из следующих четырех полей.

  • Главная версия (Major version): Главные несовместимые изменения.


  • Младшая версия (Minor version): Менее значительные несовместимые изменения.


  • Номер компоновки (Build number): Обратно совместимые изменения, сделанные в ходе разработки.


  • Пересмотр (Revision): Обратно совместимые изменения, сделанные в ходе текущего быстрого исправления (Quick Fix Engineering, QFE).


  • Вышеупомянутые соглашения относительно назначения каждого поля номера версии не предписаны общеязыковой средой выполнения CLR. Именно программист устанавливает эти или любые другие соглашения при проверке совместимости сборки и определении политики управления версиями в файле конфигурации, который мы обсудим позже в этой главе.
    Традиционно изменение значения главного или младшего номера указывает явную несовместимость с предыдущей версией. Это используется при существенных изменениях в новом выпуске сборки, и существующие клиенты не могут использовать новую версию. Изменения номера компоновки подразумевают совместимость вниз, и этот номер обычно изменяется каждый раз при очередной компоновке сборки в ходе разработки. Совместимость вниз между номерами компоновки является намеренной; однако, этого, очевидно, не может гарантировать общеязыковая среда выполнения CLR, и потому данное свойство должно быть проверено. Изменение номера пересмотра относится к изменениям, сделанным в ходе текущего быстрого исправления (Quick Fix Engineering, QFE). Это поле обычно используется для срочного исправления, которое общеязыковой средой выполнения CLR считается обратно совместимым, если в файле конфигурации не указано иное назначение этого поля. И снова, общеязыковая среда выполнения CLR не может гарантировать, что изменение, сделанное в ходе текущего быстрого исправления (Quick Fix Engineering, QFE), на 100 процентов обратно совместимо, но совместимость вниз желательна и должна быть тщательно проверена.
    Информация, относящаяся к версии, может быть определена в исходном тексте, в атрибуте _assembly::AssemblyVersionAttribute. Класс AssemblyVersionAttribute определен в пространстве имен System: :Runtime: :CompilerServices (Система:: Время выполнения::СотрПег5ешсе5). Если этот атрибут не используется, в декларации сборки по умолчанию задается номер версии 0.0.0.0, который, вообще говоря, является признаком небрежности. В проекте, созданном Мастером проектов на управляемом C++ на основе Библиотеки классов (managed C++ Class Library project wizard), исходный файл Assemblylnfo.cpp автоматически генерируется с версией 1.0.*, т.е. главная версия равна 1, младшая версия — 0, причем значения пересмотра и компоновки генерируются автоматически. Если изменить AssemblyVersionAttribute на, например, "1.1.0.0", как показано ниже, то номер версии, отображенный в декларации, изменится, и будет равен 1:1: 0:0.


    //Assemblylnfо.срр
    #using
    [_assembly::AssemblyVersionAttribute("1.1.0.0")];

    Чтобы не указывать значений пересмотра и компоновки, можно использовать символ звездочка (*). Когда вы вообще определяете какой-либо номер версии, вы должны, как минимум, определить главный номер. Если вы определяете только главный номер, остающиеся значения будут по умолчанию иметь значение нуль. Если вы определяете также младшее значение, то можете опустить оставшиеся поля, которые по умолчанию будут обнулены, или можете указать звездочку, тогда значения будут сгенерированы автоматически. Звездочка в данном случае означает, что значение компоновки будет равняться количеству дней, прошедших с 1 января 2000 года, а значение пересмотра будет установлено равным количеству секунд, прошедших с полуночи, деленному на 2. Если вы определяете значения главного и младшего номеров, а также номера компоновки, причем указываете звездочку для значения пересмотра, то только номер пересмотра будет равен количеству секунд, прошедшему с полуночи, деленному на 2. Когда все четыре поля указаны явно, все четыре значения будут отражены в декларации. Следующие примеры показывают правильные (допустимые) спецификации версии.

    Если указать звездочку, то версия автоматически будет изменяться каждый раз при компоновке компонента; однако каждая новая версия считается обратно совместимой, так как главные и младшие номера не изменяются автоматически. Чтобы определить новую обратно несовместимую версию, вы должны явно изменить главный и/или младший номер версии.

    CompEbook.ru Железо, дизайн, обучение и другие

    Строгие имена

    Гарантируется, что строгое имя будет глобально уникальным для любой версии любой сборки. Строгие имена генерируются тогда, когда сборка получает цифровую подпись. Это гарантирует, что строгое имя не только уникально, но и может быть сгенерировано только индивидуумом, который имеет секретный ключ.
    Строгое имя состоит из простого текстового имени, открытого ключа и хэш-кода, зашифрованного соответствующим секретным ключом. Хэш-код также называется профилем сообщения, а зашифрованный хэш-код— цифровой подписью, электронной подписью и цифровой сигнатурой. Хэш-код фактически эффективно идентифицирует двоичное содержимое сборки, а цифровая сигнатура (подпись) фактически эффективно идентифицирует автора сборки. Все сборки, имеющие одно и то же строгое имя, считаются идентичными (при определении идентичности во внимание принимаются также номера версии). Сборки, строгие имена которых отличаются друг от друга, считаются различными. Полагают, что строгое имя является криптографически стойким, поскольку в противоположность простому текстовому имени, оно однозначно определит сборку на основании ее содержимого и секретного ключа ее автора. Строгое имя имеет следующие полезные свойства:
  • гарантирует уникальность, основанную на технологии кодирования;

  • устанавливает уникальное пространство имен, основанное на использовании секретного ключа;

  • препятствует неправомочному персоналу изменять сборку;

  • не позволяет неправомочному персоналу управлять версиями сборки;

  • позволяет общеязыковой среде выполнения CLR выполнять проверку содержимого кода общедоступных сборок.

  • CompEbook.ru Железо, дизайн, обучение и другие

    Управление версиями общедоступных компонентов

    Как мы видели ранее, чтобы создать новую версию сборки, нужно просто изменить номер версии, который состоит из следующих четырех полей: главный (major) номер, младший (minor) номер, номер компоновки (build) и номер пересмотра (revision). He забывайте, что поля главного и младшего номеров указывают изменения версии, которые не являются обратно совместимыми. Что случается, когда вы устанавливаете две несовместимые версии одной и той же сборки? Чтобы увидеть полученный эффект, попробуйте изменить главный или младший номер версии сборки SharedComponent, которую в предыдущем подразделе мы развернули в глобальном кэше сборок. Ранее была развернута версия 1.0.584.42238 Поэтому, если вы создадите новую версию, у которой младший номер заменен (т. е. версию 1:1 *.*), и развернете ее в глобальном кэше сборок, то с помощью Проводника Windows (Windows Explorer) вы увидите, что доступны обе версии — с различными номерами версий, естественно (рис. 7.6).
    Управление версиями общедоступных компонентов

    Рис. 7.6. Проводник Windows (Windows Explorer) показывает, что в глобальном кэше сборок рядом расположены разные версии одного и того же компонента
    CompEbook.ru Железо, дизайн, обучение и другие

    Установка и развертывание проектов

    Сборки могут быть развернуты как обычные автономные файлы, содержащие двоичный код (т.е. динамически подключаемые библиотеки (DLL) или исполняемые файлы), либо при этом могут использоваться файлы CAB, MSI, или MSM. Файл CAB — файл с расширением . cab для имени файла. Он используется, чтобы сжать и объединить другие файлы в один удобный управляемый файл. Хотя файлы CAB могут использоваться для общих целей, они традиционно используются для инсталляции с компакт-диска и загрузки из Web. Файлы MSI и MSM — файлы инсталлятора Microsoft Windows Installer; имена таких файлов имеют расширения .msi и .msm. Файлы MSI (и, косвенно, файлы MSM) используются программой инсталляции в Windows— Msiexec.exe — для того, чтобы развернуть автономные приложения и компоненты многократного использования.
    Файлы MSI — пакеты инсталляции программы Microsoft Windows Installer; имена таких файлов имеют расширение .msi. Файлы MSM — модули слияния; имена таких файлов имеют расширение . msm. Инсталлятор Windows (Windows Installer) поддерживает инсталляцию программ, исправление, обновление и удаление. Пакеты инсталлятора Windows (Windows Installer) — независимые (отдельные) файлы базы данных, содержащие информацию об инсталляции, используемую службой инсталлятора Windows (Windows Installer). Несколько упрощая реальное положение дел, можно сказать, что файл MSM имеет внутреннюю структуру, которая подобна структуре файла MSI. К сожалению, инсталлятор Windows (Windows Installer) не может использовать файл MSM непосредственно, так как ему нужны некоторые важные таблицы базы данных. Поэтому для использования в фактическом инсталляционном сеансе файл MSM должен быть слит (объединен с другими файлами) в файл MSI Однако файлы MSM полезны для того, чтобы выделить общедоступную инсталляционную информацию в независимый пакет, который может быть затем слит (объединен с другими) во многие другие пакеты MSI
    Инсталляцию может выполнять инсталлятор Windows (Windows Installer) или Internet Explorer Кроме того, чтобы выполнить инсталляцию, можно просто вручную скопировать сборки и входящие в их состав файлы Чтобы помочь программисту разработать установку и решения развертывания, в Visual Studio NET предусмотрено несколько шаблонов и мастеров, генерирующих проекты установки стартера Эти инструментальные средства доступны в диалоговом окне New Project (Новый проект) под узлом Setup and Deployment Projects (Установка и Развертывание проектов), рис. 7.12 Как видно из рисунка, для того, чтобы генерировать установку стартера и проекты развертывания, предусмотрены следующие шаблоны
  • Cab Project (Проект Cab),

  • Setup Project (Проект установки),

  • Setup Wizard (Мастер установки),

  • Merge Module Project (Проект модуля слияния),

  • Web Setup Project (Проект установки по Сети)

  • CompEbook.ru Железо, дизайн, обучение и другие

    Architecture Net

    Активация

    Объекты активизируются на стороне клиента одним из трех способов, используя класс Activator (Активатор, Модуль активизации).
  • Activator: :GetObject используется для получения ссылки на активизированный сервером объект.

  • Activator: :Createlnstance используется для создания активизированного пользователем объекта. Параметры для конструктора можно передать, используя один из перегруженных методов Createlnstance, который принимает массив объектов для передачи их конструктору.

  • Синтаксис оператора new (создать) языка C++ может использоваться для создания активизированного сервером или клиентом объекта. Для описания способа применения new (создать) используется конфигурационный файл.

  • CompEbook.ru Железо, дизайн, обучение и другие

    Асинхронное программирование

    .NET поддерживает шаблон проектирования для асинхронного программирования. Этот шаблон используется во многих местах .NET (включая операции ввода-вывода, как было отмечено ранее, и как будет более подробно показано в главе 11 "Web-службы"). Асинхронное программирование может вызывать метод без блокирования вызывающей программы метода. С точки зрения клиента использовать асинхронную модель проще, чем потоки. Однако она предоставляет гораздо меньше возможностей управления синхронизацией по сравнению с использованием синхронизирующих объектов. Поэтому разработчики классов, вероятно, сочтут, что использовать потоки намного легче.
    CompEbook.ru Железо, дизайн, обучение и другие

    Асинхронные шаблоны проектирования

    Такой шаблон проектирования состоит из двух частей: набора методов и интерфейса lAsyncResult. Методы шаблона имеют следующий вид:
    lAsyncResult *BeginXXX(
    [InputParams], AsyncCallback *cb, Object *AsyncObject)
    [ReturnValue] EndXXX([OutputParams], lAsyncResult *ar);
    В шаблоне проектирования XXX обозначает реальный метод, который вызывается асинхронно (например, BeginRead/EndRead для класса System: : IO::FileStream). BeginXXX должен принимать те же входные параметры, что и его синхронный аналог (in, in/out и ref), а также параметры AsyncCallback и AsyncObject. В сигнатуре EndXXX должны присутствовать все выходные параметры (ref, out, in/out) синхронной версии. Этот метод должен возвратить любой объект или значение, которое возвращает синхронная версия данного метода. Он должен также иметь параметр lAsyncResult. Может быть предусмотрен и метод Cancelxxx, если в нем имеется необходимость.
    AsyncCallback — делегат, который представляет функцию обратного вызова. public _delegate void AsyncCallback(lAsyncResult *ar);
    AsyncObject доступен из lAsyncResult. Реализовано это так, что в функции обратного вызова можно определить, во время какого асинхронного чтения был сделан обратный вызов.
    Каркас использует данный шаблон таким образом, чтобы синхронный метод Read (Чтение) класса FileStream можно было использовать асинхронно. Ниже приведено описание синхронного метода FileStream: : Read:
    int Read( // Чтение
    _in unsigned char* array _gc[],
    int offset, int count);
    // int смещение, int счетчик);
    А вот асинхронная версия, используемая в шаблоне проектирования:
    lAsyncResult *BeginRead(
    _in unsigned char* array _gc[],
    int offset, int numBytes,
    // смещение,
    AsyncCallback *userCallback,
    Object *stateObject); // Объект
    int EndRead(lAsyncResult *asyncResult);
    Любое исключение, которое запускается в BeginXXX, должно быть запущено до начала выполнения асинхронных операций. Каждое исключение, связанное с асинхронными операциями, должно быть запущено из метода EndXXX.
    CompEbook.ru Железо, дизайн, обучение и другие

    ContextBoundObject

    Класс Broker (Брокер) должен быть производным от класса ContextBoundObject, чтобы во время выполнения можно было определить, требуется ли установить новый контекст. Если бы класс Broker (Брокер) не был производным от класса ContextBoundObject, то мы опять получили бы конфликт при организации поточной обработки: оба клиента смогли бы зарезервировать один и тот же единственный последний номер гостиницы. Это произошло бы даже несмотря на то, что класс был бы помечен атрибутом Synchronization (Синхронизация). А объект, не являющийся производным от ContextBoundOb j ect, сможет работать в любом контексте (подвижной объект).
    Поскольку другие контексты работают с заместителями или ссылками на реальный объект, вызовы из одного контекста в другой необходимо преобразовывать (приспосабливать) во время выполнения. Поэтому ContextBoundObject является производным от MarshalByRefObject. MarshalByRefOb]ect — это базовый класс для объектов, которые нужно адресовать по ссылке.
    Одно из преимуществ технологии синхронизации с использованием мониторов Monitor (Монитор), состоит в том, что Monitor (Монитор) может быть вызван из любого контекста. С другой стороны, потенциальный недостаток автоматической синхронизации — падение производительности из-за использования маршалинга и заместителей, по сравнению с работой с реальными объектами.
    Как выяснится далее при обсуждении прикладных областей, благодаря тому, что объект клиента не зависит от контекста, именно он, а не заместитель, является тем реальным объектом, к которому осуществляется доступ. Он может быть скопирован в любой контекст внутри одной и той же прикладной области.
    CompEbook.ru Железо, дизайн, обучение и другие

    Динамическое связывание

    Отражение может также использоваться для реализации динамического связывания. Динамическое связывание состоит в том, что метод, который нужно вызвать, определяется в процессе выполнения, а не на этапе компиляции. Это один из примеров того, как метаданные используются для предоставления функциональных возможностей. Предыдущий пример демонстрирует, как получить сигнатуру метода, связанного с типом. Объект Methodlnf о содержит все необходимые метаданные для метода класса. Пример Dynamic (Динамический) демонстрирует очень простой случай динамического связывания.
    Мы динамически загружаем сборку и получаем метаданные для метода определенного типа:
    // Загрузить (Load) сборку Customer (Клиент)
    Assembly *a = Assembly::Load("Customer") ;
    // Загрузка ("Клиент")
    // Получить метаданных для класса Customers (Клиенты)
    // и одного метода
    Type *t = a->GetType("01.NetCpp.Acme.Customers"); // Клиенты
    Methodlnfо *mi = t->GetMethod("GetCustomer");
    Вот что следует помнить программистам при программировании на C++. Работая со строками, которые содержат пространства имен или классы, необходимо использовать должным образом отформатированные строки, которые понятны методам класса отражения. Например, полностью определенное имя класса в вышеописанном коде — OI.NetCpp.Acme.Customers, а не OI::NetCpp::Acme::Customers, отформатированное в стиле C++. Таким образом, используемый формат подобен формату в С#, а не в C++.
    Применяя классы отражения, мы могли бы сделать все это полностью динамически, произвольно выбирая типы, методы, и конструкторы из сборки Customer (Клиент), используя методы последнего примера, но мы хотели сохранить пример Dynamic (Динамический) простым. Более честолюбивая программа могла бы делать что-нибудь гораздо более интересное, вроде реализации декомпилятора сборки, который непосредственно из откомпилированной сборки генерирует исходный текст на управляемом C++, С#илиУВ.НЕТ.

    Пространство имен System (Система) содержит класс Activator (Активатор, Модуль активизации), в котором перегружается метод Createlnstance, предназначенный для создания экземпляров любого типа .NET с помощью подходящего конструктора. Класс Activator (Активатор, Модуль активизации) рассматривается в этой главе в разделе, посвященном удаленному доступу. Чтобы создать экземпляр объекта Customers (Клиенты), мы вызываем конструктор без параметров.

    Type *t = a->GetType("01.NetCpp.Acme.Customers"); // Клиенты
    Object *customerlnstance = // Объект
    Activator::Createlnstance(t); // Активатор

    Затем, чтобы вызвать метод GetCustomer, формируем список параметров и используем метод Invoke (Вызвать) экземпляра Methodlnfо.
    Dynamic\Customer\Debug в папку DynamicXDebug перед выполнение Dynamic . ехе.

    // вызвать метод
    Object *arguments [] = new Object*[1]; // новый Объект
    int customerld = -1;
    arguments[0] = _box(customerld); // параметры
    Object *returnType = mi->Invoke( // Вызвать
    customerlnstance, arguments); // параметры

    Используя методы отражения, мы получаем информацию о типе для каждого поля в возвращаемой структуре. Обратите внимание, что метод GetValue, принадлежащий Fieldlnf о, возвращает данные для конкретного поля в объекте.

    if (returnType->GetType() ==
    Type::GetType("System.Collections.ArrayList"))
    // ("Система.Коллекции.Список массивов")
    {
    ArrayList *arrayList =
    dynamic_cast(returnType);
    for (int i = 0; iCount; i++) // Счет
    {
    Type *itemType =
    arrayList->get_Item(i)->GetType();
    Fieldlnfo *fi [] = itemType->GetFields();
    for (int j = 0; j < fi->Length; j++)
    {
    Object *fieldValue = // Объект
    fi[j]->GetValue(arrayList->get_Item(i));
    Console::Write( // Запись
    "{0, -10} = {1, -15}",
    fi[j]->Name, fieldValue); // Имя
    }
    Console::WriteLine();
    }
    }

    Снова обращаем внимание на то, что в строке System.Collections.ArrayList (Система.Коллекции.Список массивов) для отделения имен использованы точки, а не "двойные двоеточия.

    В этом коде не использованы никакие определенные объекты или типы из сборки Customer (Клиент). С целью проиллюстрировать главные принципы, мы применили некоторые знания о сборке, чтобы код был простым. Однако должно быть понятно, как сделать его полностью общим.

    Можно сделать шаг вперед и использовать классы, которые генерируют метаданные (в System: :Reflection: :Emit (Система-Отражение-Генерация)). Можно даже динамически создавать сборку в памяти, а затем загружать и выполнять ее!

    CompEbook.ru Железо, дизайн, обучение и другие



    Глава 8. Классы каркаса .NET Framework

  • Классы каркаса .NET Framework

  • Метаданные и отражение

  • Класс туре (Тип)
  • Динамическое связывание

  • Ввод и вывод в .NЕТ

  • Потоковые классы

  • Примитивные типы данных и потоки

  • TextReader И TextWriter

  • Обработка файлов

  • Сериализация, или преобразование в последовательную форму

  • Объекты сериализации

  • ISerializable

  • Модель приложений .NET

  • Потоки

  • Изоляция потоков

  • Синхронизация коллекций

  • Контекст

  • Заместители и заглушки
  • ContextBoundObject

  • Изоляция приложений

  • Прикладная область

  • Прикладные области и сборки

  • Класс AppDomain (Прикладная область)

  • События AppDomain (Прикладная область)

  • Пример AppDomain (Прикладная область)
  • Маршализация, прикладные области и контексты

  • Асинхронное программирование

  • Асинхронные шаблоны проектирования

  • lAsyncResult

  • Использование делегатов в асинхронном программировании

  • Организация поточной обработки с параметрами

  • Удаленный доступ

  • Краткий обзор удаленного доступа
  • Удаленные объекты

  • Активация

  • Пример удаленного объекта

  • Пример программы, реализующей удаленный доступ
  • Метаданные и удаленный доступ

  • Конфигурационные файлы удаленного доступа

  • Программируемые атрибуты

  • Использование самостоятельно созданного атрибута

  • Определение класса атрибута

  • Определение базового класса

  • Сборка мусора
  • Уничтожение объектов

  • Неуправляемые ресурсы и освобождение ранее выделенной области памяти

  • Поколения

  • Завершение и раскручивание стека

  • Управление сборкой мусора с помощью класса сборщика мусора GC

  • Программа-пример

  • Резюме


  • CompEbook.ru Железо, дизайн, обучение и другие

    ISerializable

    Иногда методов преобразования в последовательную форму (сериализации), предоставляемых каркасом, недостаточно. В таком случае можно предусмотреть специальную сериализацию класса. Для этого необходимо реализовать интерфейс ISerializable и добавить к классу конструктор, как это показано в проекте Serialization (Сериализация) в папке ISerializable. Интерфейс ISerializable имеет один член: GetObj ectData. Этот метод используется во время сериализации данных.
    Пример ISerializable демонстрирует, как это можно сделать. Как и прежде, класс должен быть отмечен как Serializable (Преобразуемый в последовательную форму).
    [Serializable]
    // [Преобразуемый в последовательную форму]
    public _gc class HotelBroker :
    // класс сборщика мусора HotelBroker:
    public Broker, // общедоступный Брокер
    public iHotellnfo,
    public IHotelAdmin,
    public IHotelReservation,
    public ISerializable
    {
    };
    Чтобы сохранить все данные, которые должны быть сохранены в методе ISerializable: : GetObjectData, используется класс Serializationlnfo. Для управления сохранением различных типов данных, включая Object*, перегружается метод AddValue класса Serializationlnfo. Когда сохраняется тип, необходимо назначить ему имя так, чтобы можно было позже его восстановить. Класс StreamingContext предоставляет информацию о потоке, используемом при сериализации. Например, можно узнать, является ли используемый поток файлом или удаленным потоком, который посылается на другой компьютер.
    public:
    void GetObjectData(Serializationlnfo *info,
    StreamingContext context) // контекст
    {
    long numberHotels = units->Count;
    info->AddValue("NumberHotels", numberHotels) ;
    info->AddValue("Hotels", units); // Гостиницы
    }
    Нужно также реализовать специальный конструктор, который используется каркасом, когда объект преобразуется из последовательной формы в параллельную. Он принимает те же параметры, что и GetObjectData. В нем для восстановления данных используются различные методы GetXXX класса Serializationlnfo. Обращаем внимание читателя на то, что, поскольку мы не сохраняли поле cities (города), то были вынуждены восстанавливать его вручную. Конструктор объявляется приватным, потому что использует его только каркас. Если забыть добавить конструктор, то при попытке восстановить объект будет запущено исключение SerializationException.

    private: // частный
    HotelBroker(Serializationlnfo *info,
    StreamingContext context) // контекст
    : Broker(366, 10), MAXDAY(366), MAXUNIT(IO) // Брокер
    {
    long numberHotels =
    info->Get!nt32("NumberHotels");
    units = dynamic_cast(
    info->GetValue(
    "Hotels", units->GetType())); // Гостиницы

    if (numberHotels == units->Count)
    Console::WriteLine("All hotels deserialized.");
    // " Все гостиницы преобразованы из последовательной
    // формы в параллельную. "
    else
    Console::WriteLine("Error in deserialization.");
    // "Ошибка при преобразовании из последовательной
    // формы в параллельную."
    cities = new ArrayList;
    // города
    lEnumerator *pEnum = units->GetEnumerator();
    while (pEnum->MoveNext())
    {
    Hotel *h = // Гостиница
    dynamic_cast(pEnum->Current); // Гостиница

    AddCity(h->City); // Город
    }
    }

    Необходимо помнить, что после внесения любых изменений и последующей компоновки проекта Hotel (Гостиница), нужно скопировать Hotel. dll в папку, где находится программа-клиент Serialization.exe. Это не происходит, как в С#, автоматически, если не добавить в проект специальный шаг компоновки.

    В данном примере мы выполняли пользовательскую сериализацию только объекта HotelBroker. Для всех других объектов мы использовали сериализацию, предоставляемую каркасом. Этот пример работает так же, как и пример Serialization (Сериализация). Поэтому вывод программы выглядит аналогично.

    CompEbook.ru Железо, дизайн, обучение и другие

    Использование делегатов в асинхронном программировании

    Каждый разработчик объектов .NET, желающий предоставить асинхронный интерфейс, должен следовать только что описанному шаблону. Однако большинству разработчиков не нужно разрабатывать собственное решение для асинхронного интерфейса к своим объектам. Делегаты обеспечивают очень простой способ поддержки асинхронных операций для любого метода без какого-либо изменения разработанных классов. Конечно, все это требует внимания, потому что при написании объекта, возможно, были сделаны некоторые предположения относительно потока, в котором он выполняется, а так-же относительно его требований к синхронизации.

    Два примера Asynch используют объект Customers (Клиенты) из изученной нами сборки Customer (Клиент). Первый пример, AsynchWithoutCallback, асинхронно регистрирует новых клиентов и выполняет обработку во время ожидания завершения каждой регистрации. Второй пример, AsynchWithCallback (Asynch с обратным вызовом), использует функцию обратного вызова для асинхронной обработки данных. В дополнение к тому, что программа может обрабатывать данные во время ожидания завершения регистрации, обратный вызов позволяет системе произвести некоторое асинхронное действие для каждой отдельной регистрации.

    В примерах мы только выводим информацию на консоль, чтобы показать, где могла бы быть сделана работа. Для увеличения времени ожидания, чтобы смоделировать продолжительную обработку данных, мы поместили вызовы Thread: : Sleep () (Поток::

    Режим ожидания) в Customer: : RegisterCustomer и в программу-пример. Теперь рассмотрим код в примерах.

    Предположим, что пользователь хочет вызвать метод RegisterCustomer асинхронно. Вызывающая программа просто объявляет делегат с той же самой сигнатурой, что и у метода.
    public _delegate int RegisterCustomerCbk(
    String *FirstName, // Строка
    String *LastName, // Строка
    String *EmailAddress); // Строка
    Таким образом реальный метод превращается в функцию обратного вызова:
    RegisterCustomerCbk *rcc = new
    RegisterCustomerCbk{
    customers, // клиенты
    Customers::RegisterCustomer); // Клиенты

    Вызов (invoke) начала (Begin) и конца (End)

    При объявлении делегата компилятор генерирует класс с тремя методами: Beginlnvoke, Endlnvoke и Invoke (Вызвать). Beginlnvoke и Endlnvoke — методы с типовой безопасностью. Они соответствуют методам BeginXXX и EndXXX и позволяют вызывать делегат асинхронно. При вызове делегата компилятор использует метод Invoke (Вызвать)27. Чтобы асинхронно вызвать RegisterCustomer, достаточно использовать только методы Beginlnvoke и Endlnvoke.

    RegisterCustomerCbk *rcc = new RegisterCustomerCbk(
    customers, // клиенты
    Customers::RegisterCustomer); // Клиенты for(int i = 1; i < 5; i++) {
    firstName = String::Concat( // Строка
    "FirstName", i.ToString ());
    lastName = String::Concat( // Строка
    "SecondName", (i * 2).ToString{));
    emailAddress = String::Concat ( // Строка
    i.ToString(), ".biz"); lAsyncResult *ar =
    rcc->Begin!nvoke( firstName, lastName, emailAddress, 0, 0) ;
    while(!ar->IsCompleted)
    {
    Console::WriteLine(
    "Could do some work here while waiting for customer

    registration to complete.");
    // "Могу сделать некоторую работу здесь
    //во время ожидания
    // завершения регистрации клиента.");
    ar->AsyncWaitHandle->WaitOne(1, false);
    }
    customerld = rcc->End!nvoke(ar) ;
    Console::WriteLine(
    Added Customerld: {0}",
    // " Добавлен Customerld: {0}"
    customerId.ToString());
    }

    Программа периодически ждет AsyncWaitHandle, чтобы увидеть, закончилась или нет регистрация. Если нет, то тем временем программа могла бы выполнить некоторую работу. Если Endlnvoke вызывается до завершения RegisterCustomer, то выполнение блокируется до завершения RegisterCustomer.

    Асинхронный обратный вызов

    Вместо ожидания на дескрипторе, можно передать функцию обратного вызова в Beginlnvoke (или BeginXXX) метод. Именно так и делается в примере AsynchWithCallback (Asynch с обратным вызовом).

    RegisterCustomerCbk *rcc = new RegisterCustomerCbk( customers, // клиенты
    Customers::RegisterCustomer);
    // Клиенты AsyncCallback *cb =
    new AsyncCallback(this, CustomerCallback);
    Object *objectState; // Объект
    JAsyncResult *ar; forUnt i = 5; i < 10; i++) {
    firstName = String::Concat( // Строка
    "FirstName", i.ToString());
    lastName = String::Concat( // Строка
    "SecondName", (i * 2).ToString());
    emailAddress =
    String::Concat(i.ToString(), ".biz"); // Строка
    objectState = _box(i);
    ar = rcc->Begin!nvoke( firstName,
    lastName,
    emailAddress,
    cb,
    objectState);
    }
    Console::WriteLine(
    "Finished registrations...
    could do some work here.");
    // "Закончена регистрация... могу сделать
    // некоторую работу здесь."
    Thread::Sleep(25); // Поток:: Бездействие
    Console::WriteLine(
    "Finished work..waiting to let registrations complete.");

    // "Закончена работа., жду конца регистрации.");
    Thread::Sleep(1000); // Поток:: Бездействие

    Потом мы получаем результат в функции обратного вызова:

    void CustomerCallback(lAsyncResult *ar)
    {
    int customerld;
    AsyncResult *asyncResult =
    dynamic_cast(ar);
    RegisterCustomerCbk *rcc =
    dynamic_cast (asyncResult->AsyncDelegate);

    customerld = rcc->EndInvoke(ar);
    Console::WriteLine(
    AsyncState: {0} Customerld {1} added.",
    ar->AsyncState, customerld.ToString() ) ;
    Console::WriteLine(
    " Could do processing here.");
    // " Могу сделать обработку здесь."
    return;
    }

    В этом варианте можно выполнить некоторые действия до завершения регистрации каждого клиента.

    CompEbook.ru Железо, дизайн, обучение и другие

    Использование самостоятельно созданного атрибута

    Перед тем как обсудить реализацию собственного атрибута, рассмотрим, как используется атрибут InitialDirectory. Чтобы указать начальный каталог для класса, мы сделаем класс производным от базового класса DirectoryContext. Тогда мы сможем применить к такому классу атрибут InitialDirectory, который принимает параметр типа String*, определяющий путь к начальному каталогу. Свойство DirectoryPath извлекает путь из метаданных. Если к нашему классу не применен атрибут, этот путь примет значение по умолчанию. Ниже приведен код нашей тестовой программы.

    Выполняя этот пример на произвольной системе, можно сменять каталог, указанный атрибутом, на один из существующих на этой конкретной машине.
    //AttributeDemo.h
    using namespace System;
    // использование пространства имен Система;
    using namespace System::10;
    // использование пространства имен Система::10;
    _gc class Normal : public DirectoryContext
    Т"
    };
    [InitialDirectory("С:\\OI\\NetCpp\\Chap08")
    ] _gc class Special : public DirectoryContext // класс сборщика мусора
    Специальный:DirectoryContext
    {
    };
    public _gc class AttributeDemo
    // класс сборщика мусора AttributeDemo
    {
    public:
    static void Main() {
    Normal *objNormal = new Normal;
    Console::WriteLine(
    "path = {0}",
    objNormal->DirectoryPath);
    // путь = ShowDirectoryContents(objNormal->DirectoryPath) ;
    Special *objSpecial = new Special;
    // новый Специальный Console::WriteLine(
    "path = {0}",
    objSpecial->DirectoryPath);
    // путь = ShowDirectoryContents(objSpecial->DirectoryPath);

    } private:
    static void ShowDirectoryContents(String *path)
    // Строка {
    Directorylnfo *dir = new Directorylnfo(path);
    // путь
    Filelnfo *files[] = dir->GetFiles();
    Console::WriteLine("Files:");
    // Файлы:
    lEnumerator *pEnum = files->GetEnumerator();
    // файлы
    while (pEnum->MoveNext())
    {
    Filelnfo *f =
    dynamic_cast(pEnum->Current);
    Console::WriteLine(" {0}", f->Name);
    // Имя
    }
    Directorylnfo *dirs [] = dir->GetDirectories(
    };
    Console::WriteLine("Directories:");
    // Каталоги: pEnum = dirs->GetEnumerator();
    while (pEnum->MoveNext()
    }
    {
    Directorylnfo *d =
    dynamic_cast(pEnum->Current);
    Console::WriteLine(" {0}", d->Name);
    // Имя
    }
    }
    };
    Вот выдача:
    path = c:\OI\NetCpp\Chap08\CustomAttribute // путь Files: // Файлы
    CustomAttribute.vcproj
    CustomAttribute.neb
    ReadMe.txt
    CustomAttribute.cpp
    Assemblylnfо.cpp
    stdafx.cpp
    stdafx.h
    CustomAttribute.sin
    CustomAttribute.suo
    AttributeDemo.h
    DirectoryContext.h
    DirectoryAttribute.h Directories: // Каталоги
    Debug // Отладка path = C:\OI\NetCpp\Chap08 // путь Files: // Файлы Directories: // Каталоги
    Reflection // Отражение
    Dynamic // Динамический
    Filel()
    Serialization // Преобразование в последовательную форму
    Hotel // Гостиница
    ISerialization
    Threading
    PulseAll
    Threadlsolation
    AppDomain
    Asynch
    AsynchThreading
    CustomAttribute
    MarshalByReference
    Remoting
    CompEbook.ru Железо, дизайн, обучение и другие

    Изоляция потоков

    Исключение, сгенерированное одним потоком, не приведет к отказу в работе другого потока. Пример Threadlsolation демонстрирует это.
    _gc class tm
    // класс сборщика мусора tm
    {
    public:
    void m() {
    Console::WriteLine(
    "Thread {0} started", // "Поток {0} начал работу",
    Thread::CurrentThread->GetHashCode().ToString());
    Thread::Sleep(1000); // Поток:: Бездействует forfint i = 0; i < 10; i++)
    Console::WriteLine(i); Console::WriteLine(
    "Thread {0} done",
    // "Поток {0} закончил",
    Thread::CurrentThread->GetHashCode{).ToString());
    }
    };
    _gc class te
    // класс сборщика мусора te
    {
    public:
    void tue ()
    {
    Console::WriteLine (
    "Thread {0} started", // "Поток {0} начал работу",
    Thread::CurrentThread->GetHashCode().ToString());
    Exception *e = new Exception("Thread Exception"); // Исключение *е = новое Исключение ("Исключение Потока"); throw e;
    }
    };
    _gc class Threadlsolation
    // класс сборщика мусора Threadlsolation
    {
    public:
    static void Main()
    {
    tm *tt = new tm;
    // новый tm te *tex = new te;
    // новый te
    // создать делегат для потоков
    ThreadStart *tsl = new ThreadStart(tt, tm::m);
    ThreadStart *ts2 = new ThreadStart(tex, te::tue);
    Thread *threadl = new Thread(tsl);
    // новый Поток (tsl) Thread *thread2 = new Thread(ts2);
    // новый Поток (ts2)
    Console::WriteLine(
    "Thread {0} starting new threads.",
    // "Поток {0} запускает новые потоки.",
    Thread::CurrentThread->GetHashCode().ToString());
    threadl->Start(); // Пуск
    thread2->Start(); // Пуск
    Console::WriteLine(
    "Thiead {0} done.", // "Поток {0} закончил.",

    Thread::CurrentThread->GetHashCode().ToString());
    }
    };
    Программа генерирует следующую выдачу. Обращаем внимание читателя на то, что второй поток может продолжать выводить числа даже тогда, когда первый поток прерывается в результате появления необрабатываемого исключения. И, кроме того, может закончиться главный поток, породивший другие два потока, причем это тоже не приведет к завершению этих двух потоков.

    Thread 2 starting new threads.
    Thread 2 done.
    Thread 5 started
    Thread 6 started
    Unhandled Exception: System.Exception:
    Thread Exception at te.tue() in
    c:\oi\netcpp\chap8\threadisolation\
    threadisolation.h:line 32 0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Thread 5 done

    Перевод такой:

    Поток 2 запускает новые потоки. Поток 2 работу закончил. Поток 5 стартовал Поток 6 стартовал
    Необработанное Исключение:
    Система.Исключение: Исключение Потока в te.tue () в
    c:\oi\netcpp\chap8\threadisolation\
    threadisolation.h:line 32 О
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Поток 5 работу закончил

    Класс AppDomain (Прикладная область) (обсуждаемый в данной главе позже) позволяет устанавливать обработчик для перехвата исключения UnhandledException.

    CompEbook.ru Железо, дизайн, обучение и другие

    Изоляция приложений

    При написании приложений часто возникает ситуация, когда необходимо изолировать части приложения так, чтобы сбой в одной из частей не приводил к отказу в остальной части приложения. В Windows изоляция приложений происходит традиционно на уровне процессов. Другими словами: если один из процессов останавливается или терпит крах, остальные процессы продолжают работать. Один процесс не может непосредственно адресовать память в адресном пространстве другого процесса. Однако существует несколько механизмов взаимодействия процессов.
    К сожалению, использование отдельных процессов с целью достижения изоляции частей приложения — дорогое удовольствие для отдельного приложения. Для переключения от одного процесса к другому должен быть сохранен, а впоследствии восстановлен контекст (информация о состоянии процесса). Такое действие включает в себя переключение между потоками и процессами. Для переключения между потоками требуется сохранить регистры центрального процессора, такие как стек вызовов и указатель команд.
    Необходимо также загружать информацию для нового потока и обновлять информацию для планирования потоков. Переключение между процессами требует дополнительного сохранения буферов ввода/вывода, учетной информации, а также привилегий процессора для предыдущего процесса, и восстановления этих данных для следующего процесса.
    CompEbook.ru Железо, дизайн, обучение и другие

    Класс AppDomain (Прикладная область)

    Класс AppDomain (Прикладная область) реализует абстракцию прикладной области. Пример AppDomain (Прикладная область) иллюстрирует использование прикладных областей. Этот класс имеет статические методы для создания и разгрузки прикладных областей:
    AppDomain *domain = AppDomain::CreateDomain(
    "CreatedDomain2", 0, 0);
    AppDomain::Unload(domain); // разгрузить область
    Несмотря на то, что метод CreateDomain перегружен, следующая сигнатура иллюстрирует изоляцию прикладных областей:
    static AppDomain CreateDomain( // статический
    String *friendlyName, // Строка
    Evidence *securitylnfo,
    AppDomainSetup *info);
    Параметр Evidence (Данные) — коллекция ограничений защиты для прикладной области. Этот параметр мы обсудим более подробно в главе 13 "Защита"; сейчас же мы отметим, что создатель прикладной области может модифицировать коллекцию, чтобы управлять разрешениями выполняющейся прикладной области. Параметр AppDomainSetup определяет параметры прикладной области. В параметрах указывается путь к конфигурационному файлу прикладной области и информация о том, куда загружены приватные сборки. Поэтому прикладные области могут быть сконфигурированы независимо одна от другой. Изоляция кода, изоляция параметров и управление защитой в совокупности гарантируют, что прикладные области остаются независимы друг от друга.
    CompEbook.ru Железо, дизайн, обучение и другие

    Класс туре (Тип)

    Абстрактный класс Туре (Тип) в пространстве имен System (Система) определяет типы .NET. Поскольку в .NET нет никаких функций вне классов или глобальных переменных, то получив все типы в сборке, мы получим все метаданные о коде в этой сборке. Туре (Тип) представляет все типы, имеющиеся в .NET: классы, структуры, интерфейсы, значения, массивы и перечисления.

    Класс Туре (Тип) возвращается также методом GetType класса System::0bject (Система::Объект) и статическим методом GetType самого класса Туре (Тип). Последний метод может использоваться только с типами, которые могут быть разрешены статически.

    Одно из свойств класса Туре (Тип) — сборка, к которой он принадлежит. Можно получить все типы, содержащиеся в сборке, как только будет определен Туре (Тип) одного объекта. Туре (Тип) — абстрактный класс, и во время выполнения возвращается экземпляр System::RuntimeType.

    В выдаче программы найден каждый тип в сборке, — CustomerListltem, ICustomer, Customer (Клиент) и Customers (Клиенты),— причем распечатаны его метаданные. Чтобы для каждого типа выяснить стандартные атрибуты и тип, производным от которого является класс, нужно воспользоваться свойствами Attributes (Атрибуты) и BaseType.

    Методы, связанные с классом Туре (Тип), дают возможность получить ассоциированные поля, свойства, интерфейсы, события, и методы. Например, тип Customer (Клиент) не имеет никаких интерфейсов, свойств и событий, но имеет четыре поля, три конструктора и методы, унаследованные от его базового класса BaseType из пространства имен System::Object (Система::Объект):

    Можно также загрузить и выполнить сборку с AppDomain, — мы обсудим этот вариант в данной главе позже.
    Interfaces: Fields:
    Customerld
    FirstName
    LastName
    EmailAddress Properties:
    Events:
    Constructors:
    public .ctor(System.String first, System.String last,
    System.String email)
    public .ctor()
    public .ctor(System.Int32 id) Methods:
    public Int32 GetHashCodeO
    public Boolean Equals(System.Object obj)
    public String ToStringO
    public Type GetType()

    Перевод такой:

    Интерфейсы: Поля:
    Customerld
    FirstName
    LastName
    EmailAddress Свойства:
    События:
    Конструкторы:
    public .ctor(System.String first, System.String last,
    System.String email)
    // электронная почта
    public.ctor ()
    public .ctor(System.Int32 id) Методы:
    public Int32 GetHashCodeO
    public Boolean Equals(System.Object obj)
    // Равняется
    public String ToString()
    public Type GetType()

    Тип Customers (Клиенты) наследуется от одного интерфейса и содержит один конструктор и четыре своих собственных метода в дополнение к четырем уже унаследованным от его базового класса BaseType из пространства имен System: :Object (Система-Объект):

    Interfaces:
    ICustomer
    Fields:
    Properties:
    Events:
    Constructors:
    public .ctor()
    Methods:
    public Void ChangeEmailAddress(System.Int32 id,
    System.String emailAddress)
    public ArrayList GetCustomer(System.Int32 id)
    public Void UnregisterCustomer(System.Int32 id)
    public Int32 RegisterCustomer(System.String firstName,
    System.String lastName, System.String emailAddress)
    public Int32 GetHashCode()
    public Boolean Equals(System.Object obj )
    public String ToString ()
    public Type GetTypeO()

    Перевод такой:

    Интерфейсы:
    ICustomer
    Поля:
    Свойства:
    События:
    Конструкторы:
    public .ctor() Методы:
    public Void ChangeEmailAddress(System.Int32 id,
    System.String emailAddress)
    public ArrayList GetCustomer(System.Int32 id)
    public Void UnregisterCustomer(System.Int32 id)
    public Int32 RegisterCustomer(System.String firstName,
    System.String lastName, System.String emailAddress)
    public Int32 GetHashCode()
    public Boolean Equals(System.Object obj )
    public String ToString()
    public Type GetType()

    Вся эта информация была получена с помощью методов Getlnterfaces, GetFields, GetProperties, GetEvents, GetConstructors И GetMethods класса Type (Тип). Поскольку интерфейс — тип, Getlnterfaces возвращает массив объектов Туре (Тип), представляющий интерфейсы, унаследованные или реализованные запрошенным типом Туре (Тип). А так как поля, свойства, события, и методы — не типы, их методы средств доступа не возвращают объекты Туре (Тип). Каждый из их методов доступа возвращает соответствующий класс: Fieldlnfo, Propertylnfo, Eventlnfo, Constructorlnfо и Methodlnf о. Все эти классы, а также класс Туре (Тип), — производные от класса Memberlnfo, который является абстрактным базовым классом для элементов метаданных.


    Давайте рассмотрим некоторые из метаданных, связанные с методом класса. Используя методы отражения, мы можем восстановить сигнатуры для всех классов и интерфейсов в сборке Customer (Клиент). Вот распечатка для методов класса Customer (Клиент):

    public Void ChangeEmailAddress(System.Int32 id,
    System.String emailAddress)
    public ArrayList GetCustomer(System.Int32 id)
    public Void UnregisterCustomer(System.Int32 id)
    public Int32 RegisterCustomer(System.String firstName,
    System.String lastName, System.String emailAddress)
    public Int32 GetHashCode()
    public Boolean Equals(System.Object obj)
    public String ToString()
    public Type GetType()

    Вот код, с помощью которого была получена эта распечатка:

    for (int j = 0; j < methodlnfо.Length; j++)
    {
    if (methodlnfo[j]->IsStatic)
    Console::Write (" static "); // статический
    if (methodlnfo[j]->IsPublic)
    Console::Write(" public ");
    if (methodlnfo[j]->IsFamily)
    Console::Write(" protected "}; // защищенный
    if (methodlnfo[j]->IsAssembly)
    Console::Write(" internal "); // внутренний
    if (methodlnfо[j]->IsPrivate)
    Console::Write(" private "); // частный
    Console::Write(
    "{0} ", methodlnfo[j]->ReturnType->Name); // Имя
    Console::Write( // Запись
    "{0}(", methodlnfo[j]->Name); // Имя
    Parameterlnfo *paramlnfo [] =
    methodlnfo[j]->GetParameters(};
    long last = paramlnfo->Length - 1;
    for (int k = 0; kLength; k++)
    {
    Console::Write( // Запись
    "{0} {1}",
    paramlnfо[k]->ParameterType,
    paramlnfо[k]->Name); // Имя
    if (k != last) // если не последний
    Console::Write(", "); // Запись
    }
    Console::WriteLine(")");
    }

    За исключением того, что конструктор не возвращает переменную какого-нибудь типа, тот же самый код воспроизводит вызывающие последовательности для конструкторов класса.

    Класс Methodlnfo содержит свойства, с помощью которых можно определить, является ли метод статическим, приватным, защищенным или внутренним, а также возвращаемый тип (тип результата) и название метода. Параметры метода сохраняются в массиве свойств типа (класса) Parameterlnfo.

    Этот пример показывает, что типы относятся к сборкам. Два типа, имеющие одинаковые имена, но размещенные в двух различных сборках, трактуются во время выполнения как два разных типа. Нужно быть внимательным при смешивании различных версий типов или тех же самых типов в двух различных сборках.

    Все эти метаданные позволяют общеязыковой среде времени выполнения CLR и каркасу предоставлять услуги прикладным программам, потому что они могут понять структуру созданных нами типов.

    CompEbook.ru Железо, дизайн, обучение и другие

    Классы каркаса .NET Framework

    Невозможно описать в одной главе, или даже в одной книге, все классы каркаса .NET Framework. Хотя и не полностью, классы .NET охватывают большую часть интерфейса 32-разрядных Windows-приложений (Win32 API), так же как и много чего другого. Несмотря на то, что основное внимание было уделено функциональным возможностям, связанным с Internet, однако изменилась и модель разработки приложений в среде Windows.

    В этой главе мы сосредоточим наше внимание на классах, которые иллюстрируют ключевые концепции и модели, проявляющиеся повсюду в каркасе .NET Framework. Такой подход представляется нам более плодотворным, чем просто попытаться немного рассказать о каждом классе, который когда-либо мог бы понадобиться, без того, чтобы дать читателю общее представление о классах .NET. В других главах глубже рассматриваются иные части каркаса, такие как Windows Forms (Формы Windows), ASP.NET, безопасность ADO.NET, и сетевые службы (Web Services).
    Мы начинаем с рассмотрения концепций отражения и метаданных. Метаданные появляются всюду в .NET и важно понимать, как общеязыковая среда времени выполнения CLR (Common Language Runtime) предоставляет разные услуги (службы, сервисы) прикладным программам. Затем по нескольким причинам мы исследуем файловый ввод/вывод. Во-первых, при этом вводится важное понятие сериализации (преобразования в последовательную форму). Во-вторых, класс Path (Путь) позволяет проиллюстрировать, как отдельные классы каркаса реализуют некоторые или все свои функциональные возможности с помощью статических методов. В-третьих, рассматриваемые в применении к файловому вводу/выводу классы используются для форматирования во многих местах .NET.
    Разобравшись в понятии сериализации, читатель получит конкретную идею относительно того, как каркас может управлять объектами прозрачным для нас способом. Ведь сериализация появляется во вспомогательной роли в любом месте, где объекты должны будут сохраняться или транспортироваться. Обсуждение интерфейса ISerializable снова демонстрирует, насколько проще реализовать интерфейс, используя .NET, чем с помощью модели компонентных объектов Microsoft (COM).

    С целью более углубленного понимания моделей .NET для приложений, мы рассматриваем программирование с помощью потоков в .NET, а также несколько методов синхронизации и разрешения конфликтов, связанных с многопоточностью. Различные методы синхронизации иллюстрируют компромисс использования свойств, предоставленных каркасом, по сравнению со свойствами, реализованными самостоятельно.

    Для лучшего понимания модели программирования .NET, мы введем понятие контекста, а также рассмотрим применение заместителей и заглушек для реализации системных сервисов. Мы также рассмотрим использование прикладных областей, благодаря которым достигается более эффективная изоляция приложений, чем с помощью процессов Win32.

    Асинхронные шаблоны проектирования появляются в .NET повсюду и обсуждаются довольно подробно. В книге продемонстрированы некоторые примеры удаленного доступа, поскольку это ключевая технология, включающая в себя большинство концепций, рассматриваемых в данной главе. В примерах, разбираемых здесь, используется несколько атрибутов, предоставляемых каркасом .NET Framework, а также демонстрируется реализация и использование выборочных, самостоятельно созданных атрибутов. Мы обсуждаем операцию завершения, чтобы читатель понял, как можно удостовериться в том, что приложения правильно освобождают ресурсы.

    CompEbook.ru Железо, дизайн, обучение и другие

    Конфигурационные файлы удаленного доступа

    Конфигурационные файлы используются, чтобы определить, где будет активизирован объект. Клиент затем использует оператор new (создать) для создания объекта. Большое преимущество использования такого подхода заключается в том, что при изменении места расположения (например, унифицированного указателя информационного ресурса (URL) или канала TCP), либо при замене используемого форматера, не нужно будет перепрограммировать клиент.
    Многие классы могут быть сконфигурированы клиентом. Файлы конфигурации клиент загружает с помощью метода RemotingConf iguration: : Configure.
    CompEbook.ru Железо, дизайн, обучение и другие

    Контекст

    И Чтобы понять, как с помощью атрибутов среда времени выполнения реализует требования организации поточной обработки, мы должны ввести понятие контекста. Шаг 4 из примера Threading (Организация поточной обработки) состоит из того же кода, что и шаг 3, но в выдаче есть дополнительная информация:
    Is the customer object a proxy? False
    Is the bookings object a proxy? True
    Added Boston Presidential Hotel with one room.
    Thread 13 Contextld 0 launching threads.
    MakeReservation: Thread 28 Contextld 0 starting.
    MakeReservation: Thread 29 Contextld 0 starting.
    Reserving for Customer 2 at the Boston Presidential Hotel on

    12/13/2001 12:00:00
    AM for 1 days
    Thread 29 Contextld 1 entered Reserve.
    Thread 29 finds the room is available in Broker::Reserve
    Reserving for Customer 1 at the Boston Presidential Hotel on

    12/12/2001 12:00:00
    AM for 3 days
    Thread 29 Contextld 1 left Reserve.
    Thread 28 Contextld 1 entered Reserve.
    Thread 28 Contextld 1 left Reserve.
    Reservation for Customer 1 could not be booked
    Room not available
    Reservation for Customer 2 has been booked
    Reservationld = 1
    ReservationRate = 10000
    ReservationCost = 10000
    Comment = OK
    Перевод такой:
    Объект Клиент является агентом? Ложь
    Заказывающий объект является агентом? Истина
    Добавлена Бостонская Президентская гостиница с одним номером.

    Поток 13 Contextld 0 запускает потоки.
    MakeReservation: Поток 28 Contextld 0 стартовал.
    MakeReservation: Поток 29 Contextld 0 стартовал.
    Резервирование для Клиента 2 в Бостонской Президентской гостинице на
    12/13/2001 12:00:00
    AM в течение 1 дня
    Поток 29 Contextld 1 вошел в Резерв.
    Поток 29 находит, что номер доступен в Брокер::Резерв
    Резервирование для Клиента 1 в Бостонской Президентской гостинице

    на
    12/12/2001 12:00:00
    AM в течение 3 дней
    Поток 29 Contextld 1 вышел из Резерва.
    Поток 28 Contextld 1 вошел в Резерв.
    Поток 28 Contextld 1 вышел из Резерва.
    Резервирование для Клиента 1 не могло быть заказано
    Номер не доступен
    Резервирование для Клиента 2 было заказано
    Reservationld = 1
    ReservationRate = 10000
    ReservationCost = 10000
    Комментарий = OK

    На этом последнем шаге (шаг 4) примера Threading ( Организация поточной обработки) мы видим, что во время выполнения метода класса Broker (Брокер), такого как Reserve (Резерв), поток имеет идентификатор контекста Contextld, который отличается от идентификатора контекста во время выполнения какого-нибудь метода вне класса. Поток выполняется в различных контекстах. Это объясняет, почему в вышеприведенной распечатке поток 28, у которого идентификатор контекста Contextld был равен О перед вызовом метода Reserve (Резерв), изменяет свой идентификатор контекста Contextld на 1 во время выполнения данного метода. Такое поведение обусловлено применением атрибута Synchronization (Синхронизация) к классу Broker (Брокер).

    Объекты класса Broker (Брокер) имеют отличные от других объектов требования во время выполнения. Поэтому доступ к объектам Broker (Брокер) должен быть синхронизирован, а доступ к другим объектам — нет. Среда, которая реализует требования какого-либо объекта во время выполнения, называется контекстом. В примере Threading (Организация поточной обработки) на шаге 3 и шаге 4 реализуются два контекста: контекст 1, в котором живет объект Broker (Брокер) и контекст 0, в котором сосуществуют все другие объекты. Каждый поток в программе будет выполняться либо в контексте 1 во время выполнения методов объекта класса Broker (Брокер), либо в контексте 0 в остальных случаях. Контекст не зависит от выполняющихся в нем потоков.

    Контекст — совокупность одного или более объектов с идентичными требованиями параллелизма. Концепция контекста в .NET подобна концепции апартаментов (apartment) модели компонентных объектов Microsoft (COM) и концепции контекста в СОМ+. Вообще нельзя предсказать, что нужно делать во время выполнения в рамках заданного контекста. Это зависит от требований, предъявляемых во время выполнения. Контекст с требованиями, касающимися проведения транзакций, диктует иные действия, чем контекст, к которому такие требования не предъявляются. Или, если контекст поддерживает атрибут REQUIRED (ТРЕБУЕМЫЙ) в требованиях к синхронизации, то он отличается от контекста, поддерживающего в требованиях к синхронизации атрибут REQUIRES_NEW.

    Экземпляр класса Context (Контекст), который представляет текущий контекст, можно получить с помощью статического свойства Thread: :CurrentContext. Context Id — это свойство класса Context (Контекст).

    CompEbook.ru Железо, дизайн, обучение и другие

    Краткий обзор удаленного доступа

    Ниже перечислены ключевые части удаленного доступа
  • перехват, с помощью которого генерируются сообщения для связи по каналам;

  • форматеры (Formatters), служащие для помещения сообщения в байтовый поток, который посылается по каналу Это те же самые форматеры, которые обсуждались ранее в разделе, посвященном сериализации (преобразованию в последовательную форму),

  • каналы связи для транспортировки (передачи) сообщений

  • Перехват
    Заместители и заглушки (называемые в NET диспетчерами, организующими программами и планировщиками) преобразовывают обращение к функции на клиентской или серверной стороне в сообщения, которые посылаются по сети Такой процесс называется перехватом, потому что заместители и диспетчеры перехватывают вызов метода для посылки его удаленному адресату. В отличие от модели компонентных объектов Microsoft (COM), общеязыковая среда времени выполнения CLR может генерировать заместители и заглушки с помощью информации, хранимой в метаданных.

    Заместитель убирает вызов функции из стекового фрейма вызывающей программы и преобразовывает его в сообщение. Потом сообщение посылается адресату. Диспетчер принимает сообщение и помещает его в стековый фрейм так, чтобы запрос мог быть адресован соответствующему объекту.
    Например, предположим, что метод UnregisterCustomer из сборки Customer (Клиент) выполняется в одной прикладной области и вызывается из другой. Нет никакой разницы, если прикладные области находятся в том же самом процессе или на той же самой машине.
    Заместитель принимает целочисленный параметр id (идентификатор) из стекового фрейма клиента, который делает вызов и помещает его в сообщение, кодирующее запрос и его параметр. На серверной стороне диспетчер принимает это сообщение и помещает обращение к функции в стек сервера для вызова UnregisterCustomer (int id), a также преобразует этот вызов объекта. Коды клиента и сервера могут даже не знать, что они удалены друг от друга.
    Каналы и форматеры
    Форматер преобразовывает сообщение в поток байтов. Каркас .NET Framework поставляется с двумя форматерами, двоичным и SOAP (использующим XML-документы и рассматриваемым в главе 11 "Web-службы"). Поток байтов затем посылается по каналу связи.
    Каркас .NET Framework поставляется также с двумя каналами, хотя можно создать и свой собственный. Канал HTTP, использующий протокол передачи гипертекстовых файлов HTTP хорош для соединения по Internet или через брандмауэры. Канал TCP использует протокол управления передачей TCP (сокеты) и предназначен для высокоскоростной связи. Таким образом, имеются четыре возможных перестановки форматеров и каналов транспортировки: двоичный по протоколу управления передачей TCP, двоичный по протоколу передачи гипертекстовых файлов HTTP, SOAP по протоколу управления передачей TCP и SOAP по протоколу передачи гипертекстовых файлов HTTP.
    CompEbook.ru Железо, дизайн, обучение и другие

    LAsyncResult

    Метод BeginXXX (такой как BeginRead) возвращает lAsyncResult. Следующий интерфейс содержит четыре элемента:
    public _gc _interface lAsyncResult
    // сборщик мусора - интерфейс lAsyncResult
    {
    public:
    bool get_IsCompleted(); // логический (булев)
    bool get_CompletedSynchronously();// логический (булев)
    WaitHandle* get_AsyncWaitHandle();
    Object* get_AsyncState(); }
    Полю get_IsCompleted будет присвоено значение true (истина) после обработки вызова сервером. Клиент может уничтожить все ресурсы после того, как get_IsCompleted получит значение true (истина). Полю get_CompletedSynchronously будет присвоено значение true (истина), если BeginXXX заканчивается синхронно. Чаще всего это поле игнорируется и по умолчанию его значение устанавливается равным false (ложь). Обычно клиент даже не знает, выполняется ли метод BeginXXX синхронно или асинхронно. Если асинхронная операция не закончена, метод EndXXX блокируется до завершения выполнения операции.
    Метод get_AsyncWaitHandle возвращает дескриптор WaitHandle, который можно использовать в целях синхронизации. Ранее мы видели, что этот дескриптор способен получать сигналы, так что клиент может организовать ожидание с его помощью. Поскольку можно указать период времени ожидания, то не нужно блокироваться навсегда в случае, если операция еще не закончена.
    Объект get_AsyncState — последний параметр в вызове метода BeginXXX. Он позволяет дифференцировать процессы чтения в асинхронном режиме при обратном вызове.
    CompEbook.ru Железо, дизайн, обучение и другие

    Маршализация, прикладные области и контексты

    По умолчанию объекты копируются из одной прикладной области в другую (передаются по значению). В разделе "Удаленный доступ" показано, как происходит передача по ссылке между прикладными областями. Это гарантирует изоляцию кода одной прикладной области от кода другой.
    Объекты передаются по ссылке между контекстами. Это позволяет общеязыковой среде времени выполнения CLR удовлетворять требования (например, синхронизацию или транзакции) различных объектов, независимо от того, находится ли клиент объекта в той же самой прикладной области или в другой.
    Поскольку большинство объектов не являются производными от ContextBoundOb j ect, они могут постоянно храниться в одном контексте или перемещаться при необходимости из одного контекста в другой. При этом потоки могут пересекать границы прикладных областей или контекстов в пределах одного и того же процесса Win32.
    CompEbook.ru Железо, дизайн, обучение и другие

    Метаданные и отражение

    Пример Serialization (Сериализация) из главы 2 "Основы технологии .NET" демонстрирует, как благодаря метаданным общеязыковая среда времени выполнения CLR поддерживает работу сервисов. Многие из технологий, которые мы рассматриваем в других главах книги, основаны на метаданных, хотя мы не всегда будем акцентировать на этом внимание.
    Метаданные — это информация о сборках, модулях и типах, составляющих программы в .NET. Тот, кому когда-либо приходилось создавать язык описания интерфейса (IDL) с целью сгенерировать библиотеку типов так, чтобы созданные с помощью C++ СОМ-объекты могли вызываться из Visual Basic, или создавать заместители и заглушки, оценит, насколько полезными являются метаданные, и будет благодарен, что распространяется это все "бесплатно".

    Компиляторы генерируют метаданные, а общеязыковая среда времени выполнения CLR, каркас .NET Framework и наши собственные программы могут их использовать. Чтобы лучше разобраться в том, как работают метаданные, мы сосредоточимся на обсуждении их использования, а не на их создании. Метаданные можно прочитать с помо-. шью классов из пространства имен System: : Reflection (Система::Отражение).
    Когда загружаются сборка и связанные с ней модули и типы, метаданные подгружаются вместе с ними. Затем можно сделать запрос к сборке, чтобы получить связанные с ней типы. Можно также вызвать метод GetType для любого из типов общеязыковой среды времени выполнения CLR и получить его метаданные. GetType — это метод класса System: :Object (Система::Объект), производным от которого является каждый тип общеязыковой среды времени выполнения CLR. После того, как получен Туре (Тип), связанный с объектом, можно использовать методы отражения, чтобы получить соответствующие метаданные.
    Программа-пример Reflection (Отражение) берет изучаемую сборку Customer (Клиент) и распечатывает некоторые из доступных метаданных. В следующих разделах книги вы изучите распечатку и исходный код. Особенно важно сравнить вывод программы с исходным кодом в файле customer. h.

    Программа ясно показывает, что из сборки можно извлечь все типы и восстановить структуры, интерфейсы, свойства, события и методы, связанные с этими типами. Сначала мы загружаем сборку в память и выводим ее имя.

    Assembly *a = Assembly::Load(assemblyName); // Загрузка

    Console::WriteLine (
    "Assembly {0} found.", a->FullName);

    Вывод для этого оператора соответствует неподписанной сборке:

    Assembly Customer, Version=l.О.643.18973, Culture=neutral,
    PublicKeyToken=null found.

    CodeBase — одно из свойств класса Assembly; оно обсуждалось в главе 7 "Сборки и развертывание". Подтверждение защиты, связанное с этой сборкой, — это еще одно свойство. Следующий код пробует определить точку входа сборки:

    Methodlnfo *entryMethod!nfo = a->EntryPoint;

    Так как это типичный написанный на C++ компонент сборки, то его точка входа — _DllMainCRTStartup@12. Если бы он был исполняемой программой, мы могли бы использовать метод Invoke (Вызвать) класса Methodlnfo, чтобы выполнить код инициализации сборки.
    В данном примере для поиска связанных с этой сборкой модулей используется метод GetModules сборки. В данном случае мы имеем только один модуль, customer.dll. Затем мы могли бы найти типы, связанные с этим модулем. Вместо этого мы используем метод GetTypes сборки, чтобы возвратить массив типов сборки.

    CompEbook.ru Железо, дизайн, обучение и другие

    Метаданные и удаленный доступ

    Для того чтобы запросить объект определенного типа, клиенту должны быть доступны метаданные о типе. В примере программы удаленного доступа, рассмотренной в этой главе, просто делается ссылка на реальную сборку, в которой хранится объект. Однако во многих приложениях доступ клиента к реальной сборке нежелателен, так как тогда ее можно будет декомпилировать в исходный код с помощью отражения (т.е. восстановить структурную схему и алгоритм работы по исходным текстам). Чтобы получить метаданные, необходимые для клиентской программы, нужно ссылаться только на объект, который содержит информацию об интерфейсе, а не детали реализации.
    Один из возможных способов сделать это состоит в том, чтобы создать версию объекта, которая содержит методы без реализации. Этот интерфейсный класс можно потом встроить в сборку, которая предоставляется клиенту. Можно запускать исключение System: :NotSupportedException в методах, чтобы удостовериться в том, что они никогда не будут по ошибке использованы реальным объектом.
    Для Web-служб можно использовать утилиту SOAPSUDS, с помощью которой извлекаются метаданные из службы, а затем сгенерировать сборку с требуемыми метаданными. Далее можно скомпилировать заместитель динамически подключаемой библиотеки (DLL), на который должна ссылаться программа-клиент. Концептуально это эквивалентно первому подходу. Сервер, конечно, ссылается на реальную сборку.

    В отличие от модели компонентных объектов Microsoft (COM), здесь нет счетчика ссылок, создания и регистрирования отдельных заместителей и заглушек, согласования интерфейсов, беспокойства относительно глобальных идентификаторов и использования системного реестра. Благодаря метаданным для удаленного доступа к объекту необходимо только, чтобы этот объект был производным от MarshalByRef Object.
    CompEbook.ru Железо, дизайн, обучение и другие

    Модель приложений .NET

    Сериализация дала конкретный пример гибкости среды каркаса .NET Framework, используемой при написании кода. Теперь давайте рассмотрим модель, в которой выполняются .NET-приложения. Среда платформы Win32, в которой выполняется программа, называется ее процессом. Эта среда состоит из
  • адресного пространства, в котором хранится код и данные программы;

  • набора переменных среды, связанных с программой;

  • текущего диска и папки;

  • одного или более потоков.

  • CompEbook.ru Железо, дизайн, обучение и другие

    Неуправляемые ресурсы и освобождение ранее выделенной области памяти

    Предположим, что некоторый объект, с помощью которого был открыт файл, программе больше не нужен и помечен для сборки мусора. В конечном счете будет вызван деструктор объекта, при выполнении которого файл может быть закрыт. Но, как мы уже обсуждали, сборка мусора — процесс недетерминированный (во времени, во всяком случае), и файл может оставаться открытым неопределенно долго. Более эффективным было бы иметь в клиентской программе детерминированный механизм освобождения ресурсов объекта, который стал ненужным. Каркас .NET Framework рекомендует для этой цели использовать интерфейс IDisposable.
    public _gc _interface IDisposable
    // сборщик мусора - интерфейс IDisposable
    {
    void Dispose();
    };
    В этом шаблоне проектирования определяется, что клиентская программа должна вызывать Dispose (Освободить ранее выделенную область памяти) для объекта, когда исчезает необходимость в этом объекте. Метод Dispose (Освободить ранее выделенную область памяти) реализуется так, что класс выполняет все необходимые действия по очистке. Для полной гарантии, в классе нужно реализовать и деструктор— на случай, если метод Dispose (Освободить ранее выделенную область памяти) никогда не будет вызван, возможно, из-за появления исключения33. Так как и метод Dispose (Освободить ранее выделенную область памяти), и деструктор производят очистку, код, освобождающий ресурсы, может быть помещен в Dispose (Освободить ранее выделенную область памяти), а в деструкторе можно просто вызывать Dispose (Освободить ранее выделенную область памяти). Метод Dispose (Освободить ранее выделенную область памяти) разработан так, что клиентская программа может вызывать его при завершении работы с объектом или когда будет известно, что нет никакой опасности в освобождении ресурсов, связанных с объектом.
    Отметим, что недопустимо вызывать деструктор объекта после вызова метода Dispose (Освободить ранее выделенную область памяти), потому что это приведет к повторной очистке. Объект может быть удален из очереди сборки мусора с помощью GC: : SuppressFinalize. Кроме того, полезно реализовать в классе булев флажок, назвав его, например disposeCalled. Если Dispose (Освободить ранее выделенную область памяти) вызывается дважды, проверка этого флажка предотвратит повторное выполнение очистки.

    Метод Dispose ( Освободить ранее выделенную область памяти) должен также вызвать метод Dispose (Освободить ранее выделенную область памяти) базового класса с целью удостовериться, что все его ресурсы тоже освобождаются. Причем и этот метод должен быть написан так, чтобы не возникала исключительная ситуация при его вызове, если ресурсы уже были освобождены.

    Поскольку завершение представляет собой дорогой процесс, любой объект, который больше не будет использовать ресурсы, должен вызвать статический метод GC:: SupressFinalize, передавая ему указатель this. При наличии в коде блока try/finally можно разместить вызов метода Dispose (Освободить ранее выделенную область памяти) объекта в блоке finally (наконец), чтобы удостовериться в том, что ресурсы будут освобождены.

    Пример программы DisposeDemo иллюстрирует шаблон для освобождения ресурсов. Класс SimpleLog с помощью класса StreamWriter реализует запись (информации) в файл.

    //SimpleLog.h
    using namespace System;
    // использование пространства имен Система;
    using namespace System::10;
    // использование пространства имен Система::10;
    public _gc class SimpleLog :
    public IDisposable
    // класс сборщика мусора
    SimpleLog: IDisposable
    {
    private: // частный
    StreamWriter *writer; String *name;
    // Строка bool disposeCalled;
    // логический (булев) флажок disposeCalled public:
    SimpleLog(String *fileName) : disposeCalled(false)
    // (Строка *fileName): (ложь)
    {
    name = fileName; // имя файла
    writer = new StreamWriter(fileName, false);
    // устройство записи = новый StreamWriter (имя
    // файла, ложь);
    writer->AutoFlush = true;
    // устройство записи-> Автосброс = истина;
    Console::WriteLine(
    String::Format("logfile {0} created", name));
    // Строка::Формат ("системный журнал (0}
    // создан", имя));
    }
    void WriteLine(String *str) // Строка
    {
    writer->WriteLine(str); // запись
    Console::WriteLine(str);
    }
    void Dispose ()
    {
    if(disposeCalled)
    // если
    (disposeCalled) - если все уже сделано
    return;
    writer->Close ();
    GC::SuppressFinalize (this);
    // СБОРЩИК МУСОРА Console::WriteLine(
    String::Format("logfile {0} disposed", name));
    // Строка::Формат ("системный журнал {0}
    // закрыт ", имя));
    disposeCalled = true;
    // истина - все уже сделано
    }
    -SimpleLog()
    {
    Console::WriteLine(
    String::Format("logfile {0} finalized", name));
    // Строка::Формат ("системный журнал (0)
    // завершен", имя));
    Dispose();
    }
    };


    Класс SimpleLog поддерживает интерфейс IDisposable и таким образом реализует метод Dispose (Освободить ранее выделенную область памяти). Код, освобождающий ресурсы, просто удаляет объект streamWriter. Чтобы удостовериться в том, что для удаленного объекта также не будет вызван метод завершения, вызывается GC: : SuppressFinalize. Завершающий работу объекта деструктор просто делегирует свои функции методу Dispose (Освободить ранее выделенную область памяти). Для контроля над продолжительностью жизни объекта на консоль выводится сообщение из конструктора, из метода Dispose (Освободить ранее выделенную область памяти) и из деструктора.
    Вот код тестовой программы:

    //DisposeDemo.h
    using namespace System;
    // использование пространства имен Система;
    using namespace System::Threading;
    // использование пространства имен
    // Система::Организация поточной обработки;
    public _gc class DisposeDemo
    // класс сборщика мусора DisposeDemo
    { public:
    static void Main()
    {
    SimpleLog *log = new SimpleLog("logl.txt");
    log->WriteLine("First line");
    // файл регистрации-> WriteLine ("Первая строка");

    Pause(); // Пауза
    log->Dispose(); // Первое завершение файла регистрации
    log->Dispose(); // файл регистрации - испытание -второй
    // вызов Dispose
    log = new SimpleLog("Iog2.txt");
    log->WriteLine("Second line");
    // файл регистрации-> WriteLine ("Вторая строка");

    Pause (); // Пауза log = new SimpleLog(
    "Iog3.txt"); // предыдущий (2-ой) файл
    // регистрации освобожден
    log->WriteLine("Third line");
    // файл регистрации-> WriteLine ("Третья строка");

    Pause (); // Пауза
    log =0; // последний файл регистрации освобожден
    GC::Collect();
    // СБОРЩИК МУСОРА:: Собрать ( );
    Thread::Sleep(100);
    // Поток:: Бездействие (100);
    }
    private: // частный
    static void Pause)) // Пауза
    {
    Console::Write("Press enter to continue");
    // Запись:: ("Нажмите ввод для продолжения");
    String *str = Console::ReadLine(); // Строка
    }
    };


    Указателю log ( файл регистрации) на объект SimpleLog по очереди присваиваются три различных указателя на экземпляр объекта. Первый раз ранее выделенная область памяти освобождается должным образом. Второй раз указателю присваивается указатель на третий объект. Это происходит до освобождения ранее выделенной области памяти для второго объекта. В результате второй объект становится мусором. Используя метод Pause (Пауза) для приостановки выполнения этого консольного приложения, мы можем исследовать состояние файлов logl. txt, Iog2 . txt и Iog3. txt в разные моменты выполнения программы.

    Выполнение программы приводит к следующему выводу:

    logfile logl.txt created First line
    Press enter to continue logfile logl.txt
    disposed logfile Iog2.txt created Second line
    Press enter to continue logfile Iog3.txt created Third line
    Press enter to continue logfile Iog3.txt
    finalized logfile Iog3.txt
    disposed logfile Iog2.txt
    finalized logfile Iog2.txt disposed

    Перевод такой:

    системный журнал logl.txt создан
    Первая строка
    Нажмите ввод для продолжения
    системный журнал logl.txt завершен
    системный журнал Iog2.txt создан
    Вторая строка
    Нажмите ввод для продолжения
    системный журнал Iog3.txt создан
    Третья строка
    Нажмите ввод для продолжения
    системный журнал Iog3.txt завершен
    системный журнал Iog3.txt освобожден
    системный журнал Iog2.txt завершен
    системный журнал Iog2.txt освобожден

    После первой паузы файл logl. txt уже создан, и мы можем просмотреть его содержимое с помощью стандартной системной программы Блокнот (Notepad). Если попробовать удалить файл, получим нарушение процедуры совместного использования (общих ресурсов), т.е. нарушение условий коллективного доступа (ошибка совместного доступа, т.к. файл уже открыт другим приложением), как показано на рис. 8.2.

    После второй паузы ранее выделенная для logl. txt область памяти будет освобождена и его можно будет удалить. Файл Iog2.txt к этому моменту уже был создан и открыт. А до третьей паузы будет создан Iog3.txt. Но объектной ссылке на 1од2 . txt было присвоено новое значение, поэтому теперь клиентская программа не может освободить область памяти, ранее выделенную для второго объекта. Если бы Dispose (Освободить ранее выделенную область памяти) был единственным механизмом для удаления второго объекта, нам бы не повезло. К счастью, в классе SimpleObject реализован деструктор, так что при следующей сборке мусора удастся избавиться от второго объекта должным образом. Можно увидеть результат завершения, выполнив программу до конца. Второй объект действительно завершается и ранее выделенная для него область памяти освобождается. Фактически при завершении работы прикладной области деструкторы вызываются для всех не уничтоженных объектов, даже для объектов, которые все еще являются доступными.


    Неуправляемые ресурсы и освобождение ранее выделенной области памяти

    Рис. 8.2. Попытка удалить открытый файл приводит к нарушению процедуры совместного использования, т.е. к нарушению условий коллективного доступа. В результате появляется сообщение об ошибке совместного доступа, т.к. файл уже открыт другим приложением

    В нашем коде мы явно делаем третий объект недоступным, присваивая указателю пустой указатель (log = null), и принудительно устраиваем сборку мусора, вызывая GC: : Collect (СБОРЩИК МУСОРА::Собрать). Потом приложение на некоторое время переходит в режим . ожидания, чтобы сборщик мусора выполнил свою работу до конца перед завершением прикладной области. Наша тестовая программа несколько искусственна, так как сборка мусора недетерминирована. Сборщик мусора вызывается автоматически при выходе из программы и завершении работы прикладной области. Однако при этом системные объекты, такие как Console (Консоль), также закрываются. Так как нельзя полагаться на порядок завершения, можно получить исключение при вызове WriteLine внутри деструктора. Явный вызов GC: :Collect (СБОРЩИК МУСОРА::Собрать) вызывает принудительную сборку мусора в то время, когда системные объекты все еще открыты. Если мы опустим последние три строки из метода Мат (Главный), то можем получить идентичный вывод, но можем также получить и исключение.

    Дополнительное имя для Dispose (Освободить ранее выделенную область памяти)

    Стандартное имя метода, производящего очистку— Dispose (Освободить ранее выделенную область памяти). Соглашение состоит в том, что, как только ранее выделенная для объекта область памяти освобождается, такой объект завершает свою работу. Однако в некоторых случаях, например для файла, тот же самый экземпляр объекта мог бы использоваться повторно. Файл может быть открыт, закрыт, а затем снова открыт. При этом стандартное соглашение об именовании гласит, что метод для очистки должен называться Close (Закрыть). В других случаях может использоваться другое подходящее имя.

    Чтобы вполне естественно, если бы в нашем классе SimpleLog имелся метод Open (Открыть). В этом случае метод для очистки логично было бы назвать Close (Закрыть). Простоты ради, мы не реализовали метод Open (Открыть), и поэтому придерживались имени Dispose (Освободить ранее выделенную область памяти).

    CompEbook.ru Железо, дизайн, обучение и другие

    Объекты сериализации

    Хотя каркас знает, как сохранять объект, помеченный атрибутом Serializable (Преобразуемый в последовательную форму), но все же необходимо определить формат, в котором будет сохранен объект, и носитель данных. Чтобы определить формат, в котором будет сохранен объект, нужно использовать экземпляр объекта, который поддерживает интерфейс IFormatter.
    Каркас имеет два таких класса: System: :Runtime: :Serialization: : Formatters :: Binary: : BinaryFormatter (Система::Время выполнения:: Преобразование в последовательную форму::Форматеры::Двоичный::ВтагуРогтаиег) и System :: Runtime :: Serialization :: Formatters :: Soap :: Soар Formatter (Система :: Время выполнения :: Преобразование в последовательную форму :: Форматеры :: Sоар:: Soap-Formatter). BinaryFormatter использует двоичный, компактный формат для сериализации и преобразования из последовательной формы в параллельную на платформах, которые поддерживают общеязыковую среду времени выполнения CLR. SoapForrnatter использует промышленный стандарт протокола SOAP, который обсуждается в главе 11 "Web-службы". Так как он основан на XML, и, следовательно, является текстовым протоколом, он может использоваться для связи с платформой, не основанной на общеязыковой среде времени выполнения CLR. Двоичный формат быстрее при сериализации и преобразовании данных из последовательной формы в параллельную.

    Можно, конечно, создать свои собственные классы форматирования. Это может понадобиться лишь в том случае, если при взаимодействии с внешней системой нужно учитывать ее собственный байтовый формат файловых объектов.
    Пример Serialization (Сериализация) содержит код, демонстрирующий использование FileStream для сохранения и восстановления обоих форматов: двоичного и SOAP. Конечно, можно использовать любой класс, производный от Stream (Поток, Абстрактный последовательный файл), лишь бы он представлял некоторый носитель данных. Необходимо предпринять специальные меры, чтобы гарантировать, что метод Load (Загрузка) сможет изменять параметр, который указывает на HotelBroker. Для этого параметр объявляется как ссылка на указатель, указывающий на HotelBroker.

    static void Save( // статический метод Сохранить
    HotelBroker *broker, String *formatter)
    {
    FileStream *s;
    if (String::Equals(formatter, "b"))
    // если (Строка::Равняется (форматер, "b"))
    {
    s = new FileStream(
    "hotels.bin", FileMode::Create); // Создать
    BinaryFormatter *b = new BinaryFormatter;
    b->Serialize (s, broker);
    // Преобразовать в последовательную форму (s, брокер);
    }
    else
    {
    s = new FileStream(
    "hotels.txt", FileMode::Create); // Создать
    SoapFormatter *sf = new SoapFormatter;
    sf->Serialize(s, broker);
    // Преобразовать в последовательную форму (з, брокер);
    }
    s->Close ();
    }
    static void Load( // статический метод Загрузка
    HotelBroker *&broker, /* ссылка на указатель */
    String *formatter) // Строка
    {
    FileStream *s;
    if (String::Equals(formatter, "b"))
    // если (Строка::Равняется (форматер, "b"))
    {
    s = new FileStream("hotels.bin", FileMode::Open); // Открыть
    BinaryFormatter *b = new BinaryFormatter; broker = // брокер

    dynamic_cast
    (b->Deserialize (s) ) ;
    }
    else
    {
    s = new FileStream("hotels.txt", FileMode::Open);
    // Открыть
    SoapFormatter *sf = new SoapFormatter;
    broker = // брокер
    dynamic_cast(sf->Deserialize(s));
    }
    s->Close();
    ShowHotelList(broker->GetHotels());
    }

    Ниже приведен некоторый типовой вывод примера Serialization (Сериализация): сначала мы добавляем название гостиницы и сохраняем его с помощью форматера SOAP. Затем мы выходим из программы.

    Enter command: cities
    Atlanta
    Boston
    Commands: quit, cities, list, add, fetch, save
    Enter command: list
    City Name Rooms Rate
    Atlanta Dixie 100 115
    Atlanta Marriott 500 70
    Boston Sheraton 250 95
    Commands: quit, cities, list, add, fetch, save
    Enter command: add
    Hotel City: Philadelphia
    Hotel Name: Franklin
    Number Rooms: 100
    Room Rate: 200
    Commands: quit, cities, list, add, fetch, save
    Enter command: save
    Formatter: b(inary), s(oap)s
    Commands: quit, cities, list, add, fetch, save
    Enter command: cities
    Atlanta
    Boston
    Philadelphia
    Commands: quit, cities, list, add, fetch, save
    Enter command: list
    City Name Rooms Rate
    Atlanta Dixie 100 115
    Atlanta Harriot 500 70
    Boston Sheraton 250 95
    Philadelphia Franklin 100 200
    Commands: quit, cities, list, add, fetch, save
    Enter command: quit


    Перевод такой:

    Введите команду: города
    Атланта
    Бостон
    Команды: выход, города, список, добавить, выборка, сохранить

    Введите команду: список
    Город Название Номера Цена
    Атланта Дикси 100 115
    Атланта Мэриот 500 70
    Бостон Шератон 250 95
    Команды: выход, города, список, добавить, выборка, сохранить

    Введите команду: добавить
    Город Гостиницы: Филадельфия
    Название Гостиницы: Фрэнклин
    Номера: 100
    Цена: 200
    Команды: выход, города, список, добавить, выборка, сохранить

    Введите команду: сохранить
    Форматер: b(inary), s(oap)s
    Команды: выход, города, список, добавить, выборка, сохранить

    Введите команду: города
    Атланта
    Бостон
    Филадельфия
    Команды: выход, города, список, добавить, выборка, сохранить

    Введите команду: список
    Город Название Номера Цена
    Атланта Дикси 100 115
    Атланта Мзриот 500 70
    Бостон Шератон 250 95
    Филадельфия Фрэнклин 100 200
    Команды: выход, города, список, добавить, выборка, сохранить

    Введите команду: выход

    Затем выполняем программу снова и восстанавливаем данные, сохраненныепри первом запуске.

    Enter command: cities
    Atlanta
    Boston
    Commands: quit, cities, list, add, fetch, save
    Enter command: list
    City Name Rooms Rate
    Atlanta Dixie 100 115
    Atlanta Marriot 500 70
    Boston Sheraton 250 95
    Commands: quit, cities, list, add, fetch, save
    Enter command: fetch Formatter: b(inary), s(oap)s
    City Name Rooms Rate
    Atlanta Dixie 100 115
    Atlanta Marriot 500 70
    Boston Sheraton 250 95
    Philadelphia Franklin 100 200
    Commands: quit, cities, list, add, fetch, save
    Enter command: cities
    Atlanta
    Boston
    Philadelphia

    Пере вод такой:

    Введите команду: города
    Атланта
    Бостон
    Команды: выход, города, список, добавить, выборка, сохранить

    Введите команду: список
    Город Название Номера Цена
    Атланта Дикси 100 115
    Атланта Мэриот 500 70
    Бостон Шератон 250 95
    Команды: выход, города, список, добавить, выборка, сохранить

    Введите команду: выборка
    Форматер: b(inary), s(oap)s
    Город Название Номера Цена
    Атланта Дикси 100 115
    Атланта Мэриот 500 70
    Бостон Шератон 250 95
    Филадельфия Фрэнклин 100 200
    Команды: выход, города, список, добавить, выборка, сохранить
    Введите команду: города
    Атланта
    Бостон
    Филадельфия

    CompEbook.ru Железо, дизайн, обучение и другие

    Обработка файлов

    Каркас содержит два класса File (Файл) и File Info, которые очень полезны для работы с файлами. Класс File (Файл) предоставляет основные функциональные возможности для обработки файлов в дополнение к операциям чтения и записи. Поскольку класс File (Файл) содержит только статические члены, имя файла необходимо в качестве параметра. Класс Filelnfo имеет конструктор, который создает объект, представляющий файл. Затем используются методы для обработки этого определенного файла.

    Методы класса File (Файл) всегда выполняют проверку защиты. Если необходимо непрерывно обращаться к определенному файлу, можно использовать класс Filelnfo, так как в этом классе проверка защиты производится только однажды — в конструкторе. Защита обсуждается более подробно в главе 13 "Защита".
    Класс File (Файл)
    Класс File (Файл) содержит методы для создания и открытия файлов, которые возвращают объекты FileStream, StreamWriter или StreamReader, производящие фактическое чтение и запись. Перегруженный метод Create (Создать) возвращает объект FileStream. Метод CreateText возвращает StreamWriter. Перегруженный метод Open (Открыть) в зависимости от передаваемых параметров может создавать новый файл или открывать существующий для чтения или записи. Возвращаемый объект — объект FileStream. Метод OpenText возвращает StreamReader. Метод OpenRead возвращает объект FileStream. Метод OpenWrite возвращает объект типа FileStream.
    Класс File (Файл) содержит также методы копирования, удаления и перемещения файлов. К тому же, мы можем проверить существование файла. Нижеперечисленные атрибуты файла можно прочитать и изменить:
  • время создания;

  • время последнего обращения;

  • последнее время записи;

  • архивный, скрытый, обычный, системный или временный;

  • сжатый, зашифрованный;

  • только для чтения;

  • файл — это каталог?

  • Класс Path (Путь)

  • Очень часто имя файла, передаваемое в качестве входного параметра, должно быть полным путем к файлу. Но нередко удобнее было бы обрабатывать только части пути. Довольно просто это можно сделать с помощью статических методов класса Path (Путь). Класс Path (Путь) содержит статические поля, указывающие на различные зависимые от платформы части имени пути, такие как символы разделителя для каталогов, путей и томов, а также запрещенные символы в имени файла и пути доступа к нему.

    Статические методы этого класса позволяют изменять расширение файла или находить папку с временными файлами. Особенно полезен метод GetFullPath. Ему можно передать относительный путь, например \foo.txt, и он возвратит полный путь файла. Это очень полезно для класса File (Файл) или классов защиты, для которых требуется указывать полный путь к файлу.

    Класс Filelnfo

    Конструктор Filelnfo создает объект, который представляет дисковый файл. Конструктор принимает один параметр — строку, содержащую имя файла. Объект будет иметь свойства, отражающие свойства файла, такие как время создания, размер и полный путь к файлу. Класс содержит методы для создания и открытия файла, которые аналогичны методам класса File (Файл), но работают с конкретным экземпляром файла и поэтому не нуждаются в таком параметре, как имя файла. Класс Filelnf о также содержит методы, позволяющие перемещать и копировать файлы.

    Пример File (Файл)

    Пример File (Файл) в папке FilelO иллюстрирует использование классов Filelnfo и File (Файл). В этом примере используется статический метод Delete (Удалить) класса File (Файл) для удаления указанного файла. Затем статический метод CreateText создает новый файл и возвращает экземпляр StreamWriter, который используется для записи текста в файл. Далее поток закрывается, и статический метод Move (Переслать) переименовывает файл. Потом создается экземпляр Filelnfo, который будет представлять этот переименованный файл. Полное имя файла, размер и дата его создания выводятся на консоль. Файл открывается как текстовый, после чего используется экземпляр streamReader, чтобы прочитать и вывести на консоль содержимое файла.

    File::Delete("file2.txt"); // Удалить файл "file2.txt"

    StreamWriter *sw =
    System::IO::File::CreateText("file.txt");
    sw->Write ("The time has come the Walrus said, "); // Поговорить...
    sw->WriteLine("to talk of many things.");
    sw->Write("Of shoes, and ships, and sealing wax, "); // о ботинках, и судах, и сургуче
    sw->WriteLine("of cabbages and kings."); // капусте и о королях
    sw->Write("And why the sea is boiling hot, "); // И почему
    // море кипит
    sw->WriteLine("and whether pigs have wings."); // и имеют ли
    // свиньи крылья.
    sw->Close();
    File::Move("file.txt", "file2.txt");
    //Файл:: Переслать ("file.txt", "file2.txt");

    Filelnfo *filelnfo = new Filelnfo("file2.txt");
    Console::WriteLine(
    "File {0} is {1} bytes in length, created on {2}",

    // "Файл {0} - {1} байтов, создан {2} ",
    file!nfo->FullName,
    _box(file!nfo->Length), // Длина
    _box(file!nfo->CreationTime));
    Console::WriteLine("");
    StreamReader *sr = file!nfo->OpenText();
    String *s = sr->ReadLine();
    // Строка
    while (s != 0)
    // пока (s != 0)
    {
    Console::WriteLine(s);
    s = sr->ReadLine();
    }
    sr->Close();
    Console::WriteLine("");

    CompEbook.ru Железо, дизайн, обучение и другие

    Определение базового класса

    Последний шаг при работе с самостоятельно создаваемыми атрибутами состоит в том, чтобы предусмотреть способ извлечения пользовательской информации об атрибуте из метаданных при помощи классов отражения. Можно получить Туре (Тип) любого объекта, вызывая метод GetType, который предоставляется корневым классом Object (Объект). Метод GetCustomAttributes этого класса позволяет прочитать пользовательскую информацию об атрибуте.

    Чтобы упростить создание клиентской программы, часто полезно создать базовый класс, который будет проделывать работу по чтению пользовательской информации об атрибутах30. Мы создадим базовый класс DirectoryContext, который будет использоваться классом, желающим воспользоваться преимуществом атрибута Initial-Directory. Этот базовый класс предоставляет свойство DirectoryPath, чтобы возвратить информацию о пути, хранящуюся в метаданных. Вот листинг базового класса:
    //DirectoryContext.h
    using namespace System;
    // использование пространства имен Система;
    using namespace System::Reflection;
    // использование пространства имен Система::Отражение;
    using namespace System::10;
    // использование пространства имен Система::10;
    using namespace System:Collections;
    использование пространства имен Система::Коллекции;
    _gc class DirectoryContext
    // класс сборщика мусора DirectoryContext
    {
    public:
    _property String *get_DirectoryPath()
    // Строка свойства
    {
    Type *t = this->GetType();
    lEnumerator *pEnum =
    t->GetCustomAttributes(true)->GetEnumerator();
    while (pEnum->MoveNext())
    {
    Attribute *a =
    dynamic_cast(pEnum->Current);
    // Атрибут InitialDirectoryAttribute *da =
    dynamic_cast(a);
    if (da != 0)
    // если (da ! = 0)
    {
    return da->Path;
    // Путь
    }
    }
    return Directory::GetCurrentDirectory();
    // Каталог
    }
    };
    Создав базовый класс, можно столкнуться с трудностями, если допускается только единичное наследование реализации. Если класс должен быть производным от другого класса, например ContextBoundObject, то ваш базовый класс должен быть производным от этого класса
    Нужно импортировать пространство имен System:: Reflection (Система-Отражение). GetType возвращает текущий объект Туре (Тип), и тогда можно использовать метод GetCustomAttributes, чтобы получить коллекцию объектов Attribute (Атрибут) из метаданных. Так как эта коллекция неоднородна, поскольку состоит из различных типов, используется оператор dynamic_cast, чтобы проверить, принадлежит ли данный элемент коллекции к типу InitialDirectoryAttribute. Если такой элемент найдется, возвращается свойство Path (Путь). В противном случае возвращается заданный по умолчанию текущий каталог, который можно получить из GetCurrentDirectory.
    CompEbook.ru Железо, дизайн, обучение и другие

    Определение класса атрибута

    Чтобы создать пользовательский атрибут, необходимо определить класс атрибута, производный от базового класса Attribute (Атрибут). В соответствии с соглашением, нужно дать классу имя, заканчивающееся на "Attribute" ("Атрибут"). Имя класса без суффикса "Attribute" ("Атрибут") будет названием пользовательского атрибута. В нашем примере имя класса — InitialDirectoryAttribute, поэтому название атрибута— Initial-Directory.
    Можно реализовать один или несколько конструкторов для класса атрибута. Конструкторы определяют, как передать позиционные параметры для атрибута (предоставив список параметров, разделенных запятыми). Возможно также предусмотреть "поименованные параметры" для пользовательского атрибута, чтобы при передаче информации через параметр можно было использовать синтаксис имя=значение.

    Можно также предусмотреть свойства для чтения информации, передаваемой через параметр. В нашем примере есть свойство Path (Путь), которое инициализируется в конструкторе.
    //DirectoryAttribute.h
    using namespace System;
    // использование пространства имен Система;
    public _gc class InitialDirectoryAttribute :
    // класс сборщика мусора InitialDirectoryAttribute:
    public Attribute
    // общедоступный Атрибут
    {
    private:
    // частный
    String *path;
    // Строка public:
    InitialDirectoryAttribute(String *path)
    // Строка
    {
    this->path = path;
    // путь
    }'
    _property String *get_Path()
    // Строка свойства
    {
    return path;
    // путь
    }
    };
    CompEbook.ru Железо, дизайн, обучение и другие

    Организация поточной обработки с параметрами

    Асинхронный обратный вызов выполняется в потоке, отличном от того, в котором был сделан вызов Beginlnvoke. Если требования к организации поточной обработки просты, то для передачи параметров функциям потока можно использовать асинхронные делегаты. Ссылка на пространство имен Threading (Организация поточной обработки) не нужна. Ссылка на это пространство имен в примере AsynchThreading нужна только для метода Thread: : Sleep (Поток::Режим ожидания), используемого в иллюстративных целях.
    PrintNumbers суммирует числа от начального целого числа, переданного в качестве параметра, до числа, на 10 большего чем начальное целое число. Результат (сумма) возвращается вызывающей программе. PrintNumbers может использоваться для делегата, определенного как Print (Печать).
    //AsynchThreading.h
    using namespace System;
    // использование пространства имен Система;
    using namespace System::Threading;
    // использование пространства имен Система::Потоки;
    public _delegate int Print(int i);
    // делегат int Печать (int i);
    public _gc class Numbers
    // класс сборщика мусора - Числа
    {
    public:
    int PrintNumbers(int start) // начало
    {
    int threadld = Thread::CurrentThread->GetHashCoae();
    // Поток
    Console::WriteLine (
    "PrintNumbers Id: {0}",
    // " Идентификатор PrintNumbers: {0}"
    threadld.ToString() ) ;
    int sum =0; // сумма = О for (int i = start; i < start + 10; i++)
    {
    Console::WriteLine(i.ToString());
    Thread::Sleep(500);
    // Поток:: Бездействие sum += i;
    // суммируем
    }
    return sum;
    // сумма
    }
    Функция Main (Главная) определяет два обратных вызова и вызывает их явно с различными начальными целыми числами. Она ждет, пока оба из дескрипторов синхронизации не получат сигнал. Endlnvoke вызывается для обоих и результат выводится на консоль.
    Numbers *n = new Numbers;
    // новые Числа
    Print *pfnl = new Print(n, Numbers::PrintNumbers);
    // новая Печать
    Print *pfn2 = new Print (n, Numbers::PrintNumbers};
    // новая Печать lAsyncResult *arl =
    pfnl->Begin!nvoke(0, 0, 0);
    lAsyncResult *ar2 =
    pfn2->Begin!nvoke(100, 0, 0);
    WaitHandle *wh [] = new WaitHandle*[2];
    wh[0] = arl->AsyncWaitHandle;
    wh[l] = ar2->AsyncWaitHandle;
    // удостоверимся, что все сделано перед окончанием
    WaitHandle::WaitAll(wh);
    int suml = pfnl->End!nvoke(arl);
    int sum2 = pfn2->End!nvoke(ar2);
    Console::WriteLine(
    "Suml = {0} Sum2 = {!}",
    suml.ToString(), sum2.ToString());
    Вот выдача программы.
    MainThread Id: 2 // Идентификатор
    PrintNumbers Id: 14 // Идентификатор 0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    PnntNumbers Id: 14 // Идентификатор
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    Suml =45 Sum2 =1045
    CompEbook.ru Железо, дизайн, обучение и другие

    Поколения

    Чтобы оптимизировать технологию, каждому объекту, находящемуся в управляемой динамически распределяемой области памяти, назначается поколение. Так, новому объекту назначается поколение 0, и такой объект рассматривается в качестве главного кандидата для сборки мусора. Ранее созданные объекты назначаются поколению 1. Поскольку такие объекты уже просуществовали некоторое время, есть вероятность, что их время жизни будет более продолжительным, чем у объектов поколения 0. Еще более старым объектам назначается поколение 2. Считается, что вероятность пережить сборку мусора у них еще больше. Максимальный номер поколения в текущей реализации .NET равен 2. Это число может быть взято из свойства GC : : MaxGeneration.
    Обычно сборщик мусора "выметает" только поколение 0. Именно в этом поколении находятся наиболее вероятные кандидаты на освобождение памяти. Все объекты поколения 0, пережившие сборку мусора, переходят в поколение 1. Если освобожденной памяти недостаточно, будут "выметены" объекты поколения 1, а выжившие будут продвинуты в поколение 2. Ну а затем, в случае необходимости, будут "выметены" и объекты поколения 2 и так далее вплоть до MaxGeneration — максимального количества поколений.
    CompEbook.ru Железо, дизайн, обучение и другие

    Потоки

    Поток — фактически выполняемая ветвь кода программы. Один или более потоков, запущенных внутри процесса, делают возможным сосуществование многочисленных выполняемых ветвей в рамках одного процесса. Например, используя потоки, программа может модифицировать интерфейс пользователя частичными результатами работы одного потока во время проведения вычислений другим потоком. Все потоки в одном и том же процессе совместно используют среду процесса так, чтобы все они могли обращаться к памяти процесса.
    Операционная система планирует процессорное время, занимаемое потоками. Для процессов и прикладных областей (прикладные области обсуждаются в этой главе позже) планирование процессорного времени не выполняется. Потокам периодически дается ограниченный период процессорного времени. Это делается для того, чтобы они могли совместно использовать процессор. Потокам с более высоким приоритетом отрезки времени будут предоставляться более часто, чем потокам с более низким приоритетом. После того, как проходит некоторое время, выполняющийся поток предоставляет шанс запуска другому потоку. Когда потоку снова предоставляется процессорное время, он продолжает выполнение с той точки, в которой его работа была приостановлена.
    Потоки поддерживают контекст, который сохраняется и восстанавливается, когда планировщик операционной системы переключает потоки. Контекст потока содержит регистры центрального процессора и стек, в которых хранится состояние выполняющегося кода.

    Класс System: : Threading:: Thread (Система::Организация поточной обработ-ки::Поток) моделирует выполняющийся поток. Объект Thread (Поток), который представляет текущий выполняющийся поток, может быть найден с помощью статического свойства Thread :: CurrentThread.

    Исключая случаи, когда код выполняется на многопроцессорной машине или когда какой-нибудь поток пробует занять время процессора, в то время как другой поток на однопроцессорной машине пребывает в состоянии ожидания некоторого события типа ввода-вывода, использование нескольких потоков не приводит к ускорению решения вычислительных задач. Благодаря потокам, однако, удается сократить время ответа сие--темы в тех задачах, где требуется взаимодействие с пользователем. При использовании слишком большого количества потоков производительность может уменьшиться. Это происходит из-за накладных расходов на управление потоками и из-за соперничества между конкурирующими потоками, которое также приводит к затратам вычислительных ресурсов центрального процессора.

    Чтобы помочь читателю лучше разобраться в работе потоков, мы предоставим многошаговый пример Threading (Организация поточной обработки), в котором используются сборки Customer (Клиент) и Hotel (Гостиница). Сначала мы ознакомимся с программой, а затем выполним резервирование. Итак, для начала рассмотрим шаг 0.

    Потоки .NET выполняются как делегаты, определенные классом ThreadStart. Делегат ничего не возвращает (void) и не имеет никаких параметров.

    public _gc _delegate void ThreadStart();
    // сборщик мусора - делегат ThreadStart ();

    Класс NewReservation имеет общедоступный (public) метод MakeReservation, который определяет функцию потока. Так как функция потока не принимает никаких параметров, то в качестве данных она может использовать только поля экземпляра NewReservation.

    Делегат потока создается и передается в качестве параметра для конструктора, который создает объект Thread (Поток). Метод Start (Пуск) объекта Thread (Поток) вызывается для того, чтобы начать выполнение потока. Когда мы будем рассматривать модель программирования асинхронных событий, мы покажем, как передать параметры делегату потока. Программа теперь имеет два потока: первоначальный, который выполнял код запуска потока, и поток, который мы только что создали и который попытается забронировать места в гостинице.

    public _gc class NewReservation
    // класс сборщика мусора NewReservation
    {
    public:
    void MakeReservation()
    {
    Console::WriteLine(
    "Thread {0} starting.", // Запускается поток {О}.
    Thread::CurrentThread-> // Поток
    GetHashCode().ToString());
    ReservationResult *result =
    hotelBroker->MakeReservation(
    customerld, city, hotel, date, numberDays);
    // customerld, город, гостиница, дата, numberDays
    }
    };

    Затем в методе Main (Главный) следующий код запускает поток, используя при этом делегат:

    NewReservation *reservel =
    new NewReservation(customers, hotelBroker); // клиенты
    reservel->customerld = 1;
    reservel->city = "Boston"; // город = "Бостон";

    reservel->hotel = "Presidential"; // гостиница = "Президентская";
    reservel->sdate = "12/12/2001";
    reservel->numberDays = 3;
    // создать делегат для потоков
    ThreadStart *threadStartl = new ThreadStart(
    reservel,
    reservel->MakeReservation);
    Thread *threadl = new Thread(threadStartl); // новый Поток
    Console::WriteLine(
    "Thread {0} starting a new thread.",
    // "Поток {0} запускает новый поток. "
    Thread::CurrentThread-> // Поток
    GetHashCode().ToString());
    threadl->Start ();


    Чтобы заставить первоначальный поток ожидать завершения работы второго потока, вызывается метод Join (Объединить) объекта Thread (Поток). Первоначальный поток теперь заблокирован (находится в состоянии ожидания), пока не завершит работу поток, резервирующий место в гостинице. Результат запроса резервирования места выводится на консоль резервирующим потоком.

    // Блокировать этот поток, пока не завершится рабочий поток

    threadl->Join(); // Объединение
    Console::WriteLine("Done!"); // Завершен

    Синхронизация потоков

    Приложение может создать несколько потоков. Сейчас мы рассмотрим код на шаге 1 из примера Threading (Организация поточной обработки). Теперь несколько запросов о резервировании делаются одновременно.

    NewReservation *reservel =
    new NewReservation(customers, hotelBroker); // клиенты
    NewReservation *reserve2 =
    new NewReservation(customers, hotelBroker); // клиенты
    // создать делегат для потоков
    ThreadStart *threadStartl = new ThreadStart(
    reservel,
    reservel->MakeReservation);
    ThreadStart *threadStart2 = new ThreadStart(
    reserve2,
    reserve2->MakeReservation);
    Thread *threadl = new Thread(threadStartl); // новый Поток
    Thread *thread2 = new Thread(threadStart2); // новый Поток
    Console::WriteLine(
    "Thread {0} starting a new thread.",
    // "Поток {0} запустил новый поток. "
    Thread::CurrentThread-> // Поток
    GetHashCode().ToString());
    threadl->Start(); // Пуск
    thread2->Start();// Пуск
    // Блокировать этот поток, пока не завершится рабочий поток
    threadl->Join(); // Объединение
    thread2->Join(); // Объединение

    Проблема с нашей системой резервирования мест в гостинице состоит в том, что нет никакой гарантии, что один поток не будет влиять на результаты другого. Потоки выполняются только на маленьком временном интервале до того, как уступают процессор другому потоку. Поэтому они могут быть прерваны при выполнении любой операции, над которой работали, если отведенный для них временной промежуток закончился. Например, выполнение потока может быть прервано во время изменения структуры данных. Если другой поток попробует использовать информацию в этой структуре данных или модифицировать ее, то результаты этих операций будут противоречивы и неправильны, или может произойти аварийный отказ программы. (Например, в случае, когда ссылки на устаревшие структуры данных не были еще модифицированы, может произойти необрабатываемое исключение).


    Рассмотрим несколько участков в коде, где происходит заказ места в гостинице и там, где происходит резервирование свободных мест — именно в этих точках могут возникнуть трудности подобного рода. Исследуем код для Broker: : Reserve (Брокер::Резерв) в Broker. h. Сначала сделаем проверку существующих заказов для данной гостиницы и для данной даты, чтобы узнать, есть ли свободные номера. Тогда, если есть в наличии свободный номер, он резервируется. Мы добавили вызов метода Thread: :Sleep (Поток-Режим ожидания) между кодом, который проверяет имеющиеся в распоряжении номера и кодом, который резервирует свободный номер. Вскоре будет объяснено, для чего это было сделано.

    // Проверить, есть ли номера для всех дат
    for (int i = day; i < day + numDays; i++)
    {
    if (numCust[i, unitid] >= unit->capacity)
    {
    result->Reservation!d = -1; // результат
    result->Comment = "Room not available";
    // результат-> Комментарий = "Номера не доступны";
    return result; // результат
    }
    }
    Console::WriteLine(
    "Thread {0} finds the room is available in
    Broker::Reserve",
    // "Поток {0} находит, что номер доступен в
    // Брокер:: Резерв ",
    Thread::CurrentThread-> // Поток GetHashCode{).ToString());

    Thread::Sleep(0); // Поток:: Бездействие
    // Резервировать номер для требуемых дат
    for (int i = day; i < day + numDays; i++)
    numCust[i, unitid] += 1;

    Этот код может привести к противоречивым результатам! Один из потоков может быть прерван сразу после того, как обнаружит последний имеющийся в распоряжении номер, но прежде, чем он получит шанс сделать заказ. Другой поток, которому предоставляется отрезок процессорного времени, может найти тот же самый доступный номер гостиницы и сделать заказ. Когда первый поток запускается снова, он начинает работу с точки, где был прерван, и также закажет тот же самый последний номер в гостинице.

    Чтобы смоделировать возникновение такой ситуации, на этом шаге (шаг 1) примера Threading (Организация поточной обработки) поместим вызов метода Thread:: Sleep (Поток::Режим ожидания) между кодом, который проверяет имеющиеся в распоряжении номера и кодом, который резервирует свободный номер. Вызов Sleep (0) заставляет поток прекратить выполнение и уступить остаток своего временного отрезка.
    Чтобы удостовериться в том, что мы наблюдаем именно возникновение проблемы соперничества между потоками, мы имеем в своем распоряжении всего один номер в целой гостинице! Затем мы подготавливаем нашу программу так, чтобы два потока пробовали резервировать единственный номер в гостинице на ту же самую дату. Рассмотрим код в подпрограмме Main (Главная), который все это подготавливает:


    hotelBroker->AddHotel(
    "Boston", // "Бостон",
    "Presidential", // "Президентская",

    1, // только один номер во всей гостинице!
    (Decimal) 10000); // (Десятичное число)
    NewReservation *reservel =
    new NewReservation(customers, hotelBroker); // клиенты
    reservel->customerld = 1;
    reservel->city = "Boston"; // город = "Бостон";

    reservel->hotel = "Presidential"; // гостиница = "Президентская";
    reservel->sdate = "12/12/2001";
    reservel->numberDays = 3;
    NewReservation *reserve2 =
    new NewReservation(customers, hotelBroker); // клиенты
    reserve2->customerld = 2;
    reserve2->city = "Boston"; // город = "Бостон";

    reserve2->hotel = "Presidential"; // гостиница = "Президентская";
    reserve2->sdate = "12/13/2001";
    reserve2->numberDays = 1;

    Выполнение программы даст приведенный ниже результат:

    Added Boston Presidential Hotel with one room.
    Thread 3 starting a new thread.
    Thread 5 starting.
    Thread 6 starting.
    Reserving for Customer 2 at the Boston Presidential Hotel on

    12/13/2001 12:00:00 AM for 1 days
    Reserving for Customer 1 at the Boston Presidential Hotel on

    12/12/2001 12:00:00 AM for 3 days
    Thread 6 entered Broker::Reserve
    Thread 6 finds the room is available in Broker::Reserve
    Thread 5 entered Broker::Reserve
    Thread 5 finds the room is available in Broker::Reserve
    Thread 6 left Broker::Reserve
    Reservation for Customer 2 has been booked
    Reservationld = 1
    Thread 5 left Broker::Reserve
    Reservation for Customer 1 has been booked
    Reservationld = 2
    ReservationRate = 10000
    ReservationCost = 30000
    Comment = OK
    ReservationRate = 10000
    ReservationCost = 10000
    Comment = OK
    Done!

    Перевод такой:

    Добавлена Бостонская Президентская гостиница с одним номером.

    Поток 3 стартовал новый поток.
    Поток 5 стартовал.
    Поток 6 стартовал.
    Резервирование для Клиента 2 в Бостонской Президентской гостинице на
    12/13/2001 12:00:00 AM в течение 1 дня
    Резервирование для Клиента 1 в Бостонской Президентской гостинице на
    12/12/2001 12:00:00 AM в течение 3 дней
    Поток 6 ввел Брокер:: Резерв
    Поток 6 находит, что номер доступен в Брокер::Резерв
    Поток 5 ввел Брокер::Резерв
    Поток 5 находит, что номер доступен в Брокер::Резерв
    Поток 6 выходит из Брокер::Резерв
    Резервирование для Клиента 2 было заказано
    Reservationld = 1
    Поток 5 выходит из Брокер::Резерв
    Резервирование для Клиента 1 было заказано
    Reservationld = 2
    ReservationRate = 10000
    ReservationCost = 30000
    Комментарий = OK
    ReservationRate = 10000
    ReservationCost = 10000
    Комментарий = OK
    Сделано!


    К сожалению, оба клиента добиваются резервирования последнего (единственного) номера на 13 декабря! Обратите внимание на то, как один из потоков выполняет метод Reserve (Резерв) и находит, что номер доступен прежде, чем прекратит свою работу. Затем другой поток выполняет Reserve (Резерв) и также обнаруживает свободный номер прежде, чем закончится отведенный для него промежуток времени. Потом оба потока заказывают тот же самый номер гостиницы.

    Операционные системы предоставляют средства синхронизации взаимодействия нескольких потоков и процессов, получающих доступ к совместно используемым ресурсам. Каркас .NET Framework предусматривает несколько механизмов предотвращения конфликтов между потоками.

    Каждый объект в каркасе .NET Framework может использоваться в качестве синхронизирующей секции кода (критической секции). В пределах такой секции одновременно может выполняться только один поток. Если один поток уже выполняется внутри такой синхронизирующей секции кода, любые другие потоки, пытающиеся получить доступ к данной секции, блокируются (и ждут) до тех пор, пока выполняющийся поток не покинет эту секцию кода.

    Синхронизация с помощью мониторов

    Класс System: :Threading: :Monitor (Система::Организация поточной об-работки::Монитор) позволяет потокам синхронизировать доступ к объектам, чтобы избежать нарушения целостности данных и неопределенного поведения вследствие соперничества между потоками. Шаг 2 из примера Threading (Организация поточной обработки) демонстрирует использование класса Monitor (Монитор) с указателем this экземпляра HotelBroker.

    ReservationResult *Reserve(Reservation *res) // Резервирование

    {
    Console::WriteLine(
    "Thread {0} trying to enter Broker::Reserve",
    // "Поток {0} пытается войти в Брокер::Резерв",

    Thread::CurrentThread-> // Поток
    GetHashCode() .ToString ()); Monitor::Enter(this); // Монитор::Войти

    . . . (спорный код потока здесь) Monitor::Exit(this); // Выход

    return result; // результат
    }

    Потоку, который первым вызовет метод Monitor: : Enter (this), будет разрешено выполнить метод Reserve (Резерв), потому что он овладеет замком метода Monitor (Монитор) с помощью указателя this. Потоки, пробующие выполнить те же действия, должны будут ожидать, пока первый поток не разблокирует замок с помощью метода Monitor: :Exit (this). После этого и другие потоки смогут вызвать Monitor::Enter(this) и овладеть замком.


    Поток может вызывать Monitor: :Enter (Монитор::Войти) несколько раз, но каждый вызов должен быть скомпенсирован вызовом Monitor: :Exit (Монитор::Выйти). Если поток пытается овладеть замком, но не хочет заблокировать себя, то он может использовать метол Monitor::TryEnter.

    Теперь, когда мы обеспечили синхронизацию, случай, идентичный испытанному в шаге 1, не приведет к повторному резервированию одного и того же гостиничного номера на одно и то же время. Обращаем внимание читателя на то, сколько времени второй поток не может начать выполнение метода Reserve (Резерв) до тех пор, пока первый поток, который начал выполнение этого метода раньше, не покинет критическую секцию. Это решает нашу проблему конкуренции между потоками и устанавливает правило, согласно которому один и тот же номер гостиницы не может быть забронирован двумя клиентами на ту же самую дату.

    Added Boston Presidential Hotel with one room.
    Thread 3 starting a new thread.
    Thread 5 starting.
    Thread б starting.
    Reserving for Customer 2 at the Boston Presidential Hotel on

    12/13/2001 12:00:00 AM for I days
    Thread 6 trying to enter Broker::Reserve
    Thread 6 entered Broker::Reserve
    Thread 6 finds the room is available in Broker::Reserve
    Thread 6 left Broker::Reserve
    Reservation for Customer 2 has been booked
    Reservationld = 1
    Reserving for Customer 1 at the Boston Presidential Hotel on

    12/12/2001 12:00:00 AM for 3 days
    Thread 5 trying to enter Broker::Reserve
    Thread 5 entered Broker::Reserve
    Reservation for Customer 1 could not be booked
    Room not available
    ReservationRate = 10000
    ReservationCost = 10000
    Comment = OK
    Done !

    Пере вод такой:

    Добавлена Бостонская Президентская гостиница с одним номером.

    Поток 3 стартовал новый поток.
    Поток 5 стартовал.
    Поток 6 стартовал.
    Резервирование для Клиента 2 в Бостонской Президентской гостинице на
    12/13/2001 12:00:00 AM в течение 1 дня
    Поток 6 пытается войти в Брокер::Резерв
    Поток 6 вошел в Брокер::Резерв
    Поток 6 находит, что номер доступен в Брокер::Резерв
    Поток 6 вышел из Брокер::Резерв
    Резервирование для Клиента 2 было заказано
    Reservationld = 1
    Резервирование для Клиента 1 в Бостонской Президентской гостинице на
    12/12/2001 12:00:00 AM в течение 3 дней
    Поток 5 пытается войти в Брокер::Резерв
    Поток 5 вошел в Брокер::Резерв
    Резервирование для Клиента 1 не могло быть заказано
    Номера не доступны
    ReservationRate = 10000
    ReservationCost = 10000
    Комментарий = OK
    Сделано!


    Уведомление с помощью мониторов

    Поток, овладевший замком монитора Monitor (Монитор), может, не покидая блока синхронизации, ожидать сигнал от другого потока, который выполняет операцию синхронизации на том же самом объекте. Поток вызывает метод Monitor::Wait (Монитор::Ожидать) и освобождает замок. Потом, когда он получит уведомление от другого потока, то повторно овладевает замком в целях синхронизации.

    Поток, овладевший замком монитора Monitor (Монитор), может послать уведомление другому потоку, ожидающему разрешения на доступ к тому же самому объекту с помощью методов Pulse (Импульс, Сигнал) или PulseAll. Важно, что во время посылки сигнала поток переходит в состояние ожидания. В противном случае, если сигнал будет послан, но поток не подождет некоторое время, то другой поток будет ждать вечно и никогда не сможет получить уведомление. Такое поведение не похоже на событие возврата в исходное состояние, обсуждаемое позже в этой главе. Если много потоков ожидают сигнал, метод Pulse (Импульс, Сигнал) поместит только один поток в очередь готовых для выполнения. PulseAll поместит их все в эту очередь.

    Поток, пославший импульс, больше не владеет замком монитора Monitor (Монитор), но и не блокируется; он может продолжать выполнение. Так как он больше не заблокирован, но не владеет замком, то, чтобы избежать взаимоблокировки (дедлока) или состояния гонок, этот поток должен пробовать повторно овладеть замком (с помощью методов Monitor: :Enter (Монитор::Войти) или Wait (Ожидать)), перед выполнением любых потенциально опасных действий.

    Пример PulseAll иллюстрирует применение методов Pulse (Импульс, Сигнал) и PulseAll. Пример во время выполнения генерирует следующую выдачу:

    First thread: 2 started.
    Thread: 5 started.
    Thread: 5 waiting.
    Thread: 6 started.
    Thread: 6 waiting.
    Thread 5 sleeping.
    Done .
    Thread 5 awake.
    Thread: 5 exited.
    Thread 6 sleeping.
    Thread 6 awake.
    Thread: 6 exited.

    Пере вод такой:

    Первый поток: 2 стартовал.
    Поток: 5 стартовал.
    Поток: 5 в состоянии ожидания.
    Поток: 6 стартовал.
    Поток: 6 в состоянии ожидания.
    Поток 5 бездействует.
    Сделано.
    Поток 5 активный.
    Поток: 5 вышел.
    Поток 6 бездействует.
    Поток 6 активный.
    Поток: 6 вышел.


    Класс X содержит поле "о" типа Ob j ect (Объект), которое будет использоваться в качестве синхронизирующего замка.

    Класс также содержит метод Test (Испытание), который используется в качестве делегата потока. Метод овладевает синхронизирующим замком, а затем ждет уведомления. Когда он получит уведомление, то бездействует в течение половины секунды, а затем вновь освобождает замок.

    Метод main (главный) создает два потока, которые используют метод X: :Test (Х::Испытание) в качестве делегата своего потока и совместно используют тот же самый объект, предназначенный для синхронизации. Затем он бездействует в течение 2 секунд, чтобы позволить потокам произвести запросы ожидания и освободить замки. Потом метод main (главный) вызывает метод PulseAll, чтобы уведомить оба ожидающих потока и освободить замки. В конечном счете, каждый поток повторно овладевает замком, выводит сообщение на консоль, и в последний раз освобождает замок.

    _gc class X
    // класс сборщика мусора X
    { private: // частный
    Object *o; // Объект public:
    X(Object *o) // Объект
    {
    this->o = о;
    }
    void Test () // Испытание
    {
    try
    {
    long threadld =
    "Thread->GetHashCode () ; // Поток
    console::WriteLine(
    "Thread: {0} sta.ted.",
    threadld.ToString()); // "Поток: {0} стартовал."

    Monitor::Enter(о1, // Монитор::Войти
    Console : : Write1" jre (
    '"In.-'ad: tO) waiting.",
    threadld.ToString()); // "Поток: {0} ожидает."
    Monitor::Wait(о); // Монитор::Ждет
    Console::WriteLine( "Thread {0} sleeping.",
    threadld.ToString()); // "Поток {0} бездействует."

    Thread::Sleep(500); // Поток::Бездействовать
    Console::WriteLine( "Thread {0} awake.",

    threadld.ToString()); // "Поток {0} активный. "
    Monitor::Exit (о); // Выход
    Console::WriteLine( "Thread: {0} exited.",
    threadld.ToString()); // "Поток: {0} вышел."

    }
    catch(Exception *e) // Исключение
    {
    long threadld =
    Thread::CurrentThread->GetHashCode();
    Console::WriteLine(
    "Thread: {0} Exception: {!}", // "Поток: {0} Исключение: {1} "
    threadld.ToString(), e->Message); // Сообщение Monitor::Exit(о); // Выход
    }
    }
    };
    _gc class Classl
    // класс сборщика мусора Classl
    {
    public:
    static Object *o = new Object;
    // статический Объект *о = новый Объект;
    static void Main()
    {
    Console::WriteLine(
    "First thread: {0} started.",
    // "Первый поток: {0} стартовал. ",
    Thread::CurrentThread->GetHashCode().ToString() ) ;
    X *a = new X(o); X *b = new X(o);
    ThreadStart *ats = new ThreadStart(a, X::Test); // Испытание


    ThreadStart *bts = new ThreadStart(b, X::Test); // Испытание

    Thread *at = new Thread(ats); // новый Поток
    Thread *bt = new Thread(bts); // новый Поток
    at->Start(); // Начало bt->Start (); // Начало
    // Бездействовать, чтобы позволить другим потокам ждать
    // объект перед Импульсом (Pulse) Thread::Sleep(2000);
    // Бездействие Monitor::Enter (о);
    // Монитор::Войти
    Monitor::PulseAll(о);
    //Monitor::Pulse(о);
    Monitor::Exit(о);
    // Выход
    Console::WriteLine("Done.");
    // Сделано
    }
    };

    Только один поток сможет завершить свою работу, если закомментировать вызов PulseAll и убрать комментарий с вызова метода Pulse (Импульс, Сигнал), потому что другие потоки никогда не смогут стать в очередь готовых потоков. Если удалить Sleep (2000) из главной подпрограммы main (главная), то другие потоки заблокируются навсегда, потому что посылка сигнала происходит до того, как потоки получат шанс вызвать метод Wait (Ожидать), и, следовательно, они никогда не получат уведомления. Методы Wait (Ожидать), Pulse (Импульс, Сигнал) и PulseAll могут использоваться для координирования использования несколькими потоками синхронизационных замков.

    Метод Thread: : Sleep (Поток::Режим ожидания) приводит к приостановке выполнения текущего потока на указанный период времени. Вызов Thread: : Suspend (Поток::Приостановить) блокирует выполнение потока до вызова Thread: :Resume (Поток::Продолжить) другим потоком. Поток также может быть заблокирован, если он ожидает завершения другого потока (Thread: : Join (Поток::Объединить)). Этот метод использовался в примерах Threading (Организация поточной обработки) так, чтобы главный поток мог ждать завершения выполнения запросов резервирования. Поток может также блокироваться при ожидании синхронизирующего замка (в критической секции).

    Вызов Thread: : Interrupt (Поток::Прерывание) для заблокированного потока приводит к его пробуждению. Поток получит ThreadlnterruptedException. И если он не перехватывает это исключение, то среда времени выполнения это исключение перехватит и уничтожит поток.


    Если, в качестве последней надежды, нужно уничтожить поток напрямую, необходимо для этого потока сделать вызов метода Thread: :Abort (Поток:прекратить). Метод Thread: :Abort (Поток::Прекратить) приводит к запуску исключения Thread-AbortException. Это исключение не может быть перехвачено, но оно приведет к выполнению всех блоков finally (наконец). Кроме того, Thread: :Abort (Поток:: Прекратить) не приводит к пробуждению ожидающего потока.

    Поскольку для выполнения блоков finally (наконец) может потребоваться время, или потоки могут находиться в состоянии ожидания, то потоки, выполнение которых прекращается, могут быть завершены не сразу. Поэтому, если нужно убедиться в том, что выполнение потока завершено, необходимо ожидать завершения потока, вызвав метод Thread: : Join (Поток::Объединить).

    Классы синхронизации

    Каркас .NET Framework содержит классы, представляющие собой стандартные в Win32 объекты синхронизации. Все эти классы являются производными от абстрактного класса WaitHandle. Этот класс имеет статические методы Wait All и WaitAny, которые позволяют ожидать сигнал от нескольких (или только от одного) объектов синхронизации. Он также содержит метод экземпляра WaitOne, который позволяет ожидать сигнал отданного экземпляра. Способ приема сигнала объектом зависит от конкретного типа объекта синхронизации, который является производным от WaitHandle.

    Объект Mutex (Взаимное исключение) используется для синхронизации процессов. Мониторы и критические секции работают только в пределах одного процесса. AutoResetEvent и ManualResetEvent используются для того, чтобы просигнализировать, произошло ли какое-нибудь событие. AutoResetEvent остается в сигнальном состоянии, пока ожидающий поток не будет освобожден. ManualResetEvent остается в сигнальном состоянии, пока его состояние не установлено в несигнальное с помощью метода Reset (Сброс). Следовательно, много потоков могут получить сигнал с помощью такого события. В отличие от использования мониторов, код не должен ожидать сигнала прежде, чем устанавливается сигнал сброса события, которое посылает сигнал потоку.


    Каркас предоставляет классы для разрешения некоторых стандартных задач организации поточной обработки. Методы класса Interlocked (Сблокированный) разрешают атомарные операции с совместно используемыми значениями. Это операции типа приращения, декремента, сравнения и обмена. ReaderWriterLock используется, чтобы предоставить доступ для одиночной записи и множественного чтения структур данных. Класс ThreadPool позволяет управлять пулом рабочих потоков.

    Автоматическая синхронизация

    Атрибуты можно использовать для синхронизации доступа к методам экземпляра (нестатическим) и нестатическим полям класса. Доступ к статическим полям и методам не синхронизируется. Чтобы использовать эту возможность нужно создать класс, производный от класса System: :ContextBoundObject и применить к нему атрибут Synchronization (Синхронизация). Этот атрибут не применяется к отдельному полю или методу.

    Данный атрибут находится в пространстве имен System :: Runtime :: Remoting :: Contexts (Система::Время выполнения:: Remoting::Контексты). Он описывает синхронизационные требования для экземпляра класса, к которому применяется. В конструктор SynchronizationAttribute можно передать одно из четырех значений, являющихся статическими полями класса SynchronizationAttribute: NONSUPPORTED, SUPPORTED (ПОДДЕРЖИВАЕМЫЙ), REQUIRED (ТРЕБУЕМЫЙ), REQUIRES_NEW. Пример Threading (Организация поточной обработки) на шаге 3 иллюстрирует, как это сделать.

    using namespace System :: Runtime :: Remoting :: Contexts;

    // использование пространства имен
    // Система :: Время выполнения :: Remoting :: Контексты;
    // SynchronizationAttribute :: REQUIRED (ТРЕБУЕМЫЙ) - 4
    [Synchronization(4) ]
    // [Синхронизация (4)]
    public _gc _abstract class Broker :
    // сборщик мусора - абстрактный класс Broker:
    public ContextBoundObject
    {
    };

    Общеязыковая среда времени выполнения CLR, чтобы удостовериться в том, что поток, в котором выполняются методы объекта, синхронизирован должным образом, должна отслеживать требования объекта по организации поточной обработки. Эти требования называются контекстом объекта. Если один объект должен быть синхронизирован, а другой нет, то говорят, что они находятся в двух отдельных контекстах. Общеязыковая среда времени выполнения CLR должна овладеть замком синхронизации от имени кода, когда поток, который выполняет не нуждающийся в синхронизации метод объекта, начинает выполнять нуждающийся в синхронизации метод другого объекта. Общеязыковая среда времени выполнения CLR знает, что в этом случае нужно сделать, потому что она может сравнить требования первого объекта к организации поточной обработки с требованиями второго объекта, просто сравнивая их контексты.


    Об объектах, разделяющих одинаковое состояние, говорят как о живущих в том же самом контексте. Например, два объекта, которые не должны быть синхронизированы, могут совместно использовать один и тот же контекст. ContextBoundOb j ect и контексты обсуждаются более подробно в разделе, посвященном контекстам.

    Используя это интуитивное понятие контекста, мы можем теперь объяснить назначение различных атрибутов синхронизации Synchronization (Синхронизация). NOT_SUPPORTED означает, что класс не может поддерживать синхронизацию своих методов экземпляров и нестатических полей и поэтому не должен создаваться в контексте синхронизации. REQUIRED (ТРЕБУЕМЫЙ) значит, что класс требует синхронизации доступа к своим методам и полям экземпляров. Однако, если поток уже синхронизирован, он может использовать существующий замок синхронизации и жить в существующем контексте синхронизации. REQUIRES_NEW означает, что требуется не только синхронизация, но также и доступ к методам и полям экземпляра должен происходить с уникальным замком синхронизации и в отдельном контексте. Атрибут SUPPORTED (ПОДДЕРЖИВАЕМЫЙ) применяется, если класс не требует синхронизации доступа к методам и полям экземпляра; для него не нужно создавать новый контекст.

    Можно также передать булев флажок в конструктор, чтобы указать, требуется ли реентерабельность. Если флажок установлен (т.е. реентерабельность требуется), синхронизируются обратные вызовы из методов. В противном случае синхронизируются только вызовы методов.

    Используя атрибут Synchronization (Синхронизация) в методе Brocker :: Reserve (Брокер :: Резерв), можно отказаться от Monitor :: Enter (Монитор :: Войти) и Monitor :: Exit (Монитор :: Выйти).

    Так же, как и в шаге 2, в этом примере иллюстрируется попытка дважды зарезервировать последний свободный номер в гостинице. Вот как выглядит выдача при выполнении этого примера:

    Added Boston Presidential Hotel with one room.
    Thread 13 starting a new thread.
    Thread 28 starting.
    Thread 29 starting.
    Reserving for Customer 1 at the Boston Presidential Hotel on


    12/12/2001 12:00:00 AM for 3 days
    Thread 28 entered Broker::Reserve
    Thread 28 finds the room is available in Broker::Reserve
    Thread 28 left Broker::Reserve
    Reservation for Customer 1 has been booked
    Reservationld = 1 ReservationRate = 10000
    ReservationCost = 30000
    Comment = OK
    Reserving for Customer 2 at the Boston Presidential Hotel on

    12/13/2001 12:00:00 AM for 1 days
    Thread 29 entered Broker::Reserve
    Thread 29 left Reserve.
    Reservation for Customer 2 could not be booked
    Room not available
    Done !

    Пере вод такой:

    Добавлена Бостонская Президентская гостиница с одним номером.

    Поток 13 стартовал новый поток.
    Поток 28 стартовал.
    Поток 29 стартовал.
    Резервирование для Клиента 1 в Бостонской Президентской гостинице

    на
    12/12/2001 12:00:00 AM в течение 3 дней
    Поток 28 вошел в Брокер::Резерв
    Поток 28 находит, что номер доступен в Брокер::Резерв
    Поток 28 вышел из Брокер::Резерв
    Резервирование для Клиента 1 было заказано
    Reservationld = 1
    ReservationRate = 10000
    ReservationCost = 30000
    Комментарий = OK
    Резервирование для Клиента 2 в Бостонской Президентской гостинице на
    12/13/2001 12:00:00 AM в течение 1 дня
    Поток 29 вошел в Брокер::Резерв
    Поток 29 вышел из Резерв.
    Резервирование для Клиента 2 не могло быть заказано
    Номер не доступен
    Сделано!

    Как и в предыдущем случае, второй поток не может выполнить метод Reserve (Резерв), пока поток, который начал выполнение первым, не закончится. Номер резервируется только раз.

    Отличие этого автоматического подхода заключается в том, что синхронизируется доступ ко всем методам класса независимо от того, нуждаются они в этом или нет. Доступ к другим совместно используемым данным с помощью других методов данного класса также блокируется. Такое поведение получается независимо от желания программиста.

    Обращаем внимание читателя на то, что в любой определенный момент только один поток может выполнять какой-нибудь метод класса. Предположим, что мы добавляем к классу еще один метод под названием CancelReservation. Если какой-нибудь поток вызвал метод CancelReservation, то блокируются все другие потоки, делающие попытки выполнять другие методы класса, включая и метод MakeReservation.


    Для программы, которая производит резервирование номеров в гостинице, это желательное поведение, так как нежелательно, чтобы метод MakeReservation пытался использовать структуру данных в момент ее изменения. В некоторых ситуациях, однако, такое поведение нежелательно и уменьшает производительность из-за ненужного блокирования. Увеличение числа конфликтов может стать помехой для масштабирования, так как блокируются не только определенные, нуждающиеся в синхронизации области.

    Использовать атрибуты проще, чем критические секции. Не нужно заботиться о подробностях правильной реализации синхронизации. С другой стороны, в результате мы получаем поведение, которое уменьшает масштабируемость. Разные приложения и различные части одного и того же приложения могут получать выгоду от использования того подхода, который в данном конкретном случае имеет больше смысла.

    CompEbook.ru Железо, дизайн, обучение и другие

    Потоковые классы

    Stream (Поток, Абстрактный последовательный файл) — абстрактный класс, который является базовым для чтения и записи байтов в некоторое хранилище данных типа файла. Этот класс поддерживает синхронные и асинхронные чтение и запись. Асинхронные методы обсуждаются позже в данной главе. Класс Stream (Поток, Абстрактный последовательный файл) содержит типичные и вполне ожидаемые от такого класса методы: Read (Чтение), Write (Запись), Seek (Поиск), Flush (Дозапись) и Close (Закрыть).
    Класс FileStream, который является производным от класса Stream (Поток, Абстрактный последовательный файл), предоставляет операции чтения и записи последовательности байтов в файл. Конструктор FileStream создает экземпляр потока. Перегруженные методы класса Stream (Поток, Абстрактный последовательный файл) осуществляют чтение и запись в файл.
    У класса Stream (Поток, Абстрактный последовательный файл) есть и другие производные классы: MemoryStream, BufferedStream и NetworkStream (в System: :Net: : Sockets (Система::Сеть::Сокеты)).
    Пример FileStream (в папке FilelO с примерами ввода/вывода) иллюстрирует, как использовать потоковый класс Stream (Поток, Абстрактный последовательный файл). Если файл не существует, создается новый, а потом в этот файл записываются числа от 0 до 9. Если файл уже существует, программа читает 5 байтов в конце файла и затем выводит их на консоль. (Пример нужно выполнить дважды. Первый раз программа создаст файл и запишет в него числа, а во второй раз прочитает и распечатает часть файла).
    unsigned char data _gc[] =
    // сборщик мусора - данные - символы без знака
    new unsigned char _gc [10];
    // сборщик мусора - новый символ без знака
    FileStream *fs = new FileStream(
    "FileStreamTest.txt",
    FileMode::OpenOrCreate);
    if (fs->Length == 0)
    // если Длина == 0
    {
    Console::WriteLine("Writing Data...");
    // Запись данных
    for (short i = 0; i < 10; i++) data[i] = (unsigned char)i;

    // данные = символ без знака
    fs->Write(data, 0, 10);
    // Запись данных
    }
    else
    {
    fs->Seek(-5, SeekOrigin::End);
    // Ищем конец
    int count = fs->Read(data, 0, 10);
    // Чтение данных
    for (int i = 0; i < count; i++)
    // счет
    {
    Console::WriteLine(data[i]);
    // данные
    }
    }
    fs->Close();
    CompEbook.ru Железо, дизайн, обучение и другие

    Прикладная область

    Прикладная область .NET (Application Domain, иногда называемая AppDomain) — это более простой способ изоляции приложений, безопасный и устойчивый к ошибкам. В рамках одного процесса может находиться несколько прикладных областей. Поскольку в .NET код проверяется на типовую безопасность и защищенность, общеязыковая среда времени выполнения CLR гарантирует, что прикладная область может выполняться независимо от других прикладных областей в рамках одного процесса. Для изоляции приложения переключать процессы не требуется.
    В прикладной области может быть несколько контекстов, но какой-нибудь отдельный контекст может существовать только в одной прикладной области. Поток в определенный момент может выполняться только в определенном контексте определенной прикладной области. Но, как показывают шаг 3 и шаг 4 примера Threading (Организация поточной обработки), поток, вообще говоря, может выполняться в различных контекстах (более чем в одном). Один или несколько потоков могут выполняться одновременно в рамках одной прикладной области. Какой-нибудь объект может существовать только в рамках одного контекста.
    Каждая прикладная область создается вместе с одним потоком в пределах одного контекста. Дополнительные потоки и контексты, в которых они выполняются, создаются по мере необходимости.
    Количество прикладных областей не зависит от количества потоков. Web-сервер может требовать создания отдельной прикладной области для запуска приложения на каждый сеанс соединения в рамках одного процесса. Потоков же в этом процессе может быть гораздо меньше, причем это зависит от того, сколько потоков реально сможет поддерживать процесс.
    Чтобы изолировать приложения, код из одной прикладной области не должен непосредственно вызывать кода (или даже ссылаться на ресурсы) из других прикладных областей.
    CompEbook.ru Железо, дизайн, обучение и другие

    Прикладные области и сборки

    Приложение компонуется из одной или нескольких сборок. Но каждая сборка загружается в какую-либо прикладную область. Каждая прикладная область может быть выгружена из памяти независимо от других. Однако нельзя выгрузить отдельную сборку из какой-либо прикладной области. Сборка может быть выгружена только вместе с прикладной областью. Выгрузка той или иной прикладной области из памяти освобождает также все ресурсы, ассоциированные с этой прикладной областью.
    Каждый процесс по умолчанию содержит прикладную область, которая создается при запуске процесса. Эта создаваемая по умолчанию прикладная область может быть выгружена только с завершением работы процесса.
    Для приложений, подобных ASP.NET или Internet Explorer, чрезвычайно важно (критично!) предотвратить взаимное вмешательство приложений, выполняющихся в рамках таких приложений. Код приложений никогда не загружается в прикладную область, создаваемую по умолчанию. Поэтому крах какого-либо из них не приведет к краху главного приложения.
    CompEbook.ru Железо, дизайн, обучение и другие

    AppDomain (Прикладная область)

    При выполнении примера AppDomain (Прикладная область) получается выдача, показанная на рис. 8.1.
    AppDomain (Прикладная область)

    Рис. 8.1. Выдача в примере AppDomain (Прикладная область)
    Сначала выводится имя, поток и контекст прикладной области, заданной по умолчанию
    AppDomain *currentDomain = AppDomain:: CurrentDomam;
    Console::WriteLine(
    "At startup, Default AppDomain is {0}
    Threadld: {1}
    "При запуске по умолчанию AppDomain - {0}
    Threadld: {1}
    Contextld {2}\n",
    currentDomain->FrlendlyName,
    Thread::CurrentThread->GetHashCode() .ToString(),
    Thread::CurrentContext->ContextID.ToString());
    Затем загружается и выполняется сборка Код из этой сборки только выводит строку, название прикладной области, в которую загружена сборка, а также название потока и контекста Необходимо отметить, что все это выполняется в прикладной области, создаваемой по умолчанию
    int val = domain->ExecuteAssembly(
    "TestApp\\bin\\Debug\\TestApp.exe", 0, args);

    // параметры
    Потом мы создадим экземпляр Customers (Клиенты) из сборки Customer (Клиент) в прикладной области, заданной по умолчанию Метод Createlnstance класса AppDomain (Прикладная область) возвращает экземпляр ObjectHandle Этот ObjectHandle можно передавать между прикладными областями без загрузки метаданных, ассоциированных с упакованным типом Если нужно использовать созданный экземпляр объекта, его следует распаковать, вызвав метод Unwrap (Развернуть) для объекта Ob] ectHandle
    Objecthandle *on = currentDomain->Create!nstance(
    "Customer", "01.NetCpp.Acme.Customers"), // Клиент
    Customers *custs = // Клиенты
    dynamic_cast(oh->Unwrap ());
    Затем мы добавляем нового клиента, а потом перечисляем всех существующих клиентов Необходимо отметить, что и конструктор этого типа, и его методы выполняются в том же самом потоке и контексте, что и созданная по умолчанию прикладная область
    Далее мы создаем новую прикладную область, а в ней — экземпляр того же типа, что и раньше
    AppDomain *domain = AppDomain::CreateDomain(
    "CreatedDomainl", 0, 0);
    oh = domain->CreateInstance(
    "Customer", "01.NetCpp Acme.Customers"); // Клиент
    Customers *custs2 = dynamic_cast // Клиенты
    (oh->Unwrap());

    Необходимо отметить, что в результате вызова метода Createlnstance происходит вызов конструктора, который выполняется в новой прикладной области и поэтому в контексте, отличном от того, в котором был сделан вызов Createlnstance Однако выполнение этого вызова происходит в том же потоке, в котором был сделан вызов Createlnstance
    Когда мы составляем список клиентов в этом новом объекте, мы получаем другой список клиентов И не удивительно, ведь это другой объект Customers (Клиенты) Тем не менее, метод составления списка клиентов выполняется в заданной по >молчанию прикладной области1
    Используя RemotingServices: : IsTransparentProxy, можно увидеть, что ObjectHandle — это заместитель объекта Customers (Клиенты) в новой созданной прикладной области (AppDomain) Однако, если попытаться распаковать объект, чтобы получить дескриптор экземтяра, то в результате мы получим не указатель на заместитель, а фактическую объектную ссылку По умолчанию, объекты передаются по значению (копируются) из одной прикладной области в другую
    Если объект Customers (Клиенты) — нельзя сериализовать (преобразовать в последовательную форму), то при попытке его скопировать будет запущено исключение Это исключение появляется при вызове метода Unwrap (Развернуть), а не Createlnstance Последний метод возвращает ссылку Копирование происходит только в том случае, если объект ObjectHandle распакован Если же объект не может быть преобразован в поспедоватечьную форму, он не может быть скопирован из одной прикладной области в другую

    На следующем шаге будет создан новый поток, который создаст новую прикладную область, куда затем загружается и где выполняется какая-нибудь сборка Сборка начинает выполняться со своей точки входа — подпрограммы Мат (Главная) класса
    AppDomainTest
    AppDomain *domain = AppDomain::CreateDomain( "CreatedDomain2", 0, 0);
    String * args[] = new String *[!];
    // Строка * параметры [] = новая Строка * [1];
    args[0] = "MakeReservation"; // параметры [О]
    int val = domain->ExecuteAssembly(
    "TestApp\\bin\\Debug\\TestApp.exe", 0, args); // параметры
    AppDomain::Unload(domain);


    Подпрограмма Main (Главная) загружает сборку Hotel (Гостиница) в созданную прикладную область. В этом примере приложение TestApp. exe реализовано на С#. После загрузки оно запрашивает метаданные сборки для получения информации о типе HotelBroker. Затем эта информация используется для создания объекта HotelBroker. Класс HotelBroker помечен атрибутом синхронизации. В результате конструктор HotelBroker и метод MakeReservation работают в контексте, отличном от заданного по умолчанию.
    Assembly a = AppDomain.CurrentDomain.Load("Hotel") ;
    // Сборка - Загрузить ("Гостиница"); Type typeHotelBroker =
    а.GetType("01.NetCpp.Acme.HotelBroker"); HotelBroker hotelBroker =
    (HotelBroker)Act vator.CreateInstance(typeHotelBroker); DateTime date = Date
    Time.Parse("12/2/2001"); // дата = DateTime.
    Синтаксический анализ ("12/2/2001");
    ReservationResult rr = hotelBroker.MakeReservation(1,
    "Boston", "Sheraton", date, 3);
    // "Бостон", "Шератон", дата, 3);
    Console.WriteLine("\tReservation Id: {0}", // Идентификатор

    rr.Reservationld);
    CompEbook.ru Железо, дизайн, обучение и другие

    Пример программы, реализующей удаленный доступ

    В примере Remoting клиент обращается к активизированному сервером объекту. Сервер— это класс TcpServerChannel, использующий двоичный формат с протоколом управления передачей TCP. Канал будет использовать порт 8085. Сервер регистрирует удаленный тип, имя конечной точки для ссылки на этот объект и тип активизации. Потом сервер переходит в состояние ожидания клиентских запросов.
    TcpServerChannel *chan = new TcpServerChannel(8085);
    ChannelServices::RegisterChannel(chan); // канал
    RemotingConfiguration::RegisterWellKnownServiceType(
    _typeof(Customers), // Клиенты
    "AcmeCustomer",
    WellKnownObjectMode::Singleton); // Единичный предмет
    Сервер должен быть запущен прежде, чем клиентская программа обратится к объекту. Клиент устанавливает объект TcpClientChannel и затем присоединяется к нему. . Он определяет требуемый тип объекта и конечную точку, в которой сервер будет ожидать запросы объекта. Если нужно выполнить клиент и сервер на отдельных машинах, необходимо указать имя машины сервера для локального узла в конечной точке. В отличие от прозрачности определения места расположения в модели компонентных объектов Microsoft (СОМ), клиент должен сам определить конкретную конечную точку. Здесь нет переадресации с помощью скрытой записи в системном реестре.
    TcpClientChannel *chan = new TcpClientChannel;
    ChannelServices::RegisterChannel(chan); // канал
    Customers *obj = dynamic_cast( // Клиенты

    Activator::GetObject( // Активатор
    _typeof(Customers), // Клиенты
    "tcp://localhost:8085/AcmeCustomer"));
    if (obj == 0) // если (obj == 0)
    Console::WriteLine("Could not locate server");
    // ("He могу определить местонахождение сервера");

    else
    {
    Потом клиент использует заместитель, чтобы вызвать объект так, как будто это локальный экземпляр.
    bool bRet = // логический (булев)
    RemotingServices::IsTransparentProxy(obj);
    ArrayList *ar;
    ar = obj->GetCustomer(-1);
    ShowCustomerArray(ar);
    Чтобы выполнить программу, нужно сначала запустить сервер с одной консоли, а затем запустить клиент в другом консольном окне.

    Вывод зависит от того, какой объект активизирует сервер. Если на сервере активизируется Singleton (Одноэлементное [одноточечное] множество) — объект, который может иметь состояние, то получим поведение, какого можно ожидать в случае недистан-циированного объекта. Когда новый клиент добавлен, его можно обнаружить в списке, если сделать запрос обо всех существующих клиентах. Как и следовало ожидать, начальный активизирующий вызов приводит к тому, что конструктор Customers (Клиенты) вызывается один раз при каждом инициировании работы сервера, независимо от количества запусков клиентской программы.
    Object reference a proxy?: True
    Client: AppDomain Client.exe Thread 19 Context 0
    1 Rocket Squirrel rocky@frosbitefalls.com
    2 Bullwinkle Moose moose@wossamotta.edu
    1 Rocket Squirrel rocky@frosbitefalls.com
    2 Bullwinkle Moose moose@wossamotta.edu
    3 Boris Badenough boris@no-goodnicks.com
    Результаты весьма отличаются от приведенных выше, если тип активизации — SingleCall, при котором для каждого вызова метода создается новый экземпляр объекта. При этом создаются четыре различных объекта. Первый объект создан при начальном активизирующем запросе. Второй создается при начальном вызове метода GetCustomer. Третий объект создается вследствие вызова RegisterCustomer. И четвертый — в результате повторного вызова метода GetCustomer. Последний созданный объект никогда не увидит нового клиента, потому что состояние не сохраняется. Обратите внимание, что статический член nextCustld класса Customer (Клиент) обрабатывается как статический относительно новых экземпляров объектов класса Customer (Клиент), т.е. именно так, как того и следовало ожидать. Тот же программный код клиента, а результаты различные! Поскольку объект уже активизирован, при повторном выполнении клиентской программы для той же самой инициализации работы сервера, конструктор Customer (Клиент) будет вызван только трижды.
    Object reference a proxy?: True
    Client: AppDomain Client.exe Thread 19 Context 0
    3 Rocket Squirrel rocky@frosbitefalls.com
    4 Bullwinkle Moose moose@wossamotta.edu
    8 Rocket Squirrel rocky@frosbitefalls.com
    9 Bullwinkle Moose moose@wossamotta.edu
    Поскольку клиент использует заместитель, объект выполняется в рамках прикладной области сервера, но в потоке, отличном от главного потока сервера. Конструктор объекта не вызывается до первого вызова какого-нибудь метода этого объекта. В обоих случаях
    мы получили удаленный список массивов (ArrayList) типов, не проделывая какой-либо специальной работы, кроме указания на то, что тип может быть преобразован в последовательную форму. Именно использование метаданных значительно облегчает работу программиста.
    CompEbook.ru Железо, дизайн, обучение и другие

    Пример удаленного объекта

    Для нашего примера удаленного доступа мы изолируем наш объект Customers (Клиенты) от сборки Customer (Клиент). В папке примера Remoting находятся два решения. Одно представляет собой клиентскую часть программы, другое — серверную. Сначала нужно скомпоновать серверную часть приложения. При этом также создастся Customer.dll. Необходимо скопировать эту динамически подключаемую библиотеку (DLL) в папку Debug (Отладка) для решений Server (Сервер) и Client (Клиент). Нужно обратить внимание, что сначала запускается программа-сервер, которая после запуска переходит в режим ожидания клиентского запроса. После этого можно выполнить клиентскую часть приложения, которая активизирует объекты, существующие внутри сервера. Подробности программного кода клиента и сервера мы обсудим в нескольких последующих разделах.
    Отметим, что нужно было внести два простых изменения в наш объект. Класс Customers (Клиенты) в проекте сервера — удаленный (с возможностью удаленного доступа), — для этого мы сделали его производным от MarshalByRefObject.
    public _gc class Customers :
    // класс сборщика мусора Клиенты:
    public MarshalByRefObject, public ICustomer
    CustomerListltem, который будет передаваться по значению, должен быть сделан преобразуемым в последовательную форму.
    [Serializable]
    // [Преобразуемый в последовательную форму]
    public _value struct CustomerListltem
    {
    public:
    int Customerld;
    String *FirstName; // Строка
    String *LastName; // Строка
    String *EmailAddress; // Строка
    };
    CompEbook.ru Железо, дизайн, обучение и другие

    Примитивные типы данных и потоки

    Классы, производные от Stream (Поток, Абстрактный последовательный файл), целесообразно использовать тогда, когда нужно читать или писать байты данных блоками. Если необходимо прочитать в поток или записать из потока простой тип, такой как Boolean (булев, логический), String (Строка) или Int32, следует использовать классы BinaryReader и BinaryWriter. Пример Binary (Двоичный) в папке FilelO показывает, как использовать эти классы. Нужно создать соответствующий поток (FileStream в примере) и передать его в качестве параметра в конструктор BinaryReader или BinaryWriter. Потом можно использовать один из перегруженных методов Read (Чтение) или Write (Запись) для чтения данных из потока или записи данных в поток. (Причем пример опять нужно выполнить дважды. Сначала файл создается и в него записываются данные. Во второй разданные читаются из файла.)
    FileStream *fs = new FileStream(
    "BinaryTest.bin", FileMode::OpenOrCreate);
    if (fs->Length == 0) // если Длина == 0
    {
    Console::WriteLine("Writing Data...");
    // Запись данных
    BinaryWriter *w = new BinaryWriter(fs);
    for (short i = 0; i < 10; i++) w->Write(i);
    // Запись
    w->Close () ;
    }
    else
    {
    BinaryReader *r = new BinaryReader(fs);
    for (int i = 0; i < 10; i++)
    Console::WriteLine(r->ReadInt16());
    r->Close();
    }
    fs->Close();
    CompEbook.ru Железо, дизайн, обучение и другие

    Программа-пример

    Программа GarbageCollection (Сборка мусора) иллюстрирует использование рассмотренных выше методов класса GC (СБОРЩИК МУСОРА). Пример несколько искусственен. С его помощью просто иллюстрируется продолжительность жизни объекта и эффект использования различных методов класса GC (СБОРЩИК МУСОРА). Объекты, для которых выделяется память, принадлежат классу Member (Элемент). Этот класс имеет свойство типа String (Строка) под названием Name (Имя). Операторы вывода использованы в конструкторе, деструкторе и методе Dispose (Освободить ранее выделенную область памяти). Класс Committee (Комитет) поддерживает список массивов, состоящий из экземпляров класса Member (Элемент). Метод RemoveMember просто удаляет элемент из списка массивов. Метод DisposeMember также вызывает метод Dispose (Освободить ранее выделенную область памяти) для вычеркиваемого из списка элемента. Метод ShowGenerations отображает номер поколения каждого объекта класса Member (Элемент). Испытательная программа GarbageCollection.h использует эти классы; она показывает результаты различных размещений (распределений) и освобождений памяти при использовании методов класса GC (СБОРЩИК МУСОРА). Код и вывод должны быть весьма просты для понимания.

    Вся память распределяется локально в методе DemonstrateGenerations. После того, как указанный метод завершит свою работу, и его локальная память стает недоступной, мы явно вызываем GC: :Collect (СБОРЩИК МУСОРА: :Собрать). Это приводит к вызову соответствующих деструкторов, прежде чем вся прикладная область прекратит свое существование. Таким образом мы избегаем возможного случайного исключения в закрывающемся потоке, когда метод WriteLine вызывается в методе завершения. Этот самый прием мы уже видели в более ранних примерах.
    CompEbook.ru Железо, дизайн, обучение и другие

    Программируемые атрибуты

    В главе 5 "Управляемый C++ в .NET Framework" мы ввели концепцию атрибутов, которые уже появлялись в нескольких примерах. В этой главе мы использовали атрибуты Serializable (Преобразуемый в последовательную форму) и Synchronization (Синхронизация), которые предоставляются классами .NET Framework. Каркас .NET Framework делает механизм использования атрибутов полностью расширяемым, позволяя определять собственные произвольные атрибуты, которые могут быть добавлены к метаданным класса. Эти пользовательские метаданные доступны благодаря механизму отражения и могут быть использованы во время выполнения. Чтобы упростить использование самостоятельно определенных атрибутов, можно объявить базовый класс, который будет вызывать отражающий интерфейс прикладного программирования (API) для получения информации из метаданных.
    В примере CustomAttribute иллюстрируется использование самостоятельно созданного атрибута InitialDirectory. InitialDirectory указывает начальный текущий каталог, из которого запускается программа. По умолчанию текущим каталогом является тот, в котором содержится решение, и в нашем случае это каталог С:\01\NetCpp\Chap08\CustomAttribute.
    CompEbook.ru Железо, дизайн, обучение и другие

    Данная глава представила модель приложений

    Данная глава представила модель приложений .NET. С помощью метаданных и отражения, каркас может получить всю необходимую информацию о программе, чтобы предоставить множество служб, которые ранее программистам приходилось реализовывать самостоятельно. С другой стороны, мы видели, что каркас структурирован так, чтобы можно было использовать свои собственные объекты и реализации там, где это необходимо.
    Типовая безопасность дает возможность прикладным областям обеспечить экономичную и эффективную изоляцию приложений. Контексты, заместители и перехват позволяют во время выполнения прозрачно предоставлять услуги тем частям приложения, которые их требуют.
    Еще один аспект модели приложений .NET — распространяющееся использование атрибутов, которые могут быть просто добавлены к исходному коду и сохранены вместе с метаданными. Мы видели примеры использования атрибутов для сериализации и синхронизации. Научились также реализовывать и использовать самостоятельно определяемые атрибуты.
    .NET упрощает программирование управления памятью с помощью эффективного, автоматического средства сборки мусора, которое учитывает поколения объектов. Процесс завершения объекта недетерминирован, но можно реализовать детерминированный процесс очистки с помощью шаблона освобождения памяти dispose (Освободить ранее выделенную область памяти) или явного использования оператора delete (уничтожить).
    CompEbook.ru Железо, дизайн, обучение и другие

    Сборка мусора

    Распределенная управляемая память автоматически возвращается системе с помощью алгоритма сборки мусора. Общеязыковая среда времени выполнения CLR отслеживает использование памяти, которая выделяется в управляемой ею динамически распределяемой области памяти, и любой участок памяти, на который больше ничто не ссылается, отмечается как "мусор". Когда памяти не хватает, общеязыковая среда времени выполнения CLR просматривает свои структуры данных, с помощью которых она отслеживает использование памяти, и возвращает всю память, помеченную как мусор. Таким образом, программист освобождается от ответственности за освобождение памяти.

    Хотя сборка мусора предотвращает утечки памяти в управляемой динамически распределяемой области памяти, она не помогает освобождать другие типы ресурсов. Эти ресурсы включают в себя открытые файлы или соединения с базами данных, а также соединения с сервером, которые должны быть разъединены. Программист должен написать код для явной очистки этих ресурсов. Это можно сделать в деструкторе класса или в специализированном методе для очистки. Общеязыковая среда времени выполнения CLR вызовет этот деструктор для освобождения объекта.

    При сборке мусора очень важна эффективность. С автоматической сборкой мусора связаны определенные накладные расходы. Однако общеязыковая среда времени выполнения CLR имеет эффективный алгоритм сборки мусора, учитывающий поколения объектов, участвующих в сборке мусора.
    CompEbook.ru Железо, дизайн, обучение и другие

    Сериализация, или преобразование в последовательную форму

    Сохранение сложной структуры данных со связанными объектами может стать довольно громоздким при использовании классов File (Файл) и Stream (Поток, Абстрактный последовательный файл). Необходимо сохранить все индивидуальные поля на диске. При этом нужно помнить, какое из полей какому объекту принадлежит, и какой экземпляр класса связан с другими экземплярами объектов. При восстановлении нужно воссоздать порядок расположения полей и объектные ссылки.
    Каркас .NET Framework предоставляет технологию сериализации (преобразования в последовательную форму), которая выполняет все вышесказанное самостоятельно. Сериализация преобразовывает объекты, такие как классы, структуры и массивы в поток байтов. При преобразовании из последовательной формы в параллельную поток байтов преобразовывается обратно в объекты. Сериализация и преобразование из последовательной формы в параллельную могут быть проделаны на различных машинах, если на них работает общеязыковая среда времени выполнения CLR.
    Объекты могут быть преобразованы в последовательную форму без применения специально написанного кода, потому что, как мы видели, во время выполнения можно запросить метаданные объекта, что дает возможность узнать распределение памяти, занятой этим объектом. Чтобы информировать каркас, что класс может быть преобразован в последовательную форму, нужно пометить класс атрибутом System: :Serializable (Система::Преобразуемый в последовательную форму). Любое поле или свойство, которые не должны быть преобразованы в последовательную форму, могут быть отмечены атрибутом System: :NonSerialized (Система::Непреобразуемый в последовательную форму). Например, поля, которые представляют собой кэшированные переменные, не должны преобразовываться в последовательную форму. Все, что нужно сделать — пометить класс атрибутом, указывающим, что класс может быть преобразован в последовательную форму. И тогда нет необходимости в любом другом коде, выполняющем преобразование в последовательную форму.
    Пример Serialization (Сериализация) показывает, как применить преобразование в последовательную форму для изучения класса HotelBroker в сборке Hotel (Гостиница). Атрибут Serializable (Преобразуемый в последовательную форму) применяется в определении класса HotelBroker. Атрибут сериализации Serializable (Преобразуемый в последовательную форму) применяется также ко всем классам, которые используются классом HotelBroker или производными от базовых классов класса HotelBroker: Broker (Брокер), Hotel (Гостиница), HotelReservation, Reservable и Reservation (Резервирование), потому что при сериализации HotelBroker должны быть также преобразованы в последовательную форму и эти классы. Если бы любой из этих классов не был отмечен атрибутом, то во время выполнения могло бы произойти исключение при попытке сериализации объекта этого типа.

    [Serializable]
    // [Преобразуемый в последовательную форму]
    public _gc class HotelBroker:
    // класс сборщика мусора HotelBroker:
    public Broker,
    // общедоступный Брокер
    public IHotellnfo,
    public IHotelAdmin,
    public IHotelReservation
    {
    private: // частный
    const int MAXDAY; // константа
    const int MAXUNIT; // константа
    [NonSerialized] ArrayList *cities;
    // [Непреобразуемый в последовательную форму]
    };
    [Serializable]
    // [Преобразуемый в последовательную форму]
    public _gc class Hotel :
    public Reservable
    // класс сборщика мусора Гостиница:
    Reservable
    {
    };
    [Serializable]
    // [Преобразуемый в последовательную форму]
    public _gc class HotelReservation :
    public Reservation
    // общедоступный класс сборщика мусора HotelReservation:
    Резервирование
    {
    } ;
    [Serializable]
    // [Преобразуемый в последовательную форму]
    public _gc _abstract class Reservable
    // сборщик мусора - абстрактный класс Reservable
    {
    } ;
    [Serializable]
    // [Преобразуемый в последовательную форму]
    public _gc _abstract class Reservation
    // сборщик мусора - абстрактный класс Reservation
    {
    } ;
    [Serializable]
    // [Преобразуемый в последовательную форму]
    public _gc _abstract class Broker
    // сборщик мусора - абстрактный класс Broker
    {
    };

    Поле cities (города) было помечено как NonSerialized (Непреобразуемый в последовательную форму), так как название города, где размещается гостиница, сохраняется вместе с преобразованными в последовательную форму названиями гостиниц. Поэтому оно может быть восстановлено с помощью модифицированного метода AddCity. Поле cities (города) было бы пусто после преобразования класса HotelBroker из последовательной формы в параллельную, потому что оно не было сохранено.

    private: // частный
    void AddCity(String *city) // (Строка *city)
    {
    if (cities == 0) // если (города == 0)
    {
    cities = new ArrayList; // города
    lEnumerator *pEnum = units->GetEnumerator();
    while (pEnum->MoveNext() )
    {
    Hotel *h = // Гостиница
    dynamic_cast(pEnum->Current);
    AddCity(h->City); // Город
    }
    }
    // check if city already on list, add if not
    // проверить, есть ли город уже в списке, добавить если нет
    if (!cities->Contains(city))
    // если (! города-> Содержат (город))
    cities->Add(city);
    // города-> Добавить (город);
    }

    CompEbook.ru Железо, дизайн, обучение и другие

    Синхронизация коллекций

    Некоторые списки, например TraceListeners, являются безопасными с точки зрения работы с потоками. Во время изменения этой коллекции изменяется копия и устанавливается ссылка на копию. Большинство коллекций, подобных ArrayList (Список массивов), небезопасны при работе с потоками. Но, сделав их автоматически безопасными для работы с потоками, мы уменьшили бы эффективность работы с коллекциями, даже если бы не стоял вопрос о безопасности.

    ArrayList (Список массивов) содержит статический метод Synchronization (Синхронизация) для возврата к безопасной, с точки зрения работы с потоками, версии ArrayList (Список массивов). Свойство IsSynchronized позволяет проверить, безопасна ли используемая версия ArrayList (Список массивов) с точки зрения работы с потоками. Свойство SyncRoot возвращает объект, который может использоваться для синхронизации доступа к коллекции. Это позволяет синхронизировать потоки, которые могут использовать ArrayList (Список массивов), с помощью одного и того же объекта.
    CompEbook.ru Железо, дизайн, обучение и другие

    События AppDomain (Прикладная область)

    Для поддержки изоляции приложений класс AppDomain (Прикладная область) позволяет устанавливать обработчики для следующих событий:
  • выгрузка прикладной области;

  • завершение процесса;

  • возникновение необрабатываемого исключения;

  • сбой при попытке разрешить сборки, типы или ресурсы.

  • CompEbook.ru Железо, дизайн, обучение и другие

    TextReader И TextWriter

    В абстрактных классах TextReader и TextWriter данные рассматриваются как последовательный поток символов (то есть, просто как текст). TextReader имеет следующие методы: Close (Закрыть). Peek (Считывание элемента данных), Read (Чтение), ReadBlock, ReadLine и ReadToEnd. TextWriter содержит методы типа Close (Закрыть), Flush (Дозапись), Write (Запись) и WriteLine. Перегруженные методы Read (Чтение) читают символы из потока. Перегруженные методы Write (Запись) и WriteLine записывают данные различных типов в поток. Если объект записывается в поток, то используется метод ToString объекта.

    StringReader и StringWriter являются производными классами от классов TextReader и TextWriter соответственно. StringReader и StringWriter читают и записывают данные в символьную строку, которая сохраняется в базовом объекте StringBuilder. Конструктор StringWriter может принимать объект StringBuilder. Класс StringBuilder обсуждался в главе 3 "Программирование на управляемом C++".

    StreamReader и StreamWriter также являются производными классами от классов TextReader и TextWriter. Они читают текст из объекта и записывают текст в объект Stream (Поток, Абстрактный последовательный файл). Так же как и в случае классов BinaryReader и BinaryWriter, можно создать объект Stream (Поток, Абстрактный последовательный файл) и передать его в конструктор StreamReader или StreamWriter. Следовательно, эти классы могут использовать любые унаследованные от Stream (Поток, Абстрактный последовательный файл) классы хранения данных. Пример Text (Текст) из папки File использует классы StreamReader и StreamWriter. Программу необходимо выполнить дважды: первый раз — чтобы создать файл, а затем второй раз — чтобы прочитать его.
    FileStream *fs = new FileStream(
    "TextTest.txt", FileMode::OpenOrCreate);
    if (fs->Length == 0) // если Длина == О {
    Console::WriteLine("Writing Data..."); // Запись данных

    StreamWriter *sw = new StreamWriter(fs);
    sw->Write(100); // Запись
    sw->WriteLine(" One Hundred"); // Сто
    sw->WriteLine("End of File"); // Конец Файла
    sw->Close();
    }
    else
    {
    String *text; // Строка
    StreamReader *sr = new StreamReader(fs) ;
    text = sr->ReadLine(); // текст
    while (text != 0)
    // пока (текст!= О)
    {
    Console::WriteLine(text);
    // текст
    text = sr->ReadLine();
    // текст
    }
    sr->Close ();
    }
    fs->Close ();
    CompEbook.ru Железо, дизайн, обучение и другие

    Удаленные объекты

    Клиентская часть организовывает заместитель, активизируя (вызывая) удаленный объект. Удаленные объекты должны быть производными от MarshalByRefObject, потому что вы работаете с заместителем объектной ссылки, а не непосредственно с самой объектной ссылкой. Это та же концепция, что и ранее при обсуждении понятия контекста, где для обращения к объектам, находящимся в другом контексте, также используется передача по ссылке.
    Локальные объекты, передаваемые в качестве параметров методов из одной прикладной области в другую, можно передавать по значению (копировать) или по ссылке.
    Чтобы передать объект по значению, он должен быть сериализован (преобразован в последовательную форму). Объект преобразуется в последовательную форму, передается на транспортном уровне, и воссоздается на другой стороне. Это мы уже видели в примере AppDomain (Прикладная область).
    Чтобы передать класс по ссылке, он должен быть производным от MarshalByRef Object. Пример Remoting иллюстрирует передачу объекта по ссылке.

    Удаленные объекты могут быть активированы как сервером, так и клиентом. Активизированные сервером объекты не создаются до вызова первого метода этого объекта. Они подразделяются на две разновидности, SingleCall и Singleton (Одноэлементное [одноточечное] множество). SingleCall — это объект без состояния, т.е. объект, не меняющий свое состояние в процессе выполнения. Вызов каждого метода приводит к созданию нового объекта. Другая разновидность объектов. Singleton (Одноэлементное [одноточечное] множество), работает иначе. Один и тот же экземпляр Singleton (Одноэлементное [одноточечное] множество) объекта может использоваться для обслуживания нескольких клиентских запросов. Объекты Singleton (Одноэлементное [одноточечное] множество) могут иметь состояние. Объекты Singleton (Одноэлементное [одноточечное] множество) существуют постоянно. Использование объектов SingleCall предпочтительнее, чем объектов Singleton (Одноэлементное [одноточечное] множество), если нужна лучшая масштабируемость, потому что они не сохраняют состояние и при их использовании легче сбалансировать загрузку.

    Вызванные клиентом объекты активизируются тогда, когда клиент их запрашивает. Хотя они могут использоваться для нескольких запросов и хранить свое состояние, но не могут хранить информацию о различных вызовах со стороны клиента. Это похоже на вызов CoCreatelnstanceEx в распределенной модели компонентных объектов DCOM.
    CompEbook.ru Железо, дизайн, обучение и другие

    Удаленный доступ

    Технология удаленного доступа использует все ключевые концепции прикладной модели .NET (.NET Application Model). Хотя подробное обсуждение удаленного доступа не является целью этой книги, в кратком введении мы представим хороший пример использования метаданных и передачи по ссылке (marshal by reference, MBR) Удаленный доступ представляет собой механизм, который поддерживает исполняемые серверы
    В отличие от удаленного доступа в технологии, основанной на модели компонентных объектов Microsoft (COM), в .NET для программирования инфраструктуры требуются минимальные усилия Необходимая инфраструктура программы позволяет программисту выбирать степень гибкости, т.е способность настраивать удаленный доступ для своих собственных приложений
    Каркас NET Framework предоставляет два способа устанавливать соединения между двумя прикладными программами на различных компьютерах Рассматриваемые в главе 11 "Web-службы" сетевые службы позволяют компьютерам, на которых не установлена общеязыковая среда времени выполнения CLR, связываться с компьютерами, на которых общеязыковая среда времени выполнения CLR установлена Технология удаленного доступа, обсуждаемая здесь, позволяет создать распределенное приложение при условии, что на компьютерах установлена общеязыковая среда времени выполнения CLR.
    CompEbook.ru Железо, дизайн, обучение и другие

    Уничтожение объектов

    Исключая ситуацию явного использования оператора delete (уничтожить), для управляемого объекта время разрушения недетерминировано. Деструктор для конкретного объекта, на который нет ссылок, может вызваться в любое время, когда выполняется процесс сборки мусора. Порядок вызовов деструкторов для различных объектов непредсказуем. Более того, при исключительных обстоятельствах деструктор может не вызваться совсем (например, поток войдет в бесконечный цикл или процесс завершится аварийно и тогда во время выполнения не будет возможности провести очистку). К тому же, если оператор delete (уничтожить) не используется явно, то поток, из которого будет вызван деструктор, не определен.

    В примере ExplicitDelete демонстрируется, что вызов деструктора является синхронным, и поэтому детерминированным, если явно удалить управляемый указатель. Следующий код демонстрирует создание двух объектов. Первый завершается пассивно путем обнуления указателя на него. Сборщик мусора асинхронно вызывает деструктор в своем собственном потоке. Второй объект разрушается явно с помощью оператора delete (уничтожить) и деструктор вызывается синхронно. Программа с помощью хэш-кодов отображает подробности того, что случается с каждым объектом, и в каком потоке это случается. Из вывода можно увидеть, что в случае пассивно завершаемого объекта деструктор выполняется в потоке, отличном от того, в котором выполняется функция Main (Главная). И напротив, в случае явного уничтожения объекта деструктор выполняется в том же самом потоке, что и метод Main (Главный).
    //ExplicitDelete.h
    using namespace System::Threading;
    // использование пространства имен
    // Система::Организация поточной обработки;
    public _gc class SomeClass
    // класс сборщика мусора SomeClass
    {
    public:
    -SomeClass()
    {
    Console::Write ( // Запись
    "Destructor running in thread: {0}, ",
    // "Деструктор, выполняющийся в потоке: (0), ",

    _box(Thread::CurrentThread->GetHashCode()));
    // Поток Console::WriteLine(
    "Destroying object: {0}",
    // "Уничтожение объекта: {0} ",
    _box(this->GetHashCode()));
    }
    };
    public _gc class ExplicitDelete
    // класс сборщика мусора ExplicitDelete
    {
    public:
    static void Main()
    {
    Console::WriteLine(
    "Main thread: {0}",
    // "Основной поток: {0} ",
    _box(Thread::CurrentThread->GetHashCode()));
    // Поток
    SomeClass *sc = new SomeClass;
    Console::WriteLine(
    "Main thread creating object: {0}",
    // "Основной поток, создающий объект: {0} ",
    _box(sc->GetHashCode()));
    Console::WriteLine(
    "Nulling pointer to object: {0}",
    // "Обнуление указателя на объект: {0} ",
    _box(sc->GetHashCode()));
    sc = 0;
    GC::Collect (); // СБОРЩИК МУСОРА:: Собрать();
    GC::WaitForPendingFinalizers(); // СБОРЩИК МУСОРА
    sc = new SomeClass;
    Console::WriteLine(
    "Main thread creating object: {0}",
    // "Основной поток, создавший объект: {0} ",
    _box(sc->GetHashCode()));
    Console::WriteLine(
    "Deleting pointer to object: {0}",
    // "Удаление указателя на объект: {0} ",
    _box(sc->GetHashCode()));
    delete sc; // удалить
    Console::WriteLine("All done."); // Все сделано
    }
    };

    Ниже приведена выдача.

    Main thread: 2
    Main thread creating object: 5
    Nulling pointer to object: 5
    Destructor running in thread: 6,
    Destroying object: 5
    Main thread creating object: 7
    Deleting pointer to object: 7
    Destructor running in thread: 2,
    Destroying object: 7
    All done.

    Перевод такой:

    Основной поток: 2
    Основной поток, создавший объект: 5
    Обнуление указателя на объект: 5
    Деструктор, выполняющийся в потоке: 6,
    Уничтожаемый объект: 5
    Основной поток, создавший объект: 7
    Удаление указателя на объект: 7
    Деструктор, выполняющийся в потоке: 2,
    Уничтожаемый объект: 7

    Все сделано.

    Чтобы избежать лишних накладных расходов, не стоит определять деструктор для класса, если на то нет серьезных причин. В случае, если деструктор все же реализуется, нужно, вероятно, в классе предоставить альтернативный, детерминированный механизм для выполнения необходимой очистки. Каркас .NET Framework рекомендует использовать шаблон проектирования Dispose (Освободить ранее выделенную область памяти) для детерминированной очистки, которую мы сейчас и рассмотрим.

    CompEbook.ru Железо, дизайн, обучение и другие

    Управление сборкой мусора с помощью класса сборщика мусора GC

    Обычно лучше всего не вмешиваться в работу сборщика мусора, — пусть он выполняет свою работу незаметно для вас. Иногда, однако, программе может быть выгодно вмешаться в его работу. Пространство имен System (Система) содержит класс GC (СБОРЩИК МУСОРА), который дает возможность программе изменить поведение сборщика мусора. Мы рассмотрим несколько важных методов класса GC (СБОРЩИК МУСОРА).
    SuppressFinalize
    Данный метод производит запрос к системе, чтобы та не выполняла операцию завершения, т.е. не вызвала деструктор указанного объекта. Как мы видели раньше, нужно вызвать этот метод в собственной реализации метода Dispose (Освободить ранее выделенную область памяти), чтобы предотвратить выполнение операции завершения объекта, если ранее выделенная для него область памяти уже освобождена.
    Collect (Собрать)
    Можно принудительно выполнить сборку мусора, вызвав метод Collect (Собрать). Необязательный параметр позволяет указать, какие поколения должны принять участие в сборке мусора. Этот метод нужно использовать рационально, так как общеязыковая среда времени выполнения CLR обычно имеет лучшую информацию относительно текущего состояния памяти. Данный метод можно использовать тогда, когда программа только что освободила ряд больших объектов, и нужно иметь всю эту свободную память немедленно. Другой пример использования этого метода был предоставлен в предыдущем разделе, где вызов Collect (Собрать) принудительно вызывает сборку тогда, когда системные объекты все еще доступны.
    MaxCeneration
    Это свойство возвращает максимальное поддерживаемое общеязыковой средой времени выполнения CLR число поколений.
    CetCeneration
    Данный метод возвращает текущий номер поколения, которое назначено объекту.
    CetTotalMemory
    Указанный метод возвращает объем распределенной в настоящее время памяти в байтах (а не объем свободной доступной памяти и не общий объем динамически распределяемой области памяти). Передаваемый параметр позволяет определять, должна ли система выполнить сборку мусора перед возвратом из метода. Если сборка мусора не производится, возвращаемое число байтов вероятно больше, чем фактический объем памяти, используемой объектами.
    CompEbook.ru Железо, дизайн, обучение и другие

    Ввод и вывод в .NЕТ

    Грубо обобщая, можно разделить функции ввода/вывода в каркасе .NET Framework на две широких категории, не зависящих от устройства хранения данных (диск, память, и т.д.): это запись и чтение.

    Данные могут рассматриваться как поток байтов или символов. Например, мы могли прочитать 500 байтов из файла, и записать их в буфер памяти. Данные также можно рассматривать как набор объектов. Чтение и запись объектов называются соответственно преобразованием из последовательной формы в параллельную и преобразованием в последовательную форму (сериализацией). Мы можем сериализировать (записать) список объектов типа Customer (Клиент) на диск. Затем мы можем список объектов Customer (Клиент) преобразовать из последовательной формы в параллельную, т.е. прочитать этот список обратно в память.
    В пространстве имен System::IO есть несколько классов для чтения и записи, позволяющих использовать различные устройства хранения, если только данные можно трактовать как байты или символы. Функциональные возможности сериализации могут проявляться в различных местах каркаса .NET. Пространство имен System::Runtime::Serialization (Система-Время выполнения::Сериализация) используется для сериализации в общей системе типов (Common Type System). Пространство имен System::Xml: Serialization (Система::Хml::Сериализация) используется для сериализации XML-документов.
    CompEbook.ru Железо, дизайн, обучение и другие

    Заместители и заглушки

    Как же во время выполнения удается удовлетворить различные требования различных контекстов? Когда какой-нибудь объект располагается в другом контексте (например, объект HotelBroker в экземпляре NewResevation), взамен указателя непосредственно на сам объект, возвращается указатель на некоторый заместитель. Реальный объект постоянно находится в своем первоначальном контексте. Заместитель — объект, который представляет первоначальный объект в другом контексте. Статический метод Remoting-Services: : IsTransparentProxy определяет, на что ссылается указатель: на реальный объект или на заместитель. Рассмотрим код в Threading.h на шаге 4 проекта TestThreading:
    bool bTrans; // логическая булева переменная
    bTrans bTrans = RemotingServices::IsTransparentProxy(
    customers); // клиенты
    Console::WriteLine (
    "Is the customer object a proxy? {0}",
    // "Объект Клиент представляет заместитель? {0}",
    bTrans.ToString());
    bTrans = RemotingServices::IsTransparentProxy(
    hotelBroker) ; Console::WriteLine(
    "Is the bookings object a proxy? {0}",
    // "Заказывает заместитель? {О}",
    bTrans.ToString());
    Этот код генерирует следующую выдачу:
    Is the customer object a proxy? False Is the bookings object a proxy? True
    Перевод такой:
    Объект Клиент представляет заместитель? Ложь
    Заказывает заместитель? Истина
    При запуске программа выполняется в определенном по умолчанию контексте. Все объекты, такие как Customers (Клиенты), не предъявляющие специальных требований, создаются внутри этого контекста (контекст 0). Какой-нибудь объект, такой как HotelBroker с другими требованиями к синхронизации, создается в другом контексте (контекст 1), а заместитель возвращается в контекст 0.
    Теперь, запрашивая метод MakeReservation объекта HotelBroker, мы реально получаем доступ к методу заместителя. Метод заместителя может потом применить замок синхронизации и затем делегировать функции методу реального объекта HotelBroker. Метод настоящего объекта возвращает управление заместителю. А заместитель может тогда удалить замок синхронизации и возвратить управление вызвавшей его программе. Такая технология, когда во время выполнения используются заместители для перехвата вызовов методов реальных объектов, называется перехватыванием.

    Можно также просмотреть подобный пример под названием MarshalByReference в папке с примерами. Он показывает различные способы создания объекта и эффект от создания заместителя.
    CompEbook.ru Железо, дизайн, обучение и другие

    Завершение и раскручивание стека

    Как было упомянуто ранее, одно из достоинств механизма обработки исключений заключается в том, что, поскольку при обработке исключения происходит раскрутка стека вызовов, локальные объекты выходят из области видимости и таким образом помечаются для завершения. Простая иллюстрация этого имеется в программе FinalizeStackUnwind. В этой программе используется рассмотренный ранее класс SimpleLog, в котором реализуется завершение.
    //FinalizeStackUnwind.h
    using namespace System;
    // использование пространства имен Система;
    using namespace System::Threading;
    // использование пространства имен
    // Система::Организация поточной обработки;
    public _gc class FinalizeStackUnwind
    // класс сборщика мусора FinalizeStackUnwind
    {
    public:
    static void Main()
    {
    try
    {
    SomeMethod(); }
    catch(Exception *e)
    // Исключение
    {
    Console::WriteLine(e->Message);
    // Сообщение
    }
    GC::Collect( ) ;
    // СБОРЩИК МУСОРА:: Собрать()
    Thread::Sleep(100); // Поток:: Бездействие
    }
    private: // частный
    static void SomeMethod ()
    {
    // локальная переменная
    SimpleLog *alpha = new SimpleLog("alpha.txt");
    // вызвать исключение
    throw new Exception("error!!");
    // новое Исключение ("ошибка!!");
    }
    };
    SomeMethod выделяет место для локальной переменной-указателя alpha (алфавитный) типа SimpleLog*. Перед нормальным завершением метода, из него вызывается исключение. Механизм раскручивания стека при обработке особых ситуаций видит, что переменная-указатель alpha (алфавитный) больше не доступна, и помечает ее для сборки мусора. Вызов GC: :Collect (СБОРЩИК МУСОРА::Собрать) приводит в действие сборку мусора, и мы видим в выдаче программы, что завершение действительно выполнено.
    logfile alpha.txt created
    error!!
    logfile alpha.txt finalized
    logfile alpha.txt disposed
    Перевод такой:
    системный журнал alpha.txt создан
    ошибка!!
    системный журнал alpha.txt завершен
    системный журнал alpha.txt освобожден
    CompEbook.ru Железо, дизайн, обучение и другие

    Architecture Net

    Автоматически генерируемые свойства команд

    Для определения свойств InsertCommand, UpdateCommand (Команда обновления) и DeleteCommand можно использовать класс SqlCommandBuilder. Но из-за того, что для динамического определения этих свойств класс SqlCommandBuilder должен получить нужную информацию, его использование повлечет за собой необходимость произвести несколько дополнительных обращений к базе данных и уменьшение производительности. Поэтому, если структура базы данных, используемой приложением, известна при его разработке, лучше определять свойства InsertCommand, UpdateCommand (Команда обновления) и DeleteCommand явно. Это поможет избежать снижения производительности. Если же структура базы данных точно не известна, но пользователь определил запрос, то SqlCommandBuilder можно использовать для обновления результатов "при последующих запросах.
    Этот метод действенен для экземпляров DataSet (Набор данных), соответствующих единичной таблице. Если же данные в наборе данных — результат запроса, использовавшего соединение данных, или между таблицами в наборе данных есть связи, то механизм автоматической генерации не сможет корректно определить команду, обновляющую данные в обеих таблицах. Так как SqlCommandBuilder использует при генерации команд свойство SelectCommand, оно должно быть определено.
    Для того чтобы описанный метод работал корректно, в таблице, содержащейся в наборе данных, должен быть главный или единственный столбец. Этот столбец будет результатом выполнения SQL-запроса, установленного в свойстве SelectCommand. Главный столбец используется при обновлении или удалении данных как where-фраза.
    Имена столбцов не должны содержать специальных символов, таких, как пробелы, запятые, точки, кавычки или другие символы, отличные от буквенно-цифровых. Это обязательно даже тогда, когда имя взято в скобки. Имя таблицы можно задавать полностью, как, например, SchemaName . OwnerName . TableName.
    Простейший способ использования класса SqlCommandBuilder— передача экземпляра класса SqlDataAdapter конструктору SqlCommandBuilder в качестве аргумента. После этого объект SqlCommandBuilder зарегистрирует себя в качестве обработчика события RowUpdating. А дальше он сможет генерировать необходимые команды InsertCommand, UpdateCommand (Команда обновления) и DeleteCoramand до обновления строки.
    CompEbook.ru Железо, дизайн, обучение и другие

    База данных AirlineBrokers

    Для рассмотрения доступа к данным XML мы будем использовать базу данных Air-linesBrokers. Указанную базу данных можно создать и инициализировать с помощью SqlServer Query Manager и SQL-скрипта, прилагаемого к примерам для этой главы. База данных AirlinesBrokers имеет функциональные возможности, используемые системой бронирования Acme. С ее помощью клиенты Acme могут бронировать авиабилеты. Такая база данных содержит следующие таблицы:
  • Airlines (Авиалинии): информация об авиалиниях;

  • PlaneType (Тип самолета): типы самолетов, используемые на авиалиниях;

  • Flights (Рейсы): информация о рейсах на различных авиалиниях:

  • Customers (Клиенты): информация о клиентах;

  • Reservations (Резервирование): информация о забронированных клиентами местах.

  • Хотя в реальной жизни списки клиентов систем AirlinesBroker и HotelBroker (Посредник, бронирующий места в гостинице) вряд ли могут совпасть, в нашем примере мы для простоты будем использовать те же таблицы Customers (Клиенты) и те же компоненты для доступа к данным, ч го и ранее.
    CompEbook.ru Железо, дизайн, обучение и другие

    DataReader

    При его создании SqlDataReader не указывает ни на какую запись возвращенного набора данных. Поэтому для получения доступа к данным следует вызвать метод Read (Читать). Как показано в примере Connected, для получения доступа к отдельным полям или столбцам текущей строки можно использовать свойство Item (Элемент). Получить все поля строки можно также с помощью метода GetValues.
    Object * fields [] = new Object *[NumberFields]; // новый Объект
    int NumberFields = reader->GetValues(fields); // читать поля
    GetValue возвращает значение столбца в его исходном формате Для доступа к данным определенных форматов можно использовать методы GetBoolean (Прочитать Логическое значение), GetDecimal (Прочитать Десятичное число) и GetString (Прочитать Строку). Метод GetName возвращает имя определенного столбца.
    Еще раз повторим, что при использовании DataReader в каждый момент времени доступна только одна запись. Убедитесь в том, что по завершении работы с DataReader вы его закрыли.
    CompEbook.ru Железо, дизайн, обучение и другие

    DataSet (Набор данных) и XML

    Объект DataSet (Набор данных) содержит методы WriteXml и WriteXmlSchema, которые выдают данные и схему данных, хранящихся в наборе данных. Схема XML, возвращаемая объектом DataSet (Набор данных), определяется из самих данных. Пока вы не добавите явным образом в объект DataSet (Набор данных) ограничения, такие, как первичные или внешние ключи, они не будут частью схемы.
    Объект DataSet (Набор данных) содержит также и методы, предназначенные для чтения XML: ReadXml и ReadXmlSchema. С помощью ReadXml можно считывать данные и схему в объект DataSet (Набор данных). Когда схема отсутствует, метод попытается извлечь ее изданных. Если же это не удастся, возникнет исключение. ReadXmlSchema считывает схему документа.
    При отсутствии в документе XML схемы, DataSet (Набор данных) будет извлекать данные, как если бы они были таблицами, руководствуясь при этом набором правил. Оставшиеся элементы будут считаться столбцами таблиц
    Для определения того, будет столбец записываться в документ XML как элемент или как атрибут, используется свойство Col-rr.Kapping объекта DataColamn. Запись столбца как элемента предпочтительней. Элемент, содержащий нескалярные данные, считается таблицей. Атрибуты и скалярные значения являются столбцами. Подробнее эти правила описаны в документации к .NET.
    CompEbook.ru Железо, дизайн, обучение и другие

    Теперь в объекте DataSet ( Набор данных) есть данные таблиц Airlines (Авиалинии), PlaneType (Тип Самолета), Flights (Рейсы), Customers (Клиенты) и Reservations (Резервирование). Далее извлечем из данных объекта DataSet (Набор данных) схему XML. Затем извлечем сами данные и запишем их в формате XML.

    d->WriteXmlSchema("Airlines.xsd");
    d->WriteXml("Airlines.xml");

    Приведенные операторы создают два файла: Airlines . xsd и Airlines . xml. Ниже вы видите некоторые данные, записанные в Airlines. xml. Главным элементом является AirlineBroker; именно так назывался объект DataSet (Набор данных). На один уровень ниже находятся элементы, соответствующие разным таблицам объекта DataSet (Набор данных): Airlines (Авиалинии), PlaneType (Тип самолета), Flights (Рейсы) и Customers (Клиенты). О забронированных местах информации в базе данных не было. В получившемся документе каждой строке исходных таблиц соответствует одна запись. Элементы этих записей соответствуют полям исходных таблиц.


    America West
    AW
    www.americawest.com
    555-555-1212

    Delta
    DL
    www.delta.com
    800-456-7890


    DIX/Airlj.n£>
    98^
    Atlanta
    New Orleans
    2001-10-05T20:15:00.0000000-04:00
    2001-10-05T22 : 30: 00. 0000000-04 : 00
    737
    1300
    0
    450


    737
    10
    0
    200

    Adams John
    adams@presidents.org 1



    Вот более русифицированная версия этого XML-документа:

    <Авиалинии>
    <Название> Американский Запад
    <Сокращение> AW
    <УзелИеЬ> www.americawest.com
    555-5Ь5-1212 <Авиалинии>
    <Название> Дельта
    <Сокращение> DL
    <Узeлweb> www.delta.com
    800-456-7890 <Рейсы>
    <Авиалиния> DL 987 Атланта Новый Орлеан <Вьшет> 2001-10-05Т20:15:00.0000000-04:00
    <Прибытие> 2001-10-05Т22:30:00.0000000-04:00
    <Тип самолета> 737 1300 0 450
    <Тип самолета>
    <Тип самолета>737
    10
    0
    200
    <Клиенты>
    Адаме Джон
    adams@presidents.org 1


    Исходя из этих данных, объект DataSet (Набор данных) создал схему и сохранил ее в файле Airlines.xsd. Дальше мы обсудим некоторые отрывки из этого файла. В нем нет информации о связях или первичных ключах какой бы то ни было таблицы, такой, как Airlines (Авиалинии) или Flights (Рейсы), по той простой причине, что они не были определены в исходной базе данных. Если вы просмотрите созданный файл, вы увидите, что в нем записана и информация о схеме данных таблицы Reservations (Резервирование), несмотря на то, что в этой таблице нет никаких данных.
    В первой строке заголовка схемы определено название схемы (AirlineBroker). Кроме того, в нем определены два пространства имен, используемых в этой схеме документа. Одно пространство имен, названное xsd, содержит описание стандарта схемы XML. Второе, названное msdata, содержит описание от Microsoft.




    В следующей строке описывается элемент под названием AirlineBroker, имеющий атрибут, указывающий, что эта схема получена из объекта DataSet (Набор данных). Это атрибут в определениях Microsoft, а не в пространстве имен W3C Schema. Элемент AirlineBroker относится к составному (не скалярному) типу, т.е. является структурой, состоящей из элементов других типов. Такая структура может содержать произвольное количество элементов (или не содержать ни одного) любого типа, определенного в остальной части схемы.



    Далее описывается элемент, определяющий тип данных. Этот тип — тоже структура и потому относится к составному (не скалярному) типу, очередность элементов в котором совпадает с очередностью их определения в объекте DataSet (Набор данных). Так уж получилось, что все элементы, соответствующие столбцам таблицы базы данных, определены здесь как имеющие строковый тип string, причем их напичие не считается обязательным. В исходной таблице первичные ключи не определялись, а так как все эти строки в записях базы данных обязательны, объект DataSet (Набор данных) при преобразовании данных вывел и\ из набора таблиц, ограничений и связей, определенных в объекте DataSet (Набор данных) в момент преобразования.


    minOccurs="0" /> type="xsd:string" minOccurs="0" /> minOccurs="0" /> type="xsd:string" minOccurs="0" />



    Таблица Flights (Рейсы) определена аналогичным образом. Кроме того, что в ней не определен первичный ключ, в ней нет и внешних ключей для столбцов Airline (Авиалиния) и Plane Type (Тип самолета).


    minOccurs="0" /> rainOccurs="0" /> minOccurs="0" /> minOccurs="C" /> minOccurs="0" />
    minOccurs="0" />
    minOccurs="0" />
    minOccurs="0" /> type="xsd:decimal" minOccurs="0" /> type="xsa:decimal" minOccurs="0" />




    К этому определению схемы данных мы еще вернемся позже, а сейчас продолжим рассмотрение примера.

    CompEbook.ru Железо, дизайн, обучение и другие

    Доступ к данным XML

    Как будет показано в главе 11 "Web-службы", XML имеет много преимуществ при описании данных, которые нужно перемещать между разнородными системами и источниками данных. Поскольку вы можете обеспечить данные XML описанием схемы данных XML, во многих случаях имеет смысл передавать именно такие данные, а не DataSet (Набор данных). Так как данные ХМL являются текстом, они могут проходить через порты брандмауэров, которые обычно открыты, в отличие от протокола распределенной модели компонентных объектов DCOM (Distributed COM — Distributed Component Object Model) или протокола RMI, используемого в JAVA, которые требуют открытия особых портов.
    Мы не ставим себе цель обсудить в следующих разделах все детали языка XML. Мы хотим только продемонстрировать, как можно использовать концепции данных, принятые в ХМL и в DataSet (Набор данных).
    CompEbook.ru Железо, дизайн, обучение и другие

    Использование наборов данных

    На рис. 9.5 представлена иерархия объектов, содержащихся в DataSet (Набор данных). Прежде, чем приступить к материалу, излагаемому ниже, полезно ознакомиться с этой диаграммой.
    Использование наборов данных
    Рис. 9.5. Иерархия класса DataSet (Набор данных)
    CompEbook.ru Железо, дизайн, обучение и другие

    Источники данных

    Префикс имен классов и методов указывает на источник данных. Например, префикс OleDb (OLE для баз данных) указывает на использование источника данных OleDb (OLE для баз данных). Префикс Sql указывает на использование источника данных SqlServer.
    Источник данных SQL Server использует родной протокол SQL Server. Источник данных OleDb (OLE для баз данных) через промежуточный уровень модели компонентных объектов Microsoft (COM) обращается к различным драйверам доступа OleDb (OLE для баз данных). Например, можно взаимодействовать с SqlServer через источник данных OleDb (OLE для баз данных) с целью обращения к драйверу доступа OLEDB (OLE для баз данных) для SQL Server. Но быстродействие при таком способе будет, конечно, меньше, чем при использовании источника данных SqlServer. Преимуществами источников данных OleDb (OLE для баз данных) и ODBC является то, что, используя их при работе в ADO.NET, можно работать с большинством из доступных сегодня источников данных.
    Хотя в ADO.NET есть несколько интерфейсов, определяющих общие возможности, и несколько базовых классов, которые можно использовать для обеспечения этих возможностей, к источникам данных не предъявляется требование удовлетворять спецификациям, не соответствующим принятым способам работы с используемыми источниками данных.
    Например, классы SqlDataAdapter и OleDbDataAdapter в качестве базовых используют базовые абстрактные классы DbDataAdapter и DataAdapter, находящиеся в пространстве имен System: :Data: : Common (Система::Данные::Общие). С другой стороны, классы SqlTransaction и OleDbTransaction не наследуют реализации какого-либо класса, предназначенного для работы с базами данных. Классы OleDbError и SqlError вообще не похожи друг на друга. Указатель, реализуемый сервером, не поддерживается в ADO.NET из-за того, что некоторые базы данных (например, Oracle и DB2) не имеют встроенной поддержки этой возможности. Поэтому поддержка такой возможности для источника данных SQL Server будет расширением.
    В табл. 9.1 приведены классы источников данных О1е и Sql, предназначенные для соединения, задания команд, чтения данных, преобразования данных, хранения параметров данных. Как видно из этой таблицы, приведенные там классы источников данных Ole и Sql имеют общие черты, определенные в интерфейсах IDbConnection, IDbCom-mand, IDataReader, IDbDataAdapter и IDataParameter. Ничто, конечно же, не препятствует реализовать в любом из этих классов методы, не определенные соответствующим интерфейсом.
    Таблица 9.1. Сравнение соответствующих друг другу классов источников данных OleDb (OLE для баз данных) и SqlServer
    Классы, не зависящие от какого-либо источника данных, например, DataSet (Набор данных) или DataTable (Таблица данных), не имеют префиксов.
    Если важна масштабируемость (расширяемость) базы данных, желательно запретить завершение (fmalization) объектов, не нуждающихся в нем. Тем самым повышается производительность приложения, так как уменьшается время работы потока завершителей (fmalizer).
    CompEbook.ru Железо, дизайн, обучение и другие

    Изменение объекта DataRow

    При необходимости внести значительные изменения в объект DataSet (Набор данных) и отложить при этом проверку ограничений и событий, можно использовать режим редактирования набора данных.
    BeginEdit. EndEdit, CancelEdit
    Переход в режим редактирования осуществляется вызовом метода BeginEdit объекта строки, которую необходимо изменить. Для выхода из этого режима используются методы EndEdit и CancelEdit.
    В примере DataEditing мы нарушаем ограничение, заданное внешним ключом, добавляя в таблицу Books (Книги) строку, описывающую книгу автора, идентификатор которого отсутствует в базе данных. Исключение, являющееся результатом этого нарушения, возникнет только после вызова метода EndEdit. Так, при выполнении следующего фрагмента файла DataEditing. h примера DataEditing исключение не возникает.
    DataRow *rowToEdit = books->Rows->get_Item(0); // книги-> Строки
    rowToEdit->BeginEdit();
    try
    {
    rowToEdit->set_Item("Author!d", _box(21));
    }
    catch(Exception *e) // Исключение
    {
    Console::WriteLine(
    "\n {0] while editing a row.", e->Message); // при редактировании строки, Сообщение); Console::WriteLine(); }
    Однако исключение возникает, как только в программе вызывается метод EndEdit.
    try {
    rowToEdit->EndEdit(); }
    catch(Exception *e) // Исключение {
    Console::WriteLine();
    Console::WriteLine(
    "\n{0} on EndEdit", e->Message); // Сообщение
    Console::WriteLine(); }
    В результате будет напечатано следующее сообщение, указывающее на то, что по окончании сеанса изменения содержимого строки обнаружено нарушение ограничения.
    ForeignKeyConstraint Authors->Books requires the child key values (21) to exist in the parent table, on EndEdit
    Версии объекта DataRow
    До того, как будут подтверждены внесенные в строку изменения, доступны и исходные, и измененные значения полей строки. С помощью свойства элемента строки28 Da-taRowVersion можно определить, какое именно значение вы хотите использовать. Это свойство может иметь значения Original (Первоначальное), Default (Заданное по умолчанию), Current (Текущее) и Proposed (Предложенное).
    В следующем фрагменте примера DataEditing приведен код, выполняющийся до вызова метода EndEdit:

    DataRow *rowToEdit = books->Rows->get_Item(0);
    // книги-> Строки rowToEdit->BeginEdit() ; try {
    rowToEdit->set_Item("AuthorId", _box(21)); Console::WriteLine(
    " Book Author Id Field Current Value {0}",
    // Текущее значение поля идентификатора автора книги
    rowToEdit->get_Item(
    "Authorld", DataRowVersion::Current)); Console::WriteLine(
    "Book Author Id Field Proposed Value {0}",
    // Предложенное значение поля идентификатора автора книги
    rowToEdit->get_Item(
    "Authorld", DataRowVersion::Proposed)); // Предложенное Console::WriteLine(
    "Book Author Id Field Default Value {0}",
    // Значение по умолчанию поля идентификатора автора книги
    rowToEdit->get_Item(
    "Authorld", DataRowVersion::Default));
    // Значение по умолчанию }

    В результате программа напечатает:

    Book Author Id Field Current Value I Book Author Id Field Proposed Value 21 Book Author Id Field Default Value 21

    Вот перевод:

    Текущее значение поля идентификатора автора книги 1
    Предложенное значение поля идентификатора автора книги 21
    Значение по умолчанию поля идентификатора автора книги 21

    При выполнении транзакционного редактирования доступны значения Current (Текущее) и Proposed (Предложенное). После вызова метода CancelEdit значение Proposed (Предложенное) становится недоступным. После вызова метода EndEdit значение, имевшее атрибут Proposed (Предложенное) меняет атрибут на Current (Текущее) а значение, имевшее атрибут Proposed (Предложенное), становится недоступным.

    Свойство RowState объекта DataRow

    Кроме того, что в режиме редактирования доступны значения поля Current (Текущее) и Proposed (Предложенное), сам объект DataRow имеет свойство, описывающее состояние соответствующей строки. Это свойство может принимать значения Added (Добавлено), Deleted (Удалено), Detached (Отсоединено), Modified (Изменено) или Unchanged (He изменено).
    Строка находится в состоянии Detached (Отсоединено) в случаях, когда она создана, но либо еще не добавлена ни в одну коллекцию объектов DataRow, либо удалена из какой-нибудь коллекции.
    Какое из значений будет возвращено при использовании значения Default (Заданное по умолчанию) свойства DataRowVersion определяется значением свойства RowState.


    Принятие или отмена изменений

    Вызов метода EndEdit объекта DataRow не приводит к фиксации изменений, сделанных в строке. Вызов методов AcceptChanges или RejectChanges объектов DataSet (Набор данных), DataTable (Таблица данных) или DataRow приводит к выходу из режима транзакционного редактирования для всех строк соответствующего объекта. Если до этого не были вызваны EndEdit или CancelEdit, AcceptChanges или Rejec-tChanges вызывают эти методы для всех строк соответствующего объекта.
    После вызова метода AcceptChanges значения, имевшие атрибут Proposed (Предложенное) становятся основными (т.е. имеющими атрибут Original (Первоначальное)). Если при этом свойство RowState имело значение Added (Добавлено), Modified (Изменено) или Deleted (Удалено), ему присваивается значение Unchanged (He изменено), а сами изменения вступают в силу (т.е. строки добавляются, изменяются или удаляются).
    После выполнения метода RejectChanges значение, имевшее атрибут Proposed (Предложенное), удаляется. Если при этом свойство RowState имело значение Deleted (Удалено) или Modified (Изменено), значение поля становится прежним и свойству RowState присваивается значение Unchanged (He изменено). Если же RowState имело значение Added (Добавлено), строка удаляется из коллекции Rows (Строки).
    Так как после вызова метода AcceptChanges свойство RowState имеет значение Unchanged (He изменено), вызов метода Update (Обновить) объекта DataAdapter не приведет к каким-либо изменениям базы данных. Поэтому, при необходимости внести изменения в базу данных, метод Update (Обновить) следует вызывать до вызова метода AcceptChanges строки, таблицы или объекта DataSet (Набор данных).
    Ниже приведен фрагмент реализации метода CancelReservation класса HotelBroker (Посредник, бронирующий места в гостинице) из примера CaseStudy. Фрагмент взят из файла HotelBookings . h, находящегося в папке CaseStudy\HotelBrokerAdmin\Hotel. Обратите внимание, что метод AcceptChanges объекта DataSet (Набор данных) вызывается при успешном завершении работы метода SqlDataAdapter: : Update (Обновить). В случае же возникновения исключения вызывается метод RejectChanges.


    void CancelReservation(int id) // идентификатор
    {
    DataTable *t = 0;
    try
    {
    t = dataset->Tables->get_Item("Reservations");
    // набор данных-> Таблицы-> get_Item ("Резервирование");
    DataRow *rc [] = t->Select ( // Выбор
    String::Format("Reservationld = {0} ", // Строка:: Формат
    id.ToString())); // идентификатор for (int i=0; iLength; i++) re[i]->Delete(); // Удалить
    int NumberRows = adapter->Update( // Обновление dataset, "Reservations"); // набор данных, "Резервирование"); if (NumberRows > 0) // если (NumberRows> 0)
    t->AcceptChanges () ; else
    t->RejectChanges () ; }
    catch(Exception *e) // Исключение {
    t->RejectChanges();
    throw e; }
    return;
    }

    Если вы не будете отменять внесенные изменения в случае возникновения ошибки, измененные строки останутся в объекте DataSet (Набор данных). Тогда, при попытке произвести следующее обновление, оно также будет отменено из-за того, что строки все еще не обновлены и наличие их приводит к возникновению исключения. Поскольку объект DataSet (Набор данных) независим от других баз данных, тот факт, что данные в базе данных были обновлены, не имеет никакого отношения к принятию или отмене произведенных изменений данных в самом объекте DataSet (Набор данных).

    Ошибки объекта DataRow

    Если при изменении данных строки произошла ошибка, свойство HasError объекта DataSet (Набор данных), DataTable (Таблица данных) или DataRow примет значение true (истина). Для получения информации об ошибке используются методы GetCol-umnError или GetCoiunmsInError.

    CompEbook.ru Железо, дизайн, обучение и другие

    Классы SqlDataAdapter и DataSet (Набор данных)

    Класс DataSet (Набор данных) представляет собой резидентную упрощенную реляционную базу данных, не соединенную прямо ни с какой другой базой данных. Некоторые из его свойств описывают таблицы (Tables) и отношения (Relations) между ними в наборе данных. Управлять проверкой ограничений можно с помощью свойства Enf ог-ceConstraint. Имя набора данных можно установить с помощью свойства DataSet-Name, а кроме того, его можно определить и в конструкторе DataSet (Набор данных).
    И Класс SqlDataAdapter используется для передачи данных от базы данных объекту DataSet (Наборданных). В конструкторе класса HotelBroker (Посредник, бронирующий места в гостинице) продемонстрировано, как использовать SqlDataAdapter для заполнения набора данных. Пример CaseStudy для данной главы содержит приведенный ниже исходный код15. Этот фрагмент находится в файле HotelBroker. h из папки CaseStudy\HotelBrokerAdmin\Hotel.
    conn = new SqlConnection(connString) ; citiesAdapter = new SqlDataAdapter(); citiesAdapter->SelectCommand = new SqlCommand( "select distinct City from Hotels", conn); citiesDataset = new DataSet; // новый Набор данных citiesAdapter->Fill(citiesDataset, "Cities"); // Города
    Среди свойств класса SqlDataAdapter есть такие, которые связывают его с операциями выборки, вставки, обновления или удаления данных источника данных. В нашем примере экземпляр класса SqlCommand не вызывается непосредственно одним из его методов, а связывается со свойством SelectCommand класса SqlDataAdapter.
    Затем для выполнения указанной команды используется метод Fill (Заполнить) класса SqlDataAdapter. При этом объект DataSet (Набор данных) заполняется информацией из таблицы, имя которой указано как аргумент метода Fill (Заполнить). После завершения работы этого метода соединение остается в том же состоянии, в котором оно было при вызове метода.
    Теперь соединение с базой данных можно закрыть. Но при этом, вне зависимости от наличия соединения с базой данных, можно продолжить работу с объектом DataSet (Набор данных), содержащим данные.
    Класс SqlDataAdapter реализован на основе класса SqlDataReader, поэтому при использовании последнего можно ожидать большей производительности. SqlDataReader может также эффективнее использовать память. Это зависит от структуры.
    Примером CaseStudy для этой главы является решение AcmeGui, состоящее из трех проектов AcmeGui, Customer и Hotel. Проекты Customer и Hotel реализованы на управляемом C++, проект Customer — на С». Так сделано потому, что AcmeGui реализует аспекты программы, связанные с графическим интерфейсом пользователя (GUI), а это значительно удобнее делать на С», нежели на управляемом C++. Тем не менее, поскольку в данной главе рассматриваются вопросы, связанные с доступом к базам данных, весь исходный код для работы с базами данных реализован на управляемом C++.
    приложения. Так что, если у вас нет необходимости использовать преимущества класса DataSet (Набор данных), нет смысла увеличивать накладные расходы в приложении.
    CompEbook.ru Железо, дизайн, обучение и другие

    Коллекции объектов DataSet (Набор данных)

    При помещении данных в объект DataSet (Набор данных) считываются также и связанные с этими данными таблицы и столбцы. Каждый набор данных имеет коллекции, представляющие все таблицы, столбцы и строки, связанные с данными, содержащимися в этом наборе.
    Класс HotelBroker (Посредник, бронирующий места в гостинице) из используемого нами в качестве примера приложения содержит метод ListHotelsToFile, в котором продемонстрировано, как получить такую информацию и записать ее в файл Hotels. txt. Этот метод вызывается при нажатии кнопки на форме, описанной в файле MainAdmin-Form.cs. Вывод данных осуществляется перенаправлением вывода на консоль. hotelDA-taset — набор данных, содержащий данные из базы данных HotelBroker (Посредник, бронирующий места в гостинице). Приведем фрагмент файла HotelBroker.h.
    TextWriter *tw = new StreamWriter("Hotels.txt");
    Console::SetOut(tw); // печатающее устройство -
    // переадресовать вывод
    try
    {
    Console::WriteLine("Hotels"); // Гостиницы
    DataTable *t =
    hotelsDataset->Tables->get_Item( // Таблицы
    "Hotels"); // Гостиницы
    if (t == 0) // если (t == 0)
    return;
    lEnumerator *pEnum = t->Columns->GetEnumerator(); // Столбцы

    while (pEnum->MoveNext())
    {
    DataColumn *c =
    dynamic_cast(pEnum->Current);
    Console::Write("{0, -20}", c->ColumnName);
    }
    Console::WriteLine("");
    pEnum = t->Rows->GetEnumerator();
    while (pEnum->MoveNext())
    {
    DataRow *r =
    dynamic_cast (pEnum->Current) ;
    for (int i=0; iColunms->Count; i++) // Столбцы-> Счет
    {
    Type *type = r->get_Item(i)->GetType() ;
    if (type->FullName->Equals("System::Int32"))
    // если равняется ("Система:: Int32"))
    Console::Write("{О, -20}", r->get_Item(i) ) ;
    else
    {
    String *s = r->get_Item(i)->ToString(); // Строка
    s = s->Trim(); // Вырезка
    Console::Write("{0, -20}", s);
    }
    }
    Console::WriteLine("");
    }
    Console::WriteLine("");
    }
    catch(Exception *e) // Исключение
    {
    throw e;
    }
    _finally // наконец
    {
    tw->Close ();
    }

    Коллекция Tables (Таблицы) — это коллекция всех экземпляров DataTable (Таблица данных), содержащихся в объекте DataSet (Набор данных). В нашем примере такой экземпляр один, так что нет необходимости перемещаться по коллекции. Поэтому программа просто проходит по столбцам таблицы, воспринимая их содержимое как заголовки для данных, которые будут распечатаны далее. После считывания заголовков просматривается содержимое каждой строки таблицы Для каждого из значений в строке программа выясняет тип значения и печатает его в соответствующем формате В нашем случае программа проверяет, какой тип имеет рассматриваемое поле базы данных Hotel (Гостиница) Проверка типа значения поля, а не просто вывод на печать как Object (Объект), позволяет вывести данные в соответствующем формате.
    Как мы увидим позже, заполнять набор данных можно с помощью этих коллекций, без получения данных от их источника Для этого просто следует добавить таблицы, столбцы или строки в соответствующие коллекции.

    CompEbook.ru Железо, дизайн, обучение и другие

    Коллекция параметров

    Иногда необходимо параметризировать SQL-запрос. Кроме того, бывает желательно связать входные и выходные аргументы хранимой процедуры с переменными программы.
    Для того чтобы сделать это, следует определить свойство Parameters (Параметры) класса SqlCommand, которое является коллекцией экземпляров класса SqlParameter. Процедура инсталляции, имеющаяся на Web-узле данной книги, добавляет в базу данных Northwind хранимую процедуру get_customers. To же самое можно выполнить и вручную с помощью Server Explorer в Visual Studio.NET или SQL Query Analyzer (Анализатор запросов SQL). Еще один способ — запустить макрос SQL, поставляемый вместе с примерами к данной книге. Хранимая процедура get_customers иллюстрирует, как можно использовать простую хранимую процедуру, имеющую один аргумент, а именно — название компании, и возвращающую идентификатор (ID) клиента, т.е. указанной компании.
    CREATE PROCEDURE get_customers
    (dcompanyname nvarchar ( 40), Scustomerid nchar(5) OUTPUT)
    AS
    select @customerid = CustomerlD from Customers where
    CompanyName = @companyname
    RETURN
    GO
    Пример StoredProcedure (Хранимая процедура) демонстрирует, как это можно сделать.
    command = // команда
    new SqlCommand("get_customers", conn);
    command->CommandType = // команда
    CommandType::StoredProcedure;
    SqlParameter *p = 0; p = new SqlParameter(
    "@companyname",
    SqlDbType::NVarChar, 40);
    p->Direction = ParameterDirection::Input;
    // Направление = Ввод p->set_Value(S"Ernst Handel");

    // Эрнст Хандель command->Parameters->Add(p);
    // команда-> Параметры-> Добавить
    p = new SqlParameter(
    "@customerid", SqlDbType::NChar, 5);
    p->Direction = ParameterDirection::Output;// Направление = Вывод
    command->Parameters->Add(p); // команда-> Параметры-> Добавить command->ExecuteNonQuery(); // команда
    Console::WriteLine(
    "{0} Customerld = {!}",
    command->get_Parameters()-> // команда
    get_Item("gcompanyname")->Value, // Значение
    command->get_Parameters()-> // команда
    get_Item("Scustomerid")->Value) ; // Значение

    Каждый отдельный член коллекции SqlParameterCollection, являющийся объектом SqlParameter, соответствует одному параметру SQL-запроса или хранимой процедуры. Как показано в примере, параметру не обязательно иметь какую-либо взаимосвязь с определенной таблицей или столбцом базы данных.
    Тем минимумом, который необходимо определить в конструкторе или установкой свойств, являются имя и тип параметра. Если параметр имеет непостоянную длину, необходимо также определить его размер.
    В приведенном примере к коллекции параметров добавляются два параметра. Первый соответствует аргументу хранимой процедуры. Второй соответствует возвращаемому хранимой процедурой значению.
    Имя параметра соответствует имени аргумента хранимой процедуры get_customers. Другие параметры конструктора SqlParameter определяют тип параметра. В первом случае это строка Unicode переменного размера, длиной до 40 символов. Во втором — строка Unicode постоянного размера (5 символов). Обозначение SqlDbType : :NVarChar означает постоянный подлине поток символов Unicode.
    Свойство Value (Значение) используется для установки или получения значения параметра. В нашем примере оно используется для инициализации входного параметра @companyname, соответствующего аргументу хранимой процедуры. Оно используется также для получения значения параметра @customerid, соответствующего возвращаемому хранимой процедурой значению.
    Выходной параметр должен быть определен как таковой с помощью свойства Direction (Направление). В нашем примере параметр @companyname устанавливается как входной присвоением этому свойству значения ParameterDirection: : Input (Входной параметр). Аналогично, параметр @customerid устанавливается как выходной присвоением этому свойству значения ParameterDirection: :Output (Выходной параметр). Данная операция для выходного параметра должна быть проведена обязательно, так как по умолчанию свойство Direction (Направление) имеет значение, соответствующее входному параметру. Для того чтобы связать параметр с возвращаемым хранимой процедурой значением, используется значение ParameterDirection: :ReturnValue. Для параметров, используемых в обоих направлениях, берется значение ParameterDirection: : InputOutput (Входной и выходной параметр).
    Имена параметров можно использовать для доступа к каждому из параметров коллекции параметров SqlCommand. Параметризованные команды могут работать как с классом SqlDataReader, так и с классом DataSet (Набор данных). Позже, при рассмотрении класса DataSet (Набор данных), мы расскажем, как определить свойство параметра Source (Источник), которое указывает, какому именно столбцу объекта DataSet (Набор данных) соответствует параметр.

    CompEbook.ru Железо, дизайн, обучение и другие

    Множественное результирующее множество

    Класс SqlDataReader может хранить несколько результирующих множеств, что продемонстрировано в примере DataReader. Два запроса, разделенные точкой с запятой, являются двумя SQL-запросами, которые приводят к возврату двух результирующих множеств, по одному на каждый запрос.
    String *ConnString = // Строка
    "server=localhost;uid=sa;pwd=;
    database=Northwind";
    String *cmd = // Строка
    "select Customerld,
    CompanyName from Customers where
    // выбрать Customerld,
    CompanyName из Клиентов где
    Customerld like 'T%'/select Customerld, CompanyName ..."
    // Customerld подобно "I % '; выбрать Customerld, CompanyName ...
    int ResultSetCounter = -1; int NumberFields = 0;
    reader = command->ExecuteReader(); // команда
    if (reader != 0)
    {
    NumberFields = reader->FieldCount;
    Object *fields[] = new Object*[NumberFields]; // новый
    // Объект Console::WriteLine (
    "Result Set\tCustomerId\tCompanyName");
    // "Результат Set\tCustomerId\tCompanyName");
    do
    {
    ResultSetCounter++;
    while(reader->Read() == true) // пока Чтение ()
    // == истина {
    NumberFields =
    reader->GetValues(fields); // поля Console::Write( " { 0 } " ,
    ResultSetCounter.ToStringt)); for (int i = 0; i{
    Console::Write(
    "\t\t{0}", fields[i]); // поля
    Console::Write("\n"); // Запись
    };
    }
    while(reader->NextResult() == true); // пока NextResult ()
    // == истина
    }
    Метод FieldCount возвращает количество столбцов в результирующем множестве. Поскольку метод GetValues возвращает данные в их исходном формате, в качестве аргументов ему передается массив объектов. Метод NextResult обеспечивает перемещение к следующему результирующему множеству.
    Результатом работы программы DataReader будет вывод на экран следующих строк:
    Result Set Customerld 'CompanyName
    0 THEBI The Big Cheese
    0 THECR The Cracker Box
    0 TOMSP Toms Spezialitaten
    0 TORTU Tortuga Restaurante
    0 TRADH Tradigao Hipermercados
    0 TRAIH Trail's Head Gourmet
    Provisioners
    1 WANDK Die Wandernde Kuh
    1 WARTH Wartian Herkku
    1 WELLI Wellington Importadora
    1 WHITC White Clover Markets
    1 WILMK Wilman Kala
    1 WOLZA Wolski Zajazd
    CompEbook.ru Железо, дизайн, обучение и другие

    Множественные таблицы в объекте DataSet (Набор данных)

    Каждый объект DataSet (Набор данных) содержит коллекцию из одного или более объектов1,DataTable (Таблица данных). Каждый объект DataTable (Таблица данных) соответствует одной таблице. С помощью свойства SelectCommand, в котором содержится операция соединения, можно производить выборку из нескольких таблиц базы данных в один объект DataTable (Таблица данных). При необходимости обновить содержимое множественных таблиц достаточно определить лишь команду обновления, так как информация о связях между таблицами базы данных уже известна. В файле Hotel-Bookings, h нашего примера свойство SelectCommand объекта SqlDataAdapter, содержащегося в объекте HotelBroker (Посредник, бронирующий места в гостинице), определено следующим образом:
    String *cmd = // Строка
    "select Customerld, HotelName, City, ArrivalDate, // выбрать
    DepartureDate, Reservationld from Reservations, Hotels
    where Reservations.Hotelld = Hotels.Hotelld";
    // где Резервирование.Hotelld = Гостиницы.Hotelld"; adapter->SelectCommand = new SqlCommand(cmd, conn); dataset = new DataSet; // новый Набор данных adapter->Fill(dataset, "Reservations");
    // Заполнить (набор данных, "Резервирование")
    В этом случае DataSet (Набор данных) содержит один объект DataTable (Таблица данных), представляющий таблицу, называющуюся Reservations (Резервирование). Информация о том, что некоторые данные получены из таблицы Hotels, не сохраняется.
    В один набор данных можно загрузить данные нескольких таблиц. Это продемонстрировано в примере DataSchema, в котором используется база данных Northwind.
    adapter->SelectCommand = new SqlCommand(
    "select * from [Order Details] where Productld = 1",
    // "выбрать * из [Подробности заказа] где Productld = 1 ",
    conn);
    adapter->FillSchema (
    dataset, SchemaType::Source, "Order Details");
    // набор данных, SchemaType:: Источник, " Подробности заказа");
    adapter->Fill(dataset, "Order Details");
    // Заполнить (набор данных, " Подробности заказа");
    adapter->SelectCommand =
    new SqlCommand("select * from Shippers", conn);
    // выбрать * из Грузоотправителей adapter->FillSchema(
    dataset, SchemaType::Source, "Shippers");
    // набор данных, SchemaType:: Источник, "Грузоотправители"); adapter->Fill(dataset, "Shippers"); // Заполнить (набор данных, "Грузоотправители");

    В этом случае объект DataSet (Набор данных) содержит две таблицы, OrderDetails и Shippers. Метод SqlDataAdapter: :FillSchema заполняет DataSet (Набор данных) данными из таблиц, а также информацией о первичных ключах, связанных с таблицами. Затем программа просматривает содержимое таблиц и выводит на печать данные и первичные ключи таблиц. Доступ к содержащимся в DataTable (Таблица данных) объектам DataColumn осуществляется с помощью коллекции Columns (Столбцы), также являющейся частью объекта DataTable (Таблица данных).

    lEnumerator *pEnum = dataset->Tables->GetEnumerator();
    // набор данных-> Таблицы while (pEnum->MoveNext ()) {
    DataTable *t =
    dynamic_cast(pEnum->Current); Console::WriteLine(t->TableName); DataColumn *dc [] = t->PrimaryKey; for (int i=0; iLength; i++)
    {
    Console::WriteLine(
    "\tPrimary Key Field {0} = {!}", // " \t Поле первичного ключа {0} = {1} ", _box(i),
    dc[i]->ColumnName); }
    Console::Write("\t"); // Запись
    lEnumerator *pEnum = t->Columns->GetEnumerator(); // Столбцы while (pEnum->MoveNext()) {
    DataColumn *c =
    dynamic_cast(pEnum->Current); Console::Write ("{0, -20}", c->ColumnName); // Запись }
    Console::WriteLine("");
    pEnum = t->Rows->GetEnumerator() ; // Строки while (pEnum->MoveNext()) {
    DataRow *r =
    dynamic_cast(pEnum->Current) ; Console::Write("\t"); // Запись for (int i=0; iItemArray.Length; i++) Console::Write ( // Запись "{0, -20}",
    r->get_Item(i)->ToString()->Trim()); // Вырезка Console::WriteLine(""); } }

    Программа выводит на экран название таблицы, первичные ключи, названия столбцов и данные таблиц:

    Order Details
    Primary Key Field 0 = OrderlD
    Primary Key Field 1 = ProductID
    OrderlD ProductID UnitPrice Quantity Discount
    10285 1 14.4 45 0.2
    10294 1 14.4 18 0
    Shippers
    Primary Key Field 0 = ShipperlD ShipperlD CompanyName Phone
    1 Speedy Express (503) 555-9831
    2 United Package (503) 555-3199
    3 Federal Shipping (503) 555-9931

    А вот и перевод:

    Подробности заказа
    Поле первичного ключа 0 = OrderlD
    Поле первичного ключа 1 = ProductID
    Идентификатор заказа Идентификатор продукта Цена Количество Скидка
    10285 1 14.4 45 0.2
    10294 1 14.4 18 О
    Грузоотправители
    Поле первичного ключа 0 = ShipperlD
    Идентификатор грузоотправителя Название компании Телефон
    1 Быстрый экспресс (503) 555-9831
    2 Объединение пакет (503) 555-3199
    3 Федеральная отгрузка (503) 555-9931

    CompEbook.ru Железо, дизайн, обучение и другие

    Объект DataSet (Набор данных) и сравнение пессимистического блокирования с оптимистическим

    Транзакции только помогают сохранить непротиворечивость базы данных. Если вы переводите деньги со сберегательного счета на текущий для оплаты счета за телефон, транзакции помогут гарантировать, что деньги будут сняты с одного счета и появятся на другом (или же не произойдет ни того, ни другого). Вы не столкнетесь ни с ситуацией, когда деньги придут на текущий счет, но не будут сняты со сберегательного (это было бы неплохо для вас, но плохо для банка), ни с противоположной ситуацией (неприятной для вас, но хорошей для банка). Ничто не помешает вашей супруге потратить эти деньги на ужин в модном ресторане.
    При оптимистическом блокировании предполагается, что ничего подобного не случится, но вам следует быть готовым к такой ситуации, если она все-таки возникнет. Использование пессимистического блокирования требует координации действий всех пользователей таблицы базы данных таким образом, чтобы предотвратить подобную ситуацию. Разумеется, чем меньше блокировок накладывается на столбец базы данных, тем шире возможности использования вашего приложения.
    Следует понимать, что такая ситуация влияет и на считывание данных, и на их обновление. Скажем, если ваша супруга видит, что на счету есть деньги, и строит свои планы относительно этих денег, это может привести к не меньшим проблемам, чем просто потеря денег с общего текущего счета.
    Хотя обсуждение способов решения таких проблем выходит далеко за пределы рассматриваемого в этой главе материала, важно помнить, что они возникают, если записи, считанные в объект DataSet (Набор данных), не блокированы. Использование SqlDa-taAdapter при работе с DataSet (Набор данных) предполагает применение оптимистической стратегии блокировки.
    Почему это так важно? Прежде всего потому, что от этого зависит производительность и масштабируемость вашего приложения. А почему это так сложно? Потому что нельзя дать совета, подходящего для всех приложений в любой ситуации. Когда пользователи не обращаются одновременно к одним и тем же данным, использование оптимистической стратегии блокировок является наилучшим вариантом. Если необходимо заблокировать доступ к записи на долгий период времени, время ожидания получения доступа к этой записи значительно увеличится, понижая тем самым производительность и масштабируемость приложения.
    Вы должны понимать, что такое уровни локализации транзакций, администратор блокировок базы данных, и что существует возможность конфликта при доступе к данным, а такой конфликт может привести к зависанию приложения. Вы должны понимать, сколько времени и ресурсов может потратить ваше приложение на разрешение конфликтов, как оно должно поступать с несогласованными или некорректными данными. Все это необходимо для принятия решения, в каких ситуациях допускается попытка избежать зависания любой ценой, и как следует поступать при возникновении последовательности конфликтующих операций.
    Иногда может понадобиться использовать объект DataSet (Набор данных) с дополнительно реализованными возможностями для проверки того, были ли изменены записи, содержащиеся в нем, со времени их последней выборки или модификации. А можно просто использовать SqlDataReader и заново произвести выборку. Все это зависит от ситуации.
    Так, при бронировании комнат в нашем примере HotelBroker (Посредник, бронирующий места в гостинице) нельзя делать оптимистических предположений о наличии свободных мест. Это равносильно предположению о бесконечном количестве комнат в отеле и приведет к ситуации, когда администратор должен будет распределить ограниченное количество комнат на гораздо большее количество желающих. В нашем примере для проверки того, зарезервирована ли комната, используется хранимая процедура MakeReservation.
    Иногда, даже при отсутствии одновременных запросов, объект DataSet (Набор данных) нельзя использовать для добавления новой строки без установления связи с базой данных. В нашем примере HotelBroker (Посредник, бронирующий места в гостинице) нельзя использовать произвольный первичный ключ. Бронирование могут производить одновременно несколько пользователей. Поэтому идентификаторы бронирования не могут быть локальными. Их определение должно производиться самой базой данных. В нашем примере это делает хранимая процедура MakeReservation.
    Способ использования отсоединенных операций в вашем приложении следует определить еще прежде, чем вы решите, каким образом будут использоваться объекты SqlDataReader и DataSet (Набор данных).
    Зачем вообще в приложении HotelBroker (Посредник, бронирующий места в гостинице) используется DataSet (Набор данных)? Фактически, в реализации объекта Customer (Клиент) DataSet (Набор данных) никак не используется. Но его использует объект HotelBroker (Посредник, бронирующий места в гостинице), и делается это по двум причинам. Первая — педагогическая. Мы хотели показать, как объект DataSet (Набор данных) может быть использован в полноценном приложении, а не только в простой программе. Во-вторых, в Web-ориентированной версии приложения, реализованной в последующих главах книги, удобно производить кэширование некоторых данных. Например, вполне разумно сделать так, чтобы пользователь мог работать с локальной копией системы бронирования. С другой стороны, такую информацию, как электронный адрес пользователя, достаточно запросить один раз — при его регистрации в системе. Поэтому в нашем случае нет необходимости в сложном механизме кэширования информации о пользователе, так что реализованный объект Customer (Клиент) использует методы объекта SqlCommand.
    CompEbook.ru Железо, дизайн, обучение и другие

    Обновление источника данных

    Каким образом метод SqlDataAdapter: :Update (Обновить) передает источнику данных информацию о произведенных изменениях? Изменения, внесенные в объект Da-taSet (Набор данных), передаются базе данных с помощью свойств InsertCommand, UpdateCommand (Команда обновления) и DeleteCommand класса SqlDataAdapter. Каждому из этих свойств присваивается экземпляр SqlCommand, который может быть параметризован для того, чтобы поставить в соответствие переменные программы частям SQL-запроса. Продемонстрируем это на примере кода, взятого из реализации конструктора класса HotelBroker (Посредник, бронирующий места в гостинице).
    Экземпляр SqlCommand создается для представления параметризованного SQL-запроса, который используется при вызове метода SqlDataAdapter:: Update (Обновить) для добавления в базу данных новой строки. В момент вызова метода вместо параметров будут подставлены фактические значения.
    SqlCommand *cmd = new SqlCommand(
    "insert Hotels(City, HotelName, NumberRooms, RoomRate)
    // "вставить Гостиницы (Город, HotelName,
    // NumberRooms, RoomRate)
    values(@City, @Name, SNumRooms, @RoomRate)", // значения
    conn);
    Параметры должны быть связаны с соответствующими столбцами в DataRow. Во фрагменте кода метода AddHotel, рассмотренного ранее, столбцы различались по именам: HotelName, City (Город), NumberRooms, RoomRate. В конструкторе SqlParame-ter им соответствуют параметры @Name, @City, @NumRooms, @RoomRate. Последний аргумент инициализирует свойство Source (Источник) объекта SqlParameter. Свойство Source (Источник) определяет столбец объекта DataSet (Набор данных), которому соответствует параметр. Метод Add (Добавить) помещает параметр в коллекцию объектов Parameter (Параметр), связанную с экземпляром SqlCommand.
    SqlParameter *param = new SqlParameter(
    "@City", SqlDbType::Char, 20, "City");
    cmd->Parameters->Add(param); // Параметры-> Добавить
    cmd->Parameters->Add(new SqlParameter( // Параметры-> Добавить
    "@Name", SqlDbType::Char, 20, "HotelName"));

    cmd->Parameters->Add (new SqlParameter( // Параметры-> Добавить
    "@NumRooms", SqlDbType::Int, 4, "NumberRooms"));

    cmd->Parameters->Add(new SqlParameter( // Параметры-> Добавить
    "@RoomRate", SqlDbType::Money, 8, "RoomRate"));

    И, наконец, свойству InsertCommand класса SqlDataAdapter присваивается указатель на экземпляр класса SqlCommand. Отныне именно эта команда будет использоваться при вставке строки в базу данных:

    hotelsAdapter->InsertCommand = cmd;

    Аналогичный исходный код есть в конструкторе класса HotelBroker (Посредник, бронирующий места в гостинице). Различие лишь в том, что там устанавливаются значения свойств UpdateCommand (Команда обновления) и DeleteCommand для определения команд обновления и удаления строк.

    hotelsAdapter->UpdateCommand = new SqlCommand(
    "update Hotels set NumberRooms = @NumRooms, RoomRate = @RoomRate where City = @City and HotelName = @Name",
    // где Город = @City и HotelName = @Name ", conn); hotelsAdapter->UpdateCommand->Parameters->Add(
    // Параметры-> Добавить new SqlParameter(
    "@City", SqlDbType::Char,20, "City")); hotelsAdapter->UpdateCommand->Parameters->Add(
    // Параметры-> Добавить new SqlParameter(
    "@Name", SqlDbType:-.Char, 20, "HotelName")); hotelsAdapter->UpdateCommand->Parameters->Add(
    // Параметры-> Добавить new SqlParameter(
    "@NumRooms", SqlDbType::Int, 4, "NumberRooms")); hotelsAdapter->UpdateCommand->Parameters->Add(
    // Параметры-> Добавить new SqlParameter(
    "@RoomRate",SqlDbType::Money, 8, "RoomRate"));
    hotelsAdapter->DeleteCoiranand = new SqlCommand(
    "delete from Hotels where City = @City and HotelName = // "удалить из Гостиниц где Город = @City и HotelName = @Name", conn);
    hotelsAdapter->DeleteCommand->Parameters->Add(
    // Параметры-> Добавить new SqlParameter(
    "SCity", SqlDbType::Char, 20, "City"));
    hotelsAdapter->DeleteCommand->Parameters->Add(
    // Параметры-> Добавить new SqlParameter(
    "@Name", SqlDbType::Char, 20, "HotelName"));

    Все изменения, внесенные в объект DataSet (Набор данных), будут переданы базе данных при выполнении метода SqlDataAdapter: : Update (Обновить). Как принять или отменить внесенные изменения до вызова этого метода, будет рассмотрено в следующем разделе.

    CompEbook.ru Железо, дизайн, обучение и другие

    Ограничения и связи

    Каждый объект DataTable (Таблица данных) содержит коллекцию объектов Da-taRow. Каждый такой объект представляет строку таблицы. Добавление нового объекта DataRow влияет на ограничения объекта DataColumn (мы предполагаем, что свойство Enf orceConstraints объекта DataSet (Набор данных) имеет значение true (истина)).
    Первичные ключи
    Существует несколько типов ограничений. Первичный ключ — уникальный ключ для строк таблицы. Другие ограничения единственности определяют единственность каждого значения в столбце (или столбцах). Внешний ключ используется для обозначения того, что значения в столбце являются первичными ключами для другой таблицы объекта DataSet (Набор данных). Первичный ключ объекта DataTable (Таблица данных) является свойством:
    // Определить РК для таблицы BookCategories DataColumn *bookcategoriesPK [] =
    new DataColumn*[2]; bookcategoriesPK[0] = en; bookcategoriesPK[l] = loc; bookcategories->PrimaryKey = bookcategoriesPK;
    // Определить РК для таблицы Authors (Авторы) DataColumn *authorsPK [] =
    new DataColumn*[1]; authorsPK[0] = auid; authors->PrimaryKey = authorsPK; // авторы
    // Определить РК для таблицы Books (Книги) DataColumn *booksPK [] =
    new DataColumn*[1]; booksPK[0] = ISBN; books->PrimaryKey = booksPK; // книги
    Ограничения
    Для работы со всеми ограничениями помимо первичных ключей используются абстрактный базовый класс Constraint (Ограничение) и его производные классы: UniqueConstraint и ForeignKeyConstraint. Базовый класс обеспечивает возможность помещения ограничения в коллекцию ограничений таблицы. Первичный ключ также регистрируется в этой коллекции как ограничение единственности с именем, генерируемым системой. Для определения, является ли ограничение первичным ключом, используется свойство UniqueConstraint::IsPrimaryKey.
    Определим уникальность значений столбца Category таблицы Categories (Категории). Так как последний аргумент метода Add (Добавить) имеет значение false (ложь), это ограничение не будет первичным ключом таблицы. Для этой таблицы мы не определяем первичного ключа, а задаем только ограничения единственности. Вообще говоря, задавать ограничения для значений таблицы не обязательно. Хотя это и нарушает правила реляционной целостности, никто не заставляет вас использовать объект DataSet (Набор данных) реляционным способом. // Определим ограничение единственности // для таблицы Categories (Категории) categones->Constraints->Add ( // категории-> Ограничениям Добавить

    "Unique CategoryName Constraint",
    // "Уникальное ограничение CategoryName ",
    categoryname,
    false); // ложь

    При использовании внешнего ключа можно определить действия, которые следует выполнить при изменении первичного ключа, с которым он связан. Выбор здесь стандартен: None (Ничего), Cascade (Каскад), SetNull. Для установки значения, принимаемого по умолчанию для этого параметра (он описывается в свойстве Def aultValue объекта DataColumn), используется метод SetDefault. Эти параметры могут быть определены как для условий обновления, так и для условий удаления данных.
    В нашем примере внешний ключ определяется таким образом, чтобы все идентификаторы авторов, содержащиеся в таблице Books (Книги), были описаны также и в таблице Authors (Авторы). Другими словами, у каждой книги, зарегистрированной в базе, есть автор, который также зарегистрирован в этой же базе. Мы назвали это ограничение '"Authors->Books". При изменении идентификатора автора правила обновления данных вынуждают объект DataSet (Набор данных) изменить этот идентификатор и во всех остальных строках таблиц на новое значение. Когда идентификатор удаляется, значение для этого идентификатора в строках, описывающих книги, будет установлено пустым.
    Если при этом свойство DeleteRule имеет значение Cascade (Каскад), то каскадное удаление будет выполнено для всех таких строк из таблицы Books (Книги). Свойство Ас-ceptRejectRule используется при транзакционном изменении объекта DataSet (Набор данных) и будет рассмотрено ниже. Значение этого свойства определяет, что произойдет при вызове метода AcceptChanges объектов DataSet (Набор данных), Da-taRow или DataTable (Таблица данных). В нашем примере изменения будут произведены последовательно со всеми данными. Другое возможное значение этого свойства — None (не совершать никаких действий).

    // Определить FK для таблицы Books (Книги) // (Authorld должен быть в таблице Authors (Авторы)) DataColumn *bookauthorFK [] =
    new DataColumn*[1]; bookauthorFK[0] = booksauid; ForeignKeyConstraint *fk =
    new ForeignKeyConstraint(
    "Authors->Books", authorsPK, bookauthorFK); // Авторы-> Книги
    fk->AcceptRe;jectRule = AcceptRejectRule::Cascade; // Каскад fk->DeleteRule = Rule::SetNull; fk->UpdateRule = Rule:rCascade; // Каскад books->Constraints->Add(fk); // книги-> Ограничениям Добавить


    Связи между данными

    Кроме ограничений для данных, можно задавать связи между ними, для хранения которых используется коллекция DataRelation объекта DataSet (Набор данных). Связи соединяют таблицы таким образом, что вы можете перемещаться от предка к потомку и наоборот. При добавлении связи в коллекцию ограничений автоматически добавляется и соответствующий внешний ключ.
    В нашем примере таблица Categories (Категории) сделана предком таблицы Book-Categories (Категории книг) через столбцы Categories (Категории) и CategoryName. Оба столбца, между которыми определяется связь, должны содержать данные одного типа. Эту связь можно использовать для нахождения строк в таблице-потомке или строки в таблице-предке по значению поля, соответствующего связанному столбцу. В нашем примере необходимо также установить связь между столбцами, описывающими номер книги в Библиотеке Конгресса в таблицах Books (Книги) и BookCategory.

    // Установим связь между столбцом Categories (Категории) // в таблице BookCategories (Категории книг) и
    // столбцом Categories (Категории) в таблице Categories (Категории) ds->Relations->Add( // Отношения-> Добавить
    "Category->BookCategories Relation",
    // "Категория-> Отношение BookCategories ",
    categoryname,
    en) ;
    // Установим связь между столбцом Library of Congress Number // (Номер книги в Библиотеке Конгресса) таблицы Books (Книги) и // столбцом LOC таблицы BookCategories (Категории книг) ds->Relations->Add( // Отношения-> Добавить
    "Book Category LOC->Book LOC Relation",
    loc,
    bloc);

    CompEbook.ru Железо, дизайн, обучение и другие

    Основные сведения о наборах данных

    Можно также выбрать подмножество данных из объекта DataSet (Набор данных). Метод Select (Выбрать) класса DataTable (Таблица данных) имеет синтаксис, совпадающий с синтаксисом фразы "where" в SQL-запросах. Для доступа к полям строки используются имена столбцов Ниже приведен пример из описания класса HotelBroker (Посредник, бронирующий места в гостинице), в котором этот метод используется для получения списка отелей определенного города.
    ArrayList *GetHotels(String *city)
    {
    try
    {
    DataTable *t = hotelsDataset->
    Tables->get_Item("Hotels"); // Гостиницы
    DataRow *rows [] = t->Select( // Выбор
    String::Format("City = '{0}'", city)); // Строка:: Формат ("Город = ' {0} ' ", город));
    ArrayList *hotels = new ArrayList;
    for (int i=0; i < rows->Length; i++)
    {
    String *name = rows[i]->get_Item(
    "HotelName") ->ToString {) ->Tnm() ; // Вырезка
    hotels->Add(name); // гостиницы-> Добавить (название);

    }
    return hotels; // гостиницы
    }
    catch(Exception *e) // Исключение
    {
    throw e;
    }
    }
    Метод AddHotel класса HotelBroker (Посредник, бронирующий места в гостинице) иллюстрирует, как добавляется новая строка в объект DataSet (Набор данных) При этом создается новый экземпляр класса DataRow и для добавления данных в соответствующие поля используются имена столбцов
    Если необходимо сохранить созданную строку в базе данных, используется метод Update (Обновить) класса SqlDataAdapter Он является промежуточным звеном между объектом DataSet (Набор данных) и базой данных Позже мы обсудим, как производить транзакционное редактирование набора данных для того, чтобы принять или отвергнуть изменения до их передачи в базу данных
    String *AddHptel( // Строка
    String *city, // Строка
    String *name, // Строка
    int number, // номер
    Decimal rate) // Десятичная цена
    {
    try
    {
    DataTable *t = hotelsDataset->Tables->get_Item( // Таблицы
    "Hotels"); // Гостиницы
    DataRow *r = t->NewRow();
    r->set_Item("HotelName", name); // название
    r->set_Item("City", city); // ("Город", город)
    r->set_Item("NumberRooms", _box(number));
    r->set_Item("RoomRate", _box(rate));
    t->Rows->Add(r); // Строки-> Добавить
    hotelsAdapter->Update(hotelsDataset, "Hotels"); // Обновить "Гостиницы"
    }
    catch(Exception *e) // Исключение
    {
    throw e;
    }
    }

    Для удаления строки из объекта DataSet (Набор данных) прежде всего необходимо найти эту строку или строки, а затем вызвать метод Delete (Удалить) для каждого из удаляемых экземпляров DataRow. Метод Remove (Удалить) удаляет экземпляр DataRow из коллекции Этот экземпляр не помечается как удаленный, так как он уже не является частью объекта DataSet (Набор данных) При вызове метода Update (Обновить) преобразователя данных соответствующие данные не будут удалены из базы данных Приведем фрагмент метода DeleteHotel класса HotelBroker (Посредник, бронирующий места в гостинице)

    String *DeleteHotel(String *city, String *name) // Строка
    *DeleteHotel (Строка *city, Строка *name)
    {
    try
    {
    t = hotelsDataset->Tables->get_Item("Hotels"); // Таблицы-> get_Item ("Гостиницы")
    r = t->Select ( // Выбор String::Format(
    // Строка:: Формат (
    "City = '{0}' and HotelName = '{!}'",
    // "Город = ' {0} ' и HotelName ='{!}' ",
    city, name)); // город, название
    for (i=0; iLength; i++)
    r[i]->Delete (); // Удалить
    }

    Для изменения строки набора данных достаточно просто найти эту строку и внести необходимые изменения в поля строки. В качестве примера ниже приведен фрагмент реализации метода ChangeRooms класса HotelBroker (Посредник, бронирующий места в гостинице). При вызове метода Update (Обновить) преобразователя данных, все изменения, сделанные в этом фрагменте, будут переданы базе данных.

    String *ChangeRooms( // Строка
    String *city, // Строка
    String *name, // Строка
    int numberRooms,
    Decimal rate) // Десятичная цена
    {
    DataTable *t = 0;
    try
    {
    t = hotelsDataset->Tables->get_Item("Hotels"); // Таблицы-> get_Item ("Гостиницы")
    DataRow *r [] = t->Select( // Выбор
    String::Format(
    // Строка:: Формат (
    "City = '{0}' and HotelName = '{I}1",
    // "Город = ' {0} ' и HotelName = ' {1} ' ",
    city, name)); // город, название
    for (int i = 0; i < r->Length; i++) {
    r[i]->set_Item("NumberRooms", _box(numberRooms));
    r[i]->set_Item("RoomRate", _box(rate)) ; }
    } }

    CompEbook.ru Железо, дизайн, обучение и другие

    Отсоединенный режим

    Режим работы с базами данных при отсутствии постоянного соединения с базой данных называют отсоединенным (disconnected). Соединенный режим представляет собой сильносвязанную среду, которая может содержать состояния и соединения. Среда клиент/сервер является тому подтверждением. Именно для такого подхода и были разработаны ADO и OLEDB (OLE для баз данных). В среде соединенного режима можно использовать устройства считывания данных. При необходимости для этих целей можно использовать, посредством обеспечивающих взаимодействие СОМ-компонентов, ADO. Фактически, специально для применения в .NET, изменения в ADO не вносились, так что здесь есть полная обратная совместимость, включая также ошибки и прочее.
    Однако держать соединение постоянно открытым слишком дорого в среде, в которой требуется обеспечить возможность работы нескольким пользователям. Это относится к многоузловым и Internet-ориентированным решениям. В таких средах часто нет необходимости блокировать доступ к таблицам баз данных. А это способствует масштабируемости, так как уменьшает вероятность конфликтов. Объекты DataSet (Набор данных) из коллекции таких объектов Tables (Таблицы), с их ограничениями, могут имитировать таблицы исходной базы данных и взаимосвязи между ними. В приложениях, полностью реализованных в .NET, одна часть приложения может передавать или получать экземпляр DataSet (Набор данных). Конкурентоспособным разработкам это может дать большое преимущество в масштабируемости и производительности, что справедливо также для многих типов Internet-приложений и приложений, ориентированных на внутрисете-вое применение.
    При работе в отсоединенном режиме соединение осуществляется таким же образом, как и в соединенном режиме. Данные получают с помощью классов преобразования данных источников данных. Свойство SelectCommand определяет SQL-запрос, используемый для передачи данных в набор данных. В отличие от устройства считывания данных, которое связано соединением с определенной базой данных, набор данных не имеет связей ни с какой базой данных, даже с той, из которой были получены хранящиеся в нем данные.
    CompEbook.ru Железо, дизайн, обучение и другие

    Получение информации о схеме размещения данных в объекте DataTabie (Таблица данных)

    Рассмотрим, как можно получить информацию об объекте DataTabie (Таблица данных), точнее, об ограничениях и ключах этого объекта. В предыдущем примере уже было показано, как получить доступ к объектам Data-Column объекта DataTabie (Таблица данных). Обратите внимание на использование свойства IsPrimaryKey объекта UniqueConstraint для определения, является ли ограничение первичным ключом. Следующий фрагмент взят из примера DataEditing.
    pEnum = ds->Tables->GetEnumerator(); // Таблицы
    while (pEnum->MoveNext() )
    {
    DataTabie *t =
    dynamic_cast(pEnum->Current);
    Console::WriteLine(" {0}", t->TableName);
    Console : :WriteLine ( "\tPrimary Key:")/' // Первичный ключ
    for (int i = 0; i < t->PrimaryKey.Length; i++)
    {
    DataColumn *c = t->PrimaryKey[i]; Console::WriteLine("\t\t{0}", c->ColumnName); }
    Console::WriteLine("\tConstraints:" ) ;
    lEnumerator *pEnum = t->Constraints->GetEnumerator(); // Ограничения while (pEnum->MoveNext()) {
    Constraint *c = // Ограничение
    dynamic_cast(pEnum->Current); // Ограничение
    String *constraintName; // Строка // если (с - ForeignKeyConstraint) if (dynamic_cast(c) != 0) constraintName =
    String::Concat("Foreign Key:", // Строка:: Concat ("Внешний ключ: ", c->ConstraintName);
    else if (dynamic_cast(c) != 0) {
    UniqueConstraint *u = dynamic_cast(c); if (u->IsPrimaryKey)
    constraintName = "Primary Key"; // Первичный ключ else
    constraintName = u->ConstraintName; } else
    constraintName = "Unknown Name"; // Неизвестное имя Console::WriteLine("\t\t{0, -40}", constraintName); } }
    Напечатанные программой строки приведены ниже. Обратите внимание на то, что определение связей, осуществленное нами с помощью объектов DataRelation, приводит к появлению объектов ForeignKeyConstraint в коллекции ограничений таблицы.
    Первичный ключ также появляется в коллекции ограничений как объект UniqueCon-straint. Ограничения, определенные как ограничения единственности или внешние ключи, появляются в коллекции так, как ожидается.

    Categories
    Primary Key: Constraints:
    Unique CategoryName Constraint BookCategories Primary Key:
    CategoryName
    LibraryofCongressNumber Constraints:
    Primary Key
    Foreign Key:Category->BookCategories Relation
    Constraint2 Authors
    Primary Key:
    Authorld Constraints:
    Primary Key Books
    Primary Key:
    ISBN Constraints:
    Primary Key
    Foreign Key:Authors->Books
    Foreign Key:Book Category LOC->Book LOG Relation

    Вот перевод этой выдачи:

    Категории
    Первичный ключ: Ограничения:
    Ограничение единственности CategoryName Категории книг
    Первичный ключ:
    Название категории
    Номер в Библиотеке Конгресса Ограничения:
    Первичный ключ
    Внешний ключ: Отношение Category-> BookCategories
    Constraint2 Авторы
    Первичный ключ:
    Authorld Ограничения:
    Первичный ключ Книги
    Первичный ключ:
    ISBN Ограничения:
    Первичный ключ:
    Внешний ключ:Авторы-> Книги
    Внешний ключ: Отношение Book Category LOC->Book LOG

    Обратите внимание на ограничение в таблице BookCategories (Категории книг) с именем, сгенерированным системой. Когда вы внимательно просмотрите исходный код программы, то убедитесь, что эти ограничения в ней не добавляются. Откуда же они берутся? Если бы вы просмотрели содержимое соответствующего объекта, то увидели бы, что ограничение наложено на столбец LibraryofCongressNumber. Система посчитала, что, поскольку столбец CategoryName является внешним ключом для другой таблицы, то значения в столбце LibraryOfCongressNumber должны быть уникальными.
    Можно также просмотреть коллекцию связей Relations (Отношения) объекта DataSet (Набор данных). При этом можно узнать, какие таблицы являются предками, и какие именно столбцы в них участвуют в образовании связей. То же самое можно сделать и для таблиц-потомков. Приведем соответствующий фрагмент примера DataEditing.

    pEnum = ds->Relations->GetEnumerator(); // Отношения
    while (pEnum->MoveNext())
    {
    DataRelation *dr =
    dynamic_cast(pEnum->Current); DataTable ^parentTable = dr->ParentTable; DataTable *childTable = dr->ChildTable; Console::WriteLine(
    " Relation: {0} ", dr->RelationName); // Отношение Console::WriteLine(
    ParentTable: {0, -10}", parentTable); Console::Write(" Columns: "); // Столбцы forfint j =0; j < dr->ParentColumns.Length; j++) Console::Write( // Запись
    {0, -10}",
    dr->ParentColumns[j]->ColumnName); Console::WriteLine(); Console::WriteLine(
    ChildTable: (0, -10}", childTable);
    Console::Write(" Columns: "); // Запись forfint j = 0; j < dr->ChildColumns.Length; j++) Console::Write( // Запись
    {0, -10}",
    dr->ChildColumns[j]->ColumnName); Console::WriteLine(); }

    Программа напечатает:

    Output Relations between tables in the DataSet... Relation: Category->BookCategones Relation ParentTable: Categories
    Columns: Category ChildTable: BookCategories
    Columns: CategoryName Relation: Book Category LOC->Book LOG Relation ParentTable: BookCategories
    Columns: LibraryofCongressNumber
    ChildTable: Books
    Columns: LibraryofCongressNumber

    А вот и перевод:

    Отношения между таблицами в Наборе данных...
    Отношение: Категория-> Отношение Категории книг ParentTable: Категории
    Столбцы: Категория ChildTable: Категории книг
    Столбцы: CategoryName Отношение: Отношение Категория книги LOC->Book LOC ParentTable: Категории книг
    Столбцы: Номер в Библиотеке Конгресса ChildTable: Книги
    Столбцы: Номер в Библиотеке Конгресса

    CompEbook.ru Железо, дизайн, обучение и другие

    Пример приложения Acme Travel Agency (Туристическое агентство Acme)

    К этому моменту мы изложили более чем достаточно материала, необходимого для понимания классов Customer (Клиент) и HotelBroker (Посредник, бронирующий места в гостинице) из версии приложения Acme Travel Agency (Туристическое агентство Acme), ориентированной на работу с базами данных. Как обычно, файлы с исходным кодом для этой версии находятся в папке CaseStudy. Если вы использовали программы, изменяющие содержимое базы данных HotelBroker (Посредник, бронирующий места в гостинице), не забудьте запустить макрос SQL, приводящий эту базу в исходное состояние.
    В связи с тем, что у нас не было необходимости хранить какое-либо состояние объекта Customer (Клиент), в нем для доступа к базе данных и получения данных используется объект SqlDataReader Любое состояние, которое может понадобиться программе (например, список клиентов), легко может быть получено у программы-клиента, а не у объекта среднего яруса. Объекты HotelBroker (Посредник, бронирующий места в гостинице) и HotelBookings немного более сложны. Как уже было сказано, из педагогических побуждении эти объекты были реализованы с использованием объекта Data-Set (Набор данных). Так сделано для того, чтобы продемонстрировать использование этой технологии в приложениях. Тем не менее, мы увидим, что при разработке Web-ориентированых приложении есть причины сохранять некоторые состояния в среднем ярусе. В этом случае объект DataSet (Набор данных) служит интеллектуальным кэшем.
    А теперь отвлечемся от примера и рассмотрим интеграцию ХМL с базой данных.
    CompEbook.ru Железо, дизайн, обучение и другие

    Программирование в ADO.NET

    Классы каркаса, предназначенного для работы с базами данных, собраны в ADO.NET. Класс DataSet (Набор данных) позволяет работать с реляционными данными реляционным же способом, независимо от того, есть ли в текущий момент соединение с источником данных. Разъединенный (disconnected) доступ к данным становится все более значимым в многоярусном и Internet-ориентированном мире данных. При использовании такого типа доступа к данным необходимо установить соединение с базой данных только для изменения или получения ее содержимого. Конечно, при желании, можно работать и обычным соединенным (connected) способом.
    Источники данных ADO.NET позволяют задавать команды непосредственно источнику данных. При этом не используются промежуточные объекты, такие, как объекты OLEDB (OLE для баз данных), которые находятся между ADO и источником данных. Класс DataAdapter эмулирует источник данных (как набор команд базы данных) и соединение с этим источником данных. Класс DataAdapter реализует интерфейс IDa-taAdapter, являющийся связующим звеном между объектом DataSet (Набор данных) и источником данных. Различия между источниками данных скрыты интерфейсом I DataAdapter. Источники данных OLEDB (OLE для баз данных) позволяют использовать вложенные (nested) транзакции; а источники данных SqlServer этого не позволяют.
    Источники данных .NET передают данные в набор данных или в устройство считывания данных. Набор данных— резидентная упрощенная реляционная база данных, не соединенная прямо ни с какой другой базой данных. Набор данных можно даже преобразовать в документ XML, и наоборот. Это позволяет работать с данными как с реляционными или как с иерархическими XML-данными. Устройства считывания данных моделируют обычный способ работы с базами данных.
    Классы доступа к данным, поставляемые вместе с каркасом, находятся в пространствах имен System: :Data (Система: Данные), System: : Data: :SqlClient, System:: Data::01eDb (Система::Данные::ОЬЕ для баз данных), System: : Data: :Common (Система::Данные::Обшие) и System: : Data: :SqlTypes. Пространства имен OleDb (OLE для баз данных) и Sql содержат классы, используемые при работе с источниками данных OleDb (OLE для баз данных) и SqlServer соответственно. Уже разработан источник данных ODBC, а другие драйверы доступа будут созданы в ближайшем будущем.
    В этой главе мы изменим реализацию классов Customer (Клиент) и Hotel (Гостиница) для того, чтобы ближе познакомиться с использованием SQL Server. Для демонстрации использования XML в наш пример туристического агентства Acme Travel Agency добавим возможность бронирования авиабилетов.
    В своих примерах мы будем использовать SQL Server 2000 и источник данных SQL Server. Несмотря на это, большинство материала, изложенного в главе, можно отнести и к источнику данных OleDb (OLE для баз данных).
    Кроме того, для понимания примеров читателю необходимо понимать принципы работы баз данных

    Базы данных, используемые в примерах

    В главе предполагается, что SQL Server установлен в конфигурации Local System account, причем в качестве режима аутентификации выбран Mixed Mode (Смешанный режим). Предполагается, что имя пользователя — sa, а поле пароля не заполнялось. В некоторых примерах используется база данных Northwind Trader, инсталлируемая в качестве образца базы данных в составе SQL Server. Кроме того, в некоторых примерах используются базы данных HoteLBroker (Посредник, бронирующий места в гостинице) и AirlineBroker, созданные исключительно как иллюстративный материал к данной книге. Некоторые из иллюстративных программ изменяют используемые базы данных, в то время как в других предполагается, что эти базы имеют первоначальный вид. В результате какие-то программы не будут работать подобающим образом, пока вы не восстановите исходный вид используемых в них баз данных. Это можно сделать с помощью прилагаемых к программам макросов SQL. Более подробную информацию можно найти в файле readme.txt.

    CompEbook.ru Железо, дизайн, обучение и другие

    Проводник Visual Studio.NET по серверу: Server Explorer

    Проводник Visual Studio.NET no серверу, Server Explorer— полезная утилита при работе с базами данных. Хотя и не такая мощная, как SQL Server Enterprise Manager, она обеспечивает базовые возможности, необходимые при создании и отладке приложений, работающих с базами данных.
    Для того чтобы запустить Server Explorer, выберите пункт меню View=>Server Explorer. Окно Server Explorer можно прикрепить и при необходимости перемещать. На рис. 9.1 представлено окно Server Explorer.
    С помощью Server Explorer можно легко получить информацию о любом поле таблицы, просмотреть или изменить данные в ней. Можно также создавать или изменять хранимые процедуры и разрабатывать таблицы. Далее мы рассмотрим Server Explorer в нескольких примерах для того, чтобы ближе познакомить читателя с его использованием.
    CompEbook.ru Железо, дизайн, обучение и другие

    Работа с базой данных в соединенном режиме

    Использовавшийся в предыдущем примере режим называют соединенным. Программа соединяется с базой данных, выполняет все необходимые действия, а затем отсоединяется. При этом перемещаться по данным базы можно только в одном направлении. Это соответствует однонаправленному курсору/набору записей в классической технологии доступа к данным ADO. При использовании соединенного режима следует открывать и закрывать соединение явно.
    Держать соединение постоянно открытым — не лучший способ работы, если вы хотите минимизировать потребление ресурсов (соединение само по себе недешево) для обеспечения масштабируемости. Тем не менее, как мы увидим позже, именно использование SqlDa-taReader может, в зависимости от ваших потребностей, оказаться правильным подходом.
    Далее будет показано, что SqlConnection используется вместе с DataSet (Набор данных) и SqlDataReader для установления соединения с базой данных так же, как это сделано выше с помощью SqlCommand. Объект SqlConnection, кроме того, управляет свойствами базы данных, такими, как транзакции и уровни изоляции. Основная (root) транзакция начинается вызовом метода BeginTransaction класса SqlConnection". Аналогичная строка соединения с SQL Server с использованием объекта класса OleDbConnection будет такой:
    "Provider=SQLOLEDB.1;server=localhost;uid=sa;pwd=;
    database=Northwind";
    В приведенной строке следует изменить на корректные имя сервера, идентификатор и пароль пользователя.
    Как уже было сказано, SqlCommand применяется для выполнения команд при использовании и DataSet (Набор данных) и SqlDataReader, только действует немного по-разному. Это станет более понятным после рассмотрения класса SqlDataAdapter.
    Свойство CommandType определяет тип команды, хранимой в SqlCommand. Для источника данных Sql это может быть Text (Текст) (принятое по умолчанию значение) или StoredProcedure (Хранимая процедура). CommandText также можно определить как свойство. Вскоре мы научимся использовать параметры при работе с командами, которые передаются базе данных.
    Экземпляр класса SqlDataReader возвращается посредством метода Ехе-cuteReader экземпляра класса SqlCommand. Если программа должна быть независима от используемого источника данных, вместо указанного метода следует использовать интерфейс IDataReader. При этом можно вызывать методы интерфейса, а не самого экземпляра класса.
    IDataReader *idr = command->ExecuteReader() ;
    Этот же прием можно использовать и для других классов источника данных, где реализованы интерфейсы, которые поддерживаются несколькими источниками данных. Пока экземпляр класса SqlDataReader не будет закрыт, никакие действия над объектом SqlCommand, кроме его закрытия, недоступны.
    CompEbook.ru Железо, дизайн, обучение и другие

    NET содержит классы, позвопяющие создавать

    ADO. NET содержит классы, позвопяющие создавать и использовать распределенные данные Вы можете работать с базами данных в соединенном или отсоединенном режимах, в зависимости от потребностей Объект Dt,L_Set (Набор данных) дает возможность работать с данными реляционным способом даже при отсутствии соединения с каким-либо источником данных Для модетирования реляционных данных можно использовать документы XML, содержащие информацию в нереляционном виде Типизированный DataSet (Набор данных) облегчает работу, обеспечивая определение схемы XML для данных
    CompEbook.ru Железо, дизайн, обучение и другие

    Схема и данные XML

    Язык XML не навязывает принцип организации данных или суть документа XML. Он лишь определяет правила сопоставления документов. С другой стороны, схема XML описывает метаданные, т.е. способ организации данных внутри документа XML. Схемы XML пишутся на XML.
    Например, сам по себе XML можно использовать для описания данных реляционной базы данных, а схема XML может использоваться для описания связей между данными, такими, как первичные или внешние ключи. Гораздо проще использовать схему XML и данные в одном документе или текстовом потоке, чем загружать каждую таблицу в набор данных, а затем программно устанавливать связи между таблицами.
    CompEbook.ru Железо, дизайн, обучение и другие

    Создание документа XML из объекта DataSet (Набор данных)

    Используя объект DataSet (Набор данных) можно создать новый документ XML. Используя запрос XPath, можно перейти в начало документа, а затем, с помощью объекта XmlNodeReader прочитать весь документ. Мы выведем содержимое документа на экран. Класс XmlNodeReader обеспечивает перемещение по документу. Приведем фрагмент кода из примера DataSetXML:
    XmlDataDocument *xmlDataDoc = new XmlDataDocument(d);
    XmlNodeReader *xmlNodeReader = 0;
    try
    {
    XmlNode *node = xmlDataDoc->SelectSingleNode("/");
    XmlNodeReader = new XmlNodeReader (node);
    FormatXml (XmlNodeReader); }
    catch (Exception *e) // Исключение {
    Console::WriteLine (
    "Exception: {0}", e->ToString()); // Исключение
    }
    finally // наконец
    r
    if (XmlNodeReader != 0) // если (XmlNodeReader! = 0)
    xmlNodeReader->Close(); }
    static void FormatXml (XmlReader *reader) {
    while (reader->Read()) // читатель-> Чтение () {
    switch (reader->NodeType) // переключатель
    //(читатель-> NodeType) {
    case XmlNodeType::Element: // случай
    // XmlNodeType::Элемент Format (reader, "Element"); // Формат (читатель, "Элемент"); while(reader->MoveToNextAttribute() ) Format (reader, "Attribute"); // Формат (читатель, "Атрибут"); break;
    case XmlNodeType::Text: // случай XmlNodeType:: Текст Format (reader, "Text"); // Формат (читатель, "Текст"); break;
    static String *lastNodeType = ""; // статическая Строка
    static void Format(XmlReader *reader, String *nodeType) // Формат
    {
    if (nodeType->Equals("Element"))
    // если (nodeType-> Равняется ("Элемент"))
    {
    if (lastNodeType->Equals("Element"))
    // если (lastNodeType-> Равняется ("Элемент"))
    {
    Console::WriteLine();
    }
    for (int i=0; i < reader->Depth; i++)
    {
    Console::Write(" "); // Запись
    }
    Console::Write(reader->Name) ;
    // Запись:: (читатель-> Название); }
    else if (nodeType->Equals("Text")) // если (nodeType-> Равняется ("Текст")) Console::WriteLine("={0}", reader->Value); // Значение else
    {
    Console::Write(String::Format( // Запись:: (Строка:: Формат ( "{0}<{1}>{2}", nodeType, reader->Name, // Название reader->Value)); // читатель-> Значение Console::WriteLine (); }
    lastNodeType = nodeType; }

    Вот какой документ XML будет записан объектом DataSet (Набор данных) в файл:

    AirlineBroker
    Airlines
    Name=America West
    Abbreviation=AW
    WebSite=www.americawest.com
    ReservationNumber=555-555-1212
    Airlines
    Name=Northwest
    Abbreviation=NW
    WebSite=www.northwest.com
    ReservationNumber=888-111-2222
    Airlines
    Name=Piedmont
    Abbreviation=P
    WebSite=www.piedmont.com
    ReservationNumber=888-222-333
    Airlines
    Name=Southwest
    Abbreviation's
    WebSite=www.southwest.com
    ReservationNumber=l-800-111-222
    Airlines
    Name=Unitea
    Abbreviation=UAL
    WebSite=www.ual.com
    ReservationNumber=800-123-4568
    Flights <'-- Рейсъ. -->
    Airline=DL
    FlightNumber=987
    StartCity=Atlanta
    EndCity=New Orleans
    Departure=2001-10-05T2G:15:СС.ООСГПСО-04:00
    Arnval=2001-10-05T22:30:ОС.ОЭООССО-04:СО
    PlaneType=737
    FirstCost=1300
    BusinessCost=0
    EconomyCost=450
    Flights
    Airline=UAL
    FlightNumber=54
    StartCity=Boston
    EndCity=Los Angeles
    Departure=2001-10-01T10:00:OO.OOCOOOO-r4:00
    Arriva1=2001-10-01T13:00:00.0000000-04:00
    PlaneType=767
    FirstCost=1500
    BusinessCost=1000
    EconomyCost=300
    PlaneType
    PlaneType=737
    FirstClass=10
    BusinessCldss=0
    EconomyСlass=200
    PlaneType
    PlaneType=767
    FirstClass=10
    BusinessClass=30
    EconomyCiass=300
    Customers !'-- 1лкеггы -->
    LastName=Adams
    FirstName=John
    EmailAddress=adans@presidents.erg
    Customerld

    CompEbook.ru Железо, дизайн, обучение и другие

    Создание таблицы без обращения к источнику данных

    DataSet (Набор данных) можно использовать как резидентную реляционную базу данных, не связанную ни с какой другой базой данных. Теперь на примере программы DataEditing, мы рассмотрим несколько возможностей объекта DataSet (Набор данных), связанных с добавлением данных и отношений непосредственно в набор данных, без обращения к какой бы то ни было внешней базе данных.
    Прежде всего создадим новый объект DataSet (Набор данных) и включим проверку ограничений. Затем добавим в DataSet (Набор данных) четыре объекта DataTable (Таблица данных): Books (Книги), Categories (Категории), Authors (Авторы) и BookCate-gories (Категории книг).
    DataSet *ds = new DataSet; // новый Набор данных ds->EnforceConstraints = true; // истина
    // Добавить (Add) таблицы (tables) к Набору данных (DataSet) DataTable *categories =
    ds->Tables->Add("Categories");
    // Таблицы-> Добавить ("Категории"); DataTable *bookcategories =
    ds->Tables->Add("BookCategories");
    // Таблицы-> Добавить ("BookCategories"); DataTable *authors = ds->Tables->Add("Authors");
    // Таблицы-> Добавить ("Авторы"); DataTable *books = ds->Tables->Add("Books"); // Таблицы-> Добавить ("Книги");
    Объект DataTable (Таблица данных) содержит коллекцию объектов DataColumn, каждый из которых представляет собой столбец таблицы. Теперь добавим столбцы в определения таблиц.
    // определить типы для определений столбцов
    Type *stringType = Туре::GetType("System.String");
    // Система.Строка
    Type *intType = Туре::GetType("System.Int32");
    // Определить столбцы для таблиц
    // Добавить столбец (column) в таблицу Category (Категория) DataColumn *categoryname =
    categories->Columns->Add( // категории-> Столбцы-> Добавить "Category",stringType); // Категория
    // Добавить (Add) столбцы (columns) для таблицы BookCategories DataColumn *cn = bookcategories->Columns->Add( // Столбцы-> Добавить
    "CategoryName", stringType);
    DataColumn *loc = bookcategories->Columns->Add( // Столбцы-> Добавить
    "LibraryofCongressNumber", stringType);
    // Добавить (Add) столбцы (columns) для таблицы Authors (Авторы) DataColumn *auid = authors->Columns->Add( // авторы-> Столбцы-> Добавить
    "AuthorId", intType); authors->Columns->Add( // авторы-> Столбцы-> Добавить
    "AuthorLastName", stringType); authors->Columns->Add( // авторы-> Столбцы-> Добавить
    "AuthorFirstName", stringType);
    // Добавить (Add) столбцы (columns) для таблицы Books (Книги) DataColumn *ISBN = books->Columns->Add( // книги-> Столбцы-> Добавить
    "ISBN", stringType);
    DataColumn *booksauid = books->Columns->Add( // книги-> Столбцы-> Добавить
    "AuthorId", intType);
    books->Columns->Add("Title", stringType); // книги-> Столбцы-> Добавить ("Название", stringType); DataColumn *bloc = books->Columns->Add( // книги-> Столбцы-> Добавить
    "LibraryofCongressNumber", stringType);
    CompEbook.ru Железо, дизайн, обучение и другие

    Транзакции и обновление базы данных

    Когда преобразователь данных обновляет содержимое базы данных, это не делается одной транзакцией. Если необходимо, чтобы несколько операций выполнялись за одну транзакцию, в программе следует предусмотреть управление транзакциями.
    Объект SqlConnection содержит метод BeginTransaction, возвращающий объект SqlTransaction. При вызове метода BeginTransaction следует определить уровень локализации (выполняемых операций). Когда вы точно знаете, что делаете, и понимаете внутреннюю суть вещей, вы можете повысить производительность и масштабируемость приложения установкой соответствующего уровня локализации (выполняемых операций). Если установить уровень локализации (выполняемых операций) некорректно или даже просто неподходящим образом, это может привести к некорректности или несогласованности полученных данных.
    Для выполнения или отмены транзакции в классе имеются методы Commit (Фиксировать) и Rollback (Откат). Вы открываете SqlConnection, вызываете метод BeginTransaction, используете SqlDataAdapter как обычно, а затем вызываете SqlTransaction::Commit (Фиксировать) или SqlTransaction::Rollback (Откат), в зависимости от необходимости. Затем закрываете соединение. Для установки точки сохранения (save point) транзакции используется метод Save (Сохранить).
    В целях минимизации используемых ресурсов, а, следовательно, для повышения масштабируемости вашего приложения, может оказаться желательным минимизировать промежуток времени между вызовами методов BeginTransaction и Commit (Фиксировать) или Rollback (Откат).
    Приведем фрагмент кода из примера Transaction. В нем используется база данных AirlineBroker, описанная в предыдущей главе. Для иллюстрации здесь используется объект SqlCommandBuilder, рассмотренный выше.
    conn = new SqlConnection(ConnString);
    conn->0pen(); // Открыть
    trans = conn->BeginTransaction();
    da = new SqlDataAdapter;
    ds = new DataSet; // новый Набор данных
    da->SelectCommand =
    new SqlCommand(and, conn, trans);
    SqlCommandBuilder *sb = new SqlCommandBuilder(da);
    da->Fill(ds, "Airlines"); // Авиалинии
    DataRow *newRow = ds->Tables->get_Item( "Airlines")->NewRow(); // Авиалинии
    newRow->set_Item("Name", S"Midway"); // Название, "На полпути" newRow->set_Item("Abbreviation", S"M"); // Сокращение newRow->set_Item("WebSite", S"www.midway.com"); // Web-узел newRow->set_Item("ReservationNumber", S"555-555-1212"); ds->Tables->get_Item("Airlines")->Rows->Add(newRow); // Авиалинии
    Console::WriteLine(
    sb->Get!nsertCommand()->CommandText); Console::WriteLine (
    sb->GetDeleteCommand()->CommandText); Console::WriteLine(
    sb->GetUpdateCommand()->CommandText); pEnum =
    sb->GetInsertCommand()->
    Parameters->GetEnumerator(); // Параметры while (pEnum->MoveNext()) {
    SqlParameter *p =
    dynamic_cast(pEnum->Current); Console::WriteLine (
    "{0, -10} {I, -10}", p->ParameterName, p->SourceColumn); }
    da->Update(ds, "Airlines"); // Авиалинии trans->Commit(); trans = 0; conn->Close () ;
    Для полной уверенности в корректности работы источника данных SQL Server следует использовать методы Commit (Фиксировать) и Rollback (Откат) объекта SqlTrans-action для подтверждения или отмены транзакции, выполнение которой начато вызовом метода SqlConnection: : BeginTransaction. При этом не стоит использовать операторы транзакций SQL Server.
    Если вы в своей работе с базой данных используете хранимые процедуры, вы можете, конечно, использовать операторы транзакций SQL Server внутри хранимых процедур вместо объекта SqlTransaction. Хранимые процедуры могут инкапсулировать изменения, произведенные в результате транзакций. Это делает, в частности, хранимая процедура MakeReservation базы данных HotelBroker (Посредник, бронирующий места в гостинице).
    CompEbook.ru Железо, дизайн, обучение и другие

    Установление соединения

    Начнем с небольшой программы JustConnect, единственная задача которой — просто устанавливать соединение с базой данных. Пример поможет также проверить, корректно ли установлен SQL Server и существует ли запрашиваемая база данных (в нашем случае — Northwind, входящая в состав SQL Server как ее стандартная часть)
    SqlConnection *conn = 0;
    String *ConnString =
    "server=localhost;
    uid=sa;
    pwd=;
    database=Northwind";
    try
    {
    conn = new SqlConnection(ConnString);
    conn->0pen(); // Открыть
    Console::WriteLine(
    "Connection to {0} opened successfully.", // "Соединение с {0} открыто успешно. ",
    conn->Database); // База данных
    }
    catch(Exception *e) // Исключение
    {
    Console::WriteLine(e->Message); // Сообщение
    }
    _finally // наконец
    {
    if (conn->State == ConnectionState::Open) // если открыто
    conn->Close();
    }
    Установление соединения

    Рис. 9.1. Окно среды разработки Visual Studio NET Server Explorer
    Если СУБД SQL Server установлена и работает корректно, причем база данных Northwmd существует, результатом работы программы JustConnect будет следующее сообщение:
    Connection to Northwmd opened successfully.
    (Соединение с Northwmd открылось успешно.)
    Если же что-то происходит не так, как должно, при выполнении метода Open (Открыть) возникает исключение и пользователь увидит сообщение, определенное в обработчике исключений. Например, если закрыть SQL Server, программа выведет следующее сообщение:
    General network error. Check your network documentation.
    (Общая сетевая ошибка. Сверьтесь с вашей сетевой документацией.)
    Если изменить имя базы данных, заданное в строке соединения, на имя несуществующей базы, например, Southwind, будет выведено следующее сообщение:
    Cannot open database requested in login 'Southwind'. Login fails.

    Login failed for user 'sa'.
    (He могу открыть базу данных, требуемую в регистрационном имени
    'Southwind'. Вход в систему невозможен.
    Вход в систему был безуспешным для пользователя 'за'.)
    CompEbook.ru Железо, дизайн, обучение и другие

    Устройства считывания данных

    Следующим примером станет использование классов ADO.NET для получения доступа к данным, хранящимся в базе данных. Соответствующие файлы находятся в подпапке Connected.
    Нам необходимы объекты для соединения, хранения команд, передаваемых базе данных, и хранения самих данных, поэтому мы определяем три указателя на объекты классов SqlConnection, SqlCommand и SqlDataReader:
    SqlConnectlon *conn = 0;
    SqlCommand * command = 0;
    SqlDataReader *reader = 0;
    Далее инициализируется строка соединения с базой данных Вы можете изменить значение поля, предназначенного для хранения имени сервера, на имя своего компьютера Необходимо также определить имя пользователя и пароль для получения доступа к базе данных Строку соединения можно устанавливать и как свойство объекта SqlConnection В качестве команды, которая будет передаваться базе данных в нашем примере, выбран простой оператор отбора данных:
    String *ConnString =
    "server=localhost;
    uid=sa;
    pwd=;
    database=Northwind";
    String *cmd =
    "select Customerld,
    CompanyName from Customers";
    На рис. 9.2 приведены списки таблиц и хранимых процедур базы данных Northwmd В теле блока try создается объект класса SqlConnection. Затем открывается соединение с базой данных, ведь это должно быть сделано до передачи базе данных какой-либо команды После этого создается объект класса SqlCommand, связанный с созданным ранее соединением.
    conn = new SqlConnection(ConnString);
    conn->0pen(); // Открыть
    command = new SqlCommand(cmd, conn);
    Устройства считывания данных

    Рис. 9.2. Таблицы и хранимые процедуры, входящие в состав базы данных Northwind, отображаются в окне Server Explorer
    Если команда выполняется посредством использования метода ExecuteReader объекта SqlCommand, то при этом возвращается экземпляр класса SqlDataReader Этот объект можно использовать для перемещения по полученному набору данных Для извлечения данных из текущей строки набора можно использовать имя столбца
    reader = command->ExecuteReader();
    // читатель = команда-> ExecuteReader ();
    if (reader != 0)
    {
    Console::WriteLine(
    "CustomerldXtCompanyName");
    while (reader->Read()) // Чтение
    Console::WriteLine (
    "{0}\t\t{l}",
    reader->get_Item("Customerld"),
    reader->get_Item("CompanyName"));
    }

    И в заключение, в блоке finally закрываются считывающее устройство и соединение.

    if (reader != 0)
    reader->Close() ; if (conn->State == ConnectionState::0pen) // если открыто
    conn->Close();

    Если соединение не закрыть явно, завершитель объекта SqlConnection, рано или поздно запущенный, закроет соединение. Но из-за того, что сборщик мусора не является детерминированным, никто не сможет сказать, когда это произойдет. Поэтому всегда закрывайте соединение явно. Если этого не сделать, будет использоваться больше соединений, чем необходимо (даже если вы организуете связной пул), что может снизить масштабируемость приложения. Кроме того, может исчерпаться запас соединений.
    Приведем результат работы программы:

    Customerld CompanyName
    ALFKI Alfreds Futterkiste
    ANATR Ana Trujillo Emparedados у helados
    ANTON Antonio Moreno Taqueria
    AROUT Around the Horn
    BERGS Berglunds snabbkop
    BLAUS Blauer See Delikatessen
    BLONP Blondesddsl pere et fils
    BOLID Bolido Comidas preparadas
    BONAP Bon app'
    BOTTM Bottom-Dollar Markets
    BSBEV B's Beverages
    . . .

    Для проверки корректности работы программы можно использовать Server Explorer среды разработки Visual Studio.NET. Выберите в базе данных Northwind таблицу Customers (Клиенты) и щелкните на ней правой кнопкой для того, чтобы вызвать всплывающее меню. Выберите в нем пункт Retrieve Data from Table (Получить данные из таблицы) и, просмотрев данные, хранящиеся в таблице, сравните их с результатом работы программы. Наверняка вы заметите поразительное сходство (рис. 9.3).

    Устройства считывания данных

    Рис. 9.3. Просмотр содержимого таблицы Customers (Клиенты) и ее полей с помощью Server Explorer

    CompEbook.ru Железо, дизайн, обучение и другие

    Выполнение операторов SQL

    Метод ExecuteReader класса SqlCommand возвращает экземпляр класса Da-taReader. Данные возвращаются, если в качестве команды задан запрос на выборку. Этот же метод можно использовать для обновления, вставки или удаления данных. Метод SQLCommand: : ExecuteReader использует хранимую процедуру sp_executesql. Некоторые команды, использующие операторы SET (оператор Установить), могут работать неправильно. Другие драйверы могут иметь иные ограничения на использование метода ExecuteReader.
    Обычно для команд, при выполнении которых данные не возвращаются, используется метод SqlCommand::ExecuteNonQuery. Пример NonQuery демонстрирует работу этого метода. Кроме того, соединение с SQL Server осуществляется в нем с помощью источника данных OleDb (OLE для баз данных).
    String *cmd = "update Customers set ContactName =
    // Строка *cmd = "обновить Клиентов, установить ContactName =
    Too' where ContactName = 'Maria Anders'";
    // Too', где ContactName = 'Мария Андерс";
    try {
    conn = new OleDbConnection(ConnString);
    conn->0pen(); // Открыть
    command = new OleDbCommand(cmd, conn);
    int NumberRows = command->ExecuteNonQuery(); // команда
    Console::WriteLine(
    "Number Rows: {0}", NumberRows.ToString());
    }
    Количество измененных строк, которое должно быть равным 1, показано в окне, предназначенном для консольного вывода. Если запустить программу еще раз, она не сможет найти необходимую ей запись, так как та была изменена при первом запуске программы (нет больше в базе данных Марии Андерс (Maria Anders)!), и выведет значение 0. Для приведения базы данных в исходное состояние необходимо запустить макрос SQL, как это описано в файле readme.txt для этой главы. На рис. 9.4 показан результат изменения первой строки. Значение поля ContactName изменено с Maria Anders на Foo.
    При выполнении вставки, обновления и удаления данных возвращается количество строк, которых коснулись изменения. Для всех остальных операторов SQL Server возвращает значение -1 (при использовании родного источника данных или OLEDB (OLE для баз данных)). Другие драйверы доступа могут возвращать 0 или -1.
    Для получения одного значения (например, результата вычислений) используйте метод ExecuteScalar. При работе с источниками данных, способными генерировать данные XML, более эффективным будет использовать метод SqlCommand: : ExecuteXmlReader, a не получать данные в объект DataSet (Набор данных), а затем преобразовывать их в ХМL.
    Выполнение операторов SQL

    Рис. 9.4. Таблица Customers (Клиенты) после внесения в нее изменений Сравните первую строку таблицы с ее первоначальным видом на рис. 9.3.
    CompEbook.ru Железо, дизайн, обучение и другие

    XmlDataDocument

    Документы могут содержать в себе результат вычислений, полученный от базы данных. Например, отчет о продажах содержит, кроме данных о продажах, полученных от источника данных, и некоторые пояснения. Для представления данных в виде документа ХМL используется класс XmlDataDocument.
    Класс XmlDataDocument является производным от класса XmlDocument, который представляет документы XML в библиотеке классов .Net Xml Framework. Особенно удобным делает класс XmlDataDocument то, что экземпляр этого класса можно получить из объекта DataSet (Набор данных) посредством простой передачи объекта Data-Set (Набор данных) конструктору класса XmlDataDocument в качестве аргумента. XmlDataDocument имеет свойство DataSet (Набор данных), так что вы можете работать с документом XML как с реляционными данными, если это имеет смысл.
    CompEbook.ru Железо, дизайн, обучение и другие

    Architecture Net

    Архитектура Web-форм

    Web-форма состоит из двух частей:
  • отображаемого содержимого, или презентации формы. Как правило, отображаемое содержимое описывается на языке HTML;

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

  • Физически Web-форма представлена файлом с расширением . aspx. Расширение любой HTML-страницы можно изменить на .aspx. При этом представление страницы с новым расширением будет тождественно представлению исходной страницы. Иными словами, Web-формы совместимы снизу вверх с обычными HTML-страницами.
    Особенность Web-форм состоит в способе, при помощи которого программный код отделяется от самой формы. Исходный код (написанный не на C++) может храниться в отдельном не скомпилированном файле. Или же программный код (снова таки, не на C++) может быть вложен в . aspx-файл. И, наконец, код (написанный на С#, VB или C++) может храниться в предварительно скомпилированной сборке, содержащей динамически подключаемую библиотеку (DLL). Когда страницы загружаются Web-сервером, выполняется код, описывающий пользовательский интерфейс. Этот код динамически формирует отображаемую клиентом страницу.
    Чтобы четче представить архитектуру Web-форм, написанных на C++, мы рассмотрим пример HelloCodebehind. Эта программа отображает информацию, введенную пользователем. Чтобы запустить приложение, введите в адресной строке броузера унифицированный указатель информационного ресурса (URL) http: //localhost/NetCpp/HeiloCodebehind. aspx. Код, написанный на C++, содержится в файле HelloCodebehind.aspx.h. Обратите внимание, что данный проект создает динамически подключаемую библиотеку (DLL) — файл HelloCcdebehind.dl]. Эта динамически подключаемая библиотека (DLL) затем копируется в подкаталог bin виртуального каталога. (Виртуальный каталог мы создали раньше для хранения примеров программ, которые рассматриваются в главе 10 "ASP.NET и Web-формы". Так было сделано потому, что информационный сервер Internet (US) ищет загружаемые динамически подключаемые библиотеки (DLL) в этом подкаталоге.) Код, который описывает видимые эпементы страницы (презентацию страницы), приведен ниже. Он содержится в файле HelloCodebehind.aspx:


    <%@ Assembly Name=" HelloCodebehind" °>

    <%@ Page Inherits= MyWebPage ">




    YOUR NAME: 

    asp:button id=cmdEcho onclick=cmdEcho__Click Text="Echo"
    runat="server" tooltip="Click to echo your name">









    Код, который создает интерфейс пользователя, содержится в файле HelloCodebehind. h:
    //HelloCodebehind.h
    fusing
    fusing
    using namespace System;
    // использование пространства имен Система;
    using namespace System::Web;
    // использование пространства имен Система::Сеть;
    using namespace System::Web::UI;
    // использование пространства имен
    // Система::Сеть:пользовательский интерфейс;
    using namespace System::Web::UI::WebControls;
    // использование пространства имен
    // Система::Сеть::Пользовательский интерфейс::WebControls;
    public _gc class MyWebPage : public System::Web::UI::Page
    // класс сборщика мусора MyWebPage : общедоступная Система::
    // Сеть:: Пользовательский интерфейс:: Страница
    {
    protected: // защищенный
    TextBox *txtName;
    Button *cmdEcho; // Кнопка
    Label *lblGreeting; // Метка
    public:
    void cmdEcho_Click(Object *Source, EventArgs *e)
    {
    IblGreeting-XText = // Текст
    String::Format( // Строка:: Формат "Hello, (0).
    Welcome to Managed C++ ASP.", // "Привет, {О}. Добро пожаловать
    // в Управляемый C++ ASP. ",
    txtName->Text); // Текст
    }
    };

    Этот код нужно скомпилировать отдельно и развернуть полученную сборку HelloCodebehind. dll в каталоге \OI\NetCpp\ChaplO\bin. Информационный сервер Internet (US) автоматически ищет загружаемые файлы в подкаталоге bin виртуального каталога.

    CompEbook.ru Железо, дизайн, обучение и другие

    ASP.NET и Web-формы

    Технология ASP.NET, предназначенная для создания Web-приложении, является важной частью платформы .NET. По сравнению с очередной усовершенствованной версией ASP (Active Server Pages — Активные страницы сервера), эта новая технология представляет собой более унифицированную платформу, которая значительно упрощает реализацию сложных Web-приложений В данной главе мы ознакомимся с основами технологии ASP.NET, а также рассмотрим Web-формы, облегчающие создание интерактивных Web-страниц. В главе 11 "Web-службы" мы изучим построение Web-служб на основе технологии ASP.NET. Web-службы позволяют создавать Web-приложения, которые могут совместно обрабатывать данные, будучи развернутыми в неоднородных (гетерогенных) системах В главе 12 "Web-узлы и Web-службы, работающие на основе ATL Server" иллюстрируется создание Web-страниц и Web-служб на основе сервера ATL Server.
    CompEbook.ru Железо, дизайн, обучение и другие

    Что такое ASP.NET?

    Изучение технологии ASP.NET мы начнем с рассмотрения очень простого Web-приложения. На примере этого приложения мы изучим систему отладки, используемую при программировании на основе технологии ASP.NET. Кроме того, коротко будут рассмотрены основы обработки данных в Web. Изучая этот маленький пример, мы столкнемся с некоторыми сложными задачами, которые решаются в процессе разработки Web-приложений. Благодаря этому мы сможем по достоинству оценить возможности и преимущества технологии ASP.NET; о них и будет речь в оставшейся части данной главы
    CompEbook.ru Железо, дизайн, обучение и другие

    Дополнительная информация об ASP.NET

    Конечно, используя ASP.NET, вы можете реализовать Web-приложение и на языке C++. Но, как мы уже убедились, язык C++ далеко не оптимальный язык для написания кода с целью создания тех элементов Web-приложения, которые используются при визуальном взаимодействии. Для этого гораздо более подходят языки С# и VB.NET. Чтобы узнать больше об ASP.NET, вы можете обратиться к следующим книгам из серии The Integrated .NET Series, выпущенной издательствами Object Innovations и Prentice Hall PTR:
  • Application Development Using C# and .NET. (Разработка приложений с помощью С# и.NET.)

  • Application Development Using Visual Basic and .NET. (Разработка приложений с помощью Visual Basic и .NET.)

  • Fundamentals of Web Applications Using .NET and XML. (Основные принципы создания Web-приложений с помощью .NET и XML.)

  • Язык C++ не имеет себе равных при создании вызываемых с Web-страницы эффективных компонентов для вычислительных машин баз данных. Высокопроизводительные Web-приложения на C++ можно создать также и при помощи технологии, которая имеет название ATL Server. Введение в технологию ATL Server содержится в главе 12 "Web-узлы и Web-службы, работающие на основе ATL Server".
    CompEbook.ru Железо, дизайн, обучение и другие

    Файлы конфигурации

    Сведения о конфигурации сервера и приложения хранятся в файлах в формате XML. Содержимое файлов можно легко прочесть, и, в случае необходимости, изменить.
    Файл конфигурации сервера
    Файл конфигурации сервера имеет название machine.config. Этот файл находится в каталоге \WINNT\Microsoft..NET\Framework. Каждая версия платформы .NET имеет свой каталог, в котором хранится файл конфигурации сервера. За счет этого можно одновременно использовать различные версии ASP.NET. Иными словами, вы можете продолжать использовать Web-приложения, которые работают с предыдущей версией платформы .NET, и одновременно разрабатывать приложения, использующие более новую версию.
    Файлы конфигурации приложения
    Чтобы сохранить значения параметров, которые используются конкретным Web-приложением, в корне виртуального каталога нужно создать файл web. conf ig. Если этот файл отсутствует, будут использоваться значения параметров конфигурации приложения, принятые по умолчанию. Они хранятся в файле machine. conf ig. Если же файл web. conf ig существует, то будут использованы значения параметров, которые содержатся в нем.
    Формат файлов конфигурации
    Файл web. conf ig, также как и файл machine, conf ig, хранится в формате XML. Файл состоит из разделов. В каждом разделе объединены взаимосвязанные параметры. Чтобы ознакомиться со структурой файла конфигурации и параметрами, значение которых можно изменить, просмотрите файл web. conf ig, он был создан средой Visual Studio, когда мы создавали новый проект ASP.NET Web-приложения.




    Set mode="on" or "remoteonly" to enable custom
    error messages, "off" to disable. Add
    tags for each of the errors you want to
    handle.
    -->

    mode="0ff"
    />




    CompEbook.ru Железо, дизайн, обучение и другие

    obal.asax

    В состав ASP.NET-приложения может входить файл Global. asax. В нем содержится код, который обрабатывает события уровня приложения, инициируемые ASP.NET. Этот файл расположен в корневом каталоге приложения. Если файл Global.asax в приложении отсутствует, ASP.NET считает, что обработчики событий уровня приложения не определены.
    Global.asax

    Рис. 10.23. Разблокирование отладки
    Global.asax

    Рис. 10.24. В файле Global, asax.h достигнута точка останова
    Global.asax

    Рис. 10.25. В файле WebForml. aspx. h достигнута точка останова
    В данном коде приведены наиболее часто используемые события уровня приложения. Обычно за время существования Web-приложения происходят следующие события
  • Application_Start Это событие возникает лишь один раз за все время работы приложения, когда создается первый экземпляр класса HttpApplication Приложение запускается первый раз тогда, когда его запускает информационный сервер Internet (I1S) для первого пользователя В обработчике событий можно инициализировать состояние, которое будет использоваться всем приложением

  • Session_Start возникает в начале каждого сеанса На этом этапе можно инициализировать переменные сеанса

  • Application_BeginRequest инициируется в начале каждого отдельного запроса Как правило, обработка запроса производится классом Page (Страница)

  • Application_EndRequest инициируется в конце запроса

  • Session_End инициируется в конце каждого сеанса. Как правило, не нужно освобождать переменные, инициализированные в начале сеанса (при возникновении события Session_Start) Они будут освобождены автоматически в процессе сборки мусора Но если открыт дорогостоящий ресурс, например, соединение с базой данных, тогда при возникновении этого события можно вызвать метод Dispose (Освободить ранее выделенную область памяти)

  • Application_End инициируется в самом конце срока существования приложения, когда удаляется последний экземпляр HttpApplication

  • CompEbook.ru Железо, дизайн, обучение и другие

    Изучение конкретного примера

    Мы ознакомились с основными возможностями ASP.NET и создали несколько простых Web-страниц, используя шаблоны библиотеки классов управляемого C++. Дальнейшее изучение материала главы 10 "ASP.NET и Web-формы" мы продолжим на конкретном примере. Для языков С# и VB.NET существует специальный шаблон проекта, имеющий название ASP.NET Web Application (Web-приложение на основе ASP.NET), который формирует каркас приложения. Кроме того, конструктор форм (Forms Designer) позволяет легко создавать Web-формы путем перетаскивания необходимых элементов управления с панели инструментов. Конструктор форм (Forms Designer) поддерживает языки С# и VB.NET. Если приложение создается на C++, код соответствующей формы, к сожалению, придется создавать самостоятельно.
    CompEbook.ru Железо, дизайн, обучение и другие

    Класс HttpRequest

    Пространство имен System: :Web (Система Сеть) содержит потезнын класс HttpRequest. Данный класс используется для считывания различных значении, которые клиент отсылает в запросе по протоколу передачи гипертекстовых фантов HTTP Эти значения, принимаемые по протоколу передачи гипертекстовых фантов HTTP, затем будут использованы классическими (те использующими общий шлюзовой ишер-фейс CGI (Common Gateway Interface)) программами или программами, использующими интерфейс прикладного программирования Internet-сервера ISAPI (Internet Server API), которые обрабатывают Web-запрос. На этой основе строится обработка данных на более высоком уровне. В табл. 10.1 приведены некоторые наиболее часто используемые свои-ства класса HttpRequest. Если вы знакомы с протоколом передачи гипертекстовые файлов HTTP, смысл этих свойств должен быть вам понятен Исчерпывающая инфор мация об этих и других свойствах класса HttpRequest содержится в документации по .NET Framework.
    Свойство Request (Запрос) класса Page (Страница) возвращае! объект 'tt; quest. Из свойств объекта HttpRequest можно извлечь любую необходимую информацию. Например, следующий код (этот код не содержится ни в одном примере) определяет длину (в байтах) содержимого, отосланного клиентом, и записывает поточенную информацию в объект Response (Ответ). Затем эта информация отображается в окне-броузера.
    Таблица 10.1. Общедоступные свойства экземпляра класса HttpReguest
    Page *p = dynamic_cast(sender);
    // Страница *р = dynamic_cast <Страница *> (отправитель);
    HttpRequest *request = p->get_Request();
    int length = request->ContentLength; // длина
    HttpResponse *response = p->get_Response();
    response->Write(String::Format( // ответ-> Запись (Строка:: Формат
    "ContentLength = {0}
    ", _box(length))); // длина
    Коллекции
    Многие полезные коллекции представлены как свойства объекта HttpRequest. Эти коллекции принадлежат типу NamedValueCollection (в пространстве имен System: :Collections: :Specialized (Система::Коллекции::Специализированное пространство имен)). Получить значение переменной из коллекции можно при помощи строкового ключа. Например, следующий код извлекает из коллекции ServerVan-ables значение переменных сервера QUERY_STRING и HTTP_USER_AGENT.

    Page *p = dynamic_cast(sender);
    // Страница *р = dynamic_cast <Страница *> (отправитель);
    HttpRequest *request = p->get_Request();
    HttpResponse *response = p->get_Response();
    String *strQuery = // Строка
    request->ServerVariables->get_Item( // запрос
    "QUERY_STRING");
    response->Write(String::Format( // ответ-> Запись (Строка:: Формат (
    "QUERY_STRING = {0}
    ",strQuery));

    String *strAgent = // Строка
    request->ServerVariables->get_Item( // запрос
    "HTTP_USER_AGENT");
    response->Write(String::Format( // ответ-> Запись (
    Строка:: Формат ( "HTTP_USER_AGENT = {0}
    ",
    strAgent));

    Если вслед за унифицированным указателем информационного ресурса (URL) соответствующего .aspx-файла ввести строку запроса ?foo=3, приведенный выше код отобразит в окне броузера что-то примерно следующее:

    QUERY_STRING = foo=3
    HTTP_USER_AGENT = Mozilla/4.0

    Подобные переменные при программировании классического (т.е основанного на общем шлюзовом интерфейсе CGI (Common Gateway Interface)) Web-сервера играют решающую роль. Используя переменные среды, Web-сервер передает информацию CGI-скрипту или программе. На всякий случай ASP.NET обеспечивает доступ и к этой низкоуровневой информации.
    Стандартная задача состоит в том, чтобы извлечь информацию из элементов управления формы. В HTML-коде элементы управления идентифицируются атрибутом name (имя). Этот атрибут сервер использует для определения соответствующего значения. Способ передачи данных из формы серверу зависит от того, какой метод использует форма в протоколе передачи гипертекстовых файлов HTTP: GET (Получить) или POST (Отправить почтовое сообщение).
    Если используется метод GET (Получить), данные, введенные в форму, кодируются как часть строки запроса. Чтобы затем извлечь нужные значения, используется коллекция QueryString Если же используется метод POST (Отправить почтовое сообщение), данные, введенные в форму, передаются как содержимое после заголовка протокола передачи гипертекстовых файлов HTTP. В этом случае для извлечения значения элементов управления используется коллекция Forms (Формы). Чтобы узнать, какую коллекцию следует использовать, нужно определить значение (GET (Получить) или POST (Отправить почтовое сообщение)) переменной сервера REQUEST_METHOD. (Если переменная REQUEST_METHOD имеет значение GET (Получить), используется коллекция QueryString Если же ее значение равно POST (Отправить почтовое сообщение), используется коллекция Forms (Формы))
    Если вы используете ASP NET, тогда вам не стоит волноваться о том, какой метод протокола передачи гипертекстовых файлов HTTP использовался при запросе В ASP NET имеется коллекция Params, которая представляет собой объединение (в математическом смысле) коллекций ServerVariables, QueryString, Forms (Формы) и Cookies (Небольшие фрагменты данных о предыстории обращений конкретного пользователя к конкретному Web-серверу, автоматически создаваемые сервером на машине пользователя)


    Пример программы

    Идеи, о которых мы рассказали выше, проиллюстрируем на примере простой страницы Squares. aspx Эта страница отображает столбец с квадратами натуральных чисел Числа, квадраты которых будут выведены, ограничиваются значением, вводимым в форме Страница GetSquares aspx отсылает запрос, используя метод GET (Получить), а страница PostSquares.aspx отсылает запрос, используя метод POST (Отправить почтовое сообщение) Пользовательский интерфейс обоих приложений тождественен (рис. 10.11)

    Класс HttpRequest

    Рис. 10.11. Форма, запрашивающая столбец квадратов чисел

    Ниже приведен HTML-код страницы GetSquares.aspx Обратите внимание, что мы используем чистый HTML-код И, за исключением директивы Page (Страница), которая используется для включения режима трассировки, признаки использования ASP NET отсутствуют


    <%@ Page Trace = "false" %>



    This program will print a column of square numbers




    How many









    Тэг form (форма) имеет атрибуты, при помощи которых указывается метод (в данном случае GET (Получить), а не POST (Отправить почтовое сообщение)) и действие (запрос на страницу Squares aspx) Элементы управления имеют атрибут name (имя), с помощью которого сервер извлекает нужное значение
    Запустите страницу GetSquares aspx и щелкните на кнопке "Squares" ("Квадраты чисел") Будет отображена некоторая служебная информация, относящаяся к протоколу передачи гипертекстовых файлов HTTP За ней последует столбец с квадратами чисел Поскольку режим трассировки включен, то ASP NET отображает на странице также и подробности запроса На рис 1012 приведены результаты выполнения запроса, в котором используется метод GET (Получить)


    Класс HttpRequest

    Рис. 10.12. Вывод, полученный в результате запроса GET (Получить)

    Можно видеть, что данные, введенные в форму, закодированы как часть строки запроса Длина содержимого равна нулю, поскольку используется метод GET (Получить), а не POST (Отправить почтовое сообщение) Если прокрутить трассировочную информацию дальше, можно увидеть еще много интересных сведений Например, там приводится коллекция QueryString
    А теперь давайте рассмотрим страницу PostSquares aspx Обратите внимание, — в этот раз тэг form (форма) указывает, что используется метод POST (Отправить почтовое сообщение) Остальные параметры такие же, как и в случае страницы GetSquares . aspx


    <%@ Page Trace = "false" %>




    This program will print a column of squares


    How many:






    Запустите страницу PostSquares.aspx и щелкните на кнопке Squares (Квадраты чисел) Снова будет отображена некоторая информация, относящаяся к протоколу передачи гипертекстовых файлов HTTP. За ней последует столбец, содержащий квадраты чисел Поскольку режим трассировки включен, то ASP.NET отображает на странице также и подробности запроса. На рис. 10.13 приведены результаты выполнения запроса POST (Отправить почтовое сообщение).

    Класс HttpRequest

    Рис. 10.13. Вывод, полученный в результате запроса POST (Отправить почтовое сообщение)

    Поскольку используется метод POST (Отправить почтовое сообщение), то строка запроса пуста Длина содержимого равна 29. Данные, введенные в форму, передаются в текстовом виде вслед за заголовком протокола передачи гипертекстовых файлов HTTP.
    Если прокрутить трассировочную информацию дальше, то теперь можно увидеть коллекцию Form (Форма) Именно с помощью этой коллекции ASP.NET обеспечивает доступ к данным, введенным в форме в случае использования метода POST (Отправить почтовое сообщение)

    CompEbook.ru Железо, дизайн, обучение и другие

    Класс HttpResponse

    Класс HttpResponse инкапсулирует информацию ответа, полученного по протоколу передачи гипертекстовых файлов HTTP, притом информация содержится в операции ASP NET Каркас Framework использует данный класс при формировании ответа клиенту. Формирование ответа включает запись элементов управления сервера для отправки клиенту. Созданный вами код сервера может также использовать метод Write (Запись) объекта Response (Ответ). Использование метода Response: :Write (Ответ' Запись) иллюстрировалось уже несколько раз.
    Метод Redirect (Переадресовать)
    Класс HttpResponse имеет полезный метод Redirect (Переадресовать) Благодаря этому методу сервер может переадресовать запрос, передаваемый по протоколу передачи гипертекстовых файлов HTTP на другой унифицированный указатель информационного ресурса (URL). Простая переадресация, без передачи каких-либо данных, является тривиальной задачей. Все что для этого нужно сделать— это вызвать метод Redirect (Переадресовать) и передать ему требуемый унифицированный указатель информационного ресурса (URL) Примером ситуации, когда используется метод Redirect (Переадресовать), служит реорганизация Web-узла. В процессе реорганизации некоторые страницы могут содержать недостоверную информацию Кроме того, содержимое Web-узла может быть перемещено Чтобы сохранить доступ к старым страницам, достаточно просто переадресовать трафик
    Обратите внимание, что при переадресации запроса всегда используется метод GET (Получить) протокола передачи гипертекстовых файлов HTTP Это подобно установлению связи по указанному унифицированному указателю информационного ресурса (URL) (Метод POST (Отправить почтовое сообщение) может использоваться, если данные отсылаются из формы, — тогда можно указать одно из двух действий — GET (Получить) или POST (Отправить почтовое сообщение)) Более интересный случай представляет передача данных на новую страницу. Один из способов передачи данных состоит в том, что передаваемая информация кодируется в строке запроса. При кодировании строки запроса следует придерживаться общепринятых соглашений для протокола передачи гипертекстовых файлов HTTP Класс HttpUtility имеет метод UrlEncode, который корректно кодирует отдельные составляющие элементы строки запроса Вам нужно самостоятельно написать код, который отделяет унифицированный указатель информационного ресурса (URL) от строки запроса при помощи символа "знак вопроса" (9), а также разделяет отдельные составляющие элементы строки запроса при помощи символа "амперсанд" (&).
    В папке Hotel (Гостиница) имеется простое Web-приложение, где иллюстрируется использование этого метода передачи данных при переадресации запроса. Файл de fault. aspx содержит форму, при помощи которой собираются данные, необходимые для бронирования мест в гостинице Само бронирование выполняется на странице Reservationl. aspx. Чтобы получить доступ к начальной странице default. aspx, используйте унифицированный указатель информационного ресурса (URL)http://localhost/NetCpp/Hotel/.
    Как обычно, ссылка на эту страницу содержится на начальной странице с примерами программ На рис. 10.14 показана начальная страница нашего приложения, которое бронирует место в гостинице
    Ниже приведен ASP NET-код на C++, который выполняется после щелчка на кнопке Make Reservation (Забронировать)

    void cmdMakeReservation_Click(
    Object *sender, EventArgs *e)
    {
    HttpUtility *utility = new HttpUtility;
    String *query = String:.Concat ( // Строка
    "City=", utility->UrlEncode(txtCity->Text)); // Город
    query = String::Format(
    // запрос = Строка:: Формат ( "{0}&Hotel={l}",
    query, utility->UrlEncode(txtHotel->Text));
    query = String::Format(
    // запрос = Строка:: Формат ( "{0}&Date={l}",
    query, utility->UrlEncode(txtDate-XText));
    query = String::Format( // запрос = Строка:: Формат ( "{0}&NumberDays={1}",
    query, utility->UrlEncode(txtNumberDays->Text));
    Response->Redirect(String::Concat(
    // Ответ-> Переадресовать (Строка:: Concat (
    "Reservation!.aspx?", query)); // запрос
    }

    Класс HttpResponse

    Рис. 10.14. Начальная страница для бронирования места в гостинице

    Метод cmdMakeReservation_Click строит строку запроса. Эта строка присоединяется к унифицированному указателю информационного ресурса (URL) страницы Reservation! .aspx От унифицированного указателя информационного ресурса (URL) ее отделяет символ 9 В качестве разделителя элементов строки запроса используется символ & Для кодирования отдельных элементов используется метод HttpUtility: :UrlEncode Символы "слэш" (в дате) и пробелы, например в названии "San Jose" кодируются отдельно Если щелкнуть на кнопке, будет вызван метод Page_Load класса Reservationl, отображающий страницу, с помощью которой можно забронировать место в гостинице Этот метод считывает название города, название гостиницы, дату и количество дней, которое вы планируете провести в гостинице, а затем отображает полученные данные на возвращаемой Web-странице

    void Page_Load(Object *sender, EventArgs *e)
    {
    Page *p = dynamic_cast(sender);
    // Страница *р = dynamic_cast <Страница *> (отправитель);
    HttpRequest *request = p->get_Request();
    HttpResponse *response = p->get_Response() ;
    response->Write("Making reservation for ...");
    // ответ-> Запись ("Делаю резервирование для ... "};
    response->Write("
    "); // ответ-> Запись
    String *city = request->Params->get_Item("City");
    // Строка *city = запрос-> Params-> get_Item ("Город");
    response->Write(String::Concat("City = ", city));
    // ответ-> Запись (Строка:: Concat ("Город = ", город));
    response->Write("
    "); // ответ-> Запись
    String *hotel = request->Params->get_Item("Hotel");
    // Строка *hotel = запрос-> Params-> get_Item ("Гостиница");
    response->Write(String::Concat("Hotel = ", hotel));
    // ответ-> Запись (Строка:: Concat
    // "Гостиница = ", гостиница));
    response->Write("
    "); // ответ-> Запись
    String *strDate = request->Params->get_Item("Date");
    // Строка *strDate = запрос-> Params-> get_Item ("Дата");
    response->Write(String::Concat("Date = ", strDate));
    // ответ-> Запись (Строка:: Concat ("Дата = ", strDate));
    response->Write("
    "); // ответ-> Запись
    String *strDays = // Строка
    request->Params->get_Item("NumberDays" ) ;
    response->Write(String::Concat(
    // ответ-> Запись (Строка:: Concat ( "NumberDays = ", strDays));
    response->Write("
    "); // ответ-> Запись
    }


    Строку запроса можно увидеть в адресной строке броузера На рис. 10.15. показана выходная информация, выводимая броузером (На самом деле, наша программа не бронирует место в гостинице, она всего лишь выводит переданные ей параметры )
    Включите трассировку Вывод трассировки продемонстрирует идеи, которые мы обсудили, когда говорили о программировании запросов и ответов в Web-программах В частности, стоит изучить коллекцию Query String, показанную на рис. 10.16.

    Класс HttpResponse

    Рис. 10.15. Вот что выводит броузер после бронирования места в гостинице

    Класс HttpResponse

    Рис. 10.16. Строка запроса используется для того, чтобы передать параметры при переназначении

    CompEbook.ru Железо, дизайн, обучение и другие

    Класс Page (Страница)

    Ключевым (базисным) пространством имен для Web-форм и Web-служб является пространство имен System: :Web (Система::Сеть). Поддержка Web-форм реализована в пространстве имен System: :Web: :UI (Система::Сеть::Пользовательский интерфейс). Поддержка элементов управления сервера, например текстовых полей и кнопок, реализована в пространстве имен System: :Web: :UI: :WebControls (Система::Сеть:: Пользовательский интерфейс:^еЬСоп1го1$). Выходная . aspx-страница динамически генерируется классом Page (Страница) пространства имен System: :Web: :UI (Система::Сеть::Пользовательский интерфейс), а также классами, производными от класса Page (Страница), что иллюстрируется на примере последней рассмотренной нами страницы с фоновым кодом.
    Наследование от класса Page (Страница)
    Выходная страница генерируется в результате взаимодействия элементов, которые содержатся в .aspx-файле, кода, который содержится в файле с фоновым кодом (или сценария в случае С# или VB.NET) и базового класса Page (Страница). Возможность такого взаимодействия обеспечивает ASP.NET. Для .aspx-файла ASP.NET динамически создает класс, производный от класса, реализованного с помощью фонового кода или сценария, который, в свою очередь, является производным от класса Page (Страница). Иерархия классов, производных от класса Page (Страница) показана на рис. 10.6. Созданный нами класс MyWebPage является производным от класса Page (Страница).
    Класс Page (Страница)

    Рис. 10.6. Иерархия классов, производных от класса Page (Страница)
    Последним потомком (листом) класса Page (Страница) является класс My .aspx Page (рис. 10.6). Он динамически создается средой выполнения ASP.NET. Данный класс расширяет возможности класса MyWebPage (рис. 10.6). Он объединяет элементы управления и HTML-текст Web-формы. В результате компиляции данного класса создается исполняемый модуль. Когда страницу запрашивает броузер, загружается этот модуль. Он создает HTML-страницу, которая затем отсылается броузеру.
    CompEbook.ru Железо, дизайн, обучение и другие

    Конфигурация ASP.NET

    Обсуждая состояние сеанса, мы столкнулись с рядом случаев, в которых желательно конфигурировать ASP NET. Существует два типа конфигурации.
  • Server configuration (Конфигурация сервера). В этой конфигурации определяются стандартные значения параметров, которые будут использоваться всеми ASP NET-приложениями.

  • Application configuration (Конфигурация приложения) В этой конфигурации определяются значения параметров, которые будут использоваться конкретным ASP.NET-приложением.

  • CompEbook.ru Железо, дизайн, обучение и другие

    Модель событий Web-форм

    С точки зрения программиста, модели событий Web-форм и Windows-форм очень похожи Именно благодаря этому сходству программирование Web-форм оказывается таким легким Но, по сути, события Web-форм сильно отличаются от событий Windows-форм Самое существенное отличие состоит в том, что события Web-форм инициируются клиентом, а обрабатываются сервером.
    Мы уже создали простую форму Она состоит из одного текстового поля и одной кнопки Эта форма не обладает достаточным разнообразием элементов управления для того, чтобы подробно проиллюстрировать обработку событий. Представим себе форму, более насыщенную элементами управления. Пусть она содержит несколько текстовых полей, несколько списков, несколько флажков, кнопок и т.п. Поскольку полный обход (путешествие на сервер и обратно) стоит дорого, не каждое событие вызывает автоматическое обращение к серверу. Элементы управления сервера имеют внутренний набор событий сервера. Возникновение таких событий приводит к автоматическому обращению к серверу. Наиболее часто из этих внутренних событий используется событие щелчка на кнопке Другие события, например выбор элемента из списка, не приводят к немедленному обращению к серверу Такого рода события накапливаются в буфере до тех пор, пока событие щелчка на кнопке не вызовет отправку "почтового" сообщения на сервер. Затем сервер обрабатывает различные события, изменяющие состояние формы, причем последовательность обработки событий произвольная Наконец, после этого обрабатывается и событие щелчка на кнопке.
    CompEbook.ru Железо, дизайн, обучение и другие

    Объект Application (Приложение)

    Глобальная информация приложения хранится во встроенном объекте Application (Приложение), который является экземпляром класса HttpApplicationState. Удобно получать доступ к этому объекту через свойство Application (Приложение) класса Page (Страница) Класс HttpApplicationState содержит словарь в формате "ключ — значение", используемый для хранения как объектов, так и скалярных величин.
    CompEbook.ru Железо, дизайн, обучение и другие

    Объект Session (Сеанс)

    Информация о сеансе индивидуальных пользователей может храниться во встроенном объекте Session (Сеанс), который является экземпляром класса HttpSession-State. Доступ к этому объекту удобно получать с помощью свойства Session (Сеанс) класса Page (Страница) Класс HttpSessionState, аналогично классу HttpApplicationState, имеет словарь в формате "ключ — значение", который можно использовать для хранения как объектов, так и скалярных величин.
    При реализации переменных сеанса возникают некоторые интересные вопросы.
  • Обычно для идентификации сеанса, от которого поступил запрос, используются небольшие фрагменты данных о предыстории обращений данного пользователя к данному Web-серверу, автоматически создаваемые сервером на машине пользователя (cookies). Что делать, если такие фрагменты данных (cookies) не поддерживаются броузером, или отключены пользователем?

  • Поддержка состояния сеанса для многих пользователей влечет накладные расходы. Истекает ли срок действия состояния сеанса после определенного периода времени?

  • Высокопроизводительными Web-узлами используется группа серверов. Каким образом приложение может получить необходимые ему данные, если второй запрос обслуживается не той машиной, которой обслуживался первый запрос?

  • Состояние сеанса и небольшие фрагменты данных о предыстории обращений конкретного пользователя к конкретному Web-серверу, автоматически создаваемые сервером на машине пользователя (cookies)
    По умолчанию небольшие фрагменты данных о предыстории обращений конкретного пользователя к конкретному Web-серверу, автоматически создаваемые сервером на машине пользователя (cookies), в ASP.NET используются для идентификации сеанса, в котором отправлен запрос. Но ASP.NET можно сконфигурировать так, чтобы при выполнении не создавались эти небольшие фрагменты данных о предыстории обращений конкретного пользователя к конкретному Web-серверу, обычно автоматически создаваемые сервером на машине пользователя (cookies). В такой модели идентификатор сеанса (Session ID), который обычно сохраняется в небольшом фрагменте данных о предыстории обращений конкретного пользователя к конкретному WWW-серверу, автоматически создаваемом сервером на машине пользователя (cookie), внедряется в унифицированный указатель информационного ресурса (URL). Конфигурация, где не используются небольшие фрагменты данных о предыстории обращений конкретного пользователя к конкретному Web-серверу, автоматически создаваемые сервером на машине пользователя (cookies), обсуждается в следующем разделе.

    Предельное время ожидания для состояния сеанса

    По умолчанию, предельное время ожидания для состояния сеанса истекает после 20 минут. Это означает, что если данный пользователь находится в состоянии ожидания (простоя) в течение указанного периода времени, сеанс прекращается, и запрос от клиента теперь будет обрабатываться как запрос от нового пользователя, причем будет создан новый сеанс. Как и другие параметры, предельное время ожидания указывается в файле конфигурации, — это мы обсудим в разделе "Конфигурация ASP.NET".

    Хранение состояния сеанса

    С помощью модели состояния сеанса, которая отделяет хранение информации от использования сохраненной информации приложениями, ASP.NET полностью решает не только проблему группы серверов, но и много других проблем. Модели состояния позволяют реализовать различные сценарии хранения так, чтобы не затрагивать при этом код приложения. Сервер состояния .NET не имеет "активных" объектов для запросов Вместо этого в конце каждого сетевого запроса все объекты в коллекции сеанса преобразуются в последовательную форму, чтобы запомнить состояние сеанса. Когда тот же самый клиент возвращается к странице, объекты сеанса преобразуются из последовательной формы в параллельную.
    По умолчанию, состояния сеанса кэшируются, и поэтому, как правило, хранятся в оперативной памяти Конечно, в конфигурации можно указать, что в качестве кэша следует использовать память определенной машины или же что состояния сеанса нужно сохранять в базе данных SQL-сервера В этих случаях данные не привязаны к определенному серверу, и потому данные сеанса могут безопасно использоваться группой серверов

    CompEbook.ru Железо, дизайн, обучение и другие

    Обработка страницы

    Страница совместно обрабатывается Web-сервером, средой выполнения ASP NET и написанным вами кодом. Класс Page (Страница) содержит ряд событий, с помощью которых можно управлять обработкой страницы. При этом также можно использовать свойства и методы класса Page (Страница). В данной главе возможности класса Page (Страница) будут проиллюстрированы на примере программ Мы рассмотрим лишь некоторые основные возможности. Для получения исчерпывающих сведений обратитесь к описанию каркаса .NET Framework.
    События страницы
    В процессе стандартной обработки страницы на сервере происходит ряд событий. Фактически эти события определены в базовом классе Control (Элемент управления), и, таким образом, могут быть вызваны также элементами управления сервера. Ниже приведены наиболее значимые события.
  • Init (Инициализация). Событие возникает на первом этапе жизненного цикла страницы, при ее инициализации Информация о состоянии элементов управления еще отсутствует.

  • Load (Загрузка) Событие возникает на этапе загрузки элементов управления в страницу. На данном этапе уже имеется информация о состоянии представления этих элементов управления.

  • PreRender (Предвыполнение). Событие возникает непосредственно перед воспроизведением (отображением) элементов управления в выходном потоке. Как правило, страница это событие не обрабатывает Тем не менее, оно имеет важное значения для реализации ваших собственных элементов управления сервера.

  • Unload (Разгрузка). Событие возникает тогда, когда элементы управления выгружаются из страницы Записывать ваши собственные данные в выходной поток теперь уже поздно.

  • Свойства страницы
    Класс Page (Страница) имеет ряд важных свойств. Некоторые наиболее полезные свойства приведены ниже.
  • EnableViewState. Указывает, разрешено ли сохранение состояния представления самой страницы и ее элементов управления Можно получить или установить значение этого свойства. По умолчанию оно принимает значение true (истина), т.е. сохранение состояния разрешено

  • ErrorPage. Указывает страницу, на которую будет перенаправлен броузер, если возникнет непредвиденная исключительная ситуация.


  • IsPostBack. Указывает, загружается страница впервые или повторно.


  • I sValid. Указывает, была ли успешной проверка правильности страницы


  • Request (Запрос). Получает HTTP-объект Request (Запрос), который предоставляет доступ к данным входных запросов, поступающих по протоколу передачи гипертекстовых файлов HTTP.


  • Response (Ответ). Получает HTTP-объект Response (Ответ), который отсылает запрошенные данные броузеру.


  • Session (Сеанс). Получает текущий объект Session (Сеанс), в котором ASP.NET хранит состояние сеанса.


  • Trace (Трассировка). Получает объект TraceContext страницы. В этот объект записывается трассировочная информация.


  • Пример программы

    Чтобы продемонстрировать отдельные возможности, которые используются при обработке страниц, мы расширим нашу программу Echo (Эхо). Сборка Hello-Page имеет ряд обработчиков событий страницы. Для записи простого текста в выходной поток используется свойство Response (Ответ). После возникновения каждого события мы отображаем текущий текст в элементах управления сервера txtName и IblGreeting. Обработчик события Load (Загрузка) выводит текущее значение свойства IsPostBack. Если страница запрашивается впервые, свойство IsPostBack имеет значение false (ложь). Если же страница запрашивается повторно, оно принимает значение true (истина).


    <-d@ Assembly Name = "HelloPage" %>


    < HEAD>



    Your name: 


    tooitip="Click to echo your name">


    Greeting runat = "server ">


    < !-- тело -->


    В .aspx-файле, который приведен выше, имеется ссылка на сборку HelloPage. Названная сборка содержит класс MyHelloPage. В этом файле также указано, что класс r.vj'.' (Страница) является производным от класса MyHelloPage. Это означает, что события данной страницы обрабатываются методами класса MyHelloPage.
    Если страница запрашивается впервые, текстовые поля и подписи не содержат никаких значений, поскольку никакой информации в форму мы еще не вводили. Свойство IsPostBack имеет значение false (ложь). Теперь введите имя Robert (Роберт), и щелкните на кнопке Echo (Эхо). Обработчики событий страницы выведут следующую информацию:


    Page_Init
    txtName =
    IblGreeting =
    Page_Load
    IsPostBack = True // Истина
    txtName = Robert // Роберт
    iblGreeting =
    Page_PreRender
    txtName = Robert // Роберт
    IblGreeting = Hello, Robert. Welcome again
    // Привет, Роберт. Добро пожаловать снова

    В функции Page_Init элементы управления не содержат никаких данных, поскольку состояние представления на этапе инициализации страницы не доступно. В функции Page_Load текстовое поле содержит данные. Надпись не содержит никаких данных, так как обработчик события (щелчка мыши) еще не вызван. Свойство IsPostBack теперь имеет значение true (истина). В функции Page_PreRender оба элемента управления содержат данные.
    Щелкните на кнопке Echo (Эхо) еще раз. Данные в элементах управления функции Page_Init опять отсутствуют. А вот в функции Page_Load оба элемента управления используют данные состояния представления. На рис. 10.9 представлено окно броузера, в котором отображено то, что после второго щелчка на кнопке Echo (Эхо) вывели обработчики события страницы.

    Обработка страницы

    Рис. 10.9. Вывод броузера после второго щелчка на кнопке Echo (Эхо)

    Директива Page (Страница) и директива Assembly (Сборка)

    Файл с расширением . aspx может содержать директиву Assembly (Сборка) и директиву Page (Страница). Директива Assembly (Сборка) связывает уже существующую скомпилированную сборку с текущей страницей. Директива Page (Страница) определяет различные атрибуты, которые управляют обработкой ASP.NET-страницы. Каждая директива содержит одну или несколько пар атрибут/значение. Синтаксис директивы Assembly (Сборка) и директивы Page (Страница) приведен ниже.

    <@ Assembly Name="HelloCodebehind" @>
    <@ Page Inherits=MyWebPage @>

    Программа HelloCodebehind.aspx служит примером .aspx-страницы, в которой отсутствует код сценария. Если использовать другие языки программирования, например, С# или VB.NET, сценарий можно вложить непосредственно в . aspx-файл. В таком случае использовать директиву Assembly (Сборка) нет необходимости. Указанные языки программирования также позволяют использовать файлы с фоновым исходным кодом. Для этого в директиве Page (Страница) используется атрибут Src, который указывает на нескомпилированный исходный файл. Язык C++ такие возможности не поддерживает. Если используется язык C++, директива Assembly (Сборка) должна содержать атрибут Name (Имя), который идентифицирует скомпилированную сборку. При помощи атрибута Inherits (Наследуется) указывается класс, производный от класса Page (Страница), производным от которого в свою очередь является класс данной . aspx-страницы.

    CompEbook.ru Железо, дизайн, обучение и другие

    Основные принципы создания Web-приложения

    Web-приложение состоит из страниц с документом и страниц с кодом Документ и код хранятся в своем собственном формате Простейшим документом является статическая HTML-страница. Она содержит информацию, которая будет отформатирована и затем отображена Web-броузером. HTML-страница может также содержать гиперссылки на другие HTML-страницы. Гиперссылка (или просто ссылка) содержит адрес, либо унифицированный указатель информационного ресурса (Uniform Resource Locator — URL), определяющий местонахождение искомого документа. Объединение информации, которая формирует страницу, и ссылок иногда называют гипертекстом. С помощью гипертекста обеспечивается удобство навигации в безбрежном океане информации, которая содержится во всемирной паутине (World Wide Web — WWW)
    Поддержка управляемого C++в ASP.NET
    Управляемый С+-Ь поддерживается технологией ASP.NET лишь частично. Сценарий ASP.NET, написанный на некоторых других языках платформы .NET, например на С# или VB.NET, может быть внедрен ' в Web-страницу. Такой сценарий динамически компилируется в процессе выполнения приложения. Однако использовать управляемый C++ для написания внедряемых сценариев ASP.NET нельзя. На управляемом C++ можно писать лишь предварительно компилируемые сборки, которые используются ASP.NET-странидей. Такие ASP.NET-страницы называют страницами с "предварительно компилируемым фоновым кодом" ("precompiled code-behind"). Инструментальные средства разработки графического интерфейса пользователя (Graphical User Interface, GUI) Web-страниц на основе ASP.NET, входящие в состав среды Visual Studio.NET, позволяют генерировать код на С# и VB.NET. Код на C++ они не генерируют. Тем не менее, практически все элементы пользовательского интерфейса могут быть реализованы на C++, но при этом соответствующий код нужно писать самостоятельно.
    Установка примеров web-приложений
    Как обычно, все примеры программ, которые рассматриваются в данной главе, расположены в папке, соответствующей данной главе. Но прежде чем вы сможете запустить программу, вам необходимо инсталлировать информационный сервер Internet (Internet Information Services— US) на вашей машине. Информационный сервер Internet инсталлируется по умолчанию вместе с операционной системой Windows 2000 Server. Если на вашей машине установлена операционная система Windows 2000 Workstation, информационный сервер Internet придется инсталлировать отдельно. После инсталляции информационного сервера Internet, вы получите доступ к его описанию с помощью броузера Internet Explorer. Для этого используйте унифицированный указатель информационного ресурса (URL) http: //localhost. В окне броузера откроется начальная страница документации информационного сервера Internet (рис. 10.1).

    Приложения ASP.NET

    ASP.NET-приложение состоит из всех Web-страниц и файлов с кодом, которые хранятся на Web-сервере в виртуальном каталоге или его подкаталогах. Как мы уже убедились, кроме . aspx-файлов и файлов с фоновым кодом в состав приложения также входят файлы global.азах и config.web. В этом разделе главы мы рассмотрим возможности приложений ASP.NET. Затем мы изучим механизмы, которые обеспечивают работу с состоянием приложения и состоянием сеанса, а также конфигурирование Web-приложений.
    Приложения ASP.NET

    Рис. 10.22. При выполнении на Web-странице отображается информация о городах
    CompEbook.ru Железо, дизайн, обучение и другие

    Привязка данных

    Теперь нужно заполнить первый раскрывающийся список (DropDownList) названиями городов. Необходимые для этого данные могут быть получены с помощью метода GetCi-ties объекта HotelBroker. Мы воспользуемся свойством привязка данных (data binding), которым обладает раскрывающийся список. Возможно, вы были уверены, что привязка данных используется исключительно при работе с базами данных. При разработке приложений на основе платформы .NET понятие привязка данных принимает более широкий смысл. Здесь привязка данных используется не только при работе с базами данных, но и с другими источниками данных. Привязка элементов управления к базе данных играет очень важную роль при построении двухзвенных приложений типа клиент/сервер. Мы же создаем трехзвенное приложение. В этом приложении код, описывающий логику представления, которое реализовано с помощью Windows-форм либо Web-форм, взаимодействует с компонентом бизнес-логики, а не непосредственно с базой данных.
    Каркас .NET Framework имеет ряд возможностей связывания данных. Они облегчают привязку данных, полученных при помощи промежуточного звена. Очень просто привязать данные к списку массивов (ArrayList). Эта возможность превосходно подходит для нашего примера, потому что мы должны заполнить строками раскрывающийся список (DropDownList) городов, а метод GetCities как раз возвращает список массива строк.
    В самой нижней строке уместился весь код, который мы добавили к методу Page_Load класса WebForml, для того, чтобы заполнить раскрывающийся (DropDownList) список listCities.
    void Page_Load(Object *sender, System::EventArgs *e)
    {
    if ( HsPostBack)
    {
    hotelBroker = new HotelBroker;;
    ArrayList *cities = hotelBroker->GetCities(};
    listCities->DataSource = cities; // города
    ArrayList *hotels = hotelBroker->GetHotels(
    dynamic_cast // Строка
    (cities->get_Item(0))); // города
    BindHotels(hotels); // гостиницы
    DataBind();
    }
    }
    Вызов метода DataBind() связывает все элементы управления сервера с соответствующими им источниками данных. В результате все элементы управления заполняются соответствующими данными. Метод DataBind может быть вызван для каждого элемента управления сервера отдельно. Метод DataBind принадлежит классу Control (Элемент управления). Он наследуется классом Page (Страница) и классами конкретных элементов управления сервера.

    Инициализация гостиниц

    Аналогично мы можем заполнить второй раскрывающийся список (DropDownList) названиями гостиниц. Но это немного сложнее, потому что GetHotels возвращает список массивов структур HotelListltem, а не строк. Нам необходимо заполнить раскрывающийся список listHotels названиями гостиниц. Здесь нам поможет метод BindHotels, который просматривает в цикле список гостиниц и создает список массивов с названиями гостиниц. Этот список затем привязывается к раскрывающемуся списку listHotels. Ниже приведен законченный код, содержащий логику инициализации гостиниц для первого города (который имеет индекс 0).

    void BindHotels(ArrayList *hotels)
    {
    ArrayList *hotelNames =
    new ArrayList(hotels->Count); // гостиницы
    lEnumerator *pEnum = hotels->GetEnumerator(); // гостиницы
    while (pEnum->MoveNext())
    {
    HotelListltem *hotel =
    dynaraic_cast
    (pEnum->Current) ;
    hotelNames->Add(hotel->HotelName->Trim());
    // Добавить гостиницу;
    }
    listHotels->DataSource = hotelNames;
    }

    Выбор города

    И, наконец, мы реализуем следующую особенность: при выборе города отображаются гостиницы в выбранном городе. Мы добавляем обработчик события для выбора города. Ниже приведен код для обработки события SelectedlndexChanged.

    void listCities_Selected!ndexChanged(
    Object *sender, System::EventArgs *e)
    {
    String *city = listCities->Selected!tem->Text; // Строка
    ArrayList *hotels = hotelBroker->GetHotels(city); // город
    BindHotels(hotels); // гостиницы
    DataBind();
    }

    Добавление класса Global (Глобальный)

    Добавьте к проекту новый файл Global .азах.h. Как обычно, нет необходимости самостоятельно писать соответствующий код. Его можно скопировать из файла решения, который находится в каталоге CaseStudyXAcmeWeb. Для этого выполните следующие пошаговые инструкции:
    1. Скопируйте файл Global.asax.h из каталога CaseStudy\AcmeWeb в каталог Demos\AcmeWeb, где расположен новый проект.
    2. В окне Solution Explorer (Поиск решения) щелкните правой кнопкой мыши на узле Header Files (Заголовочные файлы) под проектом AcmeWeb, и выберите Add => Add Existing Item (Добавить => Добавить существующий элемент). Затем дважды щелкните на файле Global.азах.h.
    Обратите внимание, что в Global.азах.h метод Application_Start создает экземпляр класса HotelBroker и присваивает его статическому полю hotelBroker. Это поле затем извлекается в исходном файле WebForml.aspx.h методом Page_Load, который был приведен ранее.
    //Global.asax.h
    #using <.\Hotel.dll>
    #using
    #using
    using namespace System;
    // использование пространства имен Система;
    using namespace OI::NetCpp::Acme;
    namespace AcmeWeb
    // пространство имен AcmeWeb
    {
    public _gc class Global :
    // класс сборщика мусора Глобальный:
    public System::Web::HttpApplication
    {
    public:
    static HotelBroker *hotelBroker; // статическое поле protected: // защищенный
    void Application_Start(
    Object *sender, EventArgs *e)
    {
    hotelBroker = new HotelBroker;
    }
    }; }


    Файл Global. asax И Объект Global (Глобальный)

    Чтобы вызвать методы класса Global (Глобальный), который определен в файле Global.asax.h, необходим файл Global.азах. Например, если в каталоге AcmeWeb файл Global.asax отсутствует, информационный сервер Internet (US) не сможет вызвать метод Application_Start. А это очень важный метод, так как именно он создает экземпляр класса HotelBroker. Файл Global.азах находится в папке Cas-eStudy\AcmeWeb. Скопируйте этот файл в папку Demos\AcmeWeb. Нет необходимости добавлять его в проект. Но удобства ради мы это сделаем. Чтобы добавить данный файл в проект, выполните следующие пошаговые инструкции.
    1. Скопируйте файл Global.азах из каталога CaseStudyXAcmeWeb в каталог DemosXAcmeWeb, в котором расположен ваш новый проект.
    2. В окне Solution Explorer (Поиск решения) щелкните правой кнопкой мыши на узле проекта AcmeWeb. Из контекстного меню выберите команду Add (Добавить), затем Add Existing Item (Добавить существующий элемент). Установите *.* в качестве фильтра типа файла, а затем дважды щелкните на файле Global. asax.
    Файл Global.asax содержит единственную строку (она приведена ниже), которая указывает, что объект приложения является производным от класса AcmeWeb.Global. Этот класс определен в исходном файле Global. asax. h.

    <%@ Application Inherits="AcmeWeb.Global" %>

    AcmeWeb.aspx

    Другой ASP.NET-файл, который должен обязательно присутствовать в каталоге AcmeWeb, имеет название AcmeWeb. aspx. В этом файле указана сборка, где содержится реализация класса AcmeWeb: :WebForml. Исходный код этого класса содержится в исходном файле WebForml.aspx.h. В файле AcmeWeb.aspx, кроме того, указано, что страница наследует класс AcmeWeb: :WebForml, а также некоторый HTML-код, который предназначен для создания представления, отображаемого для пользователя. Файл AcmeWeb.aspx находится в каталоге CaseStudy\AcmeWeb. Вы можете скопировать его из этого каталога в каталог Demos\AcmeWeb. Добавлять его в проект не обязательно. Но удобства ради мы сделаем это. Чтобы добавить данный файл в проект, выполните следующие пошаговые инструкции:
    1. Скопируйте файл AcmeWeb.aspx из каталога 4aseStudy\AcmeWeb в каталог Demos \AcmeWeb, в котором расположен новый пюект
    2. В окне Solution Explorer (Поиск решения) щелкште правой кнопкой мыши на узле проекта AcmeWeb Из контекстного меню вьберите команду Add (Добавить), затем Add Existing Item (Добавить существующШ элемент) В качестве фильтра типов файлов используйте * * Затем дважды щелшите на файле AcmeWeb. aspx


    <%@ Assembly Name="AcmeWeb" %>
    <%@ Page Inherits=AcmeWeb.WebForml %>



    <'— ТЕЛО —>

    City
    <' - Город —>
    Hotel
    Osp.DropDownList id=listCities style="Z-HDEX: 103; LEFT: 134px; POSITION: absolute; TOP: 80px" runa1="server" Width="120px" Height="22px" AutoPostBack="rrue"x/asp: Drop-DownList>

    <<— тело —>

    Построение AcmeWeb

    Чтобы построить проект, скопируйте файл Hotel dll в каталог Demos\AcmeWeb Это необходимо сделать потому, что проект содержит /ирективу #using для указанной сборки Скопировав этот файл из каталога CaseStudy^AcmeWeb, можно построить проект, в результате чего будет создан файл AcmeWeb. dll

    Просмотр AcmeWeb

    Прежде чем вы сможете просмотреть приложение AcmeWeb в броузере, вам нужно сначала скопировать файл AcmeWeb.dll в подкатало bin каталога \Demos\AcmeWeb Вы уже это делали в случае каталога CaseStudy\Acmefeb
    1. В каталоге DemosXAcmeWeb, в котором расположи проект, создайте подкаталог bin
    2. Скопируйте файл AcmeWeb dll из каталога Dtmos\AcmeWeb\Debug в каталог Demos\AcmeWeb\bin
    3. Кроме того, скопируйте файл Hotel. dll в катаюг Demos\AcmeWeb\bin
    Но даже после копирования AcmeWeb. dll в катало bin вы все же не можете отобразить http: //localhost/NetCpp/Demos/AcmeWeb/JcmeWeb.aspx в вашем броузере Ведь для этой Web-страницы нужно еще сконфигур1ровать виртуальный каталог как приложение для информационного сервера Internet (II>) Чтобы сделать это, выполните следующие инструкции
    4. Щелкните на кнопке Start (Пуск) и выберите команду Programs (Программы) Затем выберите команду Administrative Tools (Средства администрирования) и запустите Internet Server Manager (Диспетчер серверов Internet)
    5. Откройте дерево под стандартным Web-узлом (Default Web Site) до узла NetCpp\Demo s\AcmeWeb
    6. Щелкните правой кнопкой мыши на каталоге NetCpp \Demos \AcmeWeb Из контекстного меню выберите команду Properties (Свойства)
    7. В диалоговом окне щелкните на кнопке Create (Создать), рис. 10.20.
    8. Появится диалоговое окно, показанное на рис. 10.21.
    9. Щелкните на кнопке ОК
    Только теперь http://localhost/NetCpp/CaseStudy/AcmeWeb/AcmeWeb.aspx можно отобразить в окне броузера (рис. 10.22.) Эту страницу можно отобразить непосредственно в броузере или выполнить ее в Visual Studio, которая вызовет Internet Explorer, чтобы обратиться к приложению по протоколу передачи гипертекстовых файлов HTTP При прокрутке списка городов вы увидите города, возвращенные компонентом HotelBroker


    Привязка данных

    Рис. 10.20. Создание имени приложения (Application Name) для Acme Web

    Отладка AcmeWeb

    Чтобы отладить проект AcmeWeb, необходимо создать файл конфигурации и в нем определить используемый режим отладки Необходимо также включить режим отладки в проекте, как показано на рис 10 23 Чтобы сделать это, выполните следующие шаги
    1. Скопируйте файл Web.config из каталога CaseStudy\AcmeWeb в каталог Demos\AcmeWeb. В этом файле конфигурации можно указать несколько возможностей, но для отладки наиболее важен тэг Compilation debug="tme"/> (<отладка сборки = "истина"/>).
    2. В окне Solution Explorer (Поиск решения) щелкните правой кнопкой мыши на проекте AcmeWeb. Из контекстного меню выберите команду Properties (Свойства).
    3. В папке Configuration Properties (Свойства конфигурации) щелкните кнопкой мыши на узле Debugging (Отладка).
    4. В качестве значения свойства Command (Команда) укажите aspnet_wp. ехе.
    5. В качестве значения свойства Attach (Присоединить) укажите Yes (Да).
    6. Укажите http: //localhost/NetCpp/Demos/AcmeWeb/AcmeWeb.aspx в качестве значения свойства HTTP URL.
    Теперь можно открыть решение в отладчике. Для этого выберите из меню Debug (Отладка) команду Start (Пуск). На рис. 10.24 и рис. 10.25 показан отладчик, остановившийся в точках останова, содержащихся в файлах Global. азах. h и WebForml. aspx. h, соответственно.

    Привязка данных

    Рис. 10.21. Создание имени приложения (Application Name) для AcmeWeb завершено

    CompEbook.ru Железо, дизайн, обучение и другие

    Программа на С#: Echo (Эхо)

    Первая программа, которую мы рассмотрим в данной главе, называется Hello, aspx. На начальной странице она представлена соответствующей ссылкой. Пример называется "монолитным'', так как он выполнен в виде одного файла. В этот файл вложен код сценария для сервера ASP.NET. Поскольку писать код вложенного сценария на языке C++ нельзя, он написан на С#. На этом примере будет рассмотрена работа приложений с вложенным кодом сценария. Исходный код программы приведен ниже. Это обычный HTML-текст, в который внедрен код определенного сценария, написанный на С#. Кроме того, в нем имеются некоторые специальные тэги, которые распознает ASP. NET, описывающие элементы управления сервера

    <%@ Page Language="Cft" %>
    < !-- Язык Страницы -->







    Your name: 









    Чтобы запустить программу, укажите унифицированный указатель информационного ресурса (URL) http://local.iost/NetCpp/Hello.aspx или шепкните кнопкой мыши на ссылке Hello, aspx, расположенной на начальной странице с примерами программ. Вы увидите страницу с текстовым полем, в которое вы можете ввести свое имя, и кнопкой Echo (Эхо). Введите имя и затем щелкните на кнопке Echo (Эхо) Теперь появится введенное вами имя с предшествующим ему приветствием "Hello" ("Привет"). Пока на экране отображается эта простая форма, вы сможете ввести другое имя. Если указатель мыши расположить над кнопкой Echo (Эхо), в желтом прямоугольнике будет выведена подсказка "Click to echo your name" ("Щеткните, чтобы отобразить ваше имя"). Работа данной программы иллюстрируется на рис. 10.5.
    Написание этой маленькой программы при помощи других средств построения Web-приложений, включая ASP (Active Server Pages), является не совсем тривиальной задачей. Характерная особенность этого приложения — его пользовательский интерфейс, полностью реализованный на основе формы Пользователь представляется с помощью формы и взаимодействует с этой же формой. Введенные данные обрабатываются сервером, и пользователь продолжает видеть ту же форму. Такая мелеть пользоватетьского интерфейса— вторая сущность настольных приложений. Но в Web-приложениях подобная модель пользовательского интерфейса используется не слишком часто Как правило, Web-сервер отсылает обратно клиенту уже другую страницу. Конечно, для создания приложения с интерфейсом в виде формы можно было бы использовать и технологию наподобие ASP, но код получится несколько громоздким. Ведь серверу пришлось бы синтезировать новую страницу, идентичную старой. Для этого в новую страницу нужно включить тэги, описывающие исходную страницу, а также некоторую отсылаемую клиенту дополнительную информацию (в нашем примере Echo (Эхо) это приветствие, которое выводится внизу страницы) Иными словами, необходим механизм запоминания текущих данных, введенных в элементах управления формы Другая особенность названного Web-приложения состоит в том, что некоторая обработка данных происходит также и на стороне клиента. А именно, броузер выводит подсказку в желтом прямоугольнике Такую расширенную обработку данных в состоянии выполнить броузер Internet Explorer Некоторые другие броузеры эту функцию не поддерживают
    Программа на С#: Echo (Эхо)

    Рис. 10.5. Функционирующая программа Hello.asp
    Если вы посмотрите на код программы, то убедитесь, что реализовать подобные Web-приложения с помощью ASP.NET совсем несложно (по крайней мере, на языке С# или VB.NET).
    CompEbook.ru Железо, дизайн, обучение и другие

    Программирование запросов и ответов

    Архитектура элементов управления сервера строится на верхнем слое более фундаментальной архитектуры обработки данных, которую можно назвать архитектурой запросов и ответов. Понимание запросов и ответов протокола передачи гипертекстовых файлов HTTP поможет нам получить целостное представление о технологии ASP NET Кроме того, при программировании иногда возникают такие ситуации, когда использование запросов и ответов является естественным подходом
    CompEbook.ru Железо, дизайн, обучение и другие

    Она значительно упрощает реализацию сложных

    ASP.NET — это унифицированная платформа разработки Web-приложений. Она значительно упрощает реализацию сложных Web-приложений. В данной главе вы ознакомились с основами технологии ASP.NET, а также рассмотрели Web-формы. Web-формы используются для разработки интерактивных Web-узлов. В основе этой модели программирования на языках высокого уровня лежит модель программирования более низкого уровня, известная как модель программирования запросов и ответов. Эта модель широко использовалась в более ранних технологиях создания Web-приложений и ее использование все еще допускается технологией ASP.NET.
    В состав среды разработки приложений Visual Studio.NET входит конструктор форм (Form Designer), который поддерживает языки С# и VB.NET. Конструктор форм (Form Designer) позволяет очень легко визуально создавать макеты Web-форм. С помощью конструктора форм (Form Designer) обработчик события формы можно добавить одним щелчком кнопки мыши. К сожалению, в настоящее время конструктор форм (Form Designer) не поддерживает язык C++, и чтобы создать Web-форму на C++, соответствующий код нужно писать самостоятельно.
    В следующей главе мы рассмотрим Web-службы, позволяющие создавать объединенные (распределенные) Web-приложения, которые могут быть развернуты в неоднородных (гетерогенных) системах.
    CompEbook.ru Железо, дизайн, обучение и другие

    Сеансы

    Чтобы по достоинству оценить поддержку Web-приложений в ASP.NET, нужно иметь четкое представление о концепции Web-сеанса. Протокол передачи гипертекстовых файлов HTTP (HyperText Transfer Protocol) не хранит состояние приложения. Это значит, что не существует прямого способа, при помощи которого можно было бы узнать, исходит последовательность запросов от одного или от разных клиентов. Web-сервер, например информационный сервер Internet (US), может иметь механизм, при помощи которого исходящие от одного клиента запросы объединяются в логический сеанс. Работать с сеансами в ASP.NET очень легко.
    CompEbook.ru Железо, дизайн, обучение и другие

    Состояние представления (вида)

    Любая информация, которая вводится в форму, "запоминается" Web-сервером Это важное свойство Web-форм Протокот передачи гипертекста HTTP не хранит информацию о состоянии формы Иными словами, сохранение текущего состояния Web-формы происходит не автоматически, а должно быть запрограммировано. Информация о состоянии Web-формы называется состоянием представления, или состоянием вида (view state) ASP.NET может с помощью каркаса Framework автоматически сохранять состояние представления (вида) Web-формы. При этом используется "скрытый" элемент управления Для удобства восприятия, значительная часть длинного значения, которое описывает состояние представления формы, в приведенном ниже коде опущена.
    value="dDw2M]kzODE3NTtOPDtsPGk8M]47P ... +0z4+0z4=" />
    Позже в этой главе мы рассмотрим другие возможности ASP.NET, используя которые можно управлять состоянием сеанса и состоянием приложения.
    Состояние представления (вида)

    Рис. 10.8. После полного обхода (на сервер и обратно) на странице отображается приветствие
    CompEbook.ru Железо, дизайн, обучение и другие

    Состояния в приложениях ASP.NET

    Сохранение состояния при запросах, посылаемых по протоколу передачи гипертекстовых файлов HTTP, — главная проблема в Web-программировании ASP.NET предоставляет для этого несколько удобных функций Необходимо сохранять два типа состояния.
  • Состояние приложения — глобальная информация, которая совместно используется всеми пользователями Web-приложения

  • Состояние сеанса используется для хранения данных конкретного пользователя, который многократно обращается к Web-приложению

  • CompEbook.ru Железо, дизайн, обучение и другие

    Статические элементы данных

    Статические элементы данных класса совместно используются всеми экземплярами класса Следовательно, статические элементы данных могут использоваться для хранения состояния приложения
    CompEbook.ru Железо, дизайн, обучение и другие

    Трассировка

    Технология ASP.NET предоставляет широкие возможности для трассировки программы. Если атрибуту Trace (Трассировка) страницы присвоить значение true (истина), то выходная трассировочная информация, генерируемая ASP.NET, будет выводиться в окне броузера. Кроме того, можно вывести свою трассировочную информацию. Для этого используется метод Write (Запись) объекта TraceContext. Чтобы получить доступ к этому методу, используется свойство Trace (Трассировка) класса Page (Страница).
    Страница HelloTrace.aspx иллюстрирует использование трассировки при записи информации в объект Response (Ответ).

    <%@ Assembly Name="HelloTrace" %>
    <%@ Page Inherits=MyHelloTrace Trace = "true" %>




    Your name: 

    tooltip="Click to echo your name">









    Класс MyHelloTrace, реализованный в виде сборки rielloTrace.dll, содержит следующий код, который записывает вывод трассировки:
    void Page_Init(Gtoect *senaer, EventArgs *e)
    {
    Page *p = dynamic_cast(sender);
    // Страница "р = dynarpic__cast <Страница *> (отправитель);
    TraceContext *trace = p->get_Trace();
    trace->Write("Page_Init
    "); // трассировка-> Запись
    tгасе->Write (String::Concat(
    // трассировка-> Запись (Строка)
    "txtName = ", txtName->Text, "
    ")); // Текст
    trace->Wnte (String: :Concat (
    // трассировка-> Запись (Строка)
    "IblGreeting = ",iblGreeting->Text, "
    ")); // Текст
    }
    void Page_Load(Object *sender, EventArgs *e)
    {
    Page *p = dynamic__cast(sender);
    // Страница *р = dynamic_cast <Страница *> (отправитель);
    TraceContext *trace = p->get_Trace();
    trace->Write("Page_Load
    "); // трассировка-> Запись
    trace->Write (String: :Format(
    // трассировка-> Запись (Строка:: Формат )
    "IsPostBack = (0}
    ", _box (IsPostBack) )
    };
    trace->Write(String::Concat(
    // трассировка-> Запись (Строка)
    "txtName = ", txtName->Text, "
    ")); // Текст
    trace->Write(String::Concat(
    // трассировка-> Запись (Строка)
    "IblGreeting = ", lblGreeting->Text, "
    ")); // Текст
    }
    void Page_PreRender(Object ^sender, EventArgs *e) -
    {
    Page *p = dynamic_cast(&ender);
    // Страница *р = aynamic_cast <Страница *> (отправитель);
    TraceContext wtrace = p->get_Trace();
    trace->Write("Fage_PreRendei
    "); // трассировка-> Запись
    trace->Wnte (String: : Concat
    // трассировка-> Запись (Строка)
    "txtName == ", txtName->Text, "
    ")); // Текст
    trace->Write(String::Concat(
    // трассировка-> Запись (Строка)
    "IblGreeting = ", lblGreeting->Text, "
    ")); // Текст
    }
    На рис. 10.10 показано, что отображает броузер после первого запроса данной страницы Обратите внимание, что вывод трассировки и трассировочная информация, генерируемая ASP.NET, отображается после формы
    Трассировка

    Рис. 10.10. Броузер отображает трассировочную информацию
    CompEbook.ru Железо, дизайн, обучение и другие

    Возможности ASP.NET

    ASP.NET предоставляет модель программирования и соответствующую инфраструктуру, которая облегчает разработку Web-приложений нового типа. Частыо этой инфраструктуры является среда выполнения платформы .NET и ее каркас .NET Framework Серверная программа пишется на одном из языков программирования платформы .NET, которые компилируются либо предварительно, либо оперативно, т.е в процессе выполнения приложения. ASP.NET поддерживает две основные модели программирования.
  • Web-формы облегчают создание Web-страниц на основе форм. Среда разработки в режиме полного соответствия WYSIWYG (What You See Is What You Get — что видишь на экране, то и получишь при печати) позволяет просто перетаскивать соответствующие элементы управления на Web-страницу Специальные элементы управления сервера предоставляют программистам модель событий, похожую на модель событий, которая используется при программировании Windows-приложений. В данной главе подробно обсуждаются Web-формы.

  • Web-службы позволяют через программный интерфейс приложения (API) представить на Web-узле функции, которыми могут воспользоваться другие удаленные приложения. Для обмена данными используются стандартные Web-протоколы и форматы, например, протокол передачи гипертекстовых файлов HTTP (Hypertext Transfer Protocol) и язык XML (extensible Markup Language), что позволяет избежать проблем, связанных с прохождением пакетов данных через системы сетевой защиты (брандмауэры). Web-службы обсуждаются в следующей главе

  • Web-формы и Web-службы могут использовать дополнительные возможности, предоставляемые платформой NET, например, оттранслированные программы и среду выполнения платформы NET. Кроме того, в самой ASP NET предусмотрен ряд служб инфраструктуры, к числу которых принадлежит управление состоянием, защита, конфигурирование, кэширование и трассировка (отладка) программ
    Оттранслированные программы
    Web-формы (и Web-службы) могут быть написаны на любом языке, который поддерживается платформой .NET и интерпретируется верхним слоем общеязыковой среды выполнения CLR (Common Language Runtime) К числу таких языков принадлежат С#, VB.NET, и C++ с управляемыми расширениями. Полученная программа затем транслируется За счет этого обеспечивается более высокая производительность по сравнению с ASP-страницами, написанными на интерпретируемом языке сценариев, например, VBScnpt. Оттранслированным программам доступны все дополнительные возможности среды NET, например, управляемая среда выполнения, и, конечно же, библиотека классов .NET Framework. Традиционный неуправляемый код может быть вызван посредством .NET-служб, обеспечивающих возможность взаимодействия управляемого и неуправляемого кода. Эти службы обсуждаются в главе 15 "Смешивание управляемого и неуправляемого кода".

    Элементы управления сервера

    В ASP. NET введено существенное новшество, известное как элементы управления сервера Элементы управления сервера описываются специальными тэгами, например . Серверная программа взаимодействует с элементами управления сервера. Затем среда выполнения ASP.NET генерирует обычную HTML-страницу, которая отсылается Web-броузеру. С помощью такой модели программирования легко получить стандартный HTML-код. Этот код может быть интерпретирован любым броузером.

    Независимость кода от броузеров

    Всемирная сеть (World Wide Web — WWW) построена на стандартах, но используемые броузеры не полностью совместимы. Некоторые из них поддерживают специфические функции и это является неутешительным житейским фактом. Перед дизайнером, создающим Web-страницу, открываются две, не очень-то привлекательные, возможности: писать простейший код, понятный каждому броузеру, или писать индивидуальный код для каждого броузера Использование элементов управления сервера позволяет существенно облегчить решение этой проблемы. ASP.NET учитывает совместимость броузеров на этапе генерации кода элементов управления сервера. Если страница запрошена мощным многофункциональным броузером, тогда генерируется HTML-код, использующий дополнительные функции броузера. В противном случае генерируется простейший HTML-код. Возможности используемого броузера ASP.NET определяет автоматически.

    Отделение кода от содержимого

    Стандартные ASP-страницы представляют собой смесь кода сценариев и элементов языка HTML. В ASP.NET код сценариев четко отделен от представляемого содержимого. Если код сценария написан на языке, который поддерживает создание вложенных сценариев ASP.NET, например, на С# (но не на C++), тогда этот код можно заключить в блок