Migrating Delphi Code to Mobile from Desktop

From RAD Studio
Jump to: navigation, search

Go Up to Delphi Considerations for Multi-Device Applications


This topic describes how to migrate existing Delphi code to use the Delphi mobile compilers:

Eliminate Data Types that Are Not Supported by the Delphi Mobile Compilers

Code that uses any of the following unsupported types should either be eliminated or rewritten to use an alternate type:

WideString, AnsiString, ShortString, AnsiChar, PAnsiChar, PWideChar, Openstring

Type Used in Desktop Applications
(1-based)

Type to Use in Mobile Apps
(0-based)

System.WideString

System.String

System.AnsiString, System.ShortString

Eliminate usage.

Consider using 'array of byte'

System.AnsiChar

System.Char, System.Byte, System.UInt8

System.PAnsiChar, System.PWideChar

System.SysUtils.TStringBuilder, System.String, System.MarshaledString

System.Openstring

Consider using 'array of byte'

Details follow about replacing these unsupported types.

WideString

In some cases, WideString can be replaced with String and TWideStringDynArray can be replaced with TStringDynArray.

If you need to use WideString on the mobile platform for some reason, you should marshal it by handling the 4-byte length, and processing the Unicode character sequence and the two null characters representing the string terminator. For an example using ShortString, see the code example ShortStringToString (Delphi).

AnsiString and ShortString

This grouping includes AnsiString and explicit length ShortStrings using the syntax: type TStr = string[127];.

Note: The "derived" types UTF8String and RawByteString are supported in mobile devices.

Either remove or change instances of AnsiString and ShortString, depending on the original usage. In some cases, 'array of byte' (such as System.Types.TByteDynArray) is enough.

In many cases, you need to decode and encode the older format as needed. Most uses of AnsiString can be directly replaced with the default String type. Most information saved externally with this type used streams or classes using streams, such as TStringList or similar classes. These class types support byte order marks (BOM), and decode as necessary automatically. Where conversion is necessary, use the TEncoding class to obtain bytes directly. In fact, TStrings, the base class of TStringList, supports explicit specification of TEncoding instances in methods such as TStrings.SaveToFile and TStrings.LoadFromFile, so that your program can use the normal String type regardless of the ultimate encoding needed for storage outside of the program.

For code that was using short strings, use TEncoding to manage the difference between the UTF16 character representation used in the current String type and the 8-bit ANSI representation of the old short string.

See the code example ShortStringToString (Delphi).

AnsiChar

Use (Wide)Char or Byte(UInt8) to replace AnsiChar.

Depending on the original semantics:

  • If the original semantics is 'Character', use Char with UNICODE conversion.
  • If the original semantics is 8-bit storage, use Byte type.

PAnsiChar and PWideChar

If these types are pointed to Ansi/Unicode string, use String or TStringBuilder object instead of the PAnsiChar/PWideChar type.

If the original semantics is related to an API call, replace it with API marshaling functionality. Typically, System.MarshaledString is sufficient.

Openstring

System.Openstring is an old language element. Currently System.Generics.Defaults uses the OpenString type, but it is seldom used anywhere else in RAD Studio.

Often "array of byte" can be used in place of OpenString as seen in the ShortStringToString() function in the code example ShortStringToString (Delphi). "Array of byte" as used here constitutes an open array parameter and accepts arrays of byte of any length ,just as OpenString permitted strings of any declared size.

See http://www.drbob42.com/uk-bug/hood-03.htm

Use 0-Based Strings

For Delphi mobile compilers, strings have 0-based indexing. In addition, they are likely to become immutable (constant) in the future.

Indexing of Strings in the Delphi Compilers
Delphi Compilers String Indexing

Delphi mobile compilers:

0-based

(the starting index of the first character in a string is zero)

Delphi desktop compilers:

1-based

(the starting index of the first character in the string is one)

We recommend that you rewrite any code that assumes strings are 1-based or mutable.

  • Zero-Based Strings: For any 1-based index to access character elements of a string, rewrite the code to use 0-based indexing (an example follows).
  • Immutable Strings: If you want to change a character inside an immutable string, you have to break two or more, and combine these portions, or use a TStringBuilder.
    For example, the following common operation (indexing into a string and modifying the string) cannot be done with immutable strings:
    S[1] := 'A';
If you use a string operation such as this, the Delphi mobile compilers emit the warning W1068 Modifying strings in place may not be supported in the future (Delphi). At some point, this warning is to be replaced by an error; you can convert it into an error right now on the Hints and Warnings page in Project Options.

We Recommend Using TStringHelper to Handle Strings in Mobile and Desktop Apps

The class or record helper System.SysUtils.TStringHelper is useful for working with strings and writing platform-independent code. You can use TStringHelper in all environments (desktop and mobile). TStringHelper performs automatic conversions, so you can use TStringHelper with both 0-based and 1-based strings. Internally, all the functions and properties of TStringHelper are 0-based in all scenarios.

Some of the RTL functions that work with 1-based strings have direct replacements in TStringHelper functions, as shown in the following table:

Delphi RTL Function
(1-based)

TStringHelper Function
(0-based)*

System.Pos
TStringHelper.IndexOf
System.Delete
TStringHelper.Remove
System.Copy
TStringHelper.Substring
System.SysUtils.Trim
TStringHelper.Trim
* The helper functions work correctly for both 1-based and 0-based strings.

This topic contains examples of all of the replacements suggested above (except for Delete-Remove).
The following subtopics illustrate the changes required to migrate your code from 1-based to 0-based string indexing:

Testing Immutable Strings

To test immutable strings, do one of the following:

  • Set the compiler directive {$WARN IMMUTABLE_STRINGS <ON|ERROR>}.
  • On the Hints and Warnings page, set the warning "Modifying strings in-place...." to "true" or "error".

When strings are edited in place, the following warning/error message is displayed: W1068 Modifying strings in place may not be supported in the future (Delphi)

Example of Converting Strings from 1-based to 0-based

Here is an example showing how to change a 1-based string to work in all platforms:

 function Trim(const S: string): string;
 var
   I, L: Integer;
 begin
   L := Length(S);
   I := 1;
   if (L > 0) and (S[I] > ' ') and (S[L] > ' ') then Exit(S);
   while (I <= L) and (S[I] <= ' ') do Inc(I);
   if I > L then Exit('');
   while S[L] <= ' ' do Dec(L);
   Result := Copy(S, I, L - I + 1);
 end;

Using TStringHelper.Chars to Access Characters in a String

Chars is a useful property of TStringHelper:

Chars[Index]

This read-only property can access all characters of a string. Remember, strings are always 0-based for the Delphi mobile compilers.

Example of using the Chars property to access individual characters:

 function Trim(const S: string): string;
 var
   I, L: Integer;
 begin
   L := S.Length - 1;
   I := 0;
   if (L > -1) and (S.Chars[I] > ' ') and (S.Chars[L] > ' ') then Exit(S);
   while (I <= L) and (S.Chars[I] <= ' ') do Inc(I);
   if I > L then Exit('');
   while S.Chars[L] <= ' ' do Dec(L);
   Result := S.SubString(I, L - I + 1);
 end;

Using System.Low and System.High to Access the First and Last Index of a String

You can use the Delphi intrinsic routines High and Low applied to strings.

  • Low(s) returns 0 in our 0-based string scenario, but returns 1 for an 1-based string.
  • High(s) returns Length(s) - 1 in our 0-based string scenario, but returns Length(s) for a 1-based string.

To detect the first index of a string, use:

Low(string)

For example, you can replace this commonly used for statement:

for I := 1 to Length(S) do

with this for statement:

for I := Low(S) to High(S) do

For another example, when s = '' (empty):

  • Low(s) = 0 and High(s) = -1 for 0-based strings.
  • Low(s) = -1 and High(s) = 0 for 1-based strings.

Replacing the System.Pos Function with TStringHelper.IndexOf

The System.Pos function works with 1-based strings and not with 0-based strings. Instead of Pos, you can use TStringHelper.IndexOf. The IndexOf function returns the zero-based index position of the Value parameter (either a Char or a string) if that string is found, or -1 if it is not found.


Example:

 s := 'The quick brown fox jumps over the lazy dog'; // s is a string type variable.
 WriteLn(Pos('ow', s));    // 13
 WriteLn(s.IndexOf('ow')); // 12

Note: The TStringHelper.IndexOf function is similar to the .NET implementation, except that

if the Value string is empty, .NET returns 0, but the Delphi RTL returns -1.

Replacing the System.Copy Function with TStringHelper.Substring

The System.Copy function works with 1-based strings but not with 0-based strings. Instead of Copy, you can use TStringHelper.Substring:

  function TStringHelper.Substring(StartIndex: Integer; Length: Integer): string;

The Substring function returns a string equivalent to the substring of length that begins at StartIndex in this instance. If StartIndex is larger or equal to the length of this instance, Substring returns an empty string. If Length is zero or a negative number, Substring returns an empty string.

Example

 s := '123456789'; // s is string type variable.
 writeln( Copy(s, 2, 3) );     // 234
 writeln( s.Substring(1, 3) ); // 234

Note: The TStringHelper.Substring function is similar to the .NET implementation, except that .NET raises an ArgumentOutOfRangeException exception if StartIndex plus Length indicates a position not within this instance, or if StartIndex or Length is less than zero. The Delphi RTL, on the other hand, does not raise any exception. For the above condition, Substring returns an empty string.

Update Array Types

Update all array declarations to be dynamic. Use either of the following:

var x: array of Integer;
x: TArray<Integer>;

There are cases when a structure (record) must be passed through to an external function, and the structure contains an array declared with a specific length. This is especially true for character arrays declared "in-place" within the structure. In this case, and only this case, is the following allowed:

type
   rec = record
    Flags: Integer;
    Chars: array[MAX_PATH] of Char;
  end;

In cases where an external function takes an array directly, use a dynamic array instead. For UTF8 character arrays:

Use a Function Call in a try-except Block to Prevent Uncaught Hardware Exceptions

With compilers for iOS devices, except blocks can catch a hardware exception only if the try block contains a method or function call. This is a difference related to the LLVM backend of the compiler, which cannot return if no method/function is called in the try block.

For example, this is how to structure a try-except block that can catch a hardware exception:

var
  P: ^Integer = nil;

procedure G1;
begin
  P^ := 42;
end;

begin
  try
    G1;
  except
    writeln('Catch:G1 - pass');
  end;
end.

Even if a block of source code looks like it contains a function call, that may not be the case if the function is inlined. By the time LLVM is generating machine instructions, the inlining process has already occurred and there is no longer a function call within the try block.

Use Atomic Instrinsics Instead of Assembly Language

The Delphi mobile compilers do not support a built-in assembler. If you need to atomically exchange, compare-and-exchange, increment, and decrement memory values, you can use the new atomic intrinsic functions.

Atomic operations are used to implement multi-threaded locking primitives and provide the primitives necessary for implementing so-called "lock-free" structures. The kinds of operations needed are implemented as standard functions or "intrinsic" functions.

In a multi-platform application, atomic intrinsics can be used inside {$IFDEF} for either the AUTOREFCOUNT or NEXTGEN conditional.

Atomic Intrinsic Functions

Following are the atomic intrinsic functions supported by the Delphi mobile compilers:

Automatic Reference Counting

The Delphi mobile compilers (DCCIOS32, DCCIOS32ARM, and DCCAARM) use automatic reference counting (ARC) for classes, a reference counting scheme that is different from the scheme used by the Delphi desktop compilers (DCC32, DCC64, and DCCOSX). However, all the Delphi compilers support ARC for interfaces, strings, and dynamic arrays, so in effect the Delphi mobile compilers are merely extending ARC to classes. ARC includes automatic memory management and disposal.

Note: For Delphi compilers that supporting ARC, object instances referring to each other can effectively lock memory without one of the references being tagged with the weak reference attribute.

For more information on ARC and weak references, see:

See Also