Migrating Delphi Code to Mobile from Desktop
Go Up to Delphi Considerations for Multi-Device Applications
Contents
- 1 Eliminate Data Types that Are Not Supported by the Delphi Mobile Compilers
- 2 Use 0-Based Strings
- 2.1 We Recommend Using TStringHelper to Handle Strings in Mobile and Desktop Apps
- 2.2 Testing Immutable Strings
- 2.3 Example of Converting Strings from 1-based to 0-based
- 2.4 Using TStringHelper.Chars to Access Characters in a String
- 2.5 Using System.Low and System.High to Access the First and Last Index of a String
- 2.6 Replacing the System.Pos Function with TStringHelper.IndexOf
- 2.7 Replacing the System.Copy Function with TStringHelper.Substring
- 3 Update Array Types
- 4 Use a Function Call in a try-except Block to Prevent Uncaught Hardware Exceptions
- 5 Use Atomic Instrinsics Instead of Assembly Language
- 6 Automatic Reference Counting
- 7 See Also
This topic describes how to migrate existing Delphi code to use the Delphi mobile compilers:
- DCCIOS32.EXE, the Delphi Compiler for the iOS Simulator
- DCCIOSARM.EXE, the Delphi Compiler for the 32-bit iOS Device
- DCCIOSARM64.EXE, the Delphi Compiler for the 64-bit iOS Device
- DCCAARM.EXE, the Delphi Compiler for Android
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 |
Type to Use in Mobile Apps |
---|---|
Eliminate usage. Consider using 'array of byte' | |
| |
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 |
Delphi desktop compilers: |
1-based |
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';
- For example, the following common operation (indexing into a string and modifying the string) cannot be done with immutable strings:
- 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 |
TStringHelper Function |
---|---|
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:
- Replacing the System.Pos Function with TStringHelper.IndexOf
- Replacing the System.Copy Function with TStringHelper.Substring
- Example of Converting Strings from 1-based to 0-based
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)
returnsLength(s) - 1
in our 0-based string scenario, but returnsLength(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 andHigh(s)
= -1 for 0-based strings.Low(s)
= -1 andHigh(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:
- To convert from UTF8, use TBytes along with System.SysUtils.TEncoding.GetString(Bytes).
- To convert to UTF8, use TBytes along with System.SysUtils.TEncoding.GetBytes(S).
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
- DCCIOS32.EXE, the Delphi Compiler for the iOS Simulator
- DCCIOSARM.EXE, the Delphi Compiler for the 32-bit iOS Device
- DCCIOSARM64.EXE, the Delphi Compiler for the 64-bit iOS Device
- DCCAARM.EXE, the Delphi Compiler for Android
- Mobile Tutorials: Mobile Application Development (iOS and Android)
- Mobile Code Snippets
- System.SysUtils.TStringHelper
- System.SysUtils.TStringBuilder
- Zero-based strings (Delphi compiler directive)
- Migrating C++ Code to Mobile from Desktop