I'm creating a new app in XE3 but using some units created in D2007.
I"m getting an error when freeing a TStringList data item. Here's the code that creates the data item FSQL:
procedure TPayorDM.DataModuleCreate(Sender: TObject);
begin
FPayorDRM := TDRM.Create;
FSQL := TStringList.Create;
end;
Here's the code that is getting the error:
procedure TPayorDM.DataModuleDestroy(Sender: TObject);
begin
FreeAndNil(FSQL);
if T_Payor.Active then T_Payor.Close;
FreeAndNil(FPayorDRM);
end;
The error occurs on 'FreeAndNil(FSQL);'. I have tried 'FSQL.Free' and I get the same result.
Here's the error I'm getting:
Project: PayorUpdate.exe raised exception class EInvalidPointer with message 'Invalid pointer operation.
When I break blue arrows (debug mode) is pointing to _FreeMem(Pointer(Self)); in the procedure TObject.FreeInstance in System unit as follows:
procedure TObject.FreeInstance;
begin
CleanupInstance;
_FreeMem(Pointer(Self));
end;
If I don't free the TStringList data item I would have a memory leak in the app.
Is there possibly a configuration option I need to set? I've search with google and have not found anything that explains what I am doing wrong other than one of the three possibilities:
If I put a try ... except... I'm able to get around the issue, but I prefer not to do this.
BTW, I have another TStringList in different unit and I create and FreeAndNil and I do not get any error.
Here is entire source:
unit PayorDataMgr; interface uses SysUtils, Classes, Dialogs, NativeXML, adscnnct, DB, adsdata, adsfunc, adstable, ace, cbs.drm, cbs.utils, cbs.LogFiles; const POLICY_TYPES: array[1..3] of string = ('Primary','Secondary','Tertiary'); type TPayorRecord = Record ASSIGNBENEFITS: Boolean; AUTHORIZE: Boolean; BATCHBILL: Boolean; CLAIMMAX: Integer; DISCONTINUED: TDateTime; DISPENSEUPDATE: Boolean; EHRSIGNOFF: Boolean; EMCDEST: String; FORM: String; GOVASSIGN: Boolean; HIDE: Boolean; IGRPUNIQUE: Integer; LEGACYPLAN: String; LEGACYTYPE: String; LOCALATTN: String; LOCALCITY: String; LOCALNAME: String; LOCALPHONE: String; LOCALSTATE: String; LOCALSTREET: String; LOCALZIP: String; MASTERATTN: String; MASTERCITY: String; MASTERNAME: String; MASTERPHONE: String; MASTERSTATE: String; MASTERSTREET: String; MASTERZIP: String; MEDIGAPCODE: String; MEDIGAPPAYOR: Boolean; MEDPLANGUID: String; MODIFIED: TDateTime; NEICCODE: String; NEICTYPESTDC: Integer; OWNER: String; PAYORGUID: String; PAYORSUBTYPESTDC: Integer; PAYORTYPESTDC: Integer; PAYORUNIQUE: Integer; PAYPERCENT: Integer; RTCODE: String; SRXPLANGUID: String; STATEFILTER: String; procedure Clear; End; TPayors = Record private function _pGetCount: Integer; public Items: Array of TPayorRecord; procedure Add(const aItem:TPayorRecord); function CarriersList:TStrings; procedure Free; function GetPayorGuid(const aPAYORUNIQUE:Integer):String; function IndexOfIgrpUnique(Const aIGRPUNIQUE:Integer):Integer; function IndexOfPayorUnique(Const aPAYORUNIQUE:Integer):Integer; procedure SortByName; property Count:Integer Read _pGetCount; End; TPayorDM = class(TDataModule) CommonConnection: TAdsConnection; T_Payor: TAdsTable; Q_Payor: TAdsQuery; procedure DataModuleDestroy(Sender: TObject); procedure DataModuleCreate(Sender: TObject); private FPayorDRM: TDRM; FSQL: TStringList; function _LoadRecordFromTable:TPayorRecord; function _newIDSTRING(const aFormat:String='F'):String; { Private declarations } procedure _pSetConnectionHandle(const Value: Integer); procedure _pSetErrorMessage(const Value: String); procedure _psetSQL(const Value: TStringList); { Private properties } property ErrorMessage:String Write _pSetErrorMessage; public function AddPayor(var aPAYORRECORD:TPAYORRECORD):Boolean; function ExecuteScript(const aTo,aFrom:string):Boolean; function FindPayor(const aPAYORGUID:String):Boolean;overload; function FindPayor(const aPAYORUNIQUE:Integer):Boolean;overload; function GetPayorData:TDRM; function GetRecordCount(const aData:String):Integer; function LoadCarriers(const aHide:boolean = False):TPayors; function LoadPayor:TPayorRecord; function OpenTable:Boolean; function UpdateFromXML(const aPayorNode:TXMLNode):boolean; { Public declarations } property ConnectionHandle:Integer Write _pSetConnectionHandle; property DynamicPayorFields:TDRM Read FPayorDRM; property SQL:TStringList Read FSQL Write _psetSQL; end; var PayorDM: TPayorDM; implementation {$R *.dfm} function TPayorDM.AddPayor(var aPAYORRECORD: TPAYORRECORD): Boolean; begin Result := False; if IsNull(aPAYORRECORD.LOCALNAME) then Exit; { Create uniques } { Add Record } if not T_Payor.Active then if not OpenTable then Exit; with T_Payor do try Insert; FieldByName('PAYORGUID').AsString := _newIDSTRING; FieldByName('MASTERNAME').AsString := aPAYORRECORD.MASTERNAME; FieldByName('MASTERSTREET').AsString := aPAYORRECORD.MASTERSTREET; FieldByName('MASTERCITY').AsString := aPAYORRECORD.MASTERCITY; FieldByName('MASTERSTATE').AsString := aPAYORRECORD.MASTERSTATE; FieldByName('PAYORTYPESTDC').AsInteger := aPAYORRECORD.PAYORTYPESTDC; FieldByName('MASTERZIP').AsString := aPAYORRECORD.MASTERZIP; FieldByName('MASTERATTN').AsString := aPAYORRECORD.MASTERATTN; FieldByName('MASTERPHONE').AsString := aPAYORRECORD.MASTERPHONE; FieldByName('NEICCODE').AsString := aPAYORRECORD.NEICCODE; FieldByName('RTCODE').AsString := aPAYORRECORD.RTCODE; FieldByName('STATEFILTER').AsString := aPAYORRECORD.STATEFILTER; FieldByName('NEICTYPESTDC').AsInteger := aPAYORRECORD.NEICTYPESTDC; FieldByName('PAYORSUBTYPESTDC').AsInteger := aPAYORRECORD.PAYORSUBTYPESTDC; FieldByName('OWNER').AsString := aPAYORRECORD.OWNER; FieldByName('HIDE').AsBoolean := aPAYORRECORD.HIDE; FieldByName('IGRPUNIQUE').AsInteger := aPAYORRECORD.IGRPUNIQUE; FieldByName('FORM').AsString := aPAYORRECORD.FORM; FieldByName('GOVASSIGN').AsBoolean := aPAYORRECORD.GOVASSIGN; FieldByName('CLAIMMAX').AsInteger := aPAYORRECORD.CLAIMMAX; FieldByName('MEDIGAPCODE').AsString := aPAYORRECORD.MEDIGAPCODE; FieldByName('EMCDEST').AsString := aPAYORRECORD.EMCDEST; FieldByName('ASSIGNBENEFITS').AsBoolean := aPAYORRECORD.ASSIGNBENEFITS; FieldByName('BATCHBILL').AsBoolean := aPAYORRECORD.BATCHBILL; FieldByName('MEDIGAPPAYOR').AsBoolean := aPAYORRECORD.MEDIGAPPAYOR; FieldByName('MEDPLANGUID').AsString := aPAYORRECORD.MEDPLANGUID; FieldByName('SRXPLANGUID').AsString := aPAYORRECORD.SRXPLANGUID; FieldByName('PAYPERCENT').AsInteger := aPAYORRECORD.PAYPERCENT; FieldByName('LOCALNAME').AsString := aPAYORRECORD.LOCALNAME; FieldByName('LOCALSTREET').AsString := aPAYORRECORD.LOCALSTREET; FieldByName('LOCALCITY').AsString := aPAYORRECORD.LOCALCITY; FieldByName('LOCALSTATE').AsString := aPAYORRECORD.LOCALSTATE; FieldByName('LOCALZIP').AsString := aPAYORRECORD.LOCALZIP; FieldByName('LOCALATTN').AsString := aPAYORRECORD.LOCALATTN; FieldByName('LOCALPHONE').AsString := aPAYORRECORD.LOCALPHONE; FieldByName('EHRSIGNOFF').AsBoolean := aPAYORRECORD.EHRSIGNOFF; FieldByName('DISCONTINUED').AsDateTime := aPAYORRECORD.DISCONTINUED; FieldByName('MODIFIED').AsDateTime := Now; FieldByName('LEGACYPLAN').AsString := aPAYORRECORD.LEGACYPLAN; FieldByName('LEGACYTYPE').AsString := aPAYORRECORD.LEGACYTYPE; FieldByName('AUTHORIZE').AsBoolean := aPAYORRECORD.AUTHORIZE; FieldByName('DISPENSEUPDATE').AsBoolean := aPAYORRECORD.DISPENSEUPDATE; Post; aPAYORRECORD.PAYORUNIQUE := FieldByName('PAYORUNIQUE').AsInteger; aPAYORRECORD.PAYORGUID := FieldByName('PAYORGUID').AsString; Close; Result := True; except on E: EADSDatabaseError do begin ErrorMessage := 'AddPayor: ERRORCODE: ' + IntToStr(e.ACEErrorCode) + ' ERROR: ' + e.Message; end; end; end; procedure TPayorDM.DataModuleCreate(Sender: TObject); begin FPayorDRM := TDRM.Create; FSQL := TStringList.Create; { FSQL Created } end; procedure TPayorDM.DataModuleDestroy(Sender: TObject); begin try FSQL.Free; { FSQL destroyed - work around to get unit to run without error} except end; if T_Payor.Active then T_Payor.Close; FreeAndNil(FPayorDRM); end; function TPayorDM.ExecuteScript(const aTo, aFrom: string):Boolean; begin Result := False; if FSQL.Count = 0 then exit; with Q_Payor do try if Active then Close; SQL := FSQL; ParamByName('to').Text := aTo; ParambyName('from').Text := aFrom; ExecSQL; if Active then Close; Result := True; except on E: EADSDatabaseError do begin ErrorMessage := 'ExecuteScript: ERRORCODE: ' + IntToStr(e.ACEErrorCode) + ' ERROR: ' + e.Message + ' SQL: ' + Q_Payor.SQL.Text; end; end; end; function TPayorDM.FindPayor(const aPAYORUNIQUE: Integer): Boolean; begin T_Payor.IndexName := 'PAYORUNIQUE'; Result := T_Payor.FindKey([aPAYORUNIQUE]); end; function TPayorDM.FindPayor(const aPAYORGUID: String): Boolean; begin T_Payor.IndexName := 'PAYORGUID'; Result := T_Payor.FindKey([aPAYORGUID]); end; function TPayorDM.GetPayorData: TDRM; begin if FPayorDRM.Count = 0 then FPayorDRM.BuildDRMList(T_Payor); Result := FPayorDRM; end; function TPayorDM.GetRecordCount(const aData:string): Integer; begin Result := 0; if FSQL.Count = 0 then exit; with Q_Payor do try if Active then Close; SQL := FSQL; ParamByName('data').AsString := aData; Open; Result := RecordCount; Close; except on E: EADSDatabaseError do begin ErrorMessage := 'GetRecordCount: ERRORCODE: ' + IntToStr(e.ACEErrorCode) + ' ERROR: ' + e.Message; end; end; end; function TPayorDM.LoadCarriers(const aHide: boolean): TPayors; begin OpenTable; Result.Free; with T_Payor do begin First; while not EOF do begin if T_Payor.FieldByName('HIDE').AsBoolean = aHide then Result.Add(_LoadRecordFromTable); Next; end; First; Result.SortByName; end; end; function TPayorDM.LoadPayor: TPayorRecord; begin Result.Clear; try if not T_Payor.active then exit; if T_Payor.RecNo = 0 then exit; Result := _LoadRecordFromTable; except on E: EADSDatabaseError do begin ErrorMessage := 'LoadPayor: ERRORCODE: ' + IntToStr(e.ACEErrorCode) + ' ERROR: ' + e.Message; end; end; end; function TPayorDM.OpenTable: Boolean; begin Result := False; with T_Payor do try if not Active then Open; FPayorDRM.BuildDRMList(T_Payor); FPayorDRM.LoadValues(T_Payor); { test } FPayorDRM.ExportDRMList; { test } Result := True; except on E: EADSDatabaseError do begin ErrorMessage := 'OpenTable: ERRORCODE: ' + IntToStr(e.ACEErrorCode) + ' ERROR: ' + e.Message; end; end; end; function TPayorDM.UpdateFromXML(const aPayorNode: TXMLNode): boolean; var fKeyData:TXMLNode; Idx,fPAYORUNIQUE:Integer; begin Result := False; if not Assigned(aPayorNode) then Exit; try if FPayorDRM.Count = 0 then FPayorDRM.BuildDRMList(T_Payor); FPayorDRM.ClearValues; fKeyData := aPayorNode.FindNode('KeyData'); FPayorDRM.FindRecordFromKeyData(fKeyData,T_Payor); fPAYORUNIQUE := FPayorDRM.FieldByName('PAYORUNIQUE').AsInteger; FPayorDRM.LoadValues(aPayorNode); if fPAYORUNIQUE = 0 then begin FPayorDRM.FieldByName('PAYORUNIQUE').AsInteger := 0; FPayorDRM.FieldByName('PAYORGUID').AsString := _newIDSTRING; FPayorDRM.FieldByName('MODIFIED').AsDate := Now; FPayorDRM.AddRecord(T_Payor) end else begin FPayorDRM.FieldByName('MODIFIED').AsDate := Now; FPayorDRM.UpdateRecord(T_Payor); end; except on e:exception do begin ErrorMessage := 'UpdateFromXML: ERROR: ' + e.Message; end; end; end; function TPayorDM._LoadRecordFromTable: TPayorRecord; begin with T_Payor do begin Result.PAYORUNIQUE := FieldByName('PAYORUNIQUE').AsInteger; Result.PAYORGUID := FieldByName('PAYORGUID').AsString; Result.MASTERNAME := FieldByName('MASTERNAME').AsString; Result.MASTERSTREET := FieldByName('MASTERSTREET').AsString; Result.MASTERCITY := FieldByName('MASTERCITY').AsString; Result.MASTERSTATE := FieldByName('MASTERSTATE').AsString; Result.PAYORTYPESTDC := FieldByName('PAYORTYPESTDC').AsInteger; Result.MASTERZIP := FieldByName('MASTERZIP').AsString; Result.MASTERATTN := FieldByName('MASTERATTN').AsString; Result.MASTERPHONE := FieldByName('MASTERPHONE').AsString; Result.NEICCODE := FieldByName('NEICCODE').AsString; Result.RTCODE := FieldByName('RTCODE').AsString; Result.STATEFILTER := FieldByName('STATEFILTER').AsString; Result.NEICTYPESTDC := FieldByName('NEICTYPESTDC').AsInteger; Result.PAYORSUBTYPESTDC := FieldByName('PAYORSUBTYPESTDC').AsInteger; Result.OWNER := FieldByName('OWNER').AsString; Result.HIDE := FieldByName('HIDE').AsBoolean; Result.IGRPUNIQUE := FieldByName('IGRPUNIQUE').AsInteger; Result.FORM := FieldByName('FORM').AsString; Result.GOVASSIGN := FieldByName('GOVASSIGN').AsBoolean; Result.CLAIMMAX := FieldByName('CLAIMMAX').AsInteger; Result.MEDIGAPCODE := FieldByName('MEDIGAPCODE').AsString; Result.EMCDEST := FieldByName('EMCDEST').AsString; Result.ASSIGNBENEFITS := FieldByName('ASSIGNBENEFITS').AsBoolean; Result.BATCHBILL := FieldByName('BATCHBILL').AsBoolean; Result.MEDIGAPPAYOR := FieldByName('MEDIGAPPAYOR').AsBoolean; Result.MEDPLANGUID := FieldByName('MEDPLANGUID').AsString; Result.SRXPLANGUID := FieldByName('SRXPLANGUID').AsString; Result.PAYPERCENT := FieldByName('PAYPERCENT').AsInteger; Result.LOCALNAME := FieldByName('LOCALNAME').AsString; Result.LOCALSTREET := FieldByName('LOCALSTREET').AsString; Result.LOCALCITY := FieldByName('LOCALCITY').AsString; Result.LOCALSTATE := FieldByName('LOCALSTATE').AsString; Result.LOCALZIP := FieldByName('LOCALZIP').AsString; Result.LOCALATTN := FieldByName('LOCALATTN').AsString; Result.LOCALPHONE := FieldByName('LOCALPHONE').AsString; Result.EHRSIGNOFF := FieldByName('EHRSIGNOFF').AsBoolean; Result.DISCONTINUED := FieldByName('DISCONTINUED').AsDateTime; Result.MODIFIED := FieldByName('MODIFIED').AsDateTime; Result.LEGACYPLAN := FieldByName('LEGACYPLAN').AsString; Result.LEGACYTYPE := FieldByName('LEGACYTYPE').AsString; Result.AUTHORIZE := FieldByName('AUTHORIZE').AsBoolean; Result.DISPENSEUPDATE := FieldByName('DISPENSEUPDATE').AsBoolean; end; end; function TPayorDM._newIDSTRING(const aFormat: String): String; begin Result := ''; try with Q_Payor do try SQL.Clear; SQL.Add('SELECT NEWIDSTRING( "' + aFormat + '" ) AS GUID FROM system.iota'); Open; Result := FieldByName('GUID').AsString; Close; except on E: EADSDatabaseError do begin ErrorMessage := '_newIDSTRING: ERRORCODE: ' + IntToStr(e.ACEErrorCode) + ' ERROR: ' + e.Message; end; end; finally end; end; procedure TPayorDM._pSetConnectionHandle(const Value: Integer); begin if T_Payor.Active then T_Payor.Close; CommonConnection.SetHandle(Value); OpenTable; end; procedure TPayorDM._pSetErrorMessage(const Value: String); begin WriteError('[TPayorDM]' + Value,LogFilename); end; procedure TPayorDM._psetSQL(const Value: TStringList); begin FSQL := Value; end; { TPayorRecord } procedure TPayorRecord.Clear; begin PAYORUNIQUE := 0; PAYORGUID := ''; MASTERNAME := ''; MASTERSTREET := ''; MASTERCITY := ''; MASTERSTATE := ''; PAYORTYPESTDC := 0; MASTERZIP := ''; MASTERATTN := ''; MASTERPHONE := ''; NEICCODE := ''; RTCODE := ''; STATEFILTER := ''; NEICTYPESTDC := 0; PAYORSUBTYPESTDC := 0; OWNER := ''; HIDE := False; IGRPUNIQUE := 0; FORM := ''; GOVASSIGN := False; CLAIMMAX := 0; MEDIGAPCODE := ''; EMCDEST := ''; ASSIGNBENEFITS := False; BATCHBILL := False; MEDIGAPPAYOR := False; MEDPLANGUID := ''; SRXPLANGUID := ''; PAYPERCENT := 0; LOCALNAME := ''; LOCALSTREET := ''; LOCALCITY := ''; LOCALSTATE := ''; LOCALZIP := ''; LOCALATTN := ''; LOCALPHONE := ''; EHRSIGNOFF := False; DISCONTINUED := 0; MODIFIED := 0; LEGACYPLAN := ''; LEGACYTYPE := ''; AUTHORIZE := False; DISPENSEUPDATE := False; end; { TPayors } procedure TPayors.Add(const aItem: TPayorRecord); begin SetLength(Items,Count + 1); Items[Count - 1] := aItem; end; function TPayors.CarriersList: TStrings; var I: Integer; begin Result := TStringList.Create; Result.Clear; SortbyName; try for I := 0 to Count - 1 do Result.Add(Items[I].LOCALNAME); finally end; end; procedure TPayors.Free; begin Items := Nil; end; function TPayors.GetPayorGuid(const aPAYORUNIQUE: Integer): String; var Idx:Integer; begin Result := ''; Idx := IndexOfPayorUnique(aPAYORUNIQUE); if not (Idx = -1) then Result := Items[Idx].PAYORGUID; end; function TPayors.IndexOfIgrpUnique(const aIGRPUNIQUE: Integer): Integer; var I: Integer; begin Result := -1; for I := 0 to Count - 1 do if Items[I].IGRPUNIQUE = aIGRPUNIQUE then begin Result := I; Break; end; end; function TPayors.IndexOfPayorUnique(const aPAYORUNIQUE: Integer): Integer; var I: Integer; begin Result := -1; for I := 0 to Count - 1 do if Items[I].PAYORUNIQUE = aPAYORUNIQUE then begin Result := I; Break; end; end; procedure TPayors.SortByName; var fSort:TStringList; fParse:TStrings; I,Idx: Integer; fTempPayor:TPayors; begin fSort := TStringList.Create; fParse := TStringList.Create; fTempPayor.Items := Self.Items; fSort.Sorted := True; try for I := 0 to Count - 1 do fSort.Add(Items[I].LOCALNAME + #9 + IntToStr(I)); Items := Nil; for I := 0 to fSort.Count - 1 do begin cbs.utils.ParseDelimited(fParse,fSort[I],#9); Idx := StrToInt(fParse[1]); Add(fTempPayor.Items[Idx]); end; finally fTempPayor.Free; fParse.Free; fSort.Free; end; end; function TPayors._pGetCount: Integer; begin Result := Length(Items); end; end.
You are (most probably) double freeing a stringlist (and never freeing at least one). The problem is in the setter of your 'SQL' property (which is backed by the 'FSQL' field):
procedure TPayorDM._psetSQL(const Value: TStringList);
begin
FSQL := Value;
end;
Here you are loosing the reference to the already existing stringlist (LHS). Consider the below scenario:
You call
PayorDM.SQL := AStringList;
and the reference to the private field you created in the constructor is gone, instead you keep a reference to 'AStringList'. After that at some point you destroy 'AStringList', now the 'FSQL' field is a stale pointer. When in the destructor you call
FSQL.Free;
you get an invalid pointer operation.
Change your setter to:
procedure TPayorDM._psetSQL(const Value: TStringList);
begin
FSQL.Assign(Value);
end;