Отправляет email-рассылки с помощью сервиса Sendsay

Программирование на Visual С++

  Все выпуски  

Программирование на Visual С++ - No.58 (CLR)


Служба Рассылок Subscribe.Ru

  ПРОГРАММИРОВАНИЕ    НА    V I S U A L   C + +
РАССЫЛКА САЙТА       
RSDN.RU  

     Выпуск No. 58 от 30 декабря 2001 г.

РАССЫЛКА ЯВЛЯЕТСЯ ЧАСТЬЮ ПРОЕКТА RSDN , НА САЙТЕ КОТОРОГО ВСЕГДА МОЖНО НАЙТИ ВСЮ НЕОБХОДИМУЮ РАЗРАБОТЧИКУ ИНФОРМАЦИЮ, СТАТЬИ, ФОРУМЫ, РЕСУРСЫ, ПОЛНЫЙ АРХИВ ПРЕДЫДУЩИХ ВЫПУСКОВ РАССЫЛКИ И МНОГОЕ ДРУГОЕ.

Здравствуйте, дорогие друзья!

Сердечно поздравляю вас всех с наступающим Новым Годом! Пусть он принесет вам как можно больше успехов и радости, откроет новые горизонты и пусть лучезарное настроение никогда вас не покидает! Оно ведь вам особенно необходимо, когда вы например отслеживаете сорок шестую ошибку в программе... ;-)

Но одним поздравлением я сегодня все-таки не ограничусь :-) Cначала хочу внести ясность: предыдущему выпуску номер "58" был присвоен случайно и вне очереди, т.к. на самом деле он только пятьдесят седьмой. В архиве он за этим номером и стоит, так что просьба не искать потерявшийся 57-ой выпуск. ;-)

Cегодня я хочу представить вашему вниманию очень интересную, на мой взгляд, статью Владислава Чистякова из журнала "Технология Клиент-Сервер". Статья посвящена перспективам, ждущих нас уже в совсем недалеком будущем. Как вы вероятно уже знаете, входящая в платформу .NET Common Language Runtime (CLR) призвана поднять переносимость создаваемых приложений на новый уровень. Но давайте не будем забегать вперед...


 CТАТЬЯ

CLR
Common Language Runtime

Автор: Владислав Чистяков
Источник: <Технология Клиент-Сервер>

Прежде чем начинать говорить про VS.Net, необходимо поговорить про .Net и про рекламу в общем. Что же такое .Net и зачем он нужен?

Вы, наверное, заметили, что чем больше Интернет проникает в массы, тем больше нечестного использования этого названия встречается. Например: "Новый процессор Pentium 4 позволит поднять на НОВЫЙ уровень ваши возможности в Интернет". Интересно, ведь если даже у счастливого обладателя этого процессора будет возможность смотреть видеоролик в режиме 1900x1600 (хотя, хоть убей не пойму, как это зависит от процессора?), то где он возьмет канал в Интернет, который даст ему возможность прокачать этот ролик (ну, хотя бы с приемлемым качеством в разрешении 320x240). Но слово магическое - ИНТЕРНЕТ! Всунул его в свой пресс-релиз и порядок, продажи обеспечены.

Никуда не делся от этого искушения и Microsoft. Все без исключения продукты этого производителя будут теперь иметь суффикс - <.Net>.

Неважно, на сколько процентов продукт предназначен для Интернет. Можно сказать больше - в марку .Net вкладывается столько денег, что в один прекрасный день, чтобы объяснить молодому специалисту, что такое Интернет, ему скажут: "Интернет - это инфраструктура, предназначенная для запуска приложений и сервисов .Net". Вы думаете, я утрирую или шучу. Нисколько. Буквально за день, до того как сесть писать эту статью я слышал как ведущий радиостанции, по-моему, РДВ, заявил: "как хорошо все-таки, что Билл Гейтс придумал Интернет, а то я бы не смог получать ваши электронные письма...".

Ну да ладно. Пускай специалисты из Microsoft отдуваются за маркетинговые изыски своего начальства, объясняя: что же такое .Net? Наша задача разобраться - что же такое VS.Net?

Можно сказать, что VS.Net - это всего лишь новая версия VS - седьмая версия, но это не совсем так. Дело в том, что практически все составные части VS были полностью разрушены и выстроены заново. Короче говоря, Microsoft в очередной раз воплотил в жизнь принцип: МЫ НАШ, МЫ НОВЫЙ МИР ПОСТРОИМ... Но в отличие от прошлых разов, когда Microsoft в целях строительства нового (своего нового) мира разрушал миры своих оппонентов, в этот раз Microsoft, на первый взгляд, разрушил свой, причем уютненький такой мирок. Так что предпосылка "кто был ничем" не срабатывает ;o).

Что это, агония?

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

Стратегия "пан или пропал"

Недавно обратил внимание на незакрытое окно броузера, в котором была открыта статья с русскоязычного сайта Sun Microsystems под названием , аж в четырех частях. В ней планомерно доказывалась, что UNIX (и особенно Linux) лучше, чем NT, по всем показателям (одно только осталось непонятным, а если все так, как говорится в этой статье, почему весь мир не сбежал на UNIX с этой никчемной NT). Практически вся статья построена на демагогии, и она даже не заслуживала бы упоминания, если бы ни одно <но>. В этой статье давался подробный список недочетов, допущенных Microsoft в NT 4. Забавно, но это именно те недочеты, которые были устранены в 5-й версии (Windows 2000). Из всего упомянутого, по-моему, только отсутствие в поставке серьезного mail-сервера осталось не исправленным.

Та же история и со вторым конкурентом - с Oracle. Его маркетинг во многом строился на критике Microsoft SQL Server. Временами казалось, что специалисты из Oracle попросту подрабатывают бета-тестерами у Microsoft. И что в итоге? В первую очередь в SQL Server 2000 были внесены те исправления и замечания, о которых говорил Oracle.

Причем чем больнее задевают Microsoft, тем больше вероятность, что следующая версия продукта выбьет колючие аргументы из рук оппонентов.

Та же ситуация складывается и со средствами разработки. Похоже, Microsoft внял критике, звучавшей со всех сторон, и решил разрушить все созданное им за весь период существования. И делается это отнюдь не из мазохистских побуждений. Просто Microsoft хочет одним махом подчистить весь "баг-лист", любезно предоставляемый конкурентами и прочими доброжелателями. Складывается впечатление, что менеджеры проектов в Microsoft не знают одну из поговорок программистов: "старый баг лучше новых двух". Хотя перед выходом SQL Server, сначала 7.0, а потом и 2000, злые языки болтали, что ввиду больших переделок ядра SQL Server окажется глючным, вследствие чего непригодным для решения ответственных задач, а вышло все наоборот. Но тогда изменения были все-таки не такими глобальными, да и бета 1 была уже полностью работоспособной. Бета 1 VS.Net же работоспособной назвать можно, но глюков в ней предостаточно.

Ну да ладно. И что ж за баг-лист такой, что необходимо все создавать заново?

  1. Отсутствие собственного реально переносимого между платформами стандарта (типа Java).

  2. Слабая интеграция имеющихся (ну, может быть, за исключением InterDev) средств разработки с Интернетом.

  3. Сложность, неоднозначность и другие недостатки имеющейся компонентной модели (COM).

  4. Разный уровень интеграции средств разработки с COM и "напряженность между базовыми концепциями языков программирования и COM-ом".

  5. Увеличивающаяся популярность Java в ущерб популярности VB, фаворита от Microsoft.

Нет никакой ошибки в том, что в список недостатков два раза попали упоминания про Java. Политические и экономические аспекты в современном мире всегда главенствовали над технократическими.

Что же придумал Microsoft для решения этих, а заодно и других, более мелких проблем? Microsoft придумал очень много рекламных терминов, главным по частоте звучания, несомненно, является .Net, но действительно главным, можно сказать, поворотным является CLR и основанный на нем .NET Framework.

.NET Framework - это среда для создания, распространения и исполнения как обычных, так и Web-приложений. Она состоит из двух частей - Common Language Runtime и Framework-классов. В VS.Net Web- приложения получили особый статус. Теперь можно как создавать ASP-приложения, так и использовать новую идеологию Web-сервисов. Все эти новаторства объединены под общим названием ASP.NET, и подразумевают, что для создания приложений будут использоваться CLR-совместимые языки. Однако Web-приложения можно создавать и на старом добром C++. Для этого в VS.Net был добавлен новый ATL-шаблон - ATL Sever. Это шаблон, позволяющий создавать приложения а-ля ASP, но на C++. Доступ к Интернет-серверу осуществляется через специально созданный для ATL Sever ISAPI-фильтр. Собственно ASP.Net - это, грубо говоря, тоже ISAPI- фильтр, но с большей рекламой.

.NET Framework позволяет создавать замечательные web-приложения. Но он применим и для создания обычных десктоп- приложений. Если вы пишете любое ПО для Windows (используя ATL/COM, MFC, Visual Basic или просто стандартное Win32), вы найдете в .NET немало достоинств.

Для улучшения взаимодействия между языками в Microsoft .NET Framework введён языковый стандарт, Common Language Specification (CLS). CLS - это поднабор свойств языка, поддерживаемых CLR, и включающий свойства, общие для большинства объектно-ориентированных языков программирования. Если вы хотите, чтобы ваши компоненты и элементы управления можно было использовать из других языков программирования, их нужно создавать на CLR-совместимом языке, и обеспечить совместимость всех общих и частных членов с CLR.

Языки, поддерживаемые VS.Net

Изначально Microsoft включает в поставку VS.Net компиляторы для C#, Visual Basic, Managed C++ (MC++) и JScript. Сторонние разработчики уже создали .NET-компиляторы для других языков, включая: Java (Rational), Eiffel (Interactive Software Engineering and Monash University), Perl (ActiveState), Python (ActiveState), Scheme (Northwestern University), Smalltalk (Quasar Knowledge Systems), Cobol (Fujitsu), Component Pascal (Queensland University of Technology), APL (Dyalog), Standard ML (Microsoft Research- Cambridge), Mercury (University of Melbourne) и Oberon (ETH Zentrum). В спорах между COM и CORBA приводились аргументы о количестве применимых языков, но ни та, ни другая технология и близко не подходили к списку такой длины. А ведь это только начало!

Мало того, с помощью входящих в поставку библиотек можно даже создать свой CLR-совместимый язык программирования, который будет генерировать исполняемые модули. В качестве примеров поставляются три прототипа языков: smc (настоящий компилятор Managed C++, несколько упрощенный, но все же), MyC (ограниченная реализация языка C), и CLisp (ограниченная реализация языка Lisp). Интересно, что smc - это C++-проект, который компилируется на VC 6. В его readmе сказано, что в релиз-версии он будет компилироваться как на нормальном компиляторе C++, так и на самом себе! Есть пример отладчика командной строки и профайлера. Судя по всему, нас ждет увлекательный год!

Что же такое CLR?

CLR расшифровывается как "Common Language Runtime" (межъязыковый рантайм). Чтобы понять, зачем он нужен, необходимо предварительно проанализировать текущее состояние дел в COM и Java- технологиях.

Для начала приведем определение из материала <Microsoft .Net Common Language Runtime Architecture", базовой спецификации, поставляемой Microsoft.

<...Common Language Runtime управляет исполнением исходного кода после его компиляции в Microsoft Intermediate Language(MSIL), OptIL или машинные коды.Весь код на MSIL или OptIL исполняется как управляемый код (managed code); этот код исполняется в сотрудничестве с .Net Framework. .Net Framework обеспечивает предоставляет управление памятью, кросс- языковую интеграцию, обработку исключений, защиту кода и автоматическое управление сроком жизни объектов. В свою очередь, управляемый код должен предоставить в метаданных информацию, достаточную, чтобы позволить .Net Framework управлять исполнением кода.

Ключевым свойством CLR является возможность обеспечения программной изоляции приложений, исполняемых в общем адресном пространстве. Это осуществляется с помощью типо-безопасного доступа ко всем областям памяти при исполнении типо-безопасного управляемого кода. Некоторые компиляторы могут создавать MSIL-код, который не только типо-безопасен, но и поддается простой проверке на безопасность исполнения. Этот процесс называется верификацией и позволет серверам просто проверять написанные на MSIL пользовательские программы, и запускать только те, которые не будут производить небезопасных обращений к памяти. Такая независимая верификация важна для действительно масштабируемых серверов, исполняющих пользовательские программы и скрипты.>

MSIL - это некоторый язык инструкций, похожий на независимый от платформы ассемблер. Внутри CLR-совместимого исполняемого модуля помещается некоторый p-код, состоящий из MSIL-инструкций. Но с помощью утилиты ildasm из p-кода можно получить текстовое представление MSIL Оно выглядит примерно так:


/* Displays the error string according to the passed in error code.
     Error code must be one of the values declared by the enumeration. */
  .method virtual newslot famorassem hidebysig instance
     void ShowErrorText(value class ErrorCodes errorCode)
     synchronized il managed {
    // load the appropriate error string
    ldarg.0
    ldfld  class
         [.module CountDownErrorLabel.dll]ErrorLabel CountDownForm::errorLabel
    ldarg.0
    ldfld  class System.String[] CountDownForm::errorStrings
    ldarg  errorCode  // load the value of the enumeration
    ldelem.ref
    // error string on stack, display
    callvirt instance void class
     [.module CountDownErrorLabel.dll]ErrorLabel::set_Text(class System.String)
    ret
  }

Такой код можно скомпилировать обратно в исполняемый файл с помощью утилиты ilasm. Это позволяет, например, при программировании на языке, не поддерживающем полностью всех возможностей CLR, скомпилироваться во MSIL, дизассемблировать его и добавить недостающие элементы вручную. Такие широкие возможности дизассемблирования очень порадовали бы хакеров, но Microsoft предусмотрел средства, позволяющие предотвратить дизассемблирование готового модуля. К сожалению, в beta 1 это можно сделать только с помощью повторной компиляции дизассемблированного кода из командной строки. В будущем эта опция будет встроена непосредственно в компилятор. К выходу VS.Net Microsoft обещает сделать так, чтобы приложения компилировались непосредственно при инсталляции, или даже при создании инсталляторов для конкретных платформ. Пока же компиляция в машинный код происходит только при загрузке программы.

Ограничения информации о типах в COM и языках программирования

Одно из преимуществ, дарованных нам COM - динамическая загрузка компонентов. Причем загрузка экземпляров конкретных компонентов осуществляется на базе типов. Когда код загружен, программисты разрешают точки входа привязкой объектных ссылок к новым абстрактным типам. Для облегчения первого COM предоставляет CoCreateInstance как типо-ориентированную альтернативу файл-ориентированному вызову API LoadLibrary. Для облегчения последнего COM предоставляет метод QueryInterface как типо-ориентированную альтернативу символьно-ориентированному вызову API GetProcAddress. Посмотрите на следующий COM/C++ код:

IAntique *pAntique = 0;
HRESULT hr = CoCreateInstance(CLSID_Pinto, 0,
    CLSCTX_ALL, IID_IAntique, (void**)&pAntique);
if(SUCCEEDED(hr))
{
  ICar *pCar = 0;
  hr = pAntique->QueryInterface(IID_ICar, (void**)&pCar);
  if(SUCCEEDED(hr))
  {
    hr = pCar->AvoidFuelTankCollision();
    if(SUCCEEDED(hr))
    {
      hr = pAntique->Appreciate();
    }
    pCar->Release();
  }
  pAntique->Release();
}

Заметьте, что нигде нет вызовов LoadLibrary или GetProcAddress. Этот код избавлен от подробностей типа физического размещения DLL- библиотеки (мы даже не знаем, в DLL или в EXE хранится код компонента) или явного запроса адреса метода по символическому имени с последующим преобразованием адреса в указатель на функцию. Но этот код неуклюж и велик по сравнению с кодом создания экземпляра C++-класса и его приведения к базовому классу:

СPinto Antique;
CCar & Car = (CCar)Antique
Car.AvoidFuelTankCollision();
Antique. Appreciate();

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

Ключ к пониманию недостатков COM спрятан именно в первом листинге. Этот код иллюстрирует напряженность между системой типов COM и системой типов языка реализации (в данном случае, C++). Заметьте, что везде, где объектная ссылка возвращается вызывающей стороне, ее должен сопровождать GUID (в этом примере IID_IAntique или IID_ICar). Это потому, что идентификатор типа языка реализации (std::type_info в случае C++) несовместим с форматом идентификатора типа COM.

За долгие годы группа C++ в Microsoft представила несколько технологий, позволяющих сгладить разницу между системами типов C++ и COM, самой важной (хотя и хитрой) из которых были расширения языка: __uuidof и __declspec(uuid). Эти расширения позволили ассоциировать GUID (или, как его еще называют, UUID) с некоторым пользовательским типом. Компилятор MIDL при обработке IDL-файлов автоматически ассоциирует идентификатор типа (GUID) COM с символическим именем С++-типа. При использовании __uuidof код становится более типобезопасным:

IAntique *pAntique = 0;
HRESULT hr = CoCreateInstance(__uuidof(Pinto), 0,
    CLSCTX_ALL, __uuidof(pAntique), (void**)&pAntique);
if(SUCCEEDED(hr))
{
  ICar *pCar = 0;
  hr = pAntique->QueryInterface(__uuidof(pCar), (void**)&pCar);
  if(SUCCEEDED(hr))
  {
    hr = pCar->AvoidFuelTankCollision();
    if(SUCCEEDED(hr))
      hr = pAntique->Appreciate();
    pCar->Release();
  }
  pAntique->Release();
}

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

Более того, если воспользоваться возможностями самого C++, можно создать универсальные классы-обертки, еще более упрощающие жизнь программиста. Так при использовании CComPtr или поддержки COM компилятором (compiler COM support) можно написать примерно такой код:

CComPtr<IAntique> spIAntique;
HRESULT hr = spIAntique.CoCreateInstance(__uuidof(Pinto));
if(SUCCEEDED(hr))
{
  CComQIPtr<ICar> spICar(spIAntique);
  if(spIUnknown)
    hr = spICar->AvoidFuelTankCollision();
      if(SUCCEEDED(hr))
        hr = spIAntique->Appreciate();
}

Хотя этот код, несомненно, проще и безопаснее, он все же далек от идеала, причем как с точки зрения простоты и читабельности, так и с точки зрения типобезопасности. Ведь только во время исполнения программы будет точно известно, реализует объект эти интерфейсы или нет. Несмотря на использование __uuidof, чтобы заставить COM работать с С++, нужно отключить систему проверки типов С++ на время трансляции объектных ссылок COM в С++-типы. В отличие от этого, интеграция COM с виртуальной машиной Java Microsoft позволяет написать следующее:


IAntique antique = new Pinto();
ICar car = (ICar)antique;
car.AvoidFuelTankCollision();
antique.Appreciate();

Заметьте, что этот код наиболее близок к чистому коду на C++, разве что объект создается динамически, но компоненты и нельзя создавать на стеке.

Это пример, как и самый первый, загружает компонент на основании его типа, а не имени файла. Оба примера разрешают точки входа в компонент, используя средства приведения типов, а не символьные точки входа. Первое различие - Microsoft VM for Java выполняет огромную работу по состыковке системы типов Java с системой типов COM, и программистам не приходится делать этого вручную. Это и есть движущая сила для новой платформы - обеспечить универсальную среду исполнения компонентов, которой сможет воспользоваться любой компилятор, средство или служба. Новая среда - это и есть CLR!

А теперь взгляните на то, как выглядел аналогичный код в VB 6:

Dim thePinto as new Pinto, antique as IAntique
Set antique = thePinto
Dim car as ICar
Set car = antique
car.AvoidFuelTankCollision
antique.Appreciate

Сравните этот код с Java-кодом. На первый взгляд они идентичны, но не спешите делать окончательные выводы. Заметьте, что оператор Set в VB 6 кардинально отличается от приведения типов в Java. По сути Set аналогичен вызову QueryInterface (QI). Результат выполнения QI будет известен только на этапе выполнения, а приведение типа к базовому классу (интерфейсу) можно проверить еще на стадии компиляции.

Типобезопасность - это еще один постулат CLR.

Итак, CLR - это рантайм-среда, призванная упростить и обезопасить работу с компонентами для любого совместимого с ней средства или языка. Замечательно, скажете вы, но зачем же было ломать все и вся? Не лучше ли было подправить спецификацию COM, уточнить требования, предъявляемые к языкам программирования, ведь, например, VB совсем чуть-чуть не удовлетворяет этим требованиям? Это была бы, хотя и бурная, но эволюция. А так снова революция с неизбежным разрушением всего старого и с еще более неизбежной "наклепкой" всего нового. Причем это новое - не маленькая фитюлька, а то самое ВСЁ. На этот вопрос можно ответить, если задаться вопросом: а что, собственно, надо Microsoft? Постепенно улучшая свое программное обеспечение, с каждой новой версией доводить его до совершенства, тем самым поддерживать своих пользователей и зарабатывать уважение. Уважение?! А зачем оно Microsoft? Естественно, Microsoft жаждет не уважения, а господства, полного захвата рынка. Причем эти планы должны выполняться в среде открытой, практически честной конкуренции, где Microsoft может полагаться только на деньги, решительность и напористость.

При таком раскладе эволюция смерти подобна. Надо ломать при малейшем подозрении на несоответствие высоким идеалам, и строить заново. А уж если строить, то, конечно же, новый мир.

Есть и другое объяснения того, почему Microsoft так решительно отвергла все свои наработки и взялась за новую лучшую концепцию. Лучше всего эту версию изложил Ron Burk из WDJ. Вот его версия:

История программных революций от Microsoft, вкратце: Сначала были Windows API и DLL Hell. Революцией ?1 было DDE - помните, как ссылки позволили нам создавать статусные строки, отражающие текущую цену акций Microsoft? Примерно тогда же Microsoft создала ресурс VERSION INFO, исключающий DLL Hell. Но другая группа в Microsoft нашла в DDE фатальный недостаток - его писали не они!

Для решения этой проблемы они создали OLE (похожее на DDE, но другое), и я наивно вспоминаю докладчика на Microsoft-овской конференции, говорящего, что скоро Windows API перепишут как OLE API, и каждый элемент на экране будет ОСХ-ом. В OLE появились интерфейсы, исключающие DLL Hell. Помните болезнь с названием "по месту", при которой мы мечтали встроить все свои приложения в один (возможно, очень большой) документ Word? Где-то в то же время Microsoft уверовала в религию С++, возникла MFC решившая все наши проблемы еще раз.

Но OLE не собиралась, сложа руки смотреть на это, поэтому оно заново родилось под именем COM, и мы внезапно поняли, что OLE (или это было DDE?) будет всегда - и даже включает тщательно разработанную систему версий компонентов, исключающую DLL Hell. В это время группа отступников внутри Microsoft обнаружила в MFC фатальный недостаток - его писали не они! Они немедленно исправили этот недочет, создав ATL, который как MFC, но другой, и попытались спрятать все замечательные вещи, которым так упорно старалась обучить нас группа COM. Это заставило группу COM (или это было OLE?) переименоваться в ActiveX и выпустить около тонны новых интерфейсов (включая интерфейсы контроля версий, исключающие DLL Hell), а заодно возможность сделать весь код загружаемым через броузеры, прямо вместе с определяемыми пользователем вирусами (назло этим гадам из ATL!).

Группа операционных систем громким криком, как забытый средний ребенок, потребовала внимания, сказав, что нам следует готовиться к Cairo, некой таинственной хреновине, которую никогда не могли даже толком описать, не то, что выпустить. К их чести, следует сказать, что они не представляли концепции "System File Protection", исключающей DLL Hell. Но тут некая группа в Microsoft нашла фатальный недостаток в Java - её писали не они! Это было исправлено созданием то ли J, то ли Jole, а может, и ActiveJ (если честно, я просто не помню), точно такого же как Java, но другого. Это было круто, но Sun засудило Microsoft по какому-то дряхлому закону. Это была явная попытка задушить право Microsoft выпускать такие же продукты, как у других, но другие.

Помните менеджера по J/Jole/ActiveJ, стучащего по столу туфлей и говорящего, что Microsoft никогда не бросит этот продукт? Глупец! Все это означало только одно - недостаток внимания к группе ActiveX (или это был COM?). Эта невероятно жизнерадостная толпа вернулась с COM+ и MTS наперевес (может, это стоило назвать ActiveX+?). Непонятно почему к MTS не приставили COM или Active или X или + - они меня просто потрясли этим! Они также грозились добавить + ко всем модным тогда выражениям. Примерно тогда же кое-кто начал вопить про Windows DNA (почему не DINA) и "Windows Washboard", и вопил некоторое время, но все это почило раньше, чем все поняли, что это было.

К этому моменту Microsoft уже несколько лет с нарастающей тревогой наблюдала за интернет. Недавно они пришли к пониманию, что у Интернет есть фатальный недостаток: ну, вы поняли. И это приводит нас к текущему моменту и технологии .NET (произносится как "doughnut" (пончик по-нашему), но по-другому), похожей на Интернет, но с большим количеством пресс-релизов. Главное, что нужно очень четко понимать - .NET исключает DLL Hell.

В .NET входит новый язык, C#, (выясняется, что в Active++ Jspresso был фатальный недостаток, от которого он и помер). .NET включает виртуальную машину, которую будут использовать все языки (видимо, из-за фатальных недостатков в процессорах Интел). .NET включает единую систему защиты (есть все-таки фатальный недостаток в хранении паролей не на серверах Microsoft). Реально проще перечислить вещи, которых .NET не включает. .NET наверняка революционно изменит Windows-программирование... примерно на год.

Еще одну версию причин ломки старого можно прочесть в статье про C#.

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

Новаторства CLR

CLR - новая реализация идей, впервые в общедоступной форме изложенных в модели программирования COM. В CLR программисты загружают компоненты по типу, а не имени файла; разрешают точки входа, используя операции приведения типов, а не символьные точки входа. Модель программирования CLR в фундаментальном отношении не отличается от модели программирования COM. Итак, если модель программирования CLR так похожа модель программирования COM, зачем она вообще нужна? Ответ лежит в реализации.

По мнению группы разработчиков CLR из Microsoft, корень всех проблем кроется в эволюции информации о типах COM. Первые COM- интерфейсы вообще не имели универсального описания. Потом ввели IDL. Он был предназначен для генерации proxy/stub-DLL, которая, в свою очередь, использовалась dk вызовов методов интерфейсов между процессами или удаленными компьютерами.

Параллельно развивалась ветка VB, в которой появились бинарные описания компонентов под названием OLB (Object Library). Впоследствии OLB превратились в TLB (Type Library) но сути своей от этого не поменяли. Сначала в TLB-описаниях можно было использовать только очень ограниченный набор подтипов, что не давало применять их как единый стандарт описания компонентов. Так что существовало три вида описания типов: IDL, библиотеки типов и генерируемые MIDL /Oicf-строки, вставляемые внутрь proxy/stub-DLL. Ни один из этих трех форматов не стал абсолютным стандартом, и в одном формате можно записать информацию, которую невозможно представить в двух других. Это усложняло жизнь как разработчикам инфраструктуры COM, так и обычным программистам, создающим приложения из компонентов. Со временем библиотеки типов стали стандартом де-факто, но возможность обходиться без них, а иногда и вообще обходиться без описания типов, создавала некоторую неопределенность. Так, описание большинства низкоуровневых интерфейсов доступно только в виде заголовочных файлов C++, и, значит, недоступно для разработчиков, использующих более высокоуровневые языки типа VB или Java (например, OLE 2 API, или OLE DB API).

К тому же библиотеки типов COM описывают только типы, экспортированные из компонента. Информация об экспортированных типах позволяет средствам, работающим с COM, реализовать возможности наподобие IntelliSense в Visual Basic или декларативной архитектуры сервисов COM+. Это замечательно, но, по мнению господ из Microsoft, этого недостаточно. В COM не было возможности определить, от каких внешних компонентов зависит компонент. В то время как COM здорово поработал, чтобы сделать dumpbin.exe /exports устаревшим, он ничего не сделал, чтобы заменить dumpbin.exe /imports. Отсутствие информации о зависимостях затрудняет верное определение состава DLL (и их версий), необходимых для нормальной работы компонента.

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

Главное достоинство CLR - всепроникающая, расширяемая, качественная информация о типах. В CLR все типы - а не только экспортированные из компонента - имеют runtime-описания типов. В CLR вы можете перейти к любому объекту и исследовать каждый аспект его определения типов, включая его представление. Это фундаментальный отход от классического COM, где открытые (public) интерфейсы были доступны, но представление объекта было скрыто.

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

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

Сборки. Сборки, или в оригинале Assembly - это логическая коллекция типов, описывающая объекты, которые могут быть реализованы в нескольких модулях (DLL или EXE). Сборка определяет область имен (типов), снимая надобность в GUID-ах для каждого типа. Так как имена сборок не могут повторяться, коллизии имен типов случиться также не может.

Для компонентов, используемых в рамках одного приложения, имена файлов сборки достаточно уникальны. CLR-сборки, в которых содержатся разделяемые компоненты (используемые совместно несколькими приложениями), можно присвоить <строгие имена> (strong names). Строго именованная сборка имеет 128-битный публичный ключ, который идентифицирует разработчика компонента. Когда программа-клиент связывается со строго именованной сборкой, 64- битный хеш публичного ключа сохраняется в метаданных этой программы. Во время исполнения публичный ключ сборки сравнивается с 64-битным хешем, хранящимся в метаданных клиента, обеспечивая загрузку нужной сборки.

В COM для обеспечения уникальности почти каждому типу соответствует 128-битный GUID. В CLR каждая сборка имеет 128- битный публичный ключ, что, в сочетании с локально уникальными символьными именами типов, обеспечивает глобальную уникальность описания типов. Оба способа дают примерно одинаковый эффект, но способ CLR позволяет избежать работы с GUID- ами внутри приложений.

Единый стандарт обмена метаданными. Как уже говорилось раньше, информация о типах в COM передавалась в текстовой (IDL, заголовочные файлы) или в бинарной (TLB) форме. В CLR, напротив, информация о типах всегда передается в одной и той же документированной бинарной форме. Все работающие с CLR средства и компиляторы выдают и принимают метаданные в этом формате. Так, при определении набора интерфейсов разработчик может использовать свой любимый язык программирования и компилятор для создания описаний типов, вместо того, чтобы использовать один синтаксис (IDL) при описании типов и другой (например, C++ или Visual Basic) при их реализации.

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

Например, рассмотрим следующий COM IDL:

[ uuid(ABBAABBA-ABBA-ABBA-ABBA-ABBAABBAABBA) ]
library MyLib
{
  importlib("stdole32.tlb");
  [ uuid(87653090-D0D0-D0D0-D0D0-18121962FADE) ]
  interface ICalculator : IUnknown
  {
    HRESULT Add([in] double n,
                [in, out] VARIANT_BOOL *round,
                [out] VARIANT_BOOL *overflow,
                [out, retval] double *sum);
  }
}

Эквивалентный тип CLR на С# (это новый язык программирования, который претендует стать основным языком VS.Net, о нем мы еще подробно поговорим позже) будет выглядеть так:

namespace MyLib
{
  interface ICalculator
  {
    double Add(double n, ref bool round, out bool overflow);
  }
}

Если поместить это описание в файл, то его можно будет скомпилировать с помощью компилятора С#, следующей командной строкой:

csc.exe /t:library /out:mylib.dll mylib.cs

Полученное бинарное описание можно импортировать, например, в VB, используя ключ компилятора "/r":

vbc.exe /r:mylib.dll program.vb

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

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

namespace MyLib
{
  public interface ICalculator
  {
    double Add(double n, ref double round, out double overflow);
  }
}

Код, создающий сборку, реализован на C#, языке, похожем на C++ или Java. Мы надеемся, что у вас не возникнет проблем с пониманием кода:

using System;
using System.Reflection;
using System.Reflection.Emit;

public class emititf
{
  // Точка входа программы (объектно-ориентированный аналог функции main в С/C++)
  public static int Main(String[] argv)
  {
    // Создаем новую сборку
    AssemblyBuilder ab = DefineNewAssembly();
    // Создаем определение нового интерфейса ICalculator внутри новой сборки
    TypeBuilder tb = DefineICalculator(ab);
    // Добавляем описание метода "Add" к описанию интерфейса ICalculator
    MethodBuilder method = DefineAddMethod(tb);
    // Добавляем описание параметров
    DefineAddParameters(method);
    // Создаем тип
    Type t = tb.CreateType();
    // Записываем сборку в файл "mylib.dll"
    ab.Save("mylib.dll");
    return 0;
  }

  // Создает сборку с именем "mylib"
  static AssemblyBuilder DefineNewAssembly()
  {
    // Новая сборка создается в рамках текущего AppDomain-а
    AppDomain current = AppDomain.CurrentDomain;
    // Новая сборка нуждается в имени. Назначаем ей не строгое имя!
    AssemblyName an = new AssemblyName();
    an.Name = "mylib";
    // DefineDynamicAssembly завершает работу по созданию сборки
    return current.DefineDynamicAssembly(an, AssemblyBuilderAccess.Save);
  }

  // Создает новое описание интерфейса с именем "MyLib.ICalculator"
  static TypeBuilder DefineICalculator(AssemblyBuilder ab)
  {
    // Все описания типов находятся в модуле, определенном для нашей сборки
    ModuleBuilder mb = ab.DefineDynamicModule("mylib.dll", "mylib.dll");
    // Все описания интерфейсов должны быть помечены как Interface и Abstract
    TypeAttributes attrs = TypeAttributes.Interface|TypeAttributes.Abstract;
    // public-интерфейсы должны быть также помечены как Public
    attrs |= TypeAttributes.Public;
    // DefineType завершает работу по созданию описания для интерфейса
    return mb.DefineType("MyLib.ICalculator", attrs);
  }

  // Создает новое описание методов "double Add(double, ref double, out double)"
  static MethodBuilder DefineAddMethod(TypeBuilder itf)
  {
    // Методы интерфейса должны быть помечены как abstract, virtual и public
    MethodAttributes attrs = MethodAttributes.Public
      | MethodAttributes.Abstract | MethodAttributes.Virtual;

    // Метод определяется по имени и описанию (его параметрам)

    // Создаем описание возвращаемого значения
    Type resultType = typeof(double);
    // Создаем описание параметров
    Type[] paramTypes = new Type[]
    {
      typeof(double),
      Type.GetType("System.Boolean&"),
      Type.GetType("System.Boolean&")
    };
    // DefineMethod завершает работу по созданию описания метода
    return itf.DefineMethod("Add", attrs, resultType, paramTypes);
  }

  // Задает имя параметров и их последовательность
  static void DefineAddParameters(MethodBuilder method)
  {
    // 1-й и 2-й параметры не нуждаются в специальных атрибутах
    method.DefineParameter(1, ParameterAttributes.None, "n");
    method.DefineParameter(2, ParameterAttributes.None, "round");
    // Параметру 3 нужно задать флаг Out
    ParameterBuilder pb = method.DefineParameter(3,
                          ParameterAttributes.Out, "overflow");
    // 3-му параметру также необходимо задать атрибут Interop.Out
    AddInteropOutAttribute(pb);
  }

  // Задает атрибут Interop.Out для параметра
  static void AddInteropOutAttribute(ParameterBuilder param)
  {
    // Конструкторы идентифицируют пользовательские атрибуты 
    Type attrtype = typeof(System.Runtime.InteropServices.OutAttribute);
    ConstructorInfo outattrctor = attrtype.GetConstructors()[0];
    // CustomAttributeBuilder сериализует аргументы конструктора
    CustomAttributeBuilder outattr =
                  new CustomAttributeBuilder(outattrctor, new object[0]);
    // Всю работу выполняет SetCustomAttribute
    param.SetCustomAttribute(outattr);
  }
}

Определение типа, сгенерированное этой программой, неотличимо от производимого компилятором С# (Visual Basic, C++, Perl, Python, или любого другого совместимого с CLR).

Метаданные обязательны. В COM можно было определить на С++ частные интерфейсы, не описывая их в IDL или библиотеке типов. Это позволяло создать недокументированную лазейку в свой объект. В CLR это сделать не удастся.

В CLR все типы должны быть документированы через информацию о типах, включая private-типы (скрытых типов), не рассчитанные на внешнее использование. Для поддержки скрытых типов компонента метаданные CLR позволяют пометить типы (и их отдельные члены) как доступные только изнутри описываемой сборки. Например, следующий интерфейс виден только из сборки, в которой он определен:

internal interface IBob
{
  void hibob();
}

Напротив, следующий интерфейс виден любой сборке:

public interface IBob
{
  void hibob();
}

Метаданные полностью расширяемы. Информацию о типах COM можно было расширить за счет пользовательских атрибутов. На практике это можно было сделать только через IDL, или прямой модификацией TLB. Пользовательские атрибуты в COM ассоциировали пару GUID/VARIANT с библиотекой, интерфейсом, CoClass-ом, методом, параметром, структурой или полем. Увы, VB и многие другие средства разработки не предоставляли путей задания или чтения пользовательских атрибутов, так что эта возможность бесполезна для большинства COM-разработчиков.

Информация о типах CLR расширяема из любого языка. Пользовательские атрибуты в CLR - это просто сериализованные вызовы конструктора. Разные языки имеют разный синтаксис для применения атрибутов. В С# можно просто вставить вызов конструктора в скобках, перед каким либо определением:

[ Color("Red") ]
class MyClass { }

В Visual Basic.NET можно вставить вызов конструктора в < >:

Class <Color("Red")> MyClass
End Class

В любом случае метаданные будут указывать, что цвет MyClass - красный.

Чтобы определить новые пользовательские атрибуты, следует создать новый класс, унаследовав его от System.Attribute, и реализовать в этом классе public-конструктор:

using System;

[ AttributeUsage(AttributeTargets.All) ]
public class ColorAttribute : Attribute
{
  public String color;
  public ColorAttribute(string c) { color = c;}
}

Атрибут AttributeUsage говорит, к чему будет применим новый атрибут - к классу, методу, свойству и т.п. Если класс атрибута заканчивается на Attribute, то атрибут можно будет использовать с или без этого суффикса. Например, следующие два примера идентичны, хотя последний и реже используется:


[ Color("Red") ] class MyClass { }
[ ColorAttribute("Red") ] class MyClass { }

Пользовательские атрибуты доступны в runtime через reflection-механизм. Код, приведенный выше, определяет, какой цвет присвоен данному классу и присвоен ли он вообще:

using System;
String GetColor(Object o)
{
  Type t = o.GetType();  // Получаем тип объекта
  // Получаем тип необходимого атрибута
  Type at = typeof(ColorAttribute);
  // Получаем все атрибуты этого типа
  Attribute[] rga = t.GetCustomAttributes(at);
  // Выходим, если атрибуты не заданы
  if(rga.Length == 0)
    return null;
  // Иначе извлекаем первый атрибут
  ColorAttribute color = (ColorAttribute)rga[0];
  return color.color;
}

Атрибуты - это мощный и универсальный механизм расширения метаинформации.

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

Физическое размещение непрозрачно. CLR основывается на совершенной информации о типах. Это значит, что ни один тип не остается неописанным, вплоть до типов и имен членов данных типа. Как ни странно, физическая раскладка памяти для данного типа и его экземпляров полностью скрыты. Смещения, размеры и выравнивание членов данных неизвестны. Если нужно экспортировать CLR-объект за пределы среды исполнения, CLR создает COM-Callable Wrapper (CCW), который работает переходником между классической конвенцией вызовов COM, основанной на IUnknown и __stdcall во внутреннем формате вызовов CLR-среды.

Очень жалко, что у господ новаторов из Microsoft не хватило смелости попросту расширить стандарт COM и обеспечить поддержку этих улучшений в VS.Net. Так что всем, кто использует COM в своей работе и хочет шагать в ногу со временем, придется изучить еще и CLR. Хотя для разработчиков, чей разум не замутнен тонкостями COM, интеграция CLR и COM будет выглядеть довольно прозрачно. Чтобы использовать COM-объект в своих .Net-приложениях, необходимо будет только зарегистрировать этот объект, примерно так, как это делалось в VB 6. Для низкоуровневых программистов это значит, что для динамической генерации новых типов и перехвата доступа к уже существующим экземплярам нужна новая техника. System.Reflection.Emit дает возможность генерации новых типов; System.Runtime.Remoting дает возможность перехвата вызовов к существующим типам.

Иерархия типов с одним корнем! Почти во всех традиционных языках программирования есть <иерархия классов> и иерархия <простых типов данных>, таких, например как int, char и т.п. В COM имеется корневой тип для объектных ссылок - IUnknown. Все остальные типы отражаются в универсальном типе данных - VARIANT. И хотя в принципе VARIANT и может хранить объектные ссылки, но назвать его корнем для иерархии типов язык не поворачивается. Это подтверждается тем, что в VB 6 универсальный параметр можно было описать или как Object (то есть IDispatch), или как Variant. Да и у варианта были некоторые проблемы с информацией о типах при хранении некоторых типов данных. Например, при помещении в вариант значения Enum значение превращалось в целое число, и не было никаких возможностей узнать, к какому типу данных принадлежит значение.

В системе типов CLR нет ни IUnknown, ни VARIANT, ни (по сути) простых типов данных. Вместо этого все типы происходят от System.Object. Да, это значит, что простые типы, такие, как int или double, происходят от System.Object и являются настоящими объектами. Причем это справедливо для всех без исключения языков, поддерживающих .Net. В VB.Net и C# это становится частью языка, а в C++ осуществляется через специальное расширение - (C++ - единственный язык, который не был полностью уничтожен перед реинкарнацией. С его помощью можно будет создавать приложения сразу в бинарном виде и без CLR. По сути, родился новый язык. В новостных группах его уже окрестили MC++.). Функциональность поля vt структуры VARTYPE поглощена методом System.Object.GetType. И теперь она доступна из любого языка. Так, код на C++:

void Process(VARIANT value)
{
  switch (value.vt)
  {
    case VT_I2:
ProcessAsShort(value.iVal);     break;
case VT_R8:
ProcessAsDouble(value.dblVal);  break;
    case VT_BSTR:
ProcessAsString(value.bstrVal); break;
    case VT_UNKNOWN:
    case VT_DISPATCH:
    {
      CComQIPtr<IFoo> spFoo(value.punkVal);
      if(spFoo)
        ProcessAsFoo(spFoo);
      else
      {
        CComQIPtr<IBar> spBar(value.punkVal);
        if(spBar)
          ProcessAsBar(spBar);
      }
      break;
    }
  }
}

можно заменить на следующий код на C#:

void Process(System.Object value)
{
  if(value is short)
      ProcessAsShort((short)value);
  else if(value is double)
      ProcessAsDouble((double)value);
  else if(value is System.String)
      ProcessAsString((System.String)value);
  else if(value is IFoo)
      ProcessAsFoo((IFoo)value);
  else if(value is IBar)
      ProcessAsBar((IBar)value);
}

Заметьте, что C#-код использует специальные операторы для проверки соответствия типов, и что оператор приведения типов выполняет приведение как для объектных ссылок, так и для простых типов.

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

Классы с публичными членами. В COM классы были именованными реализациями одного или более интерфейсов. И точка. Классам не позволялось иметь методы или свойства, не принадлежащие какому- либо интерфейсу. В CLR у классов могут быть public-члены. Это позволяет использовать методы как для реализации интерфейсов компонентов, так и в качестве одиноких методов в классах конечных приложений.

Множественное наследование. В CLR есть две новации. Одна - реализация множественного наследования для интерфейсов. По всей видимости, это расширение сделано с целью избавиться от QueryInterface. QueryInterface должен быть заменен на операцию приведения типов. С точки зрения идеологии красивое решение, но не приведет ли оно к проблемам интеграции с COM? Вторая новация связана с запретом множественного наследования. Множественное наследование наряду с шаблонами и переопределением операторов были <визитной карточкой> C++. Похоже, времена меняются. Множественное наследование не запрещено в чистом C++. Но его использование ограничено в MC++. VB.Net и C# вообще не поддерживают множественного наследования, но об этом мы еще поговорим при рассмотрении этих языков.

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

CLR-делегаты используются для привязки вызова метода объекта к переменной. Это гибрид указателей на функции в С или, точнее, на функции-члены в С++. Функционально делегаты похожи на интерфейсы с единственным методом, с тем отличием, что целевому типу нужно только иметь метод, чья сигнатура соответствует сигнатуре делегата.

Посмотрите на следующий пример:


public delegate void Hook();
public class MyClassEx
{
  Hook hook;
  public void RegisterHook(Hook h) { hook = h; }
  public void f()
  {
    if (hook != null)
      hook(); // callback-вызов
  }
}

Заметьте, что вместо интерфейса определяется простой тип-делегат. Заметьте ещё, что вызов перехватчика использует синтаксис, похожий на указатели функций С/C++. Разные языки VS.Net имеют различный синтаксис работы с делегатами, но внутренний механизм един.

Чтобы написать перехватчик MyClassEx, нужно просто реализовать метод, чья сигнатура соответствует сигнатуре делегата Hook:

public class MyHookEx
{
  public void AnyNameIWant()
  {
    System.Console.WriteLine("Hook вызван!");
  }
}

Обратите внимание - MyHookEx не имеет явных ссылок на тип-делегат Hook. Вместо этого у него есть метод с сигнатурой, соответствующей сигнатуре Hook, что делает этот тип кандидатом на регистрацию в качестве обработчика для MyClassEx. Чтобы зарегистрировать обработчик, вам нужно только создать экземпляр нового делегата, основанного на типе-делегате Hook:

MyClassEx mc = new MyClassEx();
mc.RegisterHook(new Hook(MyHookEx.AnyNameIWant));
mc.f(); // вызывает MyHookEx.AnyNameIWant()

Обратите внимание на несколько необычный синтаксис инициализации делегата. Компилятор C# скрыто генерирует код инициализации нового объекта-делегата с маркером метаданных для означенного метода.

На таком же принципе построена система обработки событий. Но регистрация обработчика подразумевает возможность подключения нескольких обработчиков событий для одного источника. В C# такое подключение делается с помощью оператора <+=>.

Полная поддержка аспектно-ориентированного программирования. MTS и COM+ представили массам аспектно- ориентированное программирование, позволив разработчикам переместить независимые от логики приложения аспекты из исходного кода в декларативные атрибуты. MTS и COM+ ввели также понятие как контекст для управления областью исполнения объекта. Контексты подразделяют процессы и содержат упорядоченную коллекцию именованных контекстных свойств, контролируемых атрибутами класса наподобие Synchronization, ThreadingModel, Transaction и т.д. CLR продвигает эту концепцию дальше и значительно расчищает реализацию.

В MTS и COM+ наборы атрибутов класса и свойств контекста были фиксированными. В CLR можно определить новый атрибут класса, вносящий новые свойства в контекст объекта. Это позволяет стороннему разработчику определять сервисы, поведение которых будет задаваться через атрибуты, контекстные свойства и перехват.

В COM+ объектные ссылки были ограничены контекстом, и для использования объектных ссылок в глобальных переменных нужна была Global Interface Table (GIT). В CLR областью действия объектных ссылок является AppDomain (эквивалент процесса в CLR), и объектные ссылки можно использовать в глобальных переменных без всякого маршалинга.

В COM+ все объекты прикреплены к контексту, в котором были инициализированы, и по умолчанию маршалятся в другие контексты по ссылке. В результате даже те объекты, которым не нужны сервисы типа транзакций или декларативной безопасности, оказываются замкнуты в конкретном контексте процесса. Чтобы избежать этого, разделяемые объекты часто агрегируют freethreaded-маршалер (FTM), делающий их контекстно-независимыми при вызове изнутри процесса, но маршалящий по значению за границы процесса. Объекты, требующие межпроцессного доступа через копию объекта, вместо proxy обычно реализуют IMarshal для обеспечения семантики маршалинга по значению.

В CLR по умолчанию используется маршалинг по значению между AppDomains и контекстная независимость внутри AppDomains, см. рисунок 1.

Рис. 1. Объект CLR

Это значит, что по умолчанию объект никогда не получит proxy. Вместо этого внутри исходного AppDomain-а доступ к объекту будет осуществляться напрямую, а доступ между AppDomain-ами производится путем копирования объекта. Как показано на рисунке 2, классы, унаследованные от System.MarshalByRefObject, контекстно-независимы внутри AppDomains, но маршалятся по ссылке между AppDomain-ами (грубый говоря, это эквивалент агрегирования FTM в COM+).

Рис. 2. MarshalByRefObject в CLR

Объекты, наследующие функциональность от System.ContextBoundObject, приколоты к контексту, в котором они инициализированы (см. рисунок 3), точно так же, как по умолчанию в COM+.

Рис. 3. ContextBoundObject в CLR

И все это доступно без явного кодирования, просто изменением базового класса.

Подмена концепций или <Король пока жив, но да здравствует новый король!>

Итак, наш небольшой анализ показывает, что ориентация Microsoft на COM заменяется ориентацией на CLR. Но остается вопрос, так что же, COM умер? По сути, COM жив и жалеет всех живых. Во-первых, у CLR пока нет собственных средств межмашинного взаимодействия. Такое взаимодействие осуществляется с помощью старого доброго COM. Во-вторых, большое количество современных продуктов целиком и полностью ориентировано на COM и ActiveX.

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


 ВОПРОС - ОТВЕТ

Как отобразить индикатор прогресса на строке состояния?

Чтобы решить эту задачу, достаточно вспомнить, что строка состояния - это самое обыкновенное окно, на котором можно создавать дочерние окна. В данном случае нам потребуется создать контрол типа progress bar, задав для него стиль WS_CHILD и строку состояния в качестве родительского окна. Когда индикатор прогресса создан, мы работаем с ним, а затем уничтожаем его.

Следующий фрагмент демонстрирует создание индикатора прогресса на строке состояния.

// Получаем указатель на главное окно.
CMainFrame *pFrame = dynamic_cast<CMainFrame *>(AfxGetMainWnd());

// Находим объект строки состояния.
CStatusBar &sb = pFrame->m_wndStatusBar;

// Определяем прямоугольник, в котором будет размещаться индикатор прогресса.
// В нашем примере он будет занимать всю первую панель строки состояния.
CRect rect;
sb.GetItemRect(0, rect);

// Создаём индикатор прогресса.
CProgressCtrl pc;
pc.Create(WS_CHILD | WS_VISIBLE, rect, &sb, 0);
pc.SetRange(0, 100);
pc.SetPos(0);
pc.SetStep(1);

// Имитируем выполнение длительного процесса.
for(int i=0; i<100; i++)
{
    Sleep(30);
    pc.StepIt();
}

// Уничтожаем индикатор прогресса.
pc.DestroyWindow();
ПРИМЕЧАНИЕ
В этом фрагменте используется приведение типов с помощью dynamic_cast. Этот оператор в свою очередь использует механизм RTTI (информацию о типах на этапе выполнения). Поэтому необходимо включить поддержку RTTI, чтобы приведённый фрагмент мог работать в вашей программе. Поддержка RTTI включается, если задать компилятору ключ /GR. В настройках проекта (Project->Settings) ему соответствует настройка Enable Run-Time Type Information (RTTI) (вкладка C/C++, категория C++ Language).


Это все на сегодня. До встречи в новом году!

Алекс Jenter   jenter@rsdn.ru
Duisburg, 2001.    Публикуемые в рассылке материалы принадлежат сайту RSDN.

| Предыдущие выпуски     | Статистика рассылки


RLE Banner Network

http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу

В избранное