$EndIf — Директива компилятора Delphi


Условная компиляция в Delphi 7

Иногда программисту нужно управлять дерективами процессора и компиляцией программного кода. Для этого в Delphi реализована условная компиляция. Существуют два пути построения условной компиляции.

В ней реализован метод проверки некоторого условия. Как мы видим, она начинается с условной директивы и заканчивается . Если условие в конструкции после $IF не выполняется, то конструкция не компилируется. Можно сделать вывод о второй конструкции на основании $IF. Значит можно поставить .

Условная компиляция во втором случае ведет себя также как и полный условный оператор в теле программы. Соответственно если истина, то после не компилируется. Иначе все наоборот.

Условные директивы. Задаем само условие идентификатора: . Сама условная директива будет иметь вид: . Она проверяет, был определен указанный в ней условный идентификатор в конструкции вида .

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

Условные идентификаторы должны начинаться с буквы и может быть любой длины. Предопределены некоторые идентификаторы: VER150, MSWINDOWS, WIN32, LINUX, CPU386, CONSOLE, CONDITIONAL EXPRESSIONS. По названимям можно понять, что они способствуют настройке приложения на различные платформы.

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

дает возможность вводить в процессе отладки различные отладочные печати.

$EndIf — Директива компилятора Delphi

Профиль
Группа: Участник
Сообщений: 2090
Регистрация: 8.2.2003
Где: Великий

Репутация: нет
Всего: 32

Директивы и символы условной компиляции
—————————————-
Условная компиляция основана на оценке условных символов.

Директива условного символа DEFINE
————————————
Определяет условный символ с заданным именем

Определенный символ существует до конца компиляции, или пока он не будет убран директивой $UNDEF Имя.
Директива <$DEFINE Имя>не даст эффекта, если символ с именем «Имя» уже был определен.

Условная директива компиляции ELSE
————————————
Компилирует или пропускает исходный текст, следующий за директивой $ELSE.

Внутри исходного текста, разделенного директивами $IFDEF (или $IFNDEF) и $ENDIF, $ELSE компилирует текст, следующий за $ELSE, если условие $IFDEF (или $IFNDEF) не выполняется.
Если условие $IFDEF (или $IFNDEF) выполнено, то $ELSE игнорирует исходный текст, следующий за словом $ELSE.

Условная директива компиляции ENDIF
————————————
Завершает блок условной компиляции, начинающийся с последней условной директивы $IFxxx.

Условная директива компиляции IFDEF
————————————
Компилирует текст, следующий за директивой IFDEF, если символ с именем «Имя» определен.

Условная директива компиляции IFNDEF
————————————-
Компилирует текст, следующий за директивой IFNDEF, если символ с именем «Имя» НЕ определен.

Условная директива компиляции IFOPT
————————————
Компилирует текст, следующий за директивой IFOPT, если заданный переключатель находится в определенном состоянии.

Параметр «Переключатель» включает имя переключателя, сопровождаемое знаком «+» или «-«:
«Переключатель+» означает, что переключатель включен
«Переключатель-» означает, что переключатель выключен

Директива условного символа UNDEF
———————————-
Убирает предварительно определенный условный символ с именем «Имя»

Символ забывается до конца компиляции или пока он вновь не будет объявлен с помощью директивы $DEFINE.
Директива <$UNDEF Имя>не даст эффекта, если символ с именем «Имя» уже убран.

Символы условной компиляции
Символ Значение
CPU86 Процессор принадлежит семье 80×86
CPU87 Присутствует ли сопроцессор 80×87 во время компиляции
DPMI Указывает, что компиляция идет в защищенном режиме DOS
MSDOS Указывает, что компиляция идет в реальном режиме DOS
VER70 Номер версии Turbo Pascal
WINDOWS Указывает, что компиляция идет в среде Windows

Конструкции условной компиляции
———————————
Вы можете использовать директивы условной компиляции, чтобы генерировать различный код из одного и того же исходного текста, в зависимости от состояния условных символов.
Вы можете использовать две конструкции:
— <$IFxxx>. <$ENDIF>
— <$IFxxx>. <$ELSE>.

IF . ENDIF:
Конструкция $IFxxx . $ENDIF заставляет компилироваться исходный текст между $IFxxx и $ENDIF только в том случае, если условие, определенное в директиве $IFxxx истинно.
Если условие ложно, то исходный текст между двумя директивами игнорируется.

IF . ELSE . ENDIF:
Конструкция $IFxxx . $ELSE . $ENDIF
заставляет компилироваться
— Исходный текст между $IFxxx и $ELSE, если условие $IFxxx истинно,
— Исходный текст между $ELSE и $ENDIF, если условие $IFxxx ложно.

Условные конструкции могут иметь 16 уровней вложений.

В каждом исходном файле должно быть равное количество директив $IFxxx и $ENDIF.

Директивы по версиям компилятора Delphi: <$ IFDEF VER180>— 2020

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

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

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

Если они попытаются перекомпилировать код компонента (ваш код) — у них могут быть проблемы! Что если вы использовали параметры по умолчанию в ваших функциях, а у пользователя Delphi 3?

Директива компилятора: $ IfDef

Директивы компилятора — это специальные синтаксические комментарии, которые мы можем использовать для управления функциями компилятора Delphi. Компилятор Delphi имеет три типа директив: переключение директив, директивы параметров а также условные директивы, Условная компиляция позволяет нам выборочно компилировать части исходного кода в зависимости от того, какие условия установлены.

Директива компилятора $ IfDef запускает секцию условной компиляции.

Синтаксис выглядит так:

DefName представляет так называемый условный символ. Delphi определяет несколько стандартных условных символов. В приведенном выше «коде», если определено DefName, код выше $ Else компилируется.

Delphi Version Symbols

Распространенным применением директивы $ IfDef является тестирование версии компилятора Delphi.

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


  • УСЛОВНОЕ ОБОЗНАЧЕНИЕ — Компиляционная версия
  • VER80 — Delphi 1
  • VER90 — Delphi 2
  • VER100 — Delphi 3
  • VER120 — Delphi 4
  • VER130 — Delphi 5
  • VER140 — Delphi 6
  • VER150 — Delphi 7
  • VER160 — Delphi 8
  • VER170 — Delphi 2005
  • VER180 — Delphi 2006
  • VER180 — Delphi 2007
  • VER185 — Delphi 2007
  • VER200 — Delphi 2009
  • VER210 — Delphi 2010
  • VER220 — Delphi XE
  • VER230 — Delphi XE2
  • WIN32 — Указывает, что операционной средой является Win32 API.
  • LINUX — Указывает, что операционной средой является Linux
  • MSWindows — Указывает, что операционной средой является MS Windows / li]
  • ПРИСТАВКА — Указывает, что приложение компилируется как консольное приложение.

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

Примечание: символ VER185, например, используется для обозначения компилятора Delphi 2007 или более ранней версии.

Использование символов «VER»

Обычно (и желательно) для каждой новой версии Delphi добавлять несколько новых подпрограмм RTL к языку.

Например, функция IncludeTrailingBackslash, представленная в Delphi 5, добавляет «» в конец строки, если ее там еще нет. В проекте Delphi MP3 я использовал эту функцию, и несколько читателей пожаловались, что не могут скомпилировать проект — у них есть какая-то версия Delphi до Delphi 5.

Одним из способов решения этой проблемы является создание собственной версии этой подпрограммы — функции AddLastBackSlash.

Если проект должен быть скомпилирован на Delphi 5, вызывается IncludeTrailingBackslash. Если используются некоторые из предыдущих версий Delphi, мы моделируем функцию IncludeTrailingBackslash.

Это может выглядеть примерно так:

функция AddLastBackSlash (ул: строка) : строка; начать Результат: = IncludeTrailingBackslash (str); если Copy (str, Length (str), 1) = «» затем Результат: = ул еще Результат: = str + «»; конец;

При вызове функции AddLastBackSlash Delphi выясняет, какая часть функции должна использоваться, а другая часть просто пропускается.

Delphi 2008?

Delphi 2007 использует VER180 для обеспечения неразрывной совместимости с Delphi 2006, а затем добавляет VER185 для разработки, которая по каким-либо причинам должна быть нацелена на Delphi 2007.

Примечание. Каждый раз, когда интерфейс модуля изменяет код, использующий этот модуль, его необходимо перекомпилировать.Delphi 2007 — выпуск без прерываний, означающий, что файлы DCU из Delphi 2006 будут работать как есть.

Блог GunSmoker-а

. when altering one’s mind becomes as easy as programming a computer, what does it mean to be human.

2 мая 2013 г.

Эволюция Delphi: современные возможности

Среда Delphi не стоит на месте. Каждый год выходит новая версия Delphi с новыми возможностями. Компании Borland (Inprise) и Embarcadero всегда стремились сохранять в своих продуктах высокий уровень обратной совместимости, поэтому каждая новая версия Delphi способна почти без проблем компилировать старый код. Тем не менее, некоторые существующие возможности могут не существовать в новом мире и окружении или вести себя иначе.

В этой статье я попробую сделать небольшой обзор современных тенденций развития языка Delphi и изменений в нём. В целом, статья будет сконцентрирована на новейших измененияx в архитектуре Delphi, доступные в XE4.

Новый компилятор

Среда разработки состоит из компилятора (переводит исходный текст программы в машинный/виртуальный код), компоновщика/linker (собирает программу из готовых блоков, созданных компилятором), отладчика (debugger), редактора кода (и вообще, в целом — визуальной оболочки) и дополнительных утилит. Ну и, конечно же, среда разработки зависит от языка и библиотек на нём. Всё вместе это называется toolchain (букв. «цепочка утилит») — набор утилит для создания приложений. Слово «цепочка» намекает на то, что результат работы одной утилиты используется следующей (т.е. редактор -> компилятор -> компоновщик -> отладчик).

Среда Delphi является развитием языка Pascal. Toolchain Delphi является закрытой (проприетарной) разработкой Borland. За всю историю Delphi она поддерживала несколько платформ (Win16, Win32, Win64, Linux/CLX, .NET). Под каждую платформу был свой собственный компилятор, который был монолитным. Исходный код компилировался компилятором непосредственно в машинный код целевой платформы (файлы .dcu и .obj).

В этой ситуации добавление новой платформы было непростым делом, поскольку требовалось разрабатывать компилятор для неё с нуля. Дополнительными сложностями был перенос существующего код RTL и VCL, завязанного на конкретную платформу (Win32). Сегодня доля Windows уменьшается, а на сцену выходят более молодые платформы: от Apple и Google. Причём актуальные платформы меняются намного быстрее, чем это происходило в прошлом. В ситуации с таким динамическим изменением имеет смысл упростить разработку компилятора, чтобы более оперативно реагировать на изменения и вносить новые возможности.

Поэтому, центральной идеей ближайшего развития Delphi становится модульный компилятор. Идея заключается в том, чтобы разделить (ранее монолитный) компилятор на две части: т.н. front-end и back-end. Front-end компилятора берёт исходный код программы и переводит его не в машинный код конкретной платформы, а в (универсальный) виртуальный код — т.н. байт-код. Байт-код — это максимально универсальное представление логики программы, не зависящее от языка и платформы. Back-end работает по результату работы front-end: он преобразовывает байт-код уже непосредственно в машинный код конкретной платформы.

Таким образом, вместо того, чтобы делать компилятор полностью для каждой новой платформы, можно оставить front-end неизменным (а ведь именно он отвечает за синтаксис языка), а написать только новый back-end. Более того, вместо того, чтобы использовать собственную проприетарную (и ни с кем не совместимую) разработку, можно использовать широко известное решение (в качестве back-end, конечно же) — получив при этом не только частично готовый код, но и совместимость с некоторыми сторонними утилитами. В качестве такого известного решения разработчики Delphi решили использовать LLVM (Low Level Virtual Machine) — это универсальная система анализа, трансформации и оптимизации программ, реализующая виртуальную машину с RISC-подобными инструкциями.

LLVM используется, в частности, в компаниях Adobe, Apple и Google (например, iPhone SDK использует back-end LLVM). Apple и Google являются одними из основных спонсоров проекта. В настоящее время для LLVM есть back-end-ы для x86-32, x86-64, ARM, PowerPC, SPARC, MIPS, Qualcomm Hexagon и front-end-ы для С, C++, Objective-C, Fortran, Ada, Haskell, Java, Python, Ruby, JavaScript, GLSL (в т.ч. — Clang и GCC). А теперь ещё к front-end добавляется и Delphi. Конечно же, LLVM понятия не имеет про Паскаль и Borland-ский форматы файлов. Но Delphi может иметь свой собственный front-end, который будет компилировать исходный код Паскаль в байт-код LLVM (называемый LLVM IR — «Intermediate Representation», т.е. «промежуточное представление»). А готовый back-end от LLVM может скомпилировать IR от front-end Delphi в машинный код x86-32, x86-64 или ARM. Хотя LLVM IR похож на готовый байт-код для некой виртуальной машины или JIT-компилятора, он всё же нацелен именно на чёткое разграничение front-end и back-end и может рассматриваться как вывод компилятора — аналогично .dcu (Delphi) и .obj (C++ Builder) файлам.

Итак, теперь должно быть очевидным, что в будущем Delphi будет иметь новый компилятор, совместимый с LLVM — и начнётся это уже сейчас, начиная с компилятора для iOS (ARM). А для C++ Builder новая эра началась ещё в прошлом году: 64-битный компилятор C++ Builder сделан уже на новой архитектуре (LLVM). Конечно же, компилятор — это ещё не всё. Нужен ещё компоновщик, отладчик, библиотека поддержки языка (RTL), а для визуального языка — ещё и визуальная библиотека (такая как VCL, CLX, FMX). Также важно отметить, что LLVM в каком-то смысле «подталкивает» разработчиков front-end-ов использовать определённые подходы к управлению памятью, потоками и исключениями. Хотя это и всего лишь «толчок», а не железное ограничение. Стоит отметить, что для мобильных платформ распространена практика использовать LLVM (или виртуальные среды типа Java и .NET), которые поддерживают автоматическое управление памятью: или сборку мусора (garbage collection) или автоматические ссылки (ARC — Automatic Reference Counting). В итоге, вывод: автоматическое управление памятью более предпочтительно, т.к. оно более проработано, поддерживается мобильными устройствами и более привлекательно для новичков.


Итак, сегодня в Delphi (и я говорю про Delphi XE4) есть пять компиляторов: для Win32, Win64, MacOS, эмулятор iOS (компилирует в x86) и iOS (компилирует в ARM). Компиляторы для Win32, Win64, MacOS и эмулятор iOS являются классическими, а компилятор для iOS основан на новой архитектуре LLVM. Как я сказал выше, C++ Builder отличается тем, что компилятор для Win64 у него тоже является новым (LLVM).

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

Изменения в языке

Несмотря на то, что Delphi уже давно поддерживает несколько платформ, до сих пор язык Delphi не претерпел никаких хирургических вмешательств по отсечению старых возможностей. Компилятор для каждой новой платформы создавался полностью Borland/CodeGear/Embarcadero и педантично тащил за собой весь багаж обратной совместимости.

Сейчас ситуация несколько иная. Во-первых, необходимо сделать компилятор (front-end) из Паскаль кода в LLVM IR — что потребует тщательного воспроизведения всего накопленного багажа из обратной совместимости. Во-вторых, ввод нового компилятора совпадает с введением поддержки мобильных платформ. Перенос старого уже написанного кода на мобильную платформу, вероятно, и так потребует пересмотра. В-третьих, добавление новых платформ требует введения в язык новых возможностей. Частично они будут перекрывать старые. В языке будет несколько способов сделать одно и то же. Язык станет слишком сложным сам по себе, не говоря уже о сложностях изучения его для новичков. В четвёртых, уже сегодня в Delphi есть как избыточность (посмотрите, сколько есть в ней типов строк), так и несогласованность (сравните индексацию с 1 для строк, но с 0 — для списков и массивов).

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

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

Итак, современные (и будущие) изменения в языке Delphi заключаются в следующем:

  1. Строки:
    • всего один тип строк
    • индексируются с 0
    • «неизменяемые» строки (immutable strings)
  2. Улучшения классического механизма автоматического подсчёта ссылок:
    • Автоматические ссылки для объектов
    • Слабые (weak) ссылки
  3. Новые классы и процедуры в RTL для кросс-платформенного кода
  4. Отсутствие пакетов и DLL на некоторых платформах
  5. В будущем:
    • with — deprecated
    • object — deprecated
    • указатели — deprecated
    • ассемблер — deprecated

Строки

Больше всего изменений в новых версиях Delphi приходится на строки. Для этого есть несколько причин:

  • Упрощение модели строк (несколько типов строк)
  • Унификация (1-индексация)
  • Оптимизация (требования более слабых мобильных платформ)

Сохранение обратной совместимости со строками из времён Turbo Pascal/Delphi 1 слишком затратно как для разработчиков самой Delphi, так и для разработчиков на Delphi (особенно новичков).

Единый строковый тип

Сегодня в Delphi есть следующие типы строк:

  • Delphi-строки:
    • Родной ( string ) — псевдоним для UnicodeString
    • UnicodeString (счётчик ссылок, Unicode, длина, размер символа, нуль-терминированная)
    • AnsiString (счётчик ссылок, Ansi, длина, размер символа, нуль-терминированная)
    • AnsiString[ кодовая-страница ] (счётчик ссылок, кодовая страница, длина, размер символа, нуль-терминированная)
    • RawByteString (счётчик ссылок, длина, размер символа, нуль-терминированная)
  • Pascal-строки:
    • ShortString (Ansi-кодировка, 255 символов, счётчик длины в первом символе)
    • String[ число ] (Ansi-кодировка, менее 255 символов, счётчик длины в первом символе)
  • C-строки:
    • PChar — псевдоним для PWideChar
    • PAnsiChar (Ansi, нуль-терминированная)
    • PWideChar (Unicode, нуль-терминированная)
  • WideString ( BSTR из COM, Unicode, нуль-терминированная, счётчик длины, специальный API)

Если вы посмотрите на этот список, то заметите следующую вещь: всюду в вашей программе вы оперируете со строками типа string . Все прочие типы строк нужны вам исключительно для совместимости со сторонним кодом: вашим же старым кодом (AnsiString или Pascal-строки), ОС (нуль-терминированные или BSTR ) и т.п. Такой зоопарк не только вызывает путаницу (вопросы вида «в чём разница между WideString и UnicodeString ?»), но и весьма сложен для переноса на другие платформы (чему равен WideString на iOS?). Поэтому идея заключается в том, чтобы оставить один тип строк — самый удобный и универсальный. Гораздо лучше использовать не строковые типы (записи/классы) для коммуникации с внешним миром — так их семантика будет понятнее. А перегрузка операторов сделает безболезненным операции присваивания.

Именно поэтому на новых LLVM компиляторах iOS есть только тип string . Все прочие типы строк там не объявлены и при попытке ими воспользоваться сгенерируют вам ошибку вида «Undeclared identificator AnsiString». Новый тип string в целом равен UnicodeString (т.е. хранит данные строки в UTF-16, имеет счётчик ссылок и длины, а также поле кодовой страницы, которое перманентно равно CP_UTF16 = 1200 ($4B0), и поле размера символа, которое перманентно равно 2 байтам).

Однако сказанное не означает, что вы не сможете работать с данными строк других форматов — просто вы не сможете это делать со встроенными (native) типами данных. Например, предположим, вам нужно использовать текстовые данные в формате UTF-8. Вы можете использовать классы типа TTextReader или TEncoding (которые, кстати, тоже появились в Delphi довольно давно), например:
Этот простой код скрывает от вас всю работу с UTF-8 строками. А вот вариант с явным преобразованием:

Вам может потребоваться хранить строковые данные в других форматах в памяти (например, при вызове сторонних API функций) — в этом случае вам нужно использовать класс TEncoding и хранить строковые данные в (динамическом) массиве байтов ( TBytes ). При желании вы можете даже эмулировать поведение старого компилятора путём введения типов с перегрузкой операторов, например:
Реализация этого класса может использовать TEncoding для работы (конкретно — TUTF8Encoding ). Используя такую запись, вы можете продолжать использовать старый код вида:

0-индексируемые строки

Как известно, первый символ в любой строке Delphi имеет индекс 1, а не 0, как может ожидать любой программист, ранее не знакомый со строками в Delphi. Это называется 1-индексацией (или индексацией с единицы). 1-индексация строк усугубляется тем, что другие структуры в Delphi (динамические массивы, списки и т.п., а также не-Delphi строки) индексируются с нуля (используют 0-индексацию). Получается некоторая путаница и непривычные корректировки на +/-1 в коде по работе со строками.

Историческая справка: почему в Delphi строки индексируются с 1?
Delphi является наследником языка Pascal. В Паскале не использовались 0-терминированные строки из C. Вместо этого Паскаль использовал так называемые «короткие» строки: первый байт строки служил счётчиком символов (= «байтов» в Паскале) в строке. Таким образом, в отличие от строк C строки Паскаля могли хранить #0 внутри строки и очень быстро определять длину (не нужно было искать терминатор в строке, не было цикла), но были ограничены 255 символами (т.е. строка занимала максимум 256 байт вместе со счётчиком).

Соответственно, в Паскале строки технически индексировались с нуля, но нулевой символ отводился под счётчик длины строки, а данные строки начинались с символа №1. Т.е. данные строки индексировались с единицы.

Когда Delphi ввела длинные строки (AnsiString в Delphi 2), то, хотя у длинных строк уже не было счётчика длины в первом символе строки (теперь он хранился в скрытом заголовке строки), индексацию с 1 оставили по соображениям обратной совместимости — чтобы не пришлось переделывать уже написанный код, который работал со строками в предположении, что они индексируются с 1.

Таким образом строки в Delphi стали индексироваться с 1.

Совместно с введением одного единственного строкового типа решено было изменить и этот аспект поведения строк. Поскольку подобное изменение весьма значительно для языка, но не привязано к архитектуре компилятора, то было решено контролировать этот аспект директивой компилятора: $ZEROBASEDSTRINGS . Кстати, эта директива впервые появилась ещё в XE3. По умолчанию эта директива выключена в Delphi XE3, а в Delphi XE4 она выключена для Win32, Win64 и OSX и включена для iOS и эмулятора iOS. Поскольку эта опция контролируется директивой, то вы можете включить её для Delphi XE3 (чтобы начать миграцию раньше). Более того, вы можете выключить её для iOS, чтобы компилировать старый код.

На что нужно обратить внимание:

  • Внутренняя структура строк не меняется. Иными словами не существует такого понятия как «0-индексированная строка». Строка — это строка. Индексация — это лишь способ доступа к данным, он не влияет на сами данные. Т.е. вы можете смешивать в одном проекте модули, собранные с разными настройками. Более того, вы можете иметь разные настройки для разных функций в рамках одного модуля.
  • Все новые функции в Delphi (хэлпер TStringHelper , TStringBuilder ) используют новую семантику (0-индексацию) вне зависимости от опции $ZEROBASEDSTRINGS и компилятора.
  • Все классические функции RTL ( Copy , Pos , Delete и т.п.) всегда используют прежнюю семантику (1-индексацию) вне зависимости от опции $ZEROBASEDSTRINGS и компилятора. Тем не менее, Embarcadero рекомендуют не использовать старые RTL-функции (используйте TStringHelper и TStringBuilder ).

Другими словами, опция $ZEROBASEDSTRINGS влияет только на вычисление выражений вида StrVar[ число ] . Посмотрите на такой код:
В любых предыдущих версиях Delphi (XE2 и ниже), а также в XE3 и выше с выключенной опцией $ZEROBASEDSTRINGS вы получите ‘ HOllo foo ‘. Но если вы добавите <$ZEROBASEDSTRINGS ON>перед кодом (либо запустите его на iOS, где эта опция уже включена), то получите ‘ HeOlo foo ‘. Единственная разница между этими двумя кусками — способ вычисления S[2] : в первом случае вы обращаетесь ко второму элементу, который имеет индекс 2 (отсчёт с 1), во втором случае вы обращаетесь к третьему элементу, который имеет индекс 2 (отсчёт с 0).

Примечание: в предварительных обсуждениях релиза XE4 было несколько заблуждений относительно строк. Заметьте, что способ интерпретации выражения в квадратных скобках для строк вообще не зависит от структуры строки, а остаётся на усмотрение компилятора. В самом деле, вы и ранее использовали 1 как индекс для первого символа длинных строк, но как второй символ для коротких строк (первый символ занят под счётчик и имеет индекс 0). Т.е. строки остаются теми же самыми, меняется только способ вычисления компилятором выражения StrVar[ число ] . Вы не передаёте в функцию «0-индексированную строку», вы передаёте «просто строку». Это означает, что вы можете смешивать в одном проекте и модули функции, скомпилированные с разными настройками. Посмотрите на такой код:
По умолчанию, в Delphi XE4 этот код покажет 2/1/1/2 на Windows и 2/2/1/2 на iOS. И снова: единственное отличие — интерпретация выражения в квадратных скобочках. И снова: вы можете изменить поведение на любой платформе на обратное, используя $ZEROBASEDSTRINGS .

Если вы хотите написать универсальный код, который будет работать для обоих вариантов $ZEROBASEDSTRINGS , то вы можете определить константы, зависящие от значения Low(string) , которое будет равно 1 и 0 для <$ZEROBASEDSTRINGS OFF>и <$ZEROBASEDSTRINGS ON>, соответственно. Например: Этот код будет работать всегда одинаково, вне зависимости от настроек компилятора. А вот как вы можете работать с циклами: Low(S) возвращает 0 для 0-индексированной строки и 1 — для 1-индексированной. High(s) возвращает Length(S) — 1 для 0-индексированной строки и Length(S) — для 1-индексированной. В случае пустой строки Low , конечно же, возвращает всё то же значение, а High возвращает -1 или 0, соответственно. Вы можете передать тип вместо переменной в Low , но это не сработает для High .

Вместо Low и High вы можете использовать хэлпер для строк, который появился в Delphi XE3. Фактически, в Delphi XE3 появилась новая возможность: возможность добавлять методы любым встроенным типам данным, а не только записям и классам. Хотя синтаксис несколько необычен для Delphi: Кроме самой возможности в Delphi XE3 были введены и некоторые новые конструкции, использующие новую возможность. Среди них: TStringHelper — хэлпер для типа string . Он объявлен в модуле SysUtils и предоставляет методы вида Compare , Copy , IndexOf , Substring , Length , Insert , Join , Replace , Split и многие другие. Поэтому теперь вы можете написать: Заметьте, что все эти методы (включая индексированное свойство Chars ) используют индексацию с нуля вне зависимости от настроек компилятора.

Immutable-строки

Несмотря на то, что новый единый тип string по-прежнему эквивалентен бывшему UnicodeString , внутренняя реализация строк может быть изменена в будущем и/или на других мобильных платформах. Уже сейчас предполагается, что строки станут неизменяемыми (т.н. immutable-строки): это означает, что строку нельзя изменить когда она была создана. Этот аспект не влияет на операции типа конкатенации (сложения строк), потому что эти операции создают новую строку из каких-то других строк. Immutable-строки влияют на in-place операции вида S[1] := ‘A’; — такие операции «запрещены».

Ещё раз: сегодня строки по прежнему изменяемы в любых компиляторах (в том числе — для iOS). Конструкции вида S[1] := ‘A’; полностью разрешены (в том числе — для iOS). Тем не менее, в будущем этот аспект может быть ограничен.

Сегодня все компиляторы Delphi используют семантику copy-on-write (копирование-при-записи): если вы модифицируете строку, а она имеет счётчик ссылок больший 1, то строка копируется в новую, и изменения вносятся в копию, оставляя старую версию неизменной — так что все остальные (кто держит ссылку на строку) не увидят вашего изменения. Иными словами, вместо копирования строки изначально при присваивании, техника copy-on-write копирует строку позже — когда её необходимо изменить. Копирования может и не произойти, если вы не модифицируете строку. Внутренне это достигается (скрытыми) вызовами UniqueString для строк вида S[1] := ‘A’; . Разумеется, вам нужно вставлять вызовы UniqueString вручную, если вы работаете с содержимым строки напрямую (через указатели).

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


Уже сегодня вы можете найти потенциальные пробные места в вашем коде. Для этого вы можете включить подсказки компилятора директивой <$WARN IMMUTABLE_STRINGS ON>. С включенной опцией компилятор будет выдывать такие предупреждения:

[dcc32 Warning]: W1068 Modifying strings in place may not be supported in the future”

И снова: если вы используете в вашем коде только конкатенацию строк, то immutable-строки вас не коснутся — ни сейчас, ни в будущем (в самом деле, оптимизация конкатенации — это одна из целей для развития Delphi). Разработчики Delphi не ожидают, что конкатенация строк станет медленнее или будет запрещена в будущем. Только изменение индивидуальных символов (содержимого строки) может вызвать проблемы в будущем (а может и не вызвать).

Тем не менее, сегодня операция конкатенации может быть не самым оптимальным способом работы со строками на мобильной платформе. Вы можете знать, что в Delphi уже давно есть специализированный класс для построения строк: TStringBuilder . Несмотря на то, что этот класс присутствует в Delphi уже давно (начиная с Delphi 2009), он не пользуется популярностью. Почему? Посмотрите на такой код:
На Desktop-платформах подобный код даст следующие результаты:
Иными словами, на мощных платформах нет никакой выгоды от использования TStringBuilder , поскольку умный менеджер памяти (типа FastMM или даже встроенного в ОС) успешно выполняет ту же работу, что и TStringBuilder (работу по динамическому росту блоков памяти).

Но на мобильных платформах ситуация иная: более слабые платформы не имеют такого же сложного и умного менеджера памяти, как и Desktop-ы. Поэтому результат работы такого кода на iOS будет следующим:
В этом случае TStringBuilder почти в два раза быстрее простого сложения строк.

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

Улучшения классического механизма автоматического подсчёта ссылок

Delphi для iOS вводит в язык поддержку ARC (Automatic Reference Counting) — «автоматический подсчёт ссылок». ARC является улучшенным механизмом подсчёта ссылок, который существовал в Delphi со времён Delphi 2 — для строк, вариантов, динамических массивов и интерфейсов. Фактически, единственными данными, управляемыми вручную, в Delphi являлись объекты и указатели. И если указатели уже давно успешно вытесняются управляемыми аналогами, то объекты продолжали оставаться типами с ручным управлением, плодя бесконечные вложенные иерархии try-finally в вашем коде.

До сегодняшнего дня. Сегодня ARM компилятор Delphi вносит автоматическое управление временем жизни и в объекты.

Автоматические ссылки для объектов

ARC является механизмом автоматического учёта памяти. Часто ему противопоставляют реализацию автоматического учёта памяти из .NET, называемую (несколько ошибочно) сборкой мусора (garbage collection). Оба механизма служат одной цели, но делают это разными способами. Напомню, что менеджер памяти .NET периодически запускает подпрограмму очистки памяти, которая пытается найти блоки памяти (или группы блоков), на которые нет внешних ссылок. Здесь же видно, в чём отличие двух подходов: ARC 100% детерминирован — память освобождается всегда в один и тот же момент (когда счётчик ссылок падает до нуля), способ .NET может освобождать память позднее, чем она реально отпускается. Кроме того, освобождение памяти (и, следовательно, объектов) в ARC выполняется текущим же потоком, а не фоновым потоком-уборщиком, как это происходит в .NET. Однако, ARC всё ещё допускает возможность утечек памяти, если вы создадите циклическую ссылку (первый объект указывает на второй, а второй — на первый), в то время как .NET увидит два блока памяти, изолированные от остальных, и удалит их.

Примечание: хотя ARC реализован только в (LLVM) компиляторе для iOS, его эмуляция также доступна на (классическом) компиляторе «эмулятор iOS». ARC не доступен в компиляторах для Win32, Win64 и OSX.

Использовать ARC очень просто — вам практически не нужно думать об управлении памятью. В вашей практике вы постоянно использовали строки ( string ) и практически никогда не задумывались об управлении памяти для них. Точно так же вы теперь можете поступать и с объектами:
Ближайший аналог ARC для объектов — это работа с интерфейсами (interface) в Delphi. Если вы когда-либо работали с интерфейсами в Delphi, то теперь точно так же сможете работать и с обычными объектами.

Точно так же, как с интерфейсами (и любыми другими типами с автоматическим управлением памятью в Delphi), вы можете удалить ссылку преждевременно (до выхода переменной за область видимости) путём присвоения переменной значения nil :
Хотя строка » end » по прежнему будет содержать (скрытый) блок finally с очисткой MyObj — в этом варианте кода «магия» компилятора отработает вхолостую, поскольку вы сами освободили ссылку до выхода из подпрограммы. Разумеется, если метод DoSomething вызовет исключение, то строка с присвоением nil будет пропущена, и тогда объект, как и ранее, будет удалён из «подстилки» компилятора в строке » end «.

Заметьте, что в этих примерах отсутствуют явные блоки try-finally — и код при этом остаётся 100% корректным. Это благодаря тому, что блок try-finally теперь является скрытым. Теперь вам не нужно писать многоуровневые вложенные блоки try-finally ! Фактически, то, что делает сейчас ARC, эквивалентно такому коду (который, впрочем, вы и сами могли писать ранее вручную):

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

  • Использовать <$IFDEF AUTOREFCOUNT>, разделив код на два варианта.
  • Использовать классический подход с Free / FreeAndNil , не используя преимущества ARC. На ARC этот подход формально будет работать благодаря обратной совместимости, хотя его поведение может незначительно отличаться.

По первому пункту: новый компилятор предоставляет следующие (новые) символы условной компиляции (определения для компиляторов даны по состоянию на XE4):

Символ: Условие: Компиляторы:
NEXTGEN Новый компилятор dcciosarm, dccios32
AUTOREFCOUNT Доступен ARC dcciosarm, dccios32
CPUARM Для процессоров с архитектурой ARM dcciosarm
IOS Целевая платформа — iOS dcciosarm, dccios32
WEAKREF Компилятор может использовать слабые ссылки dcciosarm, dccios32

Примечание: все символы условной компиляции можно посмотреть здесь.

По второму пункту: разумеется, разработчики Delphi не могли просто «выбросить на свалку» базилионы написанных сторонними разработчиками строк кода на Delphi, объявив их «устаревшими» и «несовместимыми с новой моделью». К примеру, если рассмотреть такой классический код:
В классическом компиляторе, где объекты являются неуправляемыми типами данных, вызовы FreeAndNil , Destroy или Free безусловно удаляли существующий объект. В новых компиляторах с поддержкой ARC этот код будет работать немного иначе: вызовы FreeAndNil , Destroy и Free будут эквивалентны » := nil » (т.е. очистке ссылки). Иными словами, блок кода выше в компиляторе с ARC будет скомпилирован как:
Что является 100% рабочим и корректным кодом, пусть и не самым разумным и эффективным. Иными словами, старые вызовы FreeAndNil / Free / Destroy полностью допустимы и безопасны, хотя и бесполезны в компиляторах с ARC.

Однако это не означает, что вы сможете использовать весь свой старый код без модификаций. В старом коде у вас могут быть более сложные ситуации — например, несколько ссылок на один объект. С классическим компилятором висячая ссылка (вы удалили объект по одной ссылке, но остальные ссылки не были сброшены) ваш объект удаляется, но на него продолжают указывать ссылки. Это — допустимо, если вы не обращаетесь к объекту по висячим ссылкам. Но в новой модели эти висячие ссылки добавят «+1» к счётчику ссылок объекта. Таким образом, очистка ссылки вызовом FreeAndNil / Free / Destroy уменьшит счётчик, но не до 0. Т.е. объект удалён не будет. Само собой, это не означает утечки памяти — объект всё же будет удалён, но позже — когда удалится последняя (ранее «висячая») ссылка. Так что ваш код может работать и как ранее (только изменится картина выделения/освобождения памяти), но, быть может, вам необходимо очистить объект до наступления другого события (такого, как выгрузка библиотеки, из которой объект и получен). В этом случае ваш код может вылететь. Решение заключается в правиле, которому не грех было бы следовать и ранее (ещё с классическим компилятором): не оставляйте висячих ссылок. Очищайте все ссылки на объект при его удалении.

Альтернативным решением задачи гарантированного вызова деструктора может быть вызов (нового) метода DisposeOf :
Метод DisposeOf безусловно вызывает деструктор — даже несмотря на существующие ссылки на объект. После такого вызова деструктора объект переходит в состояние «зомби» («zombie state» или «disposed state») — для него был вызван деструктор, объект был очищен, но память для него ещё не была освобождена. Вы можете узнать состояние объекта через свойство Disposed — это аналог Assigned для объектов из классического компилятора.

Разница между вызовами FreeAndNil / Free и DisposeOf заключается в ваших намерениях: вызов FreeAndNil / Free отсоединяет ссылку, но не означает немедленного удаления объекта (он может быть удалён сейчас, но может быть удалён и позднее), а вызов DisposeOf всегда безусловно удаляет объект, даже если на него есть ссылки.

Примечание: «зомби»-объект никак не защищается от возможного ошибочного доступа к нему. Вы можете прочитать/записать свойство, вызывать методы (как обычные, так и виртуальные) — все эти операции будут успешными, но будут оперировать на уже очищенном объекте. И хотя это не приведёт к Access Violation, как в классическом компиляторе с висячими ссылками (потому что память под «зомби» объект всегда гарантировано выделена), но все структуры данных объекта уже были очищены деструктором, что может привести к неожиданному поведению. Всегда проверяйте статус объекта вызовом Disposed , если вы удаляете объект вручную. Кроме того, вы можете проверить доступность объекта в самих методах объекта вызовом protected -метода CheckDisposed — это некий аналог Assert(Disposed); .

Заметьте, что старый Assigned вместе с FreeAndNil больше не имеют смысла в новой архитектуре, потому что объект всегда гарантировано существует (пусть даже и как зомби), пока на него есть хоть одна ссылка — это отличается от классической модели, где вам приходилось записывать в ссылку nil , чтобы указать на уже удалённый объект. (Хотя, конечно, вы можете продолжать использовать Assigned , если вы очищаете ссылки на объекты до их выхода из области видимости.)

К счастью, вам не нужно увлекаться <$IFDEF AUTOREFCOUNT>, потому что и DisposeOf и Disposed доступны и в классических компиляторах (начиная с XE4, конечно же). Код выше будет полностью работоспособен и в Win32, где вызов DisposeOf просто вызывает Free , ну а Disposed всегда возвращает False , поскольку в классическом компиляторе нет состояния «зомби». Поэтому, если у вас есть старый код и вы хотите точно такого же поведения (т.е. удалять объект сразу, а не когда уйдёт последняя висячая ссылка на него), то вы можете просто заменить вызовы FreeAndNil / Free / Destroy на вызов DisposeOf . К несчастью, вместо двух состояний «есть объект»/»нет объекта» у вас теперь появляется три состояния: «есть объект»/»зомби»/»нет объекта» — что, впрочем, не сильно отличается от бывшего «есть объект»/»висячая ссылка — непонятно, есть объект или нет»/»нет объекта» — которое в классическом компиляторе вы должны были сводить к «есть объект»/»нет объекта». В связи с этим, вам может пригодится такая подпрограмма:
Эту функцию можно использовать во всех местах, где вы раньше использовали if Assigned(Obj) then — замените их на if ValidObject(Obj) then .

Примечание: деструктор в ARC по прежнему называется Destroy , но он заблокирован для прямого вызова (помещением в секцию protected ). Поэтому:

  1. Добавьте <$IFDEF AUTOREFCOUNT>protected <$ENDIF>перед каждым destructor Destroy; override;
  2. Замените все внешние вызовы Destroy (если они вдруг у вас есть) на FreeAndNil / Free или DisposeOf — смотря по тому, согласны ли вы с отложенным удалением объекта или вам нужно немедленное удаление.

Суммируя сказанное, вот современная реализация TObject (показан только код, имеющий отношение к циклу создание-удаления объектов):

Слабые (weak) ссылки

Однако поддержка ARC в Delphi касается не только расширением действия счётчиков ссылок на классы/объекты, но и поддержки слабых (weak) ссылок. Слабые ссылки предназначены для решения проблемы циклических ссылок. Наиболее типичный случай возникновения циклических ссылок: контейнер-коллекция, в котором его элементы содержат ссылки на него самого (как на контейнер-владелец). В классической модели ссылок из Delphi подобная конструкция порождает утечку из-за наличия циклической ссылки.

Здесь на сцену выходят слабые ссылки. Слабая ссылка — это ссылка на объект, которая не приводит к изменению счётчика ссылок. Иными словами, при присвоении объекта в переменную со слабой ссылкой не происходит увеличение счётчика ссылок объекта на единицу. Аналогично, при очистке слабой ссылки не происходит уменьшение счётчика объекта на единицу. Создать слабую ссылку очень просто — достаточно пометить переменную атрибутом [weak] , например:
В этом примере поле FOwnedBy является слабой ссылкой, потому что оно помечено атрибутом [weak] . Это означает, что присвоение этому полю не увеличивает счётчик ссылок присваевомого объекта, а его очистка — не уменьшает счётчик ссылок объекта. Таким образом, создание экземпляра TMyComplexClass не приведёт к утечке памяти, несмотря на наличие циклической ссылки — благодаря тому, что одна из ссылок в составе циклической ссылки является слабой.

Вы можете увидеть, что атрибут [weak] используется и в коде самой Delphi, например:

Примечание: вы можете использовать атрибут [weak] и в классических компиляторах, но он будет игнорироваться, поскольку в этих компиляторах нет ARC. Таким образом, если вы пишете универсальный исходный код — вам необходимо как помечать переменные атрибутом [weak] , так и использовать FreeAndNil / Free (использование которых допускается в компиляторах с ARC).

Вы также не можете проверить статус объекта по слабой ссылке. Чтобы проверить статус объекта, вам сначала нужно присвоить объект в обычную переменную, например:

Диагностика с ARC

Использование ARC упрощает работу с памятью и снижает риск утечек памяти/ресурсов в вашем коде, но поскольку всё же существует вероятность создать циклическую ссылку, то ваш код всё ещё не полностью защищён от утечек памяти.

С целью отладки вы можете использовать свойство RefCount , чтобы узнать число живых ссылок на объект. Не следует использовать это свойство для реализации логики программы. Кроме того, вы можете (крайне редко) использовать __ObjAddRef и __ObjRelease для ручного управления счётчиком ссылок — например, для записи объекта в неуправляемую переменную-указатель (к примеру, свойства типа Tag / Data ). Этот приём допустимо использовать в логике кода, хотя его и нужно избегать (предпочтительнее: создание наследника с полем нужного типа).

Вы можете проверить наличие циклических ссылок с помощью функции CheckForCycles (модуль Classes ):
Как и выше, эта функция служит для диагностики, её не следует использовать в финальной сборке программы/для реализации логики самой программы. Используйте её только для отладки.

Заметьте, что по аналогии со строками и интерфейсами ARC с объектами является потокобезопасным: при работе со счётчиком ссылок используются атомарные interlocked-операции. Заметьте, что это не означает автоматической потокобезопасности самих объектов.

Обратите внимание, что на мобильных платформах Delphi использует функции операционной системы в качестве штатного менеджера памяти. Иными словами, FastMM — штука чрезмерно сложна для мобильной платформы и, более того, написанная на x86-ассемблере (иными словами: непереносимая). Поэтому возможности Delphi по диагностике утечек памяти будут недоступны. Вы можете использовать инструментарий целевой ОС или использовать сторонние фильтры.

Новые классы и процедуры в RTL для кросс-платформенного кода

В целом вы должны избегать прямых платформенных вызовов (т.е. функций Windows/Mac/iOS API) и использовать, предлагаемые Embarcadero обёртки-переходники. Конечно же, вы также должны как чумы избегать ассемблера и, желательно, не использовать указатели.

Например, Embarcadero предлагает вам модуль IOUtils. Он доступен, начиная с Delphi 2010. Вы можете прочитать про него здесь. Как можно догадаться, этот модуль предоставляет вам кросс-платформенные возможности для работы с файлами. В нём есть классы TDirectory , TPath и TFile — для работы с каталогами, именами файлов и файлами соответственно.


К примеру, вы можете получить доступ к папке «Documents» на мобильном устройстве так же, как вы получаете доступ к папке Application Data в Windows:

А вот как вы можете искать файлы: этот код считывает подпапки заданной папки, а затем считывает файлы в найденных подпапках:

Библиотеки и пакеты

К сожалению, одна из древнейших возможностей Delphi — использование пакетов времени выполнения (run-time packages, BPL) и, более обще, DLL — не поддерживается на платформе iOS. Пакеты и библиотеки представлены DLL на Windows, dylib на MacOS и so (shared object) на Linux. Они позволяют вам создавать модульные приложения. Но на iOS приложение не может устанавливать библиотеки — это может делать только сама Apple, а iOS приложения обязаны быть монолитными программами.

Тем не менее, компилятор Delphi умеет распознавать статические ссылки на DLL (например, на midas.dll) и внедрять их в приложение статически, а не как отдельные библиотеки.

«Плохие» конструкции

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

Сейчас with в Delphi спроектирована не слишком удачно. Вероятнее всего, эта языковая конструкция рано или поздно пропадёт из языка. Вы можете начать избавляться от неё уже сейчас, если хотите.

Object (старые объекты Паскаля) устарели много лет назад. Замените их на записи (record).

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

Прямой доступ к указателям есть не на всех платформах. Хотя даже сегодня использование указателей не поощряется (как подверженное ошибкам), но они всё ещё полностью поддерживаются всеми компиляторами Delphi. Только надо иметь в виду, что в будущем их использование может быть ограничено или вовсе отсутствовать для некоторых платформ. Уже сейчас указатели удаляются из языка в пользу ARC (к примеру, в Delphi для iOS отсутствует модуль System.Contnrs , поскольку он основан на TList с указателями). Поэтому если у вас есть выбор, использовать указатели или безопасный аналог — не используйте указатели.

К примеру, TList и TStringList являются своеобразными «швейцарскими ножами»: они используются как универсальный контейнер на все случаи жизни, благодаря способности хранить произвольные ссылки (для TStringList — через свойство Objects ). Но новые версии Delphi поддерживают дженерики (generics) и имеют более узкоспециализированные классы — и их использование будет предпочтительнее по двум причинам: меньше ошибок (нет приведений типов) и быстрее выполнение (может использоваться хэш-таблица).

Рассмотрим такой код с двумя идентичными списками:
Списки заполняются случайными (но идентичными для обоих списков) значениями в цикле:
Попробуем получить каждый объект в обоих списках по его имени (ключу). Оба списка содержат идентичный набор данных, а имена ключей (объектов) хранятся в отельном списке ( sList ):
Сколько времени займёт поиск в отсортированном списке строк (который использует двоичный поиск в случае отсортированного списка) по сравнению со словарём (который использует хэш-ключи)?
Результат работы обоих вариантов кода идентичен (предполагая, что на вход поступил один и тот же набор данных), но скорость выполнения значительно отличается: TStringList работает в четыре раза медленнее словаря (пример дан для миллиона записей).

Этот пример показывает только верхушку айсберга (новых конструкций в Delphi). Конечно, он также с пользой может быть использован в классических приложениях Delphi, но наиболее ценен именно для iOS — из-за изменившейся модели памяти.

Заключение

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

Описанные в этой статье возможности языка (в частности — поддержка ARC) будут формировать будущее Delphi. Эти изменения частично обусловлены поддержкой новой платформы, а частично предназначены для исправления некоторых «плохих» мест в языке Delphi.

Суммирующая табличка по компиляторам для Delphi XE4 (компиляторы для C++ Builder не показаны, за исключением Win64):

$EndIf — Директива компилятора Delphi

Как сделать свои собственные сообщения при компилляции

Как узнать версию компиллятора ?

Какие есть директивы компилятора?

<$I+>и <$I->— директивы контроля ввода/вывода
<$M>и <$S>— директивы, определяющие размер стека

<$M+>и <$M->— директивы информации времени выполнения о типах
<$Q+>и <$Q->— директивы проверки переполнения целочисленных операций

<$R>— директива связывания ресурсов

<$R+>и <$R->— директивы проверки диапазона

<$APPTYPE CONSOLE>— директива создания консольного приложения

1) Директивы компилятора, разрешающие или запрещающие проверку утверждений.

По умолчанию <$C+>или <$ASSERTIONS ON>
Область действия локальная

Директивы компилятора $C разрешают или запрещают проверку утверждений. Они влияют на работу процедуры Assert,используемой при отладке программ. По умолчанию действует
директива <$C+>и процедура Assert генерирует исключение EAssertionFailed , если проверяемое утверждение ложно.
Так как эти проверки используются только в процессе отладки программы, то перед ее окончательной компиляцией следует указать директиву <$C->. При этом работа процедур Assert будет блокировано и генерация исключений EassertionFailed производиться не будет.
Директивы действуют на весь файл исходного кода независимо от того, в каком месте файла они расположены.

2) Директивы компилятора, включающие и выключающие контроль файлового ввода-вывода.

По умолчанию <$I+>или <$IOCHECKS ON>
Область действия локальная

Директивы компилятора $I включают или выключают автоматический контроль результата вызова процедур ввода-вывода Object Pascal . Если действует директива <$I+>, то при возвращении процедурой ввода-вывода ненулевого значения генерируется исключение EInOutError и в его свойство errorcode заносится код ошибки. Таким образом, при действующей директиве <$I+>операции ввода-вывода располагаются в блоке try . except , имеющем обработчик исключения EInOutError . Если такого блока нет, то обработка производится методом TApplication.HandleException .
Если действует директива <$I->, то исключение не генерируется. В этом случае проверить, была ли ошибка, или ее не было, можно, обратившись к функции IOResult . Эта функция очищает ошибку и возвращает ее код, который затем можно анализировать. Типичное применение директивы <$I->и функции IOResult демонстрирует следующий пример:

AssignFile ( F,s );
Rewrite (F);

<$I+>
i:=IOResult ;
if i <> 0 then
case i of
2 : .
3 : .
end ;

В этом примере на время открытия файла отключается проверка ошибок ввода вывода, затем она опять включается, переменной i присваивается значение, возвращаемое функцией IOResult и, если это значение не равно нулю (есть ошибка), то предпринимаются какие-то действия в зависимости от кода ошибки. Подобный стиль программирования был типичен до введения в Object Pascal механизма обработки исключений. Однако сейчас, по-видимому, подобный стиль устарел и применение директив $I потеряло былое значение.

3) Директивы компилятора, определяющие размер стека

По умолчанию <$M 16384,1048576>
Область действия глобальная

Локальные переменные в процедурах и функциях размещаются в стеке приложения. При каждом вызове процедуры или функции ее локальные переменные помещаются в стек. При выходе из процедуры или функции эти локальные процедуры удаляются из стека.
Директивы компилятора $M задают параметры стека приложения: его минимальный и максимальный размеры. Приложение всегда гарантировано имеет размер стека, равный его минимальной величине. Если при запуске приложения Windows обнаруживает, что не может выделить этот минимальный объем памяти, то выдается сообщение об этой ошибке.
Если во время работы выясняется, что минимального размера стека не хватает, то размер увеличивается на 4 K, но не более, чем до установленного директивой максимального размера. Если увеличение размера стека невозможно из-за нехватки памяти или из-за достижения его максимальной величины, генерируется исключение EStackOverflow . Минимальный размер стека по умолчанию равен 16384 (16K). Этот размер может изменяться параметром minstacksize директивы <$M>или параметром number директивы <$MINSTACKSIZE>.
Максимальный размер стека по умолчанию равен 1,048,576 (1M). Этот размер может изменяться параметром maxstacksize директивы <$M>или параметром number директивы <$MAXSTACKSIZE number >. Значение минимального размера стека может задаваться целым числом в диапазоне между1024 и 2147483647. Значение максимального размера стека должно быть не менее минимального размера и не более 2147483647. Директивы задания размера стека могут включаться только в программу и не должны использоваться в библиотеках и модулях.

В Delphi 1 имеется процедура компилятора <$S>, осуществляющая переключение контроля переполнения стека. Теперь этот процесс полностью автоматизирован и директива <$S>оставлена только для обратной совместимости.

4) Директивы компилятора, включающие и выключающие генерацию информации времени выполнения о типах ( runtime type information — RTTI).

По умолчанию <$M->или <$ TYPEINFO OFF>
Область действия локальная

Директивы компилятора $M включают или выключают генерацию информации времени выполнения о типах ( runtime type information — RTTI). Если класс объявляется в состоянии <$M+>или является производным от класса объявленного в этом состоянии, то компилятор генерирует RTTI о его полях, методах и свойствах, объявленных в разделе published . В противном случае раздел published в классе не допускается. Класс TPersistent , являющийся предшественником большинства классов Delphi и все классов компонентов, объявлен в модуле Classes в состоянии <$M+>. Так что для всех классов, производных от него, заботиться о директиве <$M+>не приходится.

5) Директивы компилятора, включающие и выключающие проверку переполнения при целочисленных операциях

По умолчанию <$Q->или <$OVERFLOWCHECKS OFF>
Область действия локальная

Директивы компилятора $Q включают или выключают проверку переполнения при целочисленных операциях. Под переполнением понимается получение результата, который не может сохраняться в регистре компьютера. При включенной директиве <$Q+>проверяется переполнение при целочисленных операциях +, -, *, Abs , Sqr , Succ , Pred , Inc и Dec . После каждой из этих операций размещается код, осуществляющий соответствующую проверку. Если обнаружено переполнение, то генерируется исключение EIntOverflow . Если это исключение не может быть обработано, выполнение программы завершается.
Директивы $Q проверяют только результат арифметических операций. Обычно они используются совместно с директивами <$R>, проверяющими диапазон значений при присваивании.
Директива <$Q+>замедляет выполнение программы и увеличивает ее размер. Поэтому обычно она используется только во время отладки программы. Однако, надо отдавать себе отчет, что отключение этой директивы приведет к появлению ошибочных результатов расчета в случаях, если переполнение действительно произойдет во время выполнении программы. Причем сообщений о подобных ошибках не будет.

6) Директивы компилятора, включающие и выключающие проверку диапазона целочисленных значений и индексов

По умолчанию <$R>или <$RANGECHECKS OFF>
Область действия локальная

Директивы компилятора $R включают или выключают проверку диапазона целочисленных значений и индексов. Если включена директива <$R+>, то все индексы массивов и строк и все присваивания скалярным переменным и переменным с ограниченным диапазоном значений проверяются на соответствие значения допустимому диапазону. Если требования диапазона нарушены или присваиваемое значение слишком велико, генерируется исключение ERangeError . Если оно не может быть перехвачено, выполнение программы завершается.
Проверка диапазона длинных строк типа Long strings не производится.
Директива <$R+>замедляет работу приложения и увеличивает его размер. Поэтому она обычно используется только во время отладки.


6) Директива компилятора, связывающая с выполняемым модулем файлы ресурсов

Область действия локальная

Директива компилятора <$R>указывает файлы ресурсов (.DFM, .RES), которые должны быть включены в выполняемый модуль или в библиотеку. Указанный файл должен быть файлом ресурсов Windows . По умолчанию расширение файлов ресурсов — .RES.
В процессе компоновки компилированной программы или библиотеки файлы, указанные в директивах <$R>, копируются в выполняемый модуль. Компоновщик Delphi ищет эти файлы сначала в том каталоге, в котором расположен модуль, содержащий директиву <$R>, а затем в каталогах, указанных при выполнении команды главного меню Project | Options на странице Directories / Conditionals диалогового окна в опции Search path или в опции /R командной строки DCC32.
При генерации кода модуля, содержащего форму, Delphi автоматически включает в файл . pas директиву <$R *.DFM>, обеспечивающую компоновку файлов ресурсов форм. Эту директиву нельзя удалять из текста модуля, так как в противном случае загрузочный модуль не будет создан и генерируется исключение EResNotFound .


Все установленные в настройках опции компиляции можно вставить непосредственно в текст программы нажав клавиши Ctrl-O , O

Как сделать свои собственные сообщения при компилляции ?

destructor TumbSelectionTempTable.Destroy ;
begin
// Clear the temp tables.
<$MESSAGE Warn ' - remember to free all allocated objects'>
ClearAllOuterWorldFold ;
if FSubjectsTempTableCreated then
DropTempTable ( FTableName );

FOuterWorldsFolded.Free ;
FQuery.Free ;
inherited ;
end ;

Работает только в Дельфи 6/7

Как узнать версию компилятора?

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

В Дельфи предопределены специальные константы компиляции для этого:

Ver80 — Дельфи 1
Ver90 — Дельфи 2
Ver93 — С Buider 1
Ver100 — Дельфи 3
Ver110 — С Buider 3
Ver120 — Дельфи 4
Ver125 — С Buider 4
Ver130 — Дельфи 5
Ver140 — Дельфи 6
Ver150 — Дельфи 7

procedure TForm1.Button2Click( Sender : TObject );
const Version=

$EndIf — Директива компилятора Delphi

<$I+>и <$I->— директивы контроля ввода/вывода <$M>и <$S>— директивы, определяющие размер стека <$M+>и <$M->— директивы информации времени выполнения о типах <$Q+>и <$Q->— директивы проверки переполнения целочисленных операций <$R>— директива связывания ресурсов <$R+>и <$R->— директивы проверки диапазона <$APPTYPE CONSOLE>— директива создания консольного приложения 1) Директивы компилятора, разрешающие или запрещающие проверку утверждений. По умолчанию <$C+>или <$ASSERTIONS ON>Область действия локальная Директивы компилятора $C разрешают или запрещают проверку утверждений. Они влияют на работу процедуры Assert,используемой при отладке программ. По умолчанию действует директива <$C+>и процедура Assert генерирует исключение EAssertionFailed, если проверяемое утверждение ложно. Так как эти проверки используются только в процессе отладки программы, то перед ее окончательной компиляцией следует указать директиву <$C->.

Директивы компилятора — Версии Delphi

У меня есть блок, который я написал в Delphi 7 некоторое время назад, и просто получил удовольствие (боль) от преобразования в Delphi XE (Unicode).

Устройства работают отлично после некоторых проблем, теперь я пытаюсь сделать этот модуль совместимым с разными версиями Delphi, если мне когда-либо понадобится переключить IDE обратно в Delphi 7, обновляя какой-то другой код.

У меня только Delphi 7 и Delphi XE, но из того, что я собираю, код, написанный в Delphi 1, Delphi 2007 будет компилироваться, но код из Delphi 2009 и выше будет Unicode.

. В любом случае, в модуле я разделяю un-unicode и unicode так:

Как изменить директиву компилятора, чтобы правила применялись к нескольким версиям? Например, что-то вроде:

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

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

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

Директивы компилятора — Версии Delphi

У меня есть блок, который я написал в Delphi 7 некоторое время назад, и просто получил удовольствие (боль) от преобразования в Delphi XE (Unicode).

Устройства работают отлично после некоторых проблем, теперь я пытаюсь сделать этот модуль совместимым с разными версиями Delphi, если мне когда-либо понадобится переключить IDE обратно в Delphi 7, обновляя какой-то другой код.

У меня только Delphi 7 и Delphi XE, но из того, что я собираю, код, написанный в Delphi 1, Delphi 2007 будет компилироваться, но код из Delphi 2009 и выше будет Unicode.

. В любом случае, в модуле я разделяю un-unicode и unicode так:

Как изменить директиву компилятора, чтобы правила применялись к нескольким версиям? Например, что-то вроде:

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

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

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

Условная компиляция модулей

Столкнулся с немного странным поведением Delphi 2010. Исходная проблема такова: допустим проект в зависимости от настроек может иметь некоторый модуль а может и нет. Под модулем будем понимать дата-модуль. Т.е. хочется в некотором конфиге при компиляции определить некоторую константу — использовать или не использовать указанный модуль. И вот в результате столкнулся с проблемой.

Комментарии

Это во всех версиях так.
D5,D6,D7,D2005,D2006,D2007,D2010,DXE

Не выявил закономерностей.
Регулярно проверяю *.DPR
Держу в большом коментарии шаблон, как должно быть.
После обширных правок проверяю.

Поступаю именно так же. Ну и конечно, при коммите в систему контроля версий всегда проверяю dpr-файл проекта.

common.inc «выпадает» из проекта, т.к не перечислен в USES в dpr-файле (не подключен к проекту; и наверное, вообще, не модуль — unit), он просто там включается (include), что совсем разные вещи.

TestDM «выпадает» от того, что директива компилятора не в настройках проекта задана, а в include-файле. Попробуйте вписать USE_MODULE в настройки проекта, вдруг получится.

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

Как сказано выше — это проблема любой версии Delphi.
Беда в том, что раздел uses drp-файла среда полностью создаёт заново при добалвении/исключении модулей проекта.

Я эту проблему решил следующим образом: создал юнит, например под таким названием:
MyAdditions.pas
В котором директивами включаю (или не включаю) доп. модули в uses этого файла (у меня там FastMM/FastCode/EurekaLog etc.)

А в dpr-файле в разделе uses модуль MyAdditions идёт первым.

>> Что же делать, как же быть? кто сталкивался с подобным поведением, >> или если у вас были подобные ситуации, то как вы их разрешали?
>> Поделитесь опытом, товарищи!

Директивы компилятора — Версии Delphi

У меня есть блок, который я написал в Delphi 7 некоторое время назад, и просто получил удовольствие (боль) от преобразования в Delphi XE (Unicode).

Устройства работают отлично после некоторых проблем, теперь я пытаюсь сделать этот модуль совместимым с разными версиями Delphi, если мне когда-либо понадобится переключить IDE обратно в Delphi 7, обновляя какой-то другой код.

У меня только Delphi 7 и Delphi XE, но из того, что я собираю, код, написанный в Delphi 1, Delphi 2007 будет компилироваться, но код из Delphi 2009 и выше будет Unicode.

. В любом случае, в модуле я разделяю un-unicode и unicode так:

Как изменить директиву компилятора, чтобы правила применялись к нескольким версиям? Например, что-то вроде:

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

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

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

Илон Маск рекомендует:  Php руководство по рнр 3 0 функции, связанные с http
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL