Link to home
Start Free TrialLog in
Avatar of etiennedemers
etiennedemersFlag for Canada

asked on

creating 2 instances of a form getting "invalid pointer error" on Free

Hello Experts,

I want to do things the right way and I thought I was heading the right direction but it seems I am missing something.

I have address.pas which is a simple form with edit boxes.

I have Personnel.pas which uses 2 instance of form address.pas.

Each instances are created as follow: (pseudo code)
var

  FormResidentialAddress: TFormAddress;
  FormPostalAddress: TFormAddress;


procedure TFormPersonnel.FormCreate(Sender: TObject);
begin
  FormResidentialAddress := TFormAddress.Create(Self);
  FormPostalAddress := TFormAddress.Create(Self);
end;

Problem occurs when I want to Free the 2 instances as follow:

procedure TFormPersonnel.FormDestroy(Sender: TObject);
begin
  FormResidentialAddress.Free;
  FormPostalAddress.Free;
end;

I am getting an "invalid pointer error" , but if I remove either: FormResidentialAddress.Free; or  FormPostalAddress.Free; it solves the problem.

What atrocity am I committing?
Avatar of Mike McCracken
Mike McCracken

Need to see the declaration of TFormAddress.

mlmcc
Avatar of etiennedemers

ASKER

unit Address;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, AdvSmoothPanel, ModifiedTypes,
  Vcl.StdCtrls, AdvEdit, Data.DB, DBAccess, Uni, UniProvider, SQLiteUniProvider,
  UniSQLMonitor, MySQLUniProvider,ModuleDB,MyGlobalConstants, DASQLMonitor,
  MemDS;

type
  TFormAddress = class(TForm)
    AdvSmoothPanel1: TAdvSmoothPanel;
    eAddress2: TEdit;
    eAddress1: TEdit;
    eCity: TEdit;
    eState: TEdit;
    eCountry: TEdit;
    eZip: TEdit;
    bSave: TButton;
    lAddress1: TLabel;
    lAddress2: TLabel;
    lCity: TLabel;
    lState: TLabel;
    lCountry: TLabel;
    lZip: TLabel;
    liAddresses: TLabel;
    UniQuery: TUniQuery;
    UniMonitor: TUniSQLMonitor;
    UniSQL: TUniSQL;
    UniConnection: TUniConnection;
    Datasource: TUniDataSource;
    MySQLUniProvider: TMySQLUniProvider;
    lFormMode: TLabel;
    procedure FillEditBoxes(sIndex : String);
    procedure bSaveClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormCreate(Sender: TObject);
end;

var
  FormAddress:TFormAddress;
  ModuleDB : TModuleDB; //creating a varialble to use the ModuleDB.pas whi is instanciated in the form.creat event bellow

implementation

{$R *.dfm}

{$REGION 'Form controls Click events'}
procedure TFormAddress.bSaveClick(Sender: TObject);
//Save all the items in the edit boxes regardless of if they were modified
begin
    //Load all items from the Residential Address into tbPersonnel
    ModuleDB.MyDBUpdate(
      tblA
      ,
      fldAAddress1 + '=''' + eAddress1.Text + ''',' +
      fldAAddress2 + '=''' + eAddress2.Text + ''',' +
      fldACity + '=''' + eCity.Text + ''',' +
      fldAState + '=''' + eState.Text + ''',' +
      fldAZip + '=''' + eZip.Text + ''',' +
      fldACountry + '=''' + eCountry.Text + ''''
      ,
      fldAi + '=' + liAddresses.caption
      ) ;
      ModalResult := mrOk;
end;
{$ENDREGION}

{$REGION 'Form custom procedures'}
procedure TFormAddress.FillEditBoxes(sIndex : String);
//Fills the desired EditBoxes on the form
Var
MyStringList : TStringlist;
begin
    MyStringList:=TStringList.Create;
    MyStringList:=ModuleDB.MyDBFillStringListWith1Row('*',tblA,fldAi + '=' + sIndex);


    //Load edit boxes with values from DB
    eAddress1.Text := TString(MyStringList.Objects[MyStringList.IndexOf(fldAAddress1)]).Str;
    eAddress2.Text := TString(MyStringList.Objects[MyStringList.IndexOf(fldAAddress2)]).Str;
    eCity.Text := TString(MyStringList.Objects[MyStringList.IndexOf(fldACity)]).Str;
    eState.Text := TString(MyStringList.Objects[MyStringList.IndexOf(fldAState)]).Str;
    eZip.Text := TString(MyStringList.Objects[MyStringList.IndexOf(fldaZip)]).Str;
    eCountry.Text := TString(MyStringList.Objects[MyStringList.IndexOf(fldACountry)]).Str;
 end;
{$ENDREGION}

{$REGION 'Form Events'}
procedure TFormAddress.FormCreate(Sender: TObject);
begin
      //Set the connection parameters defined in MyGLobalConstants
      UniConnection.ProviderName := MyProviderName; //MyProviderName in a constant declared in MyGLobalConstants
      UniConnection.Port := MyPort; //MyPort in a constant declared in MyGLobalConstants
      UniConnection.Database := MyDatabase; //MyDatabase in a constant declared in MyGLobalConstants
      UniConnection.Username := MyUsername; //MyUsername in a constant declared in MyGLobalConstants
      UniConnection.Server := MyServer; //MyServer in a constant declared in MyGLobalConstants

      //This creates an instance of ModuleDB for this form
      //In order to use ModuleDB it need to instanciate its Uni variables by passing this form's Uni variables who are declared as objects
      ModuleDB := TModuleDB.Create(UniQuery,
                                    UniMonitor,
                                    UniSQL,
                                    UniConnection,
                                    Datasource,
                                    MySQLUniProvider);
end;
procedure TFormAddress.FormDestroy(Sender: TObject);
begin
  ModuleDB.Destroy; //Gets rid of the ModuleDB instance and free it's allocated memory
end;
procedure TFormAddress.FormShow(Sender: TObject);
begin
      FillEditBoxes(liAddresses.caption); //Fills the desired EditBoxes on the form
end;
 {$ENDREGION}
end.

Open in new window

Problem goes away if I remove "ModuleDB.Destroy"

procedure TFormAddress.FormDestroy(Sender: TObject);
begin
  ModuleDB.Destroy; //Gets rid of the ModuleDB instance and free it's allocated memory
end;

but I want to free the memory used on this instance of ModuleDB and it works well as long as I don't have 2 instances of a form using this module.
unit ModuleDB;

interface

uses  System.Classes, Data.DB, DBAccess, Uni, UniProvider,
      SQLiteUniProvider, MemDS, DASQLMonitor, UniSQLMonitor,ModifiedTypes,
      MySQLUniProvider, Dialogs,MyGlobalConstants, Variants,System.SysUtils;

type
    TModuleDB=Class(TObject)
        ModuleUniConnection: TUniConnection;
        ModuleUniQuery: TUniQuery;
        ModuleDatasource: TUniDataSource;
        ModuleUniSQL: TUniSQL;
        ModuleUniMonitor: TUniSQLMonitor;
        ModuleMySQLUniProvider: TMySQLUniProvider;
        Constructor Create(MyUniQuery: TUniQuery;
                            MyUniMonitor: TUniSQLMonitor;
                            MyUniSQL: TUniSQL;
                            MyUniConnection: TUniConnection;
                            MyDatasource: TUniDataSource;
                            MyMySQLUniProvider: TMySQLUniProvider);
        Destructor Destroy;override;
    public
        Function GetSQLItem(sSELECT,sFROM,sWHERE : String):Variant;
        Procedure MyDBInsert(sTable,sField,sValue : String);
        Procedure MyDBUpdate(sTable,sSet,sWhere : String);
        Function GetTableTotalRows(sTable : String):Integer;
        Procedure MyDBDelete(sTable,sWhere : String);
        Function MyDBInsertNewRow(sTable,sField : String):Integer;
        function MyDBFillStringListWith1Row(sSELECT,sFROM,sWHERE : String): TStringList;
        Function MyDBFillStringList(sSQL,sSeparator : String): TStringList;
    End;


implementation

Constructor TModuleDB.create(
    MyUniQuery: TUniQuery;
    MyUniMonitor: TUniSQLMonitor;
    MyUniSQL: TUniSQL;
    MyUniConnection: TUniConnection;
    MyDatasource: TUniDataSource;
    MyMySQLUniProvider: TMySQLUniProvider
  );
  begin
     ModuleUniquery:= MyUniQuery;
     ModuleUniMonitor:= MyUniMonitor;
     ModuleUniSQL:= MyUniSQL;
     ModuleUniConnection := MyUniConnection;
     ModuleDatasource:= MyDataSource;
     ModuleMySQLUniProvider:= MyMySQLUniProvider;
  end;

Destructor TModuleDB.Destroy;
  Begin
     ModuleUniquery.Close;
     ModuleUniConnection.Close;
     ModuleUniquery.Free;
     ModuleUniConnection.Free;
     ModuleUniMonitor.Free;
     ModuleUniSQL.Free;
     ModuleDatasource.Free;
     ModuleMySQLUniProvider.Free;
  End;

Function TModuleDB.GetSQLItem(sSELECT,sFROM,sWHERE : String):Variant;
//GetSQLItem: gets one item from a db table
//sSELECT: fields you want to select
//sFROM: table
//sWHERE: condition
//Returns a variant corresponding to the result of the SQL query
var
sSQL:String;
begin

  sSQL:= 'SELECT ' + sSELECT + ' FROM ' + sFROM + ' WHERE ' + sWHERE + ';';  //Build SQL Statement
  ModuleUniConnection.Connect; //Open the connection to the Database

  ModuleUniquery.SQL.Text:=sSQL; //Assign the sSQL to the query
  ModuleUniquery.Open; //Opens "run" the query and stores results in uniquery object

  ModuleDatasource.Dataset:=ModuleUniquery; //Fill the global datasource with the result of the uniquery SQL command
  ModuleDataSource.DataSet.FindFirst; //Reset to the first record of the table

  GetSQLItem:=ModuleDataSource.DataSet.FieldValues[sSELECT]; //Load the Value from the table specified in sSELECT into the array
  ModuleUniquery.Close;
End;
Function TModuleDB.GetTableTotalRows(sTable : String):Integer;
Begin
  ModuleUniconnection.Connect; //Open the connection to the Database

  ModuleUniquery.SQL.Text:='Select * from ' + sTable + ';'; //Assign the sSQL to the query
  ModuleUniquery.Open; //Opens "run" the query and stores results in uniquery object
  GetTableTotalRows:=ModuleUniquery.RecordCount;
End;
Procedure TModuleDB.MyDBInsert(sTable,sField,sValue : String);
//MyDBUpdate: Generic function to update the database
//sTable: tbPersonnel
begin
try
  try
  ModuleUniconnection.Connect;
  ModuleUnisql.SQL.Text:='insert into ' + sTable + ' (' + sField + ') Values(' + sValue + ')';
  //'UPDATE tbPersonnel SET vcPersonnelLastName="test2" WHERE iXPersonnel = 1;' ;
  ModuleUnisql.Execute;
  except
    //outputdebugstring(PChar(ModuleDB.unisql.SQL.Text));
  end;
finally

end;
end;
Function TModuleDB.MyDBInsertNewRow(sTable,sField : String):Integer;
Begin
  try
  ModuleUniconnection.Connect;
  ModuleUnisql.SQL.Text:='INSERT INTO ' + sTable + ' (' + sField + ') VALUES (NULL);';
  ModuleUnisql.Execute;
  ModuleUniquery.SQL.Text:= 'SELECT LAST_INSERT_ID();';
  ModuleUniquery.open;
  ModuleUniquery.First;
  MyDBInsertNewRow := ModuleUniQuery.Fields[0].AsInteger;
  except

  end;
End;
Procedure TModuleDB.MyDBUpdate(sTable,sSet,sWhere : String);
//MyDBUpdate: Generic function to update the database
//sTable: table name
//sSet: set statement
//sWhere: Where statement
begin
ModuleUniconnection.Connect;
ModuleUnisql.SQL.Text:='UPDATE ' + sTable + ' SET ' + sSet + ' WHERE ' + sWhere + ';' ;
ModuleUnisql.Execute;

end;
Procedure TModuleDB.MyDBDelete(sTable,sWhere : String);
//MyDBDelete: Generic function to Delete a record from the database
//sTable: table name
//sWhere: Where statement
begin
ModuleUniconnection.Connect;
ModuleUnisql.SQL.Text:='DELETE FROM ' + sTable + ' WHERE ' + sWhere + ';' ;
ModuleUnisql.Execute;

end;
function TModuleDB.MyDBFillStringListWith1Row(sSELECT,sFROM,sWHERE : String): TStringList;
var
i:integer;
x: Integer;
sField,sValue,sSQL:String;
ots : TString;
begin
  try
  MyDBFillStringListWith1Row := TStringList.Create;
  sSQL:= 'SELECT ' + sSELECT + ' FROM ' + sFROM + ' WHERE ' + sWHERE +';';  //Build SQL Statement

  ModuleUniconnection.Connect; //Open the connection to the Database

  ModuleUniquery.SQL.Text:=sSQL; //Assign the sSQL to the query
  ModuleUniquery.Open; //Opens "run" the query and stores results in uniquery object

  ModuleDatasource.Dataset:=ModuleUniquery; //Fill the global datasource with the result of the uniquery SQL command

  i:=ModuleDataSource.DataSet.FieldCount; //Get the amount of lines to fill the array

  for x := 1 to i do //Go from one record to the next until the end
  begin
    sField:=ModuleDataSource.DataSet.FieldList.Fields[x-1].DisplayName; //Load the Value into the array

    if ModuleDataSource.DataSet.FieldValues[ModuleDataSource.DataSet.FieldList.Fields[x-1].DisplayName] = Null then sValue:=''
    else
    begin
      sValue:=ModuleDataSource.DataSet.FieldValues[ModuleDataSource.DataSet.FieldList.Fields[x-1].DisplayName]; //Load the Value into the array
    end;
    ots:= TString.Create(sValue);
    MyDBFillStringListWith1Row.AddObject(sField, ots);
  end;
  except
    showmessage(ModuleUniquery.SQL.Text);
  end;
End;
function TModuleDB.MyDBFillStringList(sSQL,sSeparator : String): TStringList;
var
i,x,y,z:integer;
sString:String;
begin
  MyDBFillStringList := TStringList.Create; //TComboBox.Create(Nil);


  //Load the database info
  Moduleuniconnection.Connect; //Open the connection to the Database
  Moduleuniquery.SQL.Text:=sSQL; //Assign the sSQL to the query
  Moduleuniquery.Open; //Opens "run" the query and stores results in uniquery object

  ModuleDatasource.Dataset:=Moduleuniquery; //Fill the global datasource with the result of the uniquery SQL command
  ModuleDataSource.DataSet.FindFirst; //Done for RecordCount to be accurate
  ModuleDataSource.DataSet.FindLast;  //Done for RecordCount to be accurate

  i:=ModuleDataSource.DataSet.RecordCount; //Get the amount of lines to fill the array
  z:=ModuleDataSource.DataSet.FieldCount;
  ModuleDataSource.DataSet.FindFirst; //Reposition the cursor to the first record of the table
  sString:='';
  for x := 1 to i do //Go from one record to the next until the end
  begin
    for y := 2 to z do
    begin
      if sString = '' then sString:=ModuleDataSource.DataSet.FieldValues[ModuleDataSource.DataSet.FieldList.Fields[y-1].DisplayName]
      else
      begin
        sString:=sString + sSeparator + ModuleDataSource.DataSet.FieldValues[ModuleDataSource.DataSet.FieldList.Fields[y-1].DisplayName];
      end;
    end;
    MyDBFillStringList.AddObject(sString, TObject(StrToInt(ModuleDataSource.DataSet.FieldValues[ModuleDataSource.DataSet.FieldList.Fields[0].DisplayName])));
    ModuleDataSource.DataSet.FindNext; //Move to the next record of the table
    sString:='';
  end;

end;


end.

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of Marco Gasi
Marco Gasi
Flag of Spain image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
oops sorry yeah, this is me fiddling around with code trying different things.

the right code is:
procedure TFormAddress.FormDestroy(Sender: TObject);
begin
  ModuleDB.Free; //Gets rid of the ModuleDB instance and free it's allocated memory
end;


But the problem is still there.

I think I am doing something wrong in ModuleDB. Should I change the following?:

Destructor TModuleDB.Destroy;
  Begin
     ModuleUniquery.Close;
     ModuleUniConnection.Close;
     ModuleUniquery.Free;
     ModuleUniConnection.Free;
     ModuleUniMonitor.Free;
     ModuleUniSQL.Free;
     ModuleDatasource.Free;
     ModuleMySQLUniProvider.Free;
  End;
Actually, even if I put Moduledb.destroy as comments, I still get the fault...

I can still clear the fault if I remove the ModuleDB.Free or if I remove

one of these guys:
  FormResidentialAddress.Free;
  FormPostalAddress.Free;
Actually, I changed my code from the formevent.destroy to formever.close and it solved the problem.

Thanks for your help
Sorry, I was away to bring my daughters to the school :) Anyway, I'm happy you solved the problem: keep in mind that Destroy method doesn't must be called directly: use always Free and you'll be happy!

Cheers