скрыть

скрыть

  Форум  

Delphi FAQ - Часто задаваемые вопросы

| Базы данных | Графика и Игры | Интернет и Сети | Компоненты и Классы | Мультимедиа |
| ОС и Железо | Программа и Интерфейс | Рабочий стол | Синтаксис | Технологии | Файловая система |



Google  
 

Delphi for DotNet – первый взгляд



Автор: Михаил Полюдов
The RSDN Group
Источник: RSDN Magazine #2
Содержание.
Комплект поставки.
Что нового в Delphi .NET
Приложение Delphi.NET
Резюме

Введение.

Такое событие, как выпуск новой, седьмой, версии Delphi, не прошло незамеченным для большинства разработчиков. Примечательно, что в поставку этого программного продукта включен дистрибутив Delphi .NET Developer Preview. Вот об этом новом продукте (вернее – его «примерочной» версии) и пойдет речь в данной статье.

Прежде всего стоит остановиться на комплекте поставки нового программного продукта.

Комплект поставки.

В поставку входят:

  • два компилятора командной строки dccil.exe и dccasp.exe. Реально это один и тотже исполняемый файл переименованный и скопированный в другую директорию.
  • модули импорта стандартных сборок CLR.
  • небольшое количество документации в формате HTML.
  • лицензия и рекомендации по распространению. В рекомендациях по распространению написано только то, что оно запрещено :).

К счастью, на Borland Developer Network (http://bdn.borland.com) имеется открытое дополнение, которое позволяет использовать компилятор Delphi .NET из IDE Delphi 7. Это дает возможность работать с привычным интерфейсом и подсветкой синтаксиса (Правда, не весь синтаксис подсветится правильно, а уж о работе Code Completion и речи быть не может. Почему - будет понятно позже).

Основные цели Borland при разработке данного продукта.

Можно предположить, что при разработке прототипа Borland стремился:

  • Создать CLR-совместимый компилятор языка Object Pascal.
  • Обеспечить возможность использования собственных библиотек в новом окружении с минимально необходимыми изменениями со стороны конечного разработчика :).
  • Добиться (хотя бы ограниченной) совместимости нового компилятора с unmanaged-версиями Delphi на уровне исходных текстов. Некоторые ограничения неизбежны в силу наличия таковых ограничений в самой платформе .NET.

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

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

Некоторую проблему для Borland представляет наличие встроенной в платформу .Net библиотеки Windows.Forms. Эта библиотека содержит функциональность, аналогичную достаточно большому сегменту библиотеки VCL, но не является ее полным аналогом. Borland должен обеспечить возможность компиляции старого кода с использованием Windows.Forms. Сейчас еще трудно оценить, насколько выполнимы обещания Borland. Но пока что все выглядит довольно оптимистично.

Добиться совместимости с VCL и при этом не сдублировать код Windows.Forms – крайне сложная задача. Однако именно ее и пытается решить Borland. В качестве решения проблемы Borland планирует использовать новую (для него) возможность – helper-классы, речь о которых пойдет ниже.

Другая проблема возникает там, где используется так называемый небезопасный код. Такие участки кода придется переписывать при переходе под новую версию компилятора. В принципе, MSIL может выдержать всё, что угодно, так как по сути является платформно-независимым ассемблером. Но Microsoft ввел такое понятие, как "безопасный" код. Во многих случаях применение небезопасного кода не допускается средой исполнения. В основном это связано с защитой. Так, при скачивании кода из Интернет происходит его верификация, и, если код содержит небезопасные фрагменты, или использует небезопасные с точки зрения системы защиты вызовы методов, он просто не допускается к исполнению. Borland, по крайней мере пока, декларирует, что Delphi.Net будет компилировать исключительно безопасный код. В компилятор Delphi 7 уже встроено предупреждение о небезопасных (unsafe) участках кода.

Еще одна проблема в том, что компилятор Delphi (претендующий на компиляцию исключительно безопасного кода) не имеет права оперировать размерами данных и их физическим расположением в памяти. Дело в том, что CLR полностью берет на себя управление памятью и предоставляет программисту высокоуровневый API, основанный на информации о типах. Манипуляции с памятью, в том числе с размерами блоков памяти, допускаются только в небезопасном коде, а стало быть, Delphi не поддерживаются.

Вот так писать в Delphi будет нельзя:

begin
  Stream.Write(IntArr[0], 4 * ArrLen);
end;
begin
  Stream.Read(IntArr[0], sizeof(integer) * ArrLen);
end;

Для работы с теми же массивами в .Net Framework предназначены специальные методы, позволяющие сериализовать содержимое массива как единый объект. Необходимость в unsafe-коде практически исчезает, хотя иногда это приводит к снижению эффективности кода.

Что нового в Delphi .NET Что такое .NET

О платформе .NET, а точнее - о CLR (Common Language Runtime) есть очень хорошая статья Владислава Чистякова, опубликованная на компакт-диске к предыдущему номеру нашего журнала. В других статьях на www.rsdn.ru можно найти материалы по другим аспектам .Net.

В этой статье я не буду подробно рассматривать платформу .NET. Здесь будет рассмотрено несколько вопросов, интересующих Delphi -программистов, рассматривающих вопрос о переходе на новую платформу.

Изменения языка в Delphi.NET

Как уже говорилось, главная задача Borland – это поддержка платформы .Net, и, одновременно, сохранение совместимости с уже существующим кодом, написанным в Delphi.

Unsafe code (небезопасный код).

Borland добавил в Delphi 7 три новых предупреждения о небезопасных типах, небезопасном коде и небезопасных преобразованиях типов.

Типы данных, не поддерживаемые в Delphi.Net:
  • PChar;
  • нетипизированные указатели;
  • нетипизированные var и out-параметры;
  • file of <тип>;
  • real48;
  • вариантные записи (записи, содержащие перекрывающиеся поля).
Небезопасный код:
  • переменные по абсолютным адресам;
  • процедуры Addr(), Ptr(), Hi(), Lo(), Swap();
  • процедуры BlockRead(), и BlockWrite();
  • функция Fail();
  • процедуры GetMem(), FreeMem(), ReallocMem().
Небезопасные преобразования типов:
  • преобразование объекта в тип, не являющийся потомком от изначального класса объекта;
  • преобразование записей.

При нарушении этих ограничений компилятор Delphi 7 с настройками по умолчанию выдаст предупреждение, а компилятор Delphi.NET – ошибку.

Кроме вышеперечисленных небезопасных конструкций, Delphi запрещает использовать встроенный ассемблер (оператор asm), конструкцию ExitProc и некоторые из конструкций для поддержки COM/OLE/ActiveX технологий:

Расширенные идентификаторы

В CLR имеются идентификаторы (типы, классы), которые совпадают по написанию с ключевыми словами Delphi, например: type, object. Для работы с такими идентификаторами нужно использовать полное именование, т.е. System.object, System.type и т.д. Borland декларирует в своей документации, что вместо полного именования можно использовать имя с префиксом & (амперсанд). Есть только одно "но": данная версия компилятора ругается на неправильный символ в исходных текстах :( .

Расширения и элементы, находящиеся в разработке.

Следующий список перечисляет конструкции языка, разработка некоторых из них еще не закончена по тем или иным причинам:

  • "запечатаные" (sealed) классы. Определено новое ключевое слово Delphi, "sealed". Атрибут "sealed" применяется к классу для указания того, что класс не может быть расширен где-либо в CLR (ни на Delphi, ни на любом другом языке программирования);
  • завершенные (final) методы. Еще одно новое ключевое слово "final". Данный атрибут применяется к методам для указания того, что они не могут быть перекрыты в потомках;
  • UTF-8 Unicode символы в исходном коде;
  • вложенные типы (т.е. описание типа внутри другого);
  • невиртуальные методы в записях;
  • статические (class, static) данные класса;
  • статические (class, static) свойства класса;
  • статические (class, static) методы;
  • виртуальные методы в helper-классах;
  • широковещательные (multicast) события (event). На текущий момент планируется использовать оператор ':=' для замены последнего присвоенного события, не влияя на обработчики событий, назначенные из управляемого кода других средств разработки. Присвоение значения nil будет вызывать удаление последнего присвоенного обработчика событий;
  • Variant будет поддерживаться как TObject, а не как TVarData (реализация внутренних механизмов работы с Variant возлагается на CLR);
  • Автоматическое упаковка (boxing) value-типов (простых типов, таких, как Integer, и записей) в CLR объекты.
Пространства имен (Namespaces).

В Delphi .NET юнит остается основным контейнером для типов. CLR предоставляет еще один слой организации – пространство имен (namespace). В .Net Framework пространство имен – это концептуально контейнер типов. В Delphi for .Net пространство имен - это контейнер юнитов Delphi. Появление пространств имен дает Delphi возможность использовать и расширять классы .Net.

В отличие от традиционных юнитов Delphi пространства имен могут быть вложенными и формировать иерархические структуры. Вложенные пространства имен предоставляют способ организации типов и идентификаторов, и используются для однозначного определения типов с одинаковыми именами. Будучи контейнерами для юнитов, пространства имен могут применяться, чтобы различать юниты с одинаковыми именами, но лежащие в разных package'ах.

Например, класс MyClass из пространства имен MyNameSpace отличается от класса MyClass из YourNamespace. Во время исполнения CLR всегда обращается к классам и типам по их полным именам: имя сборки, за которым следует название пространства имен, содержащего тип.

В Delphi.NET файл проекта (программы, библиотеки или package'а) неявно вводит собственное пространство имен, называемое пространством имен проекта по умолчанию. Юнит может как входить в это пространство имен, так и явно объявить себя членом другого пространства имен. В любом случае юнит объявляет свою принадлежность к тому или иному пространству имен в выражении Unit. Например, вот так явно объявляется пространство имен:

unit MyCompany.MyWidgets.MyUnit;

Прежде всего, обратите внимание на то, что пространства имен разделяются точками.

Во-вторых, заметьте, что точки выражают концепцию вкладывания одних пространств имен в другие. Вышеприведенный пример говорит, что юнит MyUnit – это член пространства имен MyWidgets, которое, в свою очередь, содержится в пространстве имен MyCompany.

Компилятор раскрывает пространства имен в следующей последовательности:

  1. Пространство имен данного юнита (если таковое существует).
  2. Пространство имен проекта по умолчанию.
  3. Пространство имен, указанное в выражении проекта Namespaces.
  4. Пространства имен, указанные в настройках компилятора
Приложение Delphi.NET

Попробуем написать три программы (консольное, простейшее оконное и чуть более сложное оконное приложение), пользуясь примерами и описаниями из документации.

Программа №1. “Hello .NET!”.

Данная программа – традиционный пример, используемый со времен Кернигана и Ричи. Эта программа будет всего лишь выводить на консоль строку "Hello, Delphi.NET!".

Итак, приступаем к созданию первого проекта на Delphi .NET.

ПРИМЕЧАНИЕ

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

Итак, создаем новый проект консольного приложения (File-New-Other-Console Application), сохраняем его как HelloDelphiNet.dpr.

Напишем следующее:

program HelloDelphiNet;
{$APPTYPE CONSOLE}
begin
  WriteLn('Hello, Delphi.NET!');
end.

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

dccil HelloDelphiNet.dpr

либо выбрав пункт меню Delphi с установленным .NET расширением Delphi for .NET->Compile (или Build).

После компиляции запускаем готовый EXE-файл из командной строки (иначе мы просто не увидим результатов его работы) и видим следующее:

C: \...\&gt;
HelloDelphiNet
Hello, Delphi.NET!
С: \...\&gt;
_
Что дальше

Что такое программирование в Windows без использования окон? Ну, вообще-то, это вполне нормально, есть достаточно много разнообразных программ, не использующих окон. Но Delphi не была бы Delphi без своих GUI-возможностей.

ПРИМЕЧАНИЕ

Не стоит удивляться тому, что в следующих примерах все элементы управления и формы создаются в коде, а не через модификацию/загрузку ресурсов. Это является обычным поведением для .NET-ориентированных языков. Дело в том, что одной из новинок, появившихся в .NET Framework, стала возможность сериализации в код. Тот же дизайнер форм Visual Studio сохраняет состояние формы и элементов управления в код программы. Это позволяет ускорить загрузку формы, а также вручную модифицировать код, сгенерированный дизайнером форм. Delphi поддерживает другую концепцию – сериализацию в собственный формат. Пока не ясно, как будут сериализоваться формы в Delphi.NET. Ясно одно – создать CodeDOM-провайдер, позволяющий использовать Delphi внутри VS.NET, будет несложно.

Программа №2. Нам нужны окна.

Borland пока не включил в Delphi.NET свою библиотеку VCL (но планирует сделать это, что явственно следует из документации). Основных причин, пожалуй, две:

  • сжатые сроки выпуска компилятора .NET, при продолжении развития основной линейки Delphi;
  • сложность реализации совместимости между VCL и Windows Forms.

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

Проблема №1. Множественные (multicast) обработчики событий.

В .Net все события компонентов множественные. Что это значит?

Проиллюстрирую примером на псевдокоде (псевдокод потому, что сама концепция есть не только в C# или там VB, а в CLR вообще):

Есть объект кнопка.

Прописываем в ее событие Нажато первый обработчик:

кнопка.Нажато := ПервыйОбработчик;

Так же прописываем еще один, прямо следующим оператором:

кнопка.Нажато := ПервыйОбработчик;
кнопка.Нажато := ВторойОбработчик;

В обычной Delphi вторая строка кода заменит первый обработчик вторым, а в CLR должна добавить второй обработчик в очередь за первым (правда, в Borland еще не до конца решили, так это будет реализовано или не так, но об этом уже писалось выше).

в C# данная концепция реализована так:

Button.Click + = EventHandler(MyEventHandler);

Этот код, требует некоторых пояснений. Итак: EventHandler – это так называемый делегат – специальный класс из сборки System, позволяющий ссылаться на метод некоторого класса. У данного класса есть (если верить MSDN) следующие конструкторы:

EventHandler(method)

и

EventHandler(object, method)

Второй формат недоступен из C#, а первый – из Delphi. Самое странное, что в Delphi можно и нужно передавать не метод, а обычную процедуру, причем в качестве объекта может передаваться значение nil.

Проблема №2. Совместимость CLR и VCL.

Эта проблема просто глобальна, хотя на первый взгляд так не кажется.

Для того, чтобы создавать эффективные и небольшие приложения для CLR, нужно, чтобы VCL максимально использовала CLR. Если не реализовать VCL-обертку для CLR, код, который уже существует для VCL, под CLR придется переделывать очень долго. Borland придумал очень интересный выход: для каждого класса компонента CLR создается так называемый helper-класс, который расширяет функциональность класса CLR.

Детали этой технологии в Borland до конца еще не разработали, а вся информация почерпнута все из той же документации к Delphi.NET.

Итак, helper-класс – это класс, содержащий дополнительные методы, расширяющие функциональность основного класса. Использование helper-класса – это не форма наследования, а способ обеспечения расширения функциональности базового класса. Синтаксис объявления helper-класса таков:

ClassHelper = class helper[(ancestor list)]for type
  - id
end;

Обычно перед helper-классом идет объявление типа:

type
  TMyClass = Some.VerySimilar.class;

А за ним уже собственно объявление класса

// Вводим helper-класс и объявляем функцию, ожидаемую
// существующим Delphi-кодом.
TMyClassHelper = class helper for TMyClass

function ExistingDelphiFunc: Integer;
end;

Теперь существующий Delphi-код, ожидающий наличия у типа TMyClass функции ExistingDelphiFunc, будет работать как ни в чем не бывало.

Хорошим примером может служить класс System.Object, являющийся предком для всех .NET классов. В Delphi ему соответствует класс TObject. В Delphi for .Net TObject не наследуется от System.Object. Вместо этого он объявлен helper-классом для System.Object. Это позволяет использовать такие отсутствующие в System.Object методы, как ClassName и ClassType. Компилятор, не найдя реализаций Class-Name и ClassType в классе System.Object, обратится к helper-классу и возьмет их оттуда. Таким образом, helper-классы позволяют использовать в .Net возможности, уже реализованные в VCL.

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

Первый пример - просто окно с надписью.

Создаем новый проект Delphi .NET, TestForm1.dpr. Вот его начальный код:

program TestForm1;

begin
end.

Теперь заявим наше намерение использовать сборку System.Windows.Forms (В данной сборке содержатся стандартные элементы управления):

program TestForm1;
uses System.Windows.Forms;

Теперь объявим глобальную переменную – форму:

var
  OurForm: System.Windows.Forms.Form;

В коде создадим форму.

begin
  OurForm := System.Windows.Forms.Form.Create;
  System.Windows.Forms.Application.Run(Frm);
end.

Компилируем и запускаем.

На экране мы видим простую формочку без заголовка.

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

OurForm := System.Windows.Forms.Form.Create;
OurForm.Text := 'TestForm1';
StartPosition := FormStartPosition.CenterScreen;

Получилось! Пора переходить к более сложным вещам, например к добавлению неких компонентов на форму.

Полный исходный текст TestForm1.dpr:

program TestForm1;

uses
  System.Windows.Forms;

var
  OurForm: System.Windows.Forms.Form;
begin
  OurForm := System.Windows.Forms.Form.Create;
  OurForm.Text := 'TestForm1';
  StartPosition := FormStartPosition.CenterScreen;
  System.Windows.Forms.Application.Run(OurForm);
end.
Программа №3. Добавляем на форму кнопку.

Новый проект назовем TestForm2.dpr, за основу берем TestForm1.dpr.

Для начала описываем новый класс:

uses
  System.Windows.Forms;

type
  OurFormClass = class(System.Windows.Forms.Form)
  end;

Изменяем объявление переменной и конструирование формы:

var
  OurForm: OurFormClass;
begin
  OurForm := OurFormClass.Create;

Теперь создадим конструктор:

OurFormClass = class(System.Windows.Forms.Form)
public
  constructor Create;
end;

constructor OurFormClass.Create;
begin
  inherited;
end;
ПРЕДУПРЕЖДЕНИЕ

Внимание! ОБЯЗАТЕЛЬНО нужно выполнять вызов inherited конструктора!!!

Компилируем и запускаем - все осталось как и было, то есть все работает, но форма уже нашего класса.

Пишем тело конструктора:

constructor OurFormClass.Create;
begin
  inherited;
  StartPosition := FormStartPosition.CenterScreen;
  Text := 'Центрованная форма';
end;

Запускаем – все в порядке. Но пустая форма нам неинтересна – попробуем создать на ней кнопку:

OurFormClass = class(System.Windows.Forms.Form)
public
  btnClose: System.Windows.Forms.Button;
  constructor Create;
end;

constructor OurFormClass.Create;
begin
  inherited;
  StartPosition := FormStartPosition.CenterScreen;
  Text := 'Центрованная форма';
  btnClose := System.Windows.Forms.Button.Create;
  btnClose.Left := 8;
  btnClose.Top := 8;
  btnClose.Width := 81;
  btnClose.Height := 25;
  btnClose.Text := 'Закрыть';
  Controls.Add(btnClose);
end;

Итак, у нас есть кнопка с надписью "закрыть" в левом верхнем углу формы, но сделать с ее помощью ничего пока нельзя. Пора начинать решать первую проблему Borland – обработчики событий. Пока что подключение и использование обработчиков событий в Delphi.NET до конца не реализовано. Поэтому приходится прибегать к некоторым ухищрениям, а именно – использовать в качестве обработчика событий процедуру, не являющуюся членом класса формы.

Из MSDN узнаем, что спецификация обработчика должна быть такой:

void EventHandler(object sender, EventArgs e);
На Delphi это будет выглядеть так:
  
procedure EventHandler(Sender: System.object; e: EventArgs);

Создаем обработчик:

btnClose: System.Windows.Forms.Button;

constructor Create;
end;
procedure btnCloseClick(Sender: System.object;
  e: EventArgs); forward;
//...
var
  OurForm: OurFormClass;

procedure btnCloseClick(Sender: System.object;
  e: EventArgs);
begin
  OurForm.Close;
end;

а в конструкторе вставляем следующую конструкцию:

btnClose.Text := 'Закрыть';
btnClose.Click := EventHandler.Create(Self, NativeInt(@btnCloseClick));
Controls.Add(btnClose);

Компилируем, запускаем – работает. Единственное НО - использование глобальной переменной. Но эту ошибку, похоже, получится исправить только в следующей (нормальной?) версии компилятора.

Резюме

Итак, резюмируя:

  • компилятор есть, поддерживает как минимум часть стандартных сборок .NET Framework;
  • имеется, в принципе, среда написания кода, компиляции (с позиционированием ошибок) и запуска отладчика (с перекомпиляцией в DEBUG);
  • существует возможность писать как консольные, так и оконные приложения;
  • нет пока что портированной библиотеки VCL;
  • есть некоторые несоответствия между прилагаемой документацией и компилятором.

Таким образом, вполне нормально для Developer Preview, но не достаточно для качественной среды разработки от Borland. Ждем следующих версий.






Copyright © 2004-2016 "Delphi Sources". Delphi World FAQ




Группа ВКонтакте   Ссылка на Twitter   Группа на Facebook