Чтение онлайн

ЖАНРЫ

Программирование на Visual C++. Архив рассылки

Jenter Алекс

Шрифт:

Мало того, с помощью входящих в поставку библиотек можно даже создать свой 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++-класса и его приведения к базовому классу:

CPinto 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 с символическим именем C++-типа. При использовании 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;

Поделиться с друзьями: