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过程完成之前.
我们做了一些内部调查,事实证明这取决于编写代码的方式,编译器无法真正做到这一点.它有点复杂,但简而言之,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)
我知道这远不是一个清晰的描述,我不得不重做几次才能掌握它,但这是一个编译器无法真正自动处理的场景,因此我们将按照"设计"关闭错误报告.