Parámetros de cadena constantes y el compilador Delphi XE5 para Android

Espero que simplemente estoy perdiendo algo obvio, pero parece estar encontrando constantes argumentos de cadena que se corrompen al usar el compilador Android de Delphi XE5. Código de prueba:

1) Crear un nuevo proyecto de aplicación móvil en blanco.

2) Agregue un TButton al formulario y cree un controlador OnClick para él.

3) Llene el controlador de la siguiente manera:

 procedure TForm1.Button1Click(Sender: TObject); begin GoToDirectory(PathDelim + 'alpha' + PathDelim + 'beta'); GoToDirectory(FParentDir); end; 

4) En la declaración de clase de formulario, agregue dos campos y un método como este:

 FCurrentPath, FParentDir: string; procedure GoToDirectory(const Dir: string); 

5) Implemente Foo y GoToDirectory así:

 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; 

6) Compilar y ejecutar en un dispositivo.

Cuando hago esto, los dos primeros cuadros de mensaje no indican nada malo, sin embargo Dir entonces obtiene aclarado entre la tercera y cuarta solicitudes. ¿Alguien más lo consigue, o simplemente estoy haciendo algo tonto? (No hay nada adverso cuando me dirijo a Win32 para propósitos de prueba.)

Actualizar

Para una versión libre de FMX, cree de nuevo una aplicación nueva en blanco, pero esta vez elimine el formulario del proyecto. A continuación, vaya al origen del proyecto y agregue el código siguiente:

 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. 

Para ver el resultado, ejecute primero monitor.bat en la carpeta de tools Android SDK; Para ver la madera a través de los árboles, filtro sólo para errores dado que he utilizado llamadas LOGE . Aunque no cada vez que ejecute esta aplicación de prueba revisada el argumento se corrompe, todavía lo hace a veces … que está indicando un error de compilador bastante desagradable …

Actualización 2

Con el segundo caso de prueba, especialmente estoy convenciéndome aún más, por lo que he registrado como QC 121312 .

Actualización 3

Un código en lugar de la versión en prosa de la explicación en la respuesta aceptada a continuación (tipos de interfaz que utilizan esencialmente el mismo mecanismo de referencia de conteo que las cadenas, sólo con la capacidad de rastrear fácilmente cuando el objeto se destruye):

 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. 

La salida es la siguiente:

 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... 

Como tal, reasignar el campo baja el recuento de referencia del objeto anterior, que dado que no hay otra referencia fuerte a él, lo destruye allí y luego antes de que el procedimiento ChangeCanary haya terminado.

Hicimos algunas investigaciones internas, y resulta que esto depende de la forma en que se escribe el código y no hay nada que el compilador pueda realmente hacer al respecto. Es un poco complejo, pero en resumen, el método GoToDirectory recibe un parámetro de cadena const (Dir) que se refiere a una cadena. Sin embargo, dentro del código del método reemplaza la cadena por una nueva (que podría estar en la misma o en una ubicación de memoria diferente). Dado el parámetro const no aumenta el recuento de referencia, si disminuye el recodo ref de la misma cadena en el código, se quita la cadena. Así que usted tiene un parámetro que apunta a una ubicación de memoria indefinida, y la salida real es un poco aleatoria. El mismo problema ocurre (puede ocurrir) en todas las plataformas, no en el móvil específico.

Hay muchas soluciones:

1) no tiene el parámetro const (por lo que la cuenta ref es mayor, se cambia la cadena de referencia, pero el parámetro es ahora una referencia a una cadena separada

2) Pasar un alias de la cadena:

  Tmp := FParentDir; GoToDirectory(Tmp); 

3) Asigne el parámetro "const String" a la variable local temporal:

 procedure TForm1.GoToDirectory(const Dir: string); var TmpDir: String; begin TmpDir := Dir; 

Sé que esto está lejos de ser una descripción clara, y tuve que enrojecerla unas cuantas veces para comprenderla, pero es un escenario que el compilador no puede manejar de forma automática, así que vamos a cerrar el informe de errores "según lo diseñado".

Para ampliar el comentario de Marco un poco, este pit-caída en el uso de const en un parámetro ha estado en Delphi desde la introducción de parámetros const y no es un error, sino más bien, una característica que su ejemplo es un ejemplo de un caso No debe utilizarse.

El modificador const es una promesa a la persona que llama que no hay manera de que una variable pasada como un parámetro se modifique como un efecto secundario de la llamada. La forma más simple de garantizar esto es nunca modificar una variable globalmente accesible en una función o procedimiento con un parámetro const . Esto permite al receptor basarse en el recuento de referencia de la persona que llama, evitar la semántica de copias, etc. En otras palabras, le dice al compilador que si el valor se pasa más eficientemente como var y puede tratarse como un parámetro var (es decir, Tiene un lvalue) entonces lo pasa como var vez de un valor. Si se trata de un tipo administrado, como una cadena, también puede depender de la referencia de la persona que llama para mantener la memoria viva.

Este contrato es violado por GoToDirectory cuando modifica una cadena accesible global (cualquier acceso heap debe considerarse global, en este contexto, aunque sea un campo de un objeto). GoToDirectory no debe tener un parámetro const porque viola el contrato implícito por const .

Tenga en cuenta que esto difiere significativamente del contrato implícito por const en otros idiomas, como C ++. Es lamentable que no haya una palabra mejor para usar en ese momento. Lo que realmente está diciendo es que la función o procedimiento es puro con respecto a variables compatibles con el tipo formal del argumento const , no que no modifique el argumento. Es más fácil recordar, no aplicar const a ningún parámetro de una función o procedimiento que tenga un efecto secundario.

Esa regla puede ser violada cuando el efecto secundario de la escritura a un global no será visible para el procedimiento o la función o cualquier procedimiento o funciones que llama. Normalmente, esto es muy difícil de garantizar fuera de casos triviales (como un setter de propiedad simple) y sólo debe utilizarse si una restricción de rendimiento no puede permitirse la sobrecarga de la copia de valor. En otras palabras, es mejor que tenga un rastreo de desempeño en la mano para justificarlo o mejor sea obvio para el observador casual que una copia sería costosa.

FWIW, no puedo reproducir el problema localmente usando XE5 Update 2, Android 4.4.2, en un Nexus 7 con su versión no FMX. El proyecto se creó utilizando las instrucciones paso a paso (copiar / pegar código) y ejecutar en modo de depuración en el dispositivo. La salida del registro fue:

Captura de la ventana de Android Debug Monitor

Para asegurarme de que no podía reproducirlo, construí y ejecuté la aplicación varias veces con los mismos resultados.

Sin embargo, la versión FMX tiene resultados inconsistentes. La primera vez que corrí y lo construí, produjo una violación de acceso después de la tercera ShowMessageFmt y tuvo que ser detenido. Lo construí de nuevo, lo corrí, y pude ver los cuatro ShowMessageFmt diálogo ShowMessageFmt , pero el último mostró un valor incorrecto:

 Prior to calling foo, Dir is "/alpha/beta" After to calling foo, Dir is "/alpha/beta" Prior to calling foo, Dir is "/alpha" After to calling foo, Dir is "" 

La tercera y cuarta repeticiones de construcción y ejecución produjeron la misma salida que la segunda.

Yo diría que esto es un error. Está abierto y el equipo de I + D de Embarcadero lo investigará.

  • Soporte de AdMob en Delphi XE5
  • Versión de la aplicación de Android (RAD Studio XE5)
  • Lista de todos los archivos almacenados con la aplicación Android de Delphi XE5
  • Delphi XE5 Serial Port en Android
  • Android NFC en Embarcadero XE5
  • Interceptar SMS recibido en Delphi XE5 para Android
  • Añadir pines a Google Map en un TWebBrowser y procesar clics
  • ¿Cómo puedo detectar / manejar una rotación de pantalla usando FireMonkey para Delphi XE5
  • Falla de segmentación de clase (11)
  • FireMonkey TControl.MakeScreenshot genera un mapa de bits de menor tamaño en plataformas móviles
  • Enviar correo electrónico Delphi XE5
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.