Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   OOP (https://www.clubdelphi.com/foros/forumdisplay.php?f=5)
-   -   Crear una clase descendiente de TInifile (https://www.clubdelphi.com/foros/showthread.php?t=93280)

bucanero 12-07-2018 12:54:36

Crear una clase descendiente de TInifile
 
Hola a todos

Estoy intentando crear una clase descendiente de TIniFile, con la idea de tener una unidad para gestionar la configuración de laS aplicaciones de tal forma que si se le pasa alguna clase descendiente de un TINIFILE, trabaje con esta clase, en caso de no declara nada que trabaje con la clase general de TINIFile.

Este es el código para la unidad de configs.pas
Código Delphi [-]
unit configs;

interface

uses inifiles;

type
  TClassIniFile = class of TInifile;

var
  ClassIniFile: TClassIniFile = nil;
  IniFileName: string = 'c:\tmp\config.ini';


  function config: TIniFile;

implementation

uses Dialogs;

var
  IniFile:TInifile = Nil;

function config: TIniFile;
begin
  if not Assigned(IniFile) then begin
    if not Assigned(ClassIniFile) then begin
      //Si no hay definida una clase especifica se usa la clase generia de INIFILE
      IniFile := TiniFile.Create(IniFileName)
    end
    else begin
      // se usa la clase especifica
      IniFile := ClassIniFile.Create(IniFileName)
    end;
    // esto es solo en pruebas y es para mostrar que clase se esta utilizando
    MessageDlg(IniFile.ClassName, mtWarning, [mbOK], 0);
  end;
  Result := IniFile;
end;

initialization

finalization
  if assigned(IniFile) then
    IniFile.Free;
end.

Y la forma de utilizarlo en cualquier parte del programa:

Código Delphi [-]
uses configs;
...
procedure TForm1.Button1Click(Sender: TObject);
begin
  with config do
    WriteString('prueba', 'prueba', '123');
end;

y así es como redefino una nueva clase descendiente de TInifile
Código Delphi [-]
unit MyIniFiles;

interface

uses IniFiles;

type
  TMyIniFile = class(TIniFile)
  private
  public
    constructor Create(const FileName: string); overload;
  end;

implementation

uses dialogs, configs;

{ TMyIniFile }
constructor TMyIniFile.Create(const FileName: string);
begin
   // esto es solo en pruebas y es para mostrar si pasa por aqui
  MessageDlg('TMyIniFile.create', mtInformation, [mbOK], 0);
  inherited Create(FileName);
end;

initialization
  ClassIniFile := TMyIniFile;
end.


Y aquí es donde viene el problema, tal como esta todo esto declarado, el código del constructor de la clase descendiente (TMyIniFile.Create) no se ejecuta al crear la instancia del objeto desde la variable ClassIniFile y pasa directamente a ejecutar el constructor de la clase padre (TiniFile.create).

Por el contrario si en el procedimiento config lo pusiera de esta forma, entonces si que funciona perfectamente:
Código Delphi [-]
function config: TIniFile;
begin
  if not Assigned(IniFile) then begin
    if not Assigned(ClassIniFile) then
      //Si no hay definida una clase especifica se usa la clase generia de INIFILE
      IniFile := TiniFile.Create(IniFileName)
    else if (ClassIniFile = TMyIniFile) then
      // se usa la clase especifica
      IniFile := TMyIniFile.Create(IniFileName)
    else
      // se usa la clase especifica
      IniFile := ClassIniFile.Create(IniFileName);
    // esto es solo en pruebas y es para mostrar que clase se esta utilizando
    MessageDlg(IniFile.ClassName, mtWarning, [mbOK], 0);
  end;

  Result := IniFile;
end;

Pero de esta forma no quiero ponerlo, por que así estaría forzado a tener que añadir siempre en el uses de la unidad configs la unidad que contiene la clase descendiente, pudiendo estar unas veces incluida en el proyecto y otras veces no.

Me explico mas, mi idea es si en una determinada aplicación que utiliza la unidad CONFIGS le agrego la otra unidad con la clase descendiente, entonces esa aplicación usara esa clase descendiente para gestionar como y donde guardar la configuración, pero si en otra aplicación distinta que también usa la misma unidad CONFIGS, no tiene agregada ninguna unidad descendiente de TINIFILE, entonces se pretende que utilice la clase genérica de TINIFILE.

No se si alguien me puede arrojar luz sobre por que no se ejecuta la clase constructora descendiente al llamarla desde la variable que contiene la clase descendiente.

Gracias por vuestro tiempo
y saludos

Neftali [Germán.Estévez] 13-07-2018 11:23:35

Creo que el problema está en que el create de la clase base:
Código Delphi [-]
  constructor Create(const FileName: string);
Está definido de esta forma. Ni virtual, ni abstract,...


Si añades un método como este a tu clase:
Código Delphi [-]
  TMyIniFile = class(TIniFile)
  private
  public
    constructor Create(const FileName: string); overload;
    procedure WriteString(const Section, Ident, Value: String); override;
  end;


Comprobará que realmente el objeto es de la clase correcta, porque al ejecutar este código:


Código Delphi [-]
   with config do
    WriteString('prueba', 'prueba', '123');


Realmente ejecuta el método:


Código Delphi [-]
  procedure TMyIniFile.WriteString(const Section, Ident, Value: String);

bucanero 13-07-2018 13:59:04

Hola Neftali, gracias por responder!!

Si después de unas cuantas pruebas llegue a esa misma conclusión, el problema se encuentra en esta definición class of TInifile que es la que determina el orden de búsqueda, y es que al no poder hacer override sobre el constructor por que la clase padre no lo permite, si el compilador se encuentra con varias clases constructoras declaradas de forma idéntica, entonces la búsqueda de procesos la realiza internamente a algo parecido a una lista con esta forma:

Código:

1:TINIFile{ constructor Create(const FileName: string); } 
2: Resto de clases ascendentes a TINIFile
...
N:TMyIniFile { constructor Create(const FileName: string); }

así que el compilador simplemente coge el primer método que encuentra en la lista que sea del tipo que busca, en este caso el definido para TINIFile.

Y lo solucione creando una clase intermedia (TAvIniFile) donde se define el constructor de forma virtual para así poder sobre-escribirlo en las clases descendientes,

Código Delphi [-]

interface
type
  TAvIniFile = class(TIniFile)
  public
    // se marca el constructor como virtual para poder sobre-escribirlo mas adelante
    constructor Create(const FileName: string); overload; virtual; 
  end;

  TMyIniFile = class(TAvIniFile )
  private
  public
    constructor Create(const FileName: string); override; // ya si se puede usar el override
  end;

  TClassIniFile = class of TAvIniFile;

implementation

constructor TAvIniFile.Create(const FileName: string);
begin
  inherited;
  // aqui no es necesario insertar nada mas, solo esta definido para poder hacer override despues
end;

...

y finalmente sustituyo la declaración de classInifile por TclassInifile = class of TAvInifile, de esta forma la lista de búsqueda quedaría así:
Código:

1: clases descendientes a TAvIniFile con el constructor sobre-escrito, aquí se incluye TMyIniFile 
2: TAvIniFile{ constructor Create(const FileName: string); } 
3: TINIFile{ constructor Create(const FileName: string); } 
4: Resto de clases ascendentes a TINIFile

Gracias de nuevo por responder
Un saludo

dec 13-07-2018 14:45:12

Hola a todos,

Ha estado bien visto esto último, bucanero. Yo, en mi ignorancia, iba a responder de entrada que el método constructor de tu clase INI tenía que ser "override" y no "overload". Pero, al probarlo aquí, me dí cuenta de que no era tan sencillo como hacer esto... no alcancé yo hasta donde llegó el compañero Neftalí, que fue descubrir que el constructor de la clase TIniFile no puede sobrescribirse. Vamos, ni alcabcé yo donde el Neftalí llegó, ni por supuesto se me ocurrió pensar en hacer lo que tú al final has hecho. Está bien saberlo. :)

Neftali [Germán.Estévez] 13-07-2018 14:54:39

Cita:

Empezado por dec (Mensaje 527590)
Yo, en mi ignorancia, iba a responder de entrada que el método constructor de tu clase INI tenía que ser "override" y no "overload". Pero, al probarlo aquí, me dí cuenta de que no era tan sencillo como hacer esto...


Si, yo también me di cuenta del cambio de palabra overload/override David, pero el problema está en la definición de las clases base, que como he dicho no están definidas para que se puedan "sobreescribir".
Como bien dices, al realizar el cambio da el error de "Cannot override a non-virtual method".

movorack 13-07-2018 16:49:25

Puedes usar reintroduce

Código Delphi [-]
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IniFiles;

type
  TMyIniFile = class(TIniFile)
  private
  public
    constructor Create(const FileName: string); reintroduce;
  end;

  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    FConfig: TMyIniFile;
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TMyIniFile }

constructor TMyIniFile.Create(const FileName: string);
begin
  inherited Create(FileName);

   // esto es solo en pruebas y es para mostrar si pasa por aqui
  MessageDlg('TMyIniFile.create', mtInformation, [mbOK], 0);  
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FConfig := TMyIniFile.Create(ChangeFileExt(ParamStr(0), '.ini'));
end;

end.




bucanero 13-07-2018 17:21:24

Gracias DEC y movorack por responder

Cita:

Empezado por Neftali [Germán.Estévez] (Mensaje 527591)
Si, yo también me di cuenta del cambio de palabra overload/override David, pero el problema está en la definición de las clases base, que como he dicho no están definidas para que se puedan "sobreescribir".
Como bien dices, al realizar el cambio da el error de "Cannot override a non-virtual method".

Ese es el error que me estuvo trastocando todo el tiempo :confused: y la necesidad de buscar alguna alternativa al problema.


En cuanto al reintroduce también lo intente y el resultado fue el mismo. En tu código te ha funcionado porque has llamado directamente al método create de la clase TMyIniFile, y de este modo incluso con el overload funciona
Cita:

Empezado por movorack (Mensaje 527593)
Puedes usar reintroduce

Código Delphi [-]
  FConfig := TMyIniFile.Create(ChangeFileExt(ParamStr(0), '.ini'));

pero en mi código la llamada es a traves de una variable que apunta a una clase derivada de TINIFile y no de TMyIniFile, y para este caso el reintroduce también sigue fallando.

Código Delphi [-]
type
  TClassIniFile = class of TInifile;

var
  ClassIniFile: TClassIniFile = nil;
...
  ClassIniFile:=TMyIniFile;
  ClassIniFile.create(IniFileName);



Gracias a todos por dedicarle tiempo a esta cuestión
Un saludo

gatosoft 13-07-2018 19:03:22

hola bucanero.

Sugiero que utilices la RTTI. Para ello debes cambiar tu codigo de la unidad configs asi:

Código Delphi [-]
unit configs;

interface

uses inifiles;

type
  TClassIniFile = class of TInifile;

var
  ClassIniFile: TClassIniFile = nil;
  IniFileName: string = 'c:\tmp\config.ini';
  ClassIniFileName: String = '';

  function config: TIniFile;

implementation

uses Dialogs, SysUtils, ObjectClone, RTTI;

var
  IniFile:TInifile = Nil;

function config: TIniFile;
var
  RttiContext: TRttiContext;
  RttiType: TRttiInstanceType;
Begin
   if Assigned(IniFile) then
     exit(IniFile);

   if ClassIniFileName <> '' then
      begin
      RttiContext:= TRttiContext.Create;
      RttiType := RttiContext.FindType(ClassIniFileName) as TRttiInstanceType;

      IniFile:= RttiType.GetMethod('Create')
                        .Invoke(RttiType.MetaclassType,
                               [IniFileName]).AsObject as TIniFile;

      end
   else
      IniFile := TIniFile.Create(IniFileName);

   Result:= IniFile;
end;


initialization

finalization
  if assigned(IniFile) then
    IniFile.Free;
end.

y tus unidades descendientes, algo asi:

Código Delphi [-]
unit MyIniFiles;

interface

uses IniFiles, uIMyInis;

type
  TMyIniFile = class(TIniFile)
  private
  public
    class procedure mydummy; static;
    constructor Create(const FileName: string);
  end;

implementation

uses dialogs, configs;

{ TMyIniFile }

constructor TMyIniFile.Create(const FileName: string);
begin
 inherited Create(FileName);
 MessageDlg('TMyIniFile2.Crear', mtInformation, [mbOK], 0);
end;

class procedure TMyIniFile.mydummy;
begin

end;

Initialization
  TMyIniFile.mydummy; //Me encontré con este error
  ClassIniFileName:='MyIniFiles.TMyIniFile';
end.

gatosoft 13-07-2018 19:12:59

Igual, amigo bucanero, creo que vas por un lado que no es. Lo que tu quieres implementar, puede salir mejor utilizando interfaces ==> El problema con el caso específico que propones es que las interfaces no admiten constructores....

movorack 13-07-2018 19:41:46

Al fin no nos tomaron la foto donde TopX, Tu y yo estábamos revisando este hilo.

bucanero 16-07-2018 10:36:09

Gracias gatosoft por responder

Cita:

Empezado por gatosoft (Mensaje 527595)
hola bucanero.

Sugiero que utilices la RTTI. Para ello debes cambiar tu codigo de la unidad configs asi:

En cuanto a tu sugerencia de usar la RTTI es por donde en un principio pensé que podría ir la solución a este problema, aunque en estos temas de la RTTI me pierdo...

Viendo tu código me encuentro que en la linea donde debe de buscar y localizar la classe devuelve NIL y no la encuentra.
Cita:

Empezado por gatosoft (Mensaje 527595)
Código Delphi [-]
      RttiType := RttiContext.FindType(ClassIniFileName) as TRttiInstanceType;

El problema imagino que viene de que para que la pueda localizar hay que registrar previamente la clase.
Código Delphi [-]
  RegisterClass(TMyIniFile);
Pero aquí aparece otro problema, y es que el procedimiento RegisterClass(AClass: TPersistentClass) requiere que la clase a registrar sea descendiente de TPersistentClass, y yo estoy intentando registrar una clase descendiente de TINIFile que a su vez es una una classe descendiente directamente de TObject y por este motivo el compilador da el siguiente error E2010 Incompatible types: 'TPersistentClass' and 'class of TMyIniFile'


Cita:

Empezado por gatosoft (Mensaje 527596)
Igual, amigo bucanero, creo que vas por un lado que no es. Lo que tu quieres implementar, puede salir mejor utilizando interfaces ==> El problema con el caso específico que propones es que las interfaces no admiten constructores....

En cuanto a esta propuesta... sinceramente, no veo como aplicar una solución...

Porque mi idea final a parte de lo ya explicado es sobre-escribir los métodos de lectura/escritura de mi clase descendiente de TINIFile, para que en vez de guardar los datos en un fichero los guarde directamente en la BBDD, manteniendo la máxima compatibilidad con el componente original para que en determinados casos que no quiera utilizar mi componente propio y/o guardar en la BBDD puede usar directamente un TINIFile.

bucanero 16-07-2018 10:39:10

Hola Movorack, realmente no se a que te refieres con esto :confused:....
Cita:

Empezado por movorack (Mensaje 527597)
Al fin no nos tomaron la foto donde TopX, Tu y yo estábamos revisando este hilo.


gatosoft 16-07-2018 16:46:45

Cita:

Empezado por bucanero (Mensaje 527632)
En cuanto a tu sugerencia de usar la RTTI es por donde en un principio pensé que podría ir la solución a este problema, aunque en estos temas de la RTTI me pierdo...
Viendo tu código me encuentro que en la linea donde debe de buscar y localizar la classe devuelve NIL y no la encuentra.
El problema imagino que viene de que para que la pueda localizar hay que registrar previamente la clase.

Yo también me encontré con ese problema del Nil, y por eso linkeaba la solución en ésta linea de código.

Código Delphi [-]
Initialization
  TMyIniFile.mydummy; //Me encontré con este error


en el hilo se dice:
Cita:


Probably the class has not included by the delphi linker in the final executable. A fast try can be the following:

1.Declare a static method on your class. This method should be an empty method, a simple begin end.
2.Call this static method in the initialization section of this unit.
3. Ensure that the unit is referenced in your project somewhere.
4.Now you should see the class with TRttiContext.FindType

Cita:

Empezado por bucanero (Mensaje 527632)
En cuanto a esta propuesta... sinceramente, no veo como aplicar una solución...

Porque mi idea final a parte de lo ya explicado es sobre-escribir los métodos de lectura/escritura de mi clase descendiente de TINIFile, para que en vez de guardar los datos en un fichero los guarde directamente en la BBDD, manteniendo la máxima compatibilidad con el componente original para que en determinados casos que no quiera utilizar mi componente propio y/o guardar en la BBDD puede usar directamente un TINIFile.

Bueno, eso es nuevo, creí que querías extender la funcionalidad, y no sobre escribirla.

Existe una técnica que te puede servir y es la de interceptores, pero como todo, no es para abusar de ella.

y funciona mas o menos asi:

En la clausula Uses defines la unidad Inifiles, que contiene la implementación nativa del componente.

Después, defines la clase "interceptora" TIniFile (Tiene el mismo nombre de la nativa) la cual hereda de System.IniFiles.TIniFile (debes especificar la unidad).

Y en esta nueva clase, puedes definir nueva funcionalidad y sobreescribir la existente.


Código Delphi [-]
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IniFiles, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;


  TIniFile = Class(System.IniFiles.TIniFile)
  Private
  Public
    procedure WriteString(const Section, Ident, Value: String); reintroduce;
  End;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
Var vIniFile: TIniFile;
begin
  Try
    vIniFile:= TIniFile.Create('C:\MyFile.ini');
    vIniFile.WriteInteger('prueba','numero',5); //este lo escribe
    vIniFile.WriteString('prueba','texto','helou'); //ese lo muestra en un showmessage
  Finally
    vIniFile.Free;
  End;
end;

{ TIniFile }

procedure TIniFile.WriteString(const Section, Ident, Value: String);
begin
  showMessage('Sobre-escribí el metodo '+Value);
end;

end.


en tu caso, modificarias tu codigo aqsí..

Código Delphi [-]
unit MyIniFiles;

interface

uses IniFiles;

type
  TIniFile = class(System.IniFiles.TIniFile)
  private
  public
    class procedure mydummy; static;
    constructor Create(const FileName: string);
  end;

implementation



revisa

gatosoft 16-07-2018 16:54:31

igual, no está de mas pensar en la solución tradicional: Definir una clase derivada de TIniFile, con la funcionalidad que requieres y a partir de ella definir los descendientes de los que hablas.

Cuando tienes una clase padre y muchos posibles descendientes que desconoces, es cuando comienzas a pensar en clases abstractas o en interfaces.

gatosoft 16-07-2018 16:55:33

Cita:

Empezado por bucanero (Mensaje 527633)
Hola Movorack, realmente no se a que te refieres con esto :confused:....


Es que Movorack, Topx y yo, trabajamos en el mismo piso....

Casimiro Notevi 16-07-2018 18:09:16

Cita:

Empezado por gatosoft (Mensaje 527641)
Es que Movorack, Topx y yo, trabajamos en el mismo piso....

¿De verdad? :)

bucanero 17-07-2018 12:14:41

Muchas gracias gatosoft por toda la información aportada

Cita:

Empezado por gatosoft (Mensaje 527639)
Existe una técnica que te puede servir y es la de interceptores, pero como todo, no es para abusar de ella.

y funciona mas o menos así:

En la clausula Uses defines la unidad Inifiles, que contiene la implementación nativa del componente.

Después, defines la clase "interceptora" TIniFile (Tiene el mismo nombre de la nativa) la cual hereda de System.IniFiles.TIniFile (debes especificar la unidad).

Y en esta nueva clase, puedes definir nueva funcionalidad y sobreescribir la existente.

Si esta técnica es muy interesante y algunas veces la he utilizado para otros temas un poco mas simples, pero en este caso y como bien dices en el siguiente mensaje, a veces lo mas simple es lo mas eficaz, y he optado por realizar directamente mi clase con herencia tradicional.

Cita:

Empezado por gatosoft (Mensaje 527640)
igual, no está de mas pensar en la solución tradicional: Definir una clase derivada de TIniFile, con la funcionalidad que requieres y a partir de ella definir los descendientes de los que hablas.

Cuando tienes una clase padre y muchos posibles descendientes que desconoces, es cuando comienzas a pensar en clases abstractas o en interfaces.

Todo mi problema se lío, por no poder hacer en un principio override del constructor y a la hora de crear mi clase como no se hacia directamente desde la propia clase, si no desde una instancia genérica derivada de TINIFile, pues no llegaba a ejecutarse el método constructor propio de mi clase.



Cita:

Empezado por gatosoft (Mensaje 527639)
Yo también me encontré con ese problema del Nil, y por eso linkeaba la solución en ésta linea de código.

Código Delphi [-]
Initialization
  TMyIniFile.mydummy; //Me encontré con este error

En cuanto a crear a través de RTTI, vi el procedimiento y la explicación que habías comentado y si lo utilice, pero al principio no llego a funcionarme, creo que porque en tu código, tu guardabas la unidad y el nombre de la clase en una nueva variable para después hacer la búsqueda, y yo en su lugar use la propiedad ClassName de mi variable con la clase, omitiendo el de la unidad.

Después he realizado una pequeña modificación a tu código, para que no sea necesario hacer la búsqueda de la clase por su nombre, ya que a priori se tiene ya la clase y así se pueda utilizar directamente sin necesidad de hacer su búsqueda.
Y así, si ha funcionado también:

Código Delphi [-]
function config: TIniFile;
var
  RttiContext: TRttiContext;
  RttiType: TRttiInstanceType;
Begin
  if not Assigned(IniFile) then begin
    if Assigned(ClassIniFile) then begin
      RttiContext := TRttiContext.Create;
      try
        RttiType := RttiContext.GetType(ClassIniFile) as TRttiInstanceType;
        // si se ha localizado la la clase se llama al metodo create
        if Assigned(RttiType) then
          IniFile := RttiType.GetMethod('Create').Invoke(RttiType.MetaclassType, [IniFileName]).AsObject as TIniFile;
      finally
        RttiContext.free;
      end;
    end;
    //si no hay assignada ninguna clase especifica de TINIFile o no se ha conseguido
    //localizar la clase especifica, entoces se utiliza la clase TINIFile
    if not Assigned(IniFile) then
      IniFile := TIniFile.Create(IniFileName);
  end;
  Result := IniFile;
end;


Gracias nuevamente a todos los que han dedicado parte de su tiempo a este hilo
Un saludo


La franja horaria es GMT +2. Ahora son las 14:15:07.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi