请选择 进入手机版 | 继续访问电脑版
设为首页收藏本站

吾爱极客

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 1933|回复: 0

移植Delphi桌面代码到移动平台

[复制链接]

271

主题

271

帖子

1129

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
1129
发表于 2015-9-29 00:44:16 | 显示全部楼层 |阅读模式
本文讲述的是如何移植现有的Delphi代码,使之可以用Delphi移动编译器编译。



  • DCCIOS32.EXE, Delphi iOS模拟器编译器。
  • DCCIOSARM.EXE, Delphi iOS 设备编译器
  • DCCAARM.EXE, Delphi Android编译器



去除
Delphi移动编译器不支持的数据类型


  使用下列不支持类型的代码要么删掉要么用替代类型重写:
WideString,AnsiString, ShortString, AnsiChar, PAnsiChar, PWideChar, Openstring
  
桌面应用使用的类型
  (1-based)
  
  
移动应用使用的类型
  (0-based)
  
        
      
消除使用.
  
考虑使用 'array of byte'
  
        
        
      
考虑使用'array of byte'
  

以下是不支持类型的替换细节:

WideString某些情况下, WideString 可以用String替换.
如果你出于某些原因要在移动平台上使用WideString,你要把它整改成可处理4字节长度,处理Unicode字符序列,两个null字符表示字符串结束。例如使用ShortString的代码例子 ShortStringToString (Delphi).

AnsiString和 ShortString

这一组包含有一些“衍生”类型比如UTF8StringRawByteString, 以及使用下面语法定义的长度确定的ShortStrings:
type TStr = string[127];是删除还是修改AnsiStringShortString的实例, 取决于原来的用法. 在一些情况下, 'array of byte' {比如 System.Types.TByteDynArray) 已足够。

在很多时候,你需根据需要编码和解码旧的格式。大多数AnsiString的使用可以直接用缺省String类型替换。大部分以这个类型保存在外部的信息使用流或使用流的类,例如TStringList或相似的类。这些类类型支持字节顺序标记(BOM),能在需要时自动解码。在有必要转换的地方,使用TEncoding类来直接获取字节数据。实际上,TStrings,,TstringList的基类,支持诸如TStrings.SaveToFileTStrings.LoadFromFile等方法里TEncoding 实例的显式说明,因此你的程序可以使用普通的String类型,而不用管程序外部存储所需的最后编码是什么。

对于使用short strings的代码, 用TEncoding来处理最新字符串类型使用的UTF16表示和旧的 short string使用的8位ANSI表示之间的差异。请看代码例子ShortStringToString (Delphi).

AnsiChar

用 (Wide)CharByte(UInt8) 来替换 AnsiChar.

根据原来的语义:

  • 如果原来的语义是'Character', 使用UNICODE转换的Char.
  • 如果原来的语义是8位存储, 使用Byte 类型.

PAnsiChar 和 PWideChar


如果这些类型指向Ansi/Unicodestring, 使用 StringTStringBuilder 对象代替PAnsiChar/PwideChar类型.

如果原来语义和API调用有关, 把它替换成API marshaling功能. 一般 System.MarshaledString 足够了.

Openstring

System.Openstring 是一个老旧的语言元素。目前System.Generics.Defaults 使用OpenString类型,但它很少出现在除了RAD Studio的其它地方.
通常"array of byte"可以用来代替OpenString,就像在这个例子代码的ShortStringToString()函数里面可以看到ShortStringToString (Delphi). "Array of byte" 在这里用来组成一个开放数组参数,可以接受任意长度字节数组,就像允许定义任意长度字符串的OpenString。  


使用从0开始的Strings

对于Delphi移动编译器,字符串是从0开始索引的。另外,它们很可能在未来变成不可变的(常量)。

  
Delphi编译器的字符串索引
  

  
Delphi 编译器
  
  
字符串索引
  
  
Delphi移动编译器:
        
  
0-based
  
  (字符串的第一个字符的索引是0)
  
  
Delphi桌面编译器:
  
  DCC32
  
  DCC64
  
  DCCOSX
  
  
1-based
  
  (字符串的第一个字符的索引是1)
  


我们建议你重写那些假定字符串从1开始索引或可变的代码.




  • Zero-Based Strings: 对于使用从1开始索引来访问字符串字符元素的,重写代码来使用0开始索引(下面有个例子)。     
  • Immutable Strings: 如果你想要修改一个不可变字符串的中间,你只能把它分成两或多个分段,然后把各个分段连接起来,或者使用TStringBuilder.

例如, 下面的一般操作 (索引操作与修改字符串) 不能用于不可变字符串:

S[1] := 'A';

如果你使用了这样的操作,Delphi移动编译器会弹出警告W1068 Modifying strings in place may not be supported inthe future (Delphi)。在某种意义来说,这个警告应该用错误代替;可以立刻在项目选项的Hints and Warnings 页把它转变成错误。


我们建议使用TstringHelper来处理桌面与移动应用中的字符串

这个类和记录helper System.SysUtils.TStringHelper 对于处理字符串和编写平台独立代码很有用。你可以在所有环境使用TStringHelper (桌面和移动). TstringHelper完成了自动转换, 因此你可以用TstringHelper处理从0和从1开始的字符串。TstringHelper内部的所有函数和属性在所有情况都是从0开始的。

一些用于从1开始字符串的RTL函数可以直接替换成TstringHelper函数, 如下表所述:


  
Delphi  RTL 函数
  (1-based)
  
  
TStringHelper  函数
  (0-based)*
  
  System.Pos
  
  TStringHelper.IndexOf
  
  System.Delete
  
  TStringHelper.Remove
  
  System.Copy
  
  TStringHelper.Substring
  
  System.SysUtils.Trim
  
  TStringHelper.Trim
  

* helper函数能同时用于从1和从0开始的字符串。

这个题目包含了所有上面建议的替换的例程(除了 Delete-Remove).

下面这些子题目说明了把你的代码从1索引字符串移植到0索引所需的改变





    测试不可变字符串

    为了测试不可变字符串, 执行下面之一:



  》设置编译器指令 {$WARN IMMUTABLE_STRINGS     <ON|ERROR>}。

  》在 Hints and Warnings ,      "Modifying strings     in-place...." 设置为 "true"      "error".

     如果字符串的某些位置被修改,就会显示警告/错误信息: W1068 Modifying strings in place may notbe supported in the future (Delphi)


1-based字符串转换到0-based 的例子

这时一个演示如何修改1-based字符串的例子,适用所有平台。

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;


使用TStringHelper.Chars来访问一个String中的字符
CharsTStringHelper的一个很有用的属性:

Chars[Index]这个只读的属性可以访问字符串的所有字符。记住Delphi移动编译器的字符串数组都是从0开始的。

使用Chars 属性来访问单个字符的例子:

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;


使用 System.LowSystem.High来访问String的第一个和最后一个索引

你可以将Delphi内置例程HighLow 应用于字符串数组。



     》Low(s) 在我们的0-based字符串场景返回0, 1-based字符串返回1..
     》High(s) 在我们的0-based字符串场景返回Length(s) – 1对于1-based字符串返回Length(s)

要检测字符串的第一位索引,使用:

Low(string)例如, 你可以替换这个常用的for语句:

for I := 1 to Length(S) do为这个for语句:

for I := Low(S) to High(S) do另外一个例子是, 当S为空时:

》        Low(s) = 0 和 High(s) = -1 ,对于0-based 字符串数组.

》        Low(s) = -1 和 High(s) = 0 ,对于1-based字符串数组.


用TStringHelper.IndexOf替换System.Pos函数

System.Pos 函数能用于1-based 字符串数组,不能用于0-based 字符串数组。可以用TStringHelper.IndexOf 代替Pos, 你可以用IndexOf 函数返回数值参数(一个字符或字符串)的从0开始索引的位置如果字符串能找到的话,或者如果找不到时返回-1。

例子:

s := 'The quick brown fox jumps over the lazy dog'; // s 是string 类型的变量
WriteLn(Pos('ow', s));    // 13
WriteLn(s.IndexOf('ow')); // 12

: TStringHelper.IndexOf 函数与.NET实现类似,除了当字符串值为空时,.NET 返回 0, 但是 Delphi RTL 返回 -1.


用TStringHelper.Substring替换System.Copy函数

System.Copy 函数能工作于1-based 字符串数组但是不能工作于 0-based 字符串数组. 你可以用TStringHelper.Substring来替换Copy:

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

Substring 函数返回一个等同于这个实例串中从StartIndex 开始长度length的子字符串。 如果StartIndex 大于等于实例的长度,Substring 返回一个空的字符串。如果Length 为0或负值, Substring 返回空字符串。

例子

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

: TStringHelper.Substring函数和.NET 实现类似, 除了.NET 会引发一个ArgumentOutOfRangeException 异常---StartIndex 加上Length 指示的位置不在样例的范围内, 或者如果StartIndex Length 小于0时。 但另一方面,DelphiRTL却不会引发异常. 对于上面情况,Substring 返回空字符串.


更新数组类型

把所有数组定义更新为动态的。使用下面其中一种:

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

有一些案例是当必须传递一个结构(记录)到外部函数时,这个结构含有一个指定长度的数组。特别是结构内已经定义好的字符数组. 在这种情况,也只有这种情况下,下面写法是允许的:

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

对于外部函数直接带数组的,换用动态数组。对于UTF8字符数组:



   》为了从UTF8转换过来, 使用TBytes 还有System.SysUtils.TEncoding.GetString(Bytes).
   》为了转换到UTF8, 使用TBytes 还有 System.SysUtils.TEncoding.GetBytes(S).


在一个try-except 代码块中使用函数来防止捕捉不到硬件异常


对于DCCIOSARM 编译器, 异常块只有在try代码块包含有方法或函数调用的时候才能够捕捉硬件异常。这是一个和编译器LLVM 后端有关联的差异,如果try块里面没有方法/函数调用LLVM后端不能返回。

这是一个怎么构造可以捕捉硬件异常的try-except 块的例子:

var  
P: ^Integer = nil;
procedure G1;
begin  
    P^ := 42;
end;

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

尽管有时候一个代码块看起来像是包含有函数调用,但实际上不是的,如果那个函数是内联的。到LLVM 发生机器指令那时, 内联过程早已经展开,因此try块里面已经没了函数调用。


使用原子内建代替汇编语言

Delphi 移动编译器不支持内建汇编。如果你需要原子性的交换, 比较-和-交换, 加,减内存数值, 你可以使用新的内置原子函数。
原子操作是用来实现多线程锁定的原语,为实现所谓“lock-free”架构所提供的原语。这种所需要的操作是用标准函数或内建函数实现的。
在一个多平台应用内部,原子内建可用于{$IFDEF} 内的AUTOREFCOUNT 或 NEXTGEN 条件语句.

原子内建函数 以下是Delphi移动编译器支持的原子内建函数:


自动引用计数


Delphi移动编译器 (DCCIOS32, DCCIOS32ARM, 和 DCCAARM) 在类上使用自动引用计数(ARC),一种和Delphi桌面编译器(DCC32, DCC64, 和DCCOSX)不同的自动引用计数方案。不管怎样,所有Delphi编译器都已在接口,字符串,动态数组上支持ARC,所以实际上Delphi移动编译器仅仅是将ARC扩展到了类。ARC包含内存的自动管理和处置。

: 对于支持ARCDelphi编译器, 相互参照的对象实例可以有效的锁定内存,无须其中一个引用设置为弱引用属性。

更多信息请看ARC和弱引用:


原文链接


www.52jike.com  ruanzhuo 翻译整理,转载请注明出处。


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|吾爱极客 ( 粤ICP备15067754号-1  

GMT+8, 2019-3-23 13:40 , Processed in 0.158600 second(s), 28 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表