常量字符串参数和适用于Android的Delphi XE5编译器

Chr*_*ton 8 delphi android delphi-xe5

希望我只是遗漏了一些明显的东西,但我似乎发现在使用Delphi XE5 Android编译器时,常量字符串参数被破坏了.测试代码:

1)创建一个新的空白移动应用程序项目.

2)TButton在表单中添加一个,并为其创建一个OnClick处理程序.

3)像这样填写处理程序:

procedure TForm1.Button1Click(Sender: TObject);
begin
  GoToDirectory(PathDelim + 'alpha' + PathDelim + 'beta');
  GoToDirectory(FParentDir);
end;
Run Code Online (Sandbox Code Playgroud)

4)在表单类声明中,添加两个字段和一个方法,如下所示:

FCurrentPath, FParentDir: string;
procedure GoToDirectory(const Dir: string);
Run Code Online (Sandbox Code Playgroud)

5)实施Foo和GoToDirectory喜欢:

function Foo(const S: string): Boolean;
begin
  Result := (Now <> 0);
end;

procedure TForm1.GoToDirectory(const Dir: string);
begin
  FCurrentPath := IncludeTrailingPathDelimiter(Dir);
  FParentDir := ExcludeTrailingPathDelimiter(ExtractFilePath(Dir));
  ShowMessageFmt('Prior to calling Foo, Dir is "%s"', [Dir]);
  Foo(FParentDir);
  ShowMessageFmt('After calling Foo, Dir is "%s"', [Dir]);
end;
Run Code Online (Sandbox Code Playgroud)

6)编译并在设备上运行.

当我这样做时,前两个消息框没有指出任何错误,但是Dir然后在第三和第四个提示之间清除.有没有其他人得到这个,或者我只是做一些愚蠢的事情?(当我为测试目的而定位Win32时,没有什么不好的.)

更新

对于无FMX版本,请再次创建一个新的空白移动应用程序,但这次从项目中删除该表单.然后,进入项目源并添加以下代码:

program Project1;

uses
  System.SysUtils,
  Androidapi.Log;

type
  TTest = class
  private
    FCurrentPath, FParentDir: string;
    procedure GoToDirectory(const Dir: string);
  public
    procedure Execute;
  end;

function Foo(const S: string): Boolean;
begin
  Result := (Now <> 0);
end;

procedure TTest.GoToDirectory(const Dir: string);
var
  M: TMarshaller;
begin
  FCurrentPath := IncludeTrailingPathDelimiter(Dir);
  FParentDir := ExcludeTrailingPathDelimiter(ExtractFilePath(Dir));

  LOGE(M.AsUtf8(Format('Prior to calling Foo, Dir is "%s"', [Dir])).ToPointer);
  Foo(FParentDir);
  LOGE(M.AsUtf8(Format('After to calling Foo, Dir is "%s"', [Dir])).ToPointer);
end;

procedure TTest.Execute;
begin
  GoToDirectory(PathDelim + 'alpha' + PathDelim + 'beta');
  GoToDirectory(FParentDir);
end;

var
  Test: TTest;
begin
  Test := TTest.Create;
  Test.Execute;
end.
Run Code Online (Sandbox Code Playgroud)

要查看结果,请先monitor.bat在Android SDK tools文件夹中运行; 通过树木看到木头,只有在我使用过LOGE电话时才会过滤错误.虽然不是每次我运行这个修改过的测试应用程序时参数都会被破坏,但有时候它仍然会...这表明一个相当讨厌的编译器错误...

更新2

特别是第二个测试用例我更加信服自己,所以我把它记录为QC 121312.

更新3

下面接受的答案中的解释的代码而不是散文版本(接口类型使用与字符串基本相同的引用计数机制,只有能够轻松跟踪对象何时被销毁):

program CanaryInCoalmine;

{$APPTYPE CONSOLE}

uses
  System.SysUtils;

type
  ICanary = interface
    function GetName: string;
    property Name: string read GetName;
  end;

  TCanary = class(TInterfacedObject, ICanary)
  strict private
    FName: string;
    function GetName: string;
  public
    constructor Create(const AName: string);
    destructor Destroy; override;
  end;

  TCoalmine = class
  private
    FCanary: ICanary;
    procedure ChangeCanary(const Arg: ICanary);
  public
    procedure Dig;
  end;

constructor TCanary.Create(const AName: string);
begin
  inherited Create;
  FName := AName;
  WriteLn(FName + ' is born!');
end;

destructor TCanary.Destroy;
begin
  WriteLn(FName + ' has tweeted its last song');
  inherited;
end;

function TCanary.GetName: string;
begin
  Result := FName;
end;

procedure TCoalmine.ChangeCanary(const Arg: ICanary);
var
  OldName: string;
begin
  Writeln('Start of ChangeCanary - reassigning FCanary...');
  OldName := Arg.Name;
  FCanary := TCanary.Create('Yellow Meanie');
  Writeln('FCanary reassigned - is ' + OldName + ' still alive...?');
  Writeln('Exiting ChangeCanary...');
end;

procedure TCoalmine.Dig;
begin
  FCanary := TCanary.Create('Tweety Pie');
  ChangeCanary(FCanary);
end;

var
  Coalmine: TCoalmine;
begin
  Coalmine := TCoalmine.Create;
  Coalmine.Dig;
  ReadLn;
end.
Run Code Online (Sandbox Code Playgroud)

输出是这样的:

Tweety Pie is born!
Start of ChangeCanary - reassigning FCanary...
Yellow Meanie is born!
Tweety Pie has tweeted its last song
FCanary reassigned - is Tweety Pie still alive...?
Exiting ChangeCanary...
Run Code Online (Sandbox Code Playgroud)

因此,重新分配字段会丢弃前一个对象的引用计数,如果没有其它强引用,则在那里销毁它,然后在该ChangeCanary过程完成之前.

Mar*_*ntù 5

我们做了一些内部调查,事实证明这取决于编写代码的方式,编译器无法真正做到这一点.它有点复杂,但简而言之,GoToDirectory方法接收一个引用字符串的const字符串参数(Dir).但是,在方法的代码中,用新的字符串替换字符串(可能位于相同或不同的内存位置).鉴于const参数不会增加引用计数,如果减少代码中相同字符串的引用计数,则删除该字符串.所以你有一个指向未定义内存位置的参数,实际输出是随机的.在所有平台上发生(可能发生)同样的问题,而不是移动特定的.

有许多变通方法:

1)没有const参数(因此引用计数更高,您更改引用的字符串,但param现在是对单独字符串的引用

2)传递字符串的别名:

  Tmp := FParentDir;
  GoToDirectory(Tmp);
Run Code Online (Sandbox Code Playgroud)

3)将"const String"参数分配给临时局部变量:

procedure TForm1.GoToDirectory(const Dir: string);
var
  TmpDir: String;
begin
  TmpDir := Dir;
Run Code Online (Sandbox Code Playgroud)

我知道这远不是一个清晰的描述,我不得不重做几次才能掌握它,但这是一个编译器无法真正自动处理的场景,因此我们将按照"设计"关闭错误报告.