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

Содержание

Директивы по версиям компилятора 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 будут работать как есть.

Список директив компилятора Delphi, нужно ли указывать директивы перед именем устройства? — compiler-construction

im работает над приложением, используя delphi 7, и я просто наткнулся на это

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

может кто-нибудь сказать мне

  • Как использовать директивы перед именем элемента, делает ли это глобальным?
  • И можем ли мы создать наши собственные директивы в некоторых конкретных ситуациях?
  • где указаны директивы компилятора?
    3 3
  • 31 окт 2020 2020-10-31 11:20:41
  • PresleyDias

3 ответа

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

  • 31 окт 2020 2020-10-31 11:20:42
  • Gilberto Da Silva Campos
  • Как использовать директивы перед именем элемента, делает ли это глобальным?

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

  • И можем ли мы создать наши собственные директивы в некоторых конкретных ситуациях?

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

Они определены в источниках компилятора. Они задокументированы в документации по компилятору, и вы уже нашли ссылку на решетку (вторая ссылка)

  • 31 окт 2020 2020-10-31 11:20:42
  • Cosmin Prund
  • Ключевая тема, объясняющая принципы, приведена здесь: директивы компилятора Delphi.
  • Директивы компилятора перечислены здесь: Директивы компилятора Delphi (список).

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

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

Директивы переключателей являются глобальными или локальными:

  • Глобальные директивы влияют на всю компиляцию и должны отображаться перед объявлением части программы или скомпилированного устройства.
  • Локальные директивы влияют только на часть компиляции, которая простирается от директивы до следующего появления той же директивы. Они могут появляться где угодно.

Однако рассмотрите директиву DENYPACKAGEUNIT (внимание мое):

Директива <$DENYPACKAGEUNIT ON>предотвращает создание блока Delphi, из которого он помещается в пакет.

Если эта директива имеет единую область видимости, то она просто должна присутствовать в блоке, чтобы вступить в силу.

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

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

То, что, по-видимому, произошло в представленном вами коде, заключается в том, что автор набрал CTRL+O O , а среда IDE вставила различные параметры, определенные в параметрах проекта в этот момент времени.

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

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

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

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

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

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

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

2 ответов

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

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

Список директив компилятора Delphi, нужно ли размещать директивы перед именем модуля?

я работаю над приложением, использующим Delphi 7 , и я только что столкнулся с этим

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

может кто-нибудь сказать мне

  1. Какая польза от наличия Директив перед именем единицы, это делает их глобальными?
  2. И можем ли мы создавать свои собственные директивы в некоторых конкретных ситуациях?
  3. где определены директивы компилятора?

2 ответа

  • Ключевая тема, объясняющая принципы, здесь: Директивы компилятора Delphi .
  • Директивы компилятора перечислены здесь: Директивы компилятора Delphi (список) .

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

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

Директивы Switch являются глобальными или локальными:

  • Глобальные директивы влияют на всю компиляцию и должны появляться перед объявлением части программы или компилируемого модуля.
  • Локальные директивы влияют только на ту часть компиляции, которая простирается от директивы до следующего появления той же директивы. Они могут появиться где угодно.

Однако рассмотрим директиву DENYPACKAGEUNIT (выделено мной):

Директива <$DENYPACKAGEUNIT ON>предотвращает помещение модуля Delphi, в котором он находится, в пакет.

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

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

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

Похоже, что произошло в коде, который вы представляете, что автор набрал CTRL + O O, и среда IDE вставила различные параметры, определенные в настройках проекта в тот момент времени.

Delphi список Директивы компилятора, мы должны поместить директивы перед именем блока?

им работать на приложения с помощью Delphi 7 , и я просто наткнулся на это

автор кода поставил так много директив компилятора перед основным названием блока.

может ли один скажите мне

  1. Что толк иметь директивы перед единичным именем, это делает их глобальными?
  2. И мы можем создать наши собственные директивы в некоторых конкретных ситуациях?
  3. где определены директивы компилятора?
  • Ключевая тема объяснения принципов здесь: директивы Delphi компилятора .
  • Директивы компилятора перечислены здесь: Delphi Compiler директивы (Список) .

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

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

директивы коммутатора либо глобальные или локальные:

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

Тем не менее, рассмотрим DENYPACKAGEUNIT директиву (курсив мой):

<$DENYPACKAGEUNIT ON>Директива запрещает Delphi блок , в котором он появляется из помещаются в пакет.

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

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

Обратите внимание, что вам нужно только установить директивы в коде, если вы хотите изменить настройки, сделанные в настройках проекта. Это вполне разумно, чтобы установить параметры в параметрах проекта, а не устанавливать их в коде.

Что , похоже, произошло в коде вы представляете, что автор напечатал CTRL+O O и IDE вставлены различные параметры, определенные в проекте вариантов на тот момент времени.

Блог 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):

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

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

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

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

Теперь нажмите F9 и проверьте, что написано в отладчике в «Events»:

Разберемся с тем, что мы только что написали.

$IFDEF — это директива компилятора;

DEBUG — условное определение или символ условной компиляции. Символ обязательно должен начинаться с буквы за которой может следовать любое количество букв, цифр и знаков подчеркивания, однако использоваться будут лишь первые 255 символов.

Процедура отправляет строку в отладчик для отображения.

Завершает условную компиляцию, инициированную последней директивой <$IFxxx>(почему не <$IFDEF>— смотрим далее).

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

Где определено условное определение DEBUG? Конкретно в этом случае, символ DEBUG можно найти, если зайти в настройки проекта: Project -> Options ->Delphi Compiler :

Здесь же можно определить и свои собственные символы. Давайте, например, добавим свой символ условной компиляции TEST. Для этого открываем диалоговое окно редактирования символов условной компиляции (жмем кнопку «…» в строке «Conditional defines») и заносим наш символ в список:

Теперь изменим наш код, приведенный в начале статьи, чтобы он работал с нашим символом условной компиляции:

Теперь можете снова запустить приложения в режиме отладки и посмотреть, что в Events появится строка «TEST IS ON».

Сейчас мы с вами рассмотрели самый простой способ использования условной компиляции в Delphi — использование директив и , а также определение собственного символа условной компиляции через свойства проекта. Вместе с этим, определить (включить) свой символ условной компиляции можно и в коде программы. Для этого используется директива компилятора . Воспользуемся этой директивой:

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

и убедиться, что символ DEBUG выключен, а в окне Events не появится строка «debug is on».

Двигаемся далее. Что делать, если нам необходимо вывести строку не когда символ включен, а именно тогда, когда он выключен? Здесь, опять же, есть варианты. Короткий вариант — воспользоваться директивой противоположной — она называется и код между и выполняется, если символ выключен:

Второй вариант — использование директивы , если в зависимости от состояния символа условной компиляции вам надо выполнять различные участки кода:

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

Также следует обратить внимание на то, что все условные символы оцениваются в Delphi, когда вы выполняете Build проекта. Справка Delphi рекомендует для надежности пользоваться командой Project -> Build All Projects, чтобы быть уверенным, что все символы условной компиляции определены верно.

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

Например, символ условной компиляции VER330 определен для Delphi 10.3 Rio и с его помощью можно определить какой код должен или не должен выполняться, в случае, если версия компилятора Delphi — 33. Например, воспользуемся фичей Delphi 10.3 Rio под названием Inline Variable Declaration:

Сразу может возникнуть вопрос: как сделать так, чтобы приведенный выше код сработал не только в Delphi 10.3 Rio, но и в последующих версиях?
Это можно сделать воспользовавшись, например, такой конструкцией:

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

Здесь же стоит обратить внимание и на окончание блока — мы использовали директиву , как того требовала Delphi до версии Delphi XE4:

  • для директивы $IFDEF должна быть определена директива $ENDIF
  • для директивы $IF должна быть определена директива $IFEND

В XE4 нам разрешили использовать для закрытия блоков <$IF>, и . Однако, если у вас возникают проблемы при использовании связки и , то вы можете использовать специальную директиву , чтобы потребовать использовать для именно <$IFEND>:

Теперь, если в коде выше использовать директиву $ENDIF, то получим сообщение об ошибке:

Директиву , кстати, можно использовать и с другими константами проекта, например, так:

Так как наша константа Version содержит значение 2, то выполнится участок кода расположенный после . Можете сменить значение константы Version на 1, чтобы убедиться, что выполнится участок кода, где определена переменная s.

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

  1. Использование условной компиляции позволяет нам выполнять тот или иной код, в зависимости от того, какие константы и символы условной компиляции определены или не определены в проекте.
  2. Используя предопредленные символы условной компиляции можно указывать Delphi какой код необходимо выполнить, например, если программа собирается под Android, или, если поддерживается архитектура x64 и т.д.
  3. Директива $IF может использоваться с различными константами, в том числе и определенными самим разработчиком.

При подготовке статьи использовалась следующая информация официальной справки по Delphi:

Директива компилятора Delphi область — обратно совместимы единичные файлы?

Учитывая желание использовать директиву компилятора полезной области в единичных файлах, что лучший подход, чтобы эти же единица от использования в предыдущих версиях Delphi? Там, кажется, не быть «достойной» подход. (Желаемый IDE версия Delphi 7)

Мне нравится иметь регионы выше определений методов, чтобы скрыть / показать комментарии помогают определению метода и версии истории, но я должен был бы окружать директивы области с другими директивами компилятора, который своего родом убивает в «ЧИСТОТУ» директивы компилятора области.

Я полагаю, препроцессор может быть написано закомментировать все директивы Региона / EndRegion при использовании версии Delphi, которая не поддерживает regions..and добавить затем обратно для более поздних версий?

Я не 100% перешли на Delphi 2009 и должны поддерживать несколько версий IDE.

Я предполагаю, что D7 не хочет copile <$ REGION>или <$ ENDREGION>? У меня есть только D2007 / 9 установлен и не может проверить, что.

редактор D2009 создает область, как и ожидалось. D7 следует игнорировать $ РЕГИОН / $ ENDREGION если «UNDEF» не определен.

Вы можете использовать следующий код:

Есть несколько Delphi (7) редактор повышение-компоненты там, которые обеспечивают что — то вроде этого. Я думаю , что GExperts делает.

Если вы нормально с воздействием на вашем коде, вы можете рассмотреть <$ I>, чтобы разбивать большие файлы.

Список директив компилятора Delphi, нужно ли размещать директивы перед именем модуля?

я работаю над приложением, использующим Delphi 7 , и я только что столкнулся с этим

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

может кто-нибудь сказать мне

  1. Какая польза от наличия Директив перед именем единицы, это делает их глобальными?
  2. И можем ли мы создавать свои собственные директивы в некоторых конкретных ситуациях?
  3. где определены директивы компилятора?

2 ответа

  • Ключевая тема, объясняющая принципы, здесь: Директивы компилятора Delphi .
  • Директивы компилятора перечислены здесь: Директивы компилятора Delphi (список) .

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

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

Директивы Switch являются глобальными или локальными:

  • Глобальные директивы влияют на всю компиляцию и должны появляться перед объявлением части программы или компилируемого модуля.
  • Локальные директивы влияют только на ту часть компиляции, которая простирается от директивы до следующего появления той же директивы. Они могут появиться где угодно.

Однако рассмотрим директиву DENYPACKAGEUNIT (выделено мной):

Директива <$DENYPACKAGEUNIT ON>предотвращает помещение модуля Delphi, в котором он находится, в пакет.

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

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

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

Похоже, что произошло в коде, который вы представляете, что автор набрал CTRL + O O, и среда IDE вставила различные параметры, определенные в настройках проекта в тот момент времени.

Директива компилятора Delphi область — обратно совместимы единичные файлы?

Учитывая желание использовать директиву компилятора полезной области в единичных файлах, что лучший подход, чтобы эти же единица от использования в предыдущих версиях Delphi? Там, кажется, не быть «достойной» подход. (Желаемый IDE версия Delphi 7)

Мне нравится иметь регионы выше определений методов, чтобы скрыть / показать комментарии помогают определению метода и версии истории, но я должен был бы окружать директивы области с другими директивами компилятора, который своего родом убивает в «ЧИСТОТУ» директивы компилятора области.

Я полагаю, препроцессор может быть написано закомментировать все директивы Региона / EndRegion при использовании версии Delphi, которая не поддерживает regions..and добавить затем обратно для более поздних версий?

Я не 100% перешли на Delphi 2009 и должны поддерживать несколько версий IDE.

Я предполагаю, что D7 не хочет copile <$ REGION>или <$ ENDREGION>? У меня есть только D2007 / 9 установлен и не может проверить, что.

редактор D2009 создает область, как и ожидалось. D7 следует игнорировать $ РЕГИОН / $ ENDREGION если «UNDEF» не определен.

Вы можете использовать следующий код:

Есть несколько Delphi (7) редактор повышение-компоненты там, которые обеспечивают что — то вроде этого. Я думаю , что GExperts делает.

Если вы нормально с воздействием на вашем коде, вы можете рассмотреть <$ I>, чтобы разбивать большие файлы.

Илон Маск рекомендует:  Загадочное свойство hasLayout
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL