Migrating Delphi Code to Oxygene

Delphi is more than a language, it is an entire development eco-system with libraries, third party components, and more. Oxygene shares its basic language – Object Pascal – with Delphi, but it does not share the rest of the eco-system.

While simple pure-pascal Delphi code should easily migrate to Oxygene without many changes, most complex Delphi projects will need adjustment – minor or major – to get the code base over to Oxygene. Whether it is gto adjust for (minor) language differences, or different platform APIs.

Oxygene provides tools and libraries on many levels to help you get your Delphi code into Oxygene.

Note that it might not make sense for every project to migrate fully to 100% Oxygene, depending on how deeply your project depends on things such as VCL, TDataSet, or Delphi-specific third-party components.

New Oxygene code can also interact well with code compiled in Delphi, for example using Hydra, direct P/Invoke, COM, or external method imports.

Preparing Your Delphi Code for the Oxygene Language

Leaving aside differences in available APIs (Delphi RTL, VCL, etc. vs. what is available on the Platforms targeted by Oxygene), most Delphi Object Pascal code should compile easily in Oxygene, with no or very few changes.

But there are a few arreas you might want to look at:

Minor Language Differences

Oxygene branched off from Delphi around the Delphi 7 time-frame, so its language design is very close to that of Delphi, and mainly focuses on extending the language, rather than diverging from it. That said, there are a few areas where we made explicit design choices to differ in syntax, and a couple others where Delphi gained features later than Oxygene, and chose different syntaxes where Oxygene's preceded (for example, Generic Constraints).

These differences are documented in the Minor Differences topic, and virtually all of them can be solved by one of two options:

The first option leaves your code unchanged, and lets the compiler accept more Delphi-isms in code than it does in normal Oxygene mode. This is especially great if you want to share code between Oxygene and Delphi, rather than migrating it fully to Oxygene-only.

The second option adjusts your code, to automatically change (where possible) legacy Delphi syntax into the correct Oxygene variant.

It is perfectly valid to use both options in combination, as well.

New Keywords

Oxygene has added (and keeps adding) a lot of new language features to 2004-style Object Pascal, and with that comes the introduction of a range of new keywords to support those features.

Many of these keywords are (just as in Delphi) limited to specific contexts, and this will not interfere with your regular code much (for example, order is used only within LINQ Expressions, so you will have no problem using it as a regular identifier in your code. Others (such as for example event or new are global keywords, and if your Delphi code uses them as identifiers (for example as name of a class or field), this will cause compile time errors.

Oxygene has a few ways to mitigate this:

  • In Oxygene, any identifier after a "." will not be treated as a keyword, as no keyword is valid in that context. This means that in many cases, when accessing methods, properties or fields with Oxygene keywords as names, this will compile just fine.

  • For all other cases, Oxygene allows the use of an ampersand ("&") before a keyword to "escape" it, and use it as a regular identifier.

var &Event: SomeClass; // & escapes the "event" keyword
...
x := MyClass.Event; // no escape needed after "."

Of course, for a large Delphi code-base, manually escaping any such cases could be a lot of work, that's why Oxygene comes with a tool that allows you to bulk-escape all known Oxygene keywords (that are not also keywords in Delphi) found in your code.

Note that Keyword Escaper is a one-time process that you should run on your Delphi code before starting to use Oxygene-specific features in it. If your code already uses Oxygene-only keywords, running Keyword Escaper will most likely break it!

Smarter {$IFDEF...} Processing

Oxygene uses a smarter (but in some cases more restrictive) pre-processor for {$IFDEF...} and other related directives for Conditional Compilation.

While not recommended, if your code depends on "unstructured" $IFDEFs and cannot be easily reworked, you might want to – at least temporarily – enable the (undocumented) UseLegacyPreprocessor project setting my manually setting it in your .elements project file.

API Differences

Finally, a big difference when moving Delphi code to Oxygene is the availability of APIs, which is virtually completely different between the Delphi RTL/VCL ecosystem and Oxygene, which uses each platform's native APIs and class libraries (e.g. the .NET Base library on .NET, Java/Android classes on Java and Apple's Cocoa classes on macOS and iOS.

By default, pretty much none of the classes and standard System methods you are familiar with from Delphi will be available in Oxygene.

In particular, Oxygene uses each platform's native Object class as the root of the class tree, which notably does not have a .Free method (as all Oxygene platforms use automatic life-cycle management.

Oxygene also uses the native String type, which on all platfroms is reference based, read-only, and 0-indexed – compared to Delphi's special String type that's writable, 1-based and a copy-on-write value type.

The open-source Delphi RTL library can mitigate some of this, it can be used by adding the "Delphi" library reference to your project and adding the RemObjects.Elements.RTL.Delphi namespace to your uses clause of individual files, or to the global DefaultUses project setting.

Delphi RTL provides:

  • An TObject alias type and extensions that provides Delphi-Compatibility for that class, including the ability to call .Free (even though the calls will be ignored as Oxygene relies on GC or ARC to actually free objects.

  • A replacement String type that behaves like Delphi's string, with seamless bridging semantics to the platform's native Strings.

  • Clean implementations of many (but far from all) common methods and classes from Delphi's RTL, such as SysUtils and the like.

You can read more about Delphi RTL, here.