Why is my procedure not changing the array - arrays

I have a procedure to shift the position of the array values
The string at position 1 is supposed to take the value of the string at position 2, and the string at position 2 is supposed to take the value of the string at position 1(and so forth)
eg.
The array at the start:
Pos Value
1 A
2 B
3 C
The array at the end (what it's supposed to become):
Pos Value
1 B
2 C
3 A
This is the code:
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls;
type
arrRotor = array [1 .. 10] of string;
TForm2 = class(TForm)
Button1: TButton;
RichEdit1: TRichEdit;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure ArrayShiftRowUp(arrArrayShift: arrRotor );
end;
var
Form2: TForm2;
j, i : integer;
temps:string;
arrTest: arrRotor;
implementation
{$R *.dfm}
procedure TForm2.ArrayShiftRowUp(arrArrayShift: arrRotor );
begin
for i := 1 to Length(arrArrayShift)-1 do
begin
tempS := arrArrayShift[i];
arrArrayShift[i] := arrArrayShift[i + 1];
arrArrayShift[i + 1] := tempS;
RichEdit1.Lines.Add(arrArrayShift[i]);
end;
RichEdit1.Lines.Add(arrArrayShift[10]);
end;
procedure TForm2.Button1Click(Sender: TObject);
begin
for i := 1 to 10 do
begin
arrTest[i]:=Chr(64+i);
end;
for i := 1 to 10 do
begin
RichEdit1.Lines.Add(arrTest[i]);
end;
ArrayShiftRowUp(arrTest);
for i := 1 to 10 do
begin
RichEdit1.Lines.Add(arrTest[i]);
end;
end;
end.
But for some reason, the actual array is not changing. The array parameter is changing (as evident of the display in the rich edit) but for some reason, arrTest is not changing.
What is the issue?

Related

Removing elements from an array of records containing string fields?

Check the example below... I have an array TSrvClientList.Items with record elements. These elements have string fields. When I remove an element, I need to move the following ones in that empty space left. I don't like to copy field by field... And I thought I'd use the Move function to do it faster, but I'm not sure if this is a proper way to do it. If the record contained only unmanaged types, I'm sure it's OK, I uesed many times. But with those strings, I don't know... Should I call a Finalize first ? Or do it differently ? My test code seems it works as it is, directly moving those strings, but I'd like to make sure it's not just a coincidence.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, SynCommons, System.SyncObjs,
Vcl.StdCtrls;
type
TSrvClientInfo = record
ClientIP: String;
ClientGUID: Cardinal;
AESKey: THash256;
TransCons: Integer;
end;
TSrvClientList = record
private
Valid: DWord;
public
Items: array of TSrvClientInfo;
procedure Init;
procedure Free;
procedure AddClient(const IP: String; GUID: Cardinal; AESKey: THash256);
procedure RemoveClient(const IP: String; GUID: Cardinal);
end;
TForm1 = class(TForm)
BAddItem: TButton;
BRemoveItem: TButton;
procedure FormCreate(Sender: TObject);
procedure BAddItemClick(Sender: TObject);
procedure BRemoveItemClick(Sender: TObject);
public
Code: Byte;
Clients: TSrvClientList;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
//===== TSrvClientList =======================================================
procedure TSrvClientList.Init;
begin
if Valid <> $12344321 then begin
Valid:= $12344321;
SetLength(Items, 0);
end;
end;
procedure TSrvClientList.Free;
begin
if Valid = $12344321 then begin
SetLength(Items, 0);
Valid:= 0;
end;
end;
procedure TSrvClientList.AddClient(const IP: String; GUID: Cardinal; AESKey: THash256);
var I: Integer;
begin
if Valid <> $12344321 then Exit;
I:= Length(Items); SetLength(Items, I+1);
Items[I].ClientIP:= IP;
Items[I].ClientGUID:= GUID;
Items[I].AESKey:= AESKey;
Items[I].TransCons:= 0;
end;
procedure TSrvClientList.RemoveClient(const IP: String; GUID: Cardinal);
var I, R: Integer;
begin
if Valid <> $12344321 then Exit;
I:= 0; while (I < Length(Items)) and ((Items[I].ClientIP <> IP) or (Items[I].ClientGUID <> GUID)) do Inc(I);
if (I > High(Items)) then Exit;
R:= High(Items) - I;
if R > 0 then Move(Items[I+1], Items[I], SizeOf(TSrvClientInfo) * R);
SetLength(Items, Length(Items)-1);
end;
// ----------------------------------------------------
procedure TForm1.FormCreate(Sender: TObject);
begin
Clients.Init;
Code:= 1;
end;
procedure TForm1.BAddItemClick(Sender: TObject);
var IP: String;
GUID: Cardinal;
AESKey: THash256;
begin
IP:= '192.168.0.3';
GUID:= $12345678;
FillChar(AESKey[0], 32, 0); AESKey[0]:= Code; Inc(Code);
Clients.AddClient(IP, GUID, AESKey);
Caption:= IntToStr(Length(Clients.Items));
end;
procedure TForm1.BRemoveItemClick(Sender: TObject);
var IP: String;
GUID: Cardinal;
begin
IP:= '192.168.0.3';
GUID:= $12345678;
Clients.RemoveClient(IP, GUID);
Caption:= IntToStr(Length(Clients.Items));
end;
end.
No need to call Finalize, Move and SetLength. The intrinsic Delete already handles all this for you:
Delete(Items, I, 1);
As you suspect, you mustn't do this because of the string member. This is because strings are managed types, as you say.
Consider the following much smaller example:
procedure TForm1.FormCreate(Sender: TObject);
var
A: array of string;
begin
ReportMemoryLeaksOnShutdown := True;
SetLength(A, 6);
A[0] := 'cats'; A[1] := 'dogs'; A[2] := 'rats';
A[3] := 'rabbits'; A[4] := 'horses'; A[5] := 'guinea pigs';
Move(A[3], A[2], 3 * SizeOf(string));
SetLength(A, Length(A) - 1);
end;
Note: I choose not to use the simpler (post-XE7) approach
A := ['cats', 'dogs', 'rats', 'rabbits', 'horses', 'guinea pigs'];
because then the reference count of the dynamic array heap object will be 2 instead of 1, and the analysis will be more complicated.
This may seem to work until you exit the application. Then you are notified of this:
And this is very expected. Recall the internal data format for long strings. Long strings are reference counted. Let's look at the heap object for rats before the Move (open the Memory panel and go to Pointer(A[2])^):
Notice that the reference count is 1 (and the string length is 4). If you step through the code you will notice that this heap object is never touched again. It is leaked because the RTL never gets a chance to clear it up.
You can fix this leak by adding Finalize(A[2]) before the Move.
However, there is another issue. Immediately after the move, both A[4] and A[5] point to the same long string heap object. Here's the array heap object (refcount, length, and six pointers):
Going to this address, we find this:
This is a long string with reference count 1 -- but we would expect it to be 2. What will happen now when you call SetLength to remove the last item? Will that not lead to the heap object hitting refcount 0 and being freed, meaning that the new last string pointer, A[4], will be dangling?
Yes, I think so.
And if I try
procedure TForm1.FormCreate(Sender: TObject);
var
A: array of string;
begin
ReportMemoryLeaksOnShutdown := True;
SetLength(A, 6);
A[0] := 'cats'; A[1] := 'dogs'; A[2] := 'rats';
A[3] := 'rabbits'; A[4] := 'horses'; A[5] := 'guinea pigs';
Finalize(A[2]);
Move(A[3], A[2], 3 * SizeOf(string));
SetLength(A, Length(A) - 1);
for var s in A do
ShowMessage(s);
end;
I am shown "cats", "dogs", "rabbits", "horses", "d". You will probably observe a different behaviour.
Likely this can be fixed by clearing the last pointer behind the back of the RTL prior to the SetLength:
procedure TForm1.FormCreate(Sender: TObject);
var
A: array of string;
begin
ReportMemoryLeaksOnShutdown := True;
SetLength(A, 6);
A[0] := 'cats'; A[1] := 'dogs'; A[2] := 'rats';
A[3] := 'rabbits'; A[4] := 'horses'; A[5] := 'guinea pigs';
Finalize(A[2]); // frees the A[2]^ string and sets A[2] to nil
Move(A[3], A[2], 3 * SizeOf(string));
NativeInt(A[5]) := 0; // sets A[5] to nil
SetLength(A, Length(A) - 1);
for var s in A do
ShowMessage(s);
end;
Do I need to tell you this is a hack that shouldn't be used?
Interestingly, if I use an "array literal" instead, the initial reference count of the dynamic array is 2, so the SetLength will create a new dynamic array heap object, and at least on my system right now, the behaviour appears to be correct, but I haven't analysed the program in detail.

Aggregate of multiple UI controls bound to one database column

I currently have a TDBRadioGroup bound to a CHAR column on the database using the Values property to specify the value stored in the database column for each radio button. Now we have a requirement to add 2 new radio groups along side the existing one that would modify the value. In other words I basically need 3 controls on the form to act as one in a data-aware configuration for the purposes of determining the value stored. A simple example might be if each radio group has a one-character value, and in the database you concatenate them into a 3-character string. Our mapping is more complex than that but that's the general idea. What would be a good way to do this? If someone tells me live bindings would be the way to go, that would be good to know, as it would be help me to convince management to upgrade Delphi. We're stuck on XE.
Interesting q!
I hope I've understood you correctly. Suppose there is a form with 3 radiogroups, one per line, like so
*A B C
D *E F
G H *I
where the asterisks indicates the positions of the selected buttons and suppose
that these settings translate into a field value AEI in a row of a dataset.
If the G entry of the 3rd RG is clicked, the dataset field value should become
GEI. That's what I've assumed you are looking for.
The code below is a "proof of concept" which implements the above functionality.
It does so using 3 standard (non-db-aware) TRadioGroups and an adaptor class
TDBRadioGroupAdaptor which makes these operate as db-aware RadioGroups. The
TDBRadioGroupAdaptor uses a descendant, TRGFieldDataLink, of the standard TFieldDataLink (see DBCtrls.Pas)
to interface with the dataset. I've used a TClientDataSet so the project can be completely
self-contained and I've avoided the use of generices as you didn't specify a Delphi version.
Most of the db-aware functionality is contained in the TRGFieldDataLink, which
serves to interface the dataset and the RadioGroups and differs from a standard
TFieldDataLink in that it treats the field value as composed of N (3 in the code example but it supports an arbitrary number)
sub-fields which each have their own RadioGroup. As ever with implementing db-aware
functionality, the code for the TDBRadioGroupAdaptor and TRGFieldDataLink is fairly
long-winded, as most of it is necessary but not very interesting. About the only
ones that are interesting, in that they make the wheels go around, are
function TRGFieldDataLink.GetGroupString : String;
// Returns a string from the ItemIndexes of the RadioGroups
procedure TRGFieldDataLink.GetGroupValues;
// Sets the DataSet field from the RadioGroup ItemIndexes
which I hope are self-explanatory from the embedded comments.
Because it's only supposed to be a proof-of-concept, the implementation is incomplete,
lacking f.i. keyboard support, but that could easily be added by mimicking the
source code of the standard TDBRadioGroup.
Obviously, something similar could be used to treat subfields of a dataset field as
a group of strings independently editable in a group of TEdits.
Also obviously, it would be possible to turn the TDBRadioGroupAdaptor into a full-blown compound component which includes its own radiogroups, but I've left that as an exercise for the reader.
Code (warning: long!)
type
TDBRadioGroupAdaptor = class;
TRGFieldDataLink = class(TFieldDataLink)
private
FAdaptor: TDBRadioGroupAdaptor;
FRecordChanging : Boolean;
procedure GetGroupValues;
function GetGroupString: String;
procedure SetGroupValues(AValue: String);
public
constructor Create(AAdaptor : TDBRadioGroupAdaptor);
destructor Destroy; override;
procedure RecordChanged(Field : TField); override;
procedure UpdateData; override;
property Adaptor : TDBRadioGroupAdaptor read FAdaptor write FAdaptor;
end;
TDBRadioGroupAdaptor = class
private
FDataLink : TRGFieldDataLink;
FRadioGroups : TList;
procedure SetDataSource(Value : TDataSource);
function GetDataSource : TDataSource;
function GetRadioGroup(Index: Integer): TRadioGroup;
procedure ItemClicked(Sender : TObject);
procedure SetFieldName(const Value: string);
function GetFieldName : String;
public
constructor Create;
destructor Destroy; override;
procedure Add(ARadioGroup : TRadioGroup);
property DataSource : TDataSource read GetDataSource write SetDataSource;
property FieldName : string read GetFieldName write SetFieldName;
property RadioGroup[Index : Integer] : TRadioGroup read GetRadioGroup;
end;
type
TForm1 = class(TForm)
DBGrid1: TDBGrid;
ClientDataSet1: TClientDataSet;
DataSource1: TDataSource;
DBNavigator1: TDBNavigator;
Button1: TButton;
RadioGroup1: TRadioGroup;
RadioGroup2: TRadioGroup;
RadioGroup3: TRadioGroup;
DBEdit1: TDBEdit;
DBRadioGroup1: TDBRadioGroup;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
protected
public
Adaptor : TDBRadioGroupAdaptor;
end;
[...]
{ TRGFieldDataLink }
constructor TRGFieldDataLink.Create(AAdaptor : TDBRadioGroupAdaptor);
begin
inherited Create;
Adaptor := AAdaptor;
end;
destructor TRGFieldDataLink.Destroy;
begin
inherited;
end;
procedure TRGFieldDataLink.SetGroupValues(AValue : String);
// Sets the ItemIndexes of the RadioGroups by matching each character of AValue
// to the contents of their Items
var
i,
Index : Integer;
S : String;
begin
if AValue = '' then Exit; // To avoid error when CreateDataSet is called
for i := 0 to Adaptor.FRadioGroups.Count - 1 do begin
S := AValue[i + 1];
Index := Adaptor.RadioGroup[i].Items.IndexOf(S);
Adaptor.RadioGroup[i].ItemIndex := Index;
end;
end;
procedure TRGFieldDataLink.RecordChanged(Field : TField);
// called when the dataset goes to a new record, e.g. during scrolling
// and sets the RadioGroups' ItemIndexes from the dataset data
var
FieldValue : String;
begin
Assert(DataSet <> Nil);
if FRecordChanging then exit; // just in case, avoid re-entrancy
try
FRecordChanging := True;
if Field = Nil then
Field := DataSet.FieldByName(FieldName); // Yukky way of setting Field
FieldValue := Field.AsString;
SetGroupValues(FieldValue);
finally
FRecordChanging := False;
end;
end;
function TRGFieldDataLink.GetGroupString : String;
// Returns a string from the ItemIndexes of the RadioGroups
var
i : Integer;
S : String;
begin
Result := '';
for i := 0 to Adaptor.FRadioGroups.Count - 1 do begin
S := Adaptor.RadioGroup[i].Items[Adaptor.RadioGroup[i].ItemIndex];
Result := Result + S [1];
end;
end;
procedure TRGFieldDataLink.GetGroupValues;
// Sets the DataSet field from the RadioGroup ItemIndexes
var
FieldValue,
S : String;
begin
Assert(DataSet <> Nil);
S := Field.AsString;
FieldValue := GetGroupString;
Field.AsString := FieldValue;
end;
procedure TRGFieldDataLink.UpdateData;
// Called by RTL to update the dataset record from the RadioGroups
begin
GetGroupValues;
end;
{ TDBRadioGroupAdaptor }
procedure TDBRadioGroupAdaptor.Add(ARadioGroup: TRadioGroup);
begin
FRadioGroups.Add(ARadioGroup);
ARadioGroup.OnClick := ItemClicked;
end;
constructor TDBRadioGroupAdaptor.Create;
begin
inherited;
FRadioGroups := TList.Create;
FDataLink := TRGFieldDataLink.Create(Self);
end;
destructor TDBRadioGroupAdaptor.Destroy;
begin
FDataLink.Free;
FRadioGroups.Free;
inherited Destroy;
end;
function TDBRadioGroupAdaptor.GetDataSource: TDataSource;
begin
Result := FDataLink.DataSource;
end;
procedure TDBRadioGroupAdaptor.SetDataSource(Value: TDataSource);
begin
FDataLink.DataSource := Value;
end;
function TDBRadioGroupAdaptor.GetRadioGroup(Index: Integer): TRadioGroup;
begin
Result := TRadioGroup(FRadioGroups[Index]);
end;
procedure TDBRadioGroupAdaptor.ItemClicked(Sender: TObject);
// Responds to one of the RadioGroups being clicked to put the DataSet into Edit state
// and updates the DataSet field from the ItemIndexes of the RadioGroups
var
S : String;
begin
if not FDataLink.FRecordChanging then begin
S := FDataLink.GetGroupString;
FDataLink.Edit;
FDataLink.SetGroupValues(S);
end;
end;
procedure TDBRadioGroupAdaptor.SetFieldName(const Value: string);
begin
FDataLink.FieldName := Value;
end;
function TDBRadioGroupAdaptor.GetFieldName: string;
begin
Result := FDataLink.FieldName;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
Field : TField;
begin
// Create 2 fields in the CDS
Field := TIntegerField.Create(Self);
Field.FieldName := 'ID';
Field.FieldKind := fkData;
Field.DataSet := ClientDataSet1;
Field := TStringField.Create(Self);
Field.FieldName := 'Value';
Field.Size := 40;
Field.FieldKind := fkData;
Field.DataSet := ClientDataSet1;
RadioGroup1.Items.Add('A');
RadioGroup1.Items.Add('B');
RadioGroup1.Items.Add('C');
RadioGroup2.Items.Add('D');
RadioGroup2.Items.Add('E');
RadioGroup2.Items.Add('F');
RadioGroup3.Items.Add('G');
RadioGroup3.Items.Add('H');
RadioGroup3.Items.Add('I');
Adaptor := TDBRadioGroupAdaptor.Create;
Adaptor.Add(RadioGroup1);
Adaptor.Add(RadioGroup2);
Adaptor.Add(RadioGroup3);
Adaptor.DataSource := DataSource1;
Adaptor.FieldName := 'Value';
// Next, set up the CDS
ClientDataSet1.CreateDataSet;
ClientDataSet1.InsertRecord([1, 'AEI']);
ClientDataSet1.InsertRecord([2, 'BDG']);
ClientDataSet1.InsertRecord([3, 'ADG']);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
Adaptor.Free;
end;

How to make “serialize” and “unserialize” an array in delphi?

I need to do “serialize” and “unserialize” on delphi, like on php, so that the resulting array is then stored in the site database. Those. I need to get something like this kind.
a:10:{i:0;a:3:{i:0;s:4:"Obj1";i:1;i:383;i:2;i:339;}i:1;a:3:{i:0;s:4:"Obj2";i:1;i:339;i:2;i:18;}i:2;a:3:{i:0;s:4:"Obj3";i:1;i:386;i:2;i:98;}i:3;a:3:{i:0;s:4:"Obj4";i:1;i:428;i:2;i:286;}i:4;a:3:{i:0;s:4:"Obj5";i:1;i:54;i:2;i:47;}i:5;a:3:{i:0;s:4:"Obj6";i:1;i:328;i:2;i:27;}i:6;a:3:{i:0;s:4:"Obj7";i:1;i:332;i:2;i:371;}i:7;a:3:{i:0;s:4:"Obj8";i:1;i:42;i:2;i:187;}i:8;a:3:{i:0;s:4:"Obj9";i:1;i:314;i:2;i:68;}i:9;a:3:{i:0;s:5:"Obj10";i:1;i:124;i:2;i:120;}}
Here is my code adding an object in delphi:
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ExtCtrls, Contnrs, StdCtrls;
type
TForm1 = class(TForm)
Image1: TImage;
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TRect = class
private
p:TPoint;
width, height: Integer;
color : TColor;
str:String;
public
constructor Create(APt:TPoint;w1, h1: Integer; _Color:TColor;str1:String);
Procedure Draw(Cs:TCanvas;x:Integer;y:Integer);
end;
TRectList=class(TObjectList)
private
function GetItems(index:integer):TRect;
procedure SetItems(index:integer; Value:TRect);
public
property Items[index:integer]: TRect read GetItems write SetItems; default;
end;
var
Form1: TForm1;
ImPic : TBitmap;
RectList: TRectList;
rect : TRect;
start : TPoint;
ObjId:Integer;
implementation
{$R *.dfm}
constructor TRect.Create(APt:TPoint; w1, h1: Integer; _Color: TColor;str1:String);
begin
p:=APt;
width := w1;
height := h1;
color := _Color;
str:=str1;
end;
procedure TRect.Draw(Cs:TCanvas; x, y: Integer);
begin
Cs.Brush.Color:=color;
Cs.Rectangle(p.X,p.Y,p.X+width,p.Y+height);
end;
procedure TForm1.Button1Click(Sender: TObject);
var i,x,y:Integer;
begin
x := Random(500);
y := Random(400);
ObjId := ObjId+1;
start := Point(x, y);
rect := TRect.Create(start,20,20,clBlue,'Obj'+IntToStr(ObjId));
RectList.Add(rect);
Memo1.Lines.Clear;
Image1.Canvas.Brush.Color:=clWhite;
Image1.Canvas.FillRect(Image1.ClientRect);
Memo1.Lines.Add('[');
for i := 0 to RectList.Count-1 do
begin
RectList[i].Draw(Image1.Canvas,RectList[i].p.X,RectList[i].p.Y);
Memo1.Lines.Add('["'+RectList[i].str+'",'+IntToStr(RectList[i].p.X)+','+IntToStr(RectList[i].p.Y)+'],');
end;
Memo1.Lines.Add(']');
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
RectList := TRectList.Create(true);
randomize();
with Image1.Canvas do
begin
brush.Color:=clSilver;
Rectangle(0,0,width,height);
end;
end;
function TRectList.GetItems(index: integer):TRect;
begin
Result := inherited Items[index] as TRect;
end;
procedure TRectList.SetItems(index: integer; Value: TRect);
begin
inherited Items[index] := Value;
end;
end.
An array is generated in “memo”. Example in the picture:
img
Then you can add this array to the php file, apply serialize, and you get the array that is shown at the top of the post. Php code:
$array = [
["Obj1",383,339],
["Obj2",339,18],
["Obj3",386,98],
["Obj4",428,286],
["Obj5",54,47],
["Obj6",328,27],
["Obj7",332,371],
["Obj8",42,187],
["Obj9",314,68],
["Obj10",124,120],
]
;
$string = serialize( $array );
echo $string."<br>";
$array = unserialize($string);
echo "<pre>";
var_dump($array);
echo "</pre>";
?>
How to do the same thing on delphi “serialize” and “unserialize”, for subsequent storage in the mysql database on the site?

Cannot split word into individual letters in a memo

unit frmDisplaySentence_u;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls;
type
TfrmDispOneChar = class(TForm)
edtCode: TEdit;
btnDisplay: TButton;
lblMsg: TLabel;
memOutput: TMemo;
procedure btnDisplayClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
frmDispOneChar: TfrmDispOneChar;
implementation
{$R *.dfm}
procedure TfrmDispOneChar.btnDisplayClick(Sender: TObject);
var
K, iLength : integer;
cOne : char;
sCode : string;
begin
sCode := edtCode.Text;
iLength := Length(sCode);
for K := 1 to iLength do
cOne := sCode[K];
memOutput.Lines.Add(cOne);
end;
This is my code to split a word into it's individual letters in an memo, each in separate lines.
My code only returns the last letter of the word. I feel this is very close but I do not know how to proceed further. Any help would be appreciated.
Delphi is not like ie Python where whitespace is significant and defines block. In Delphi you have to use begin and end to mark the block, in this case your for loop:
procedure TfrmDispOneChar.btnDisplayClick(Sender: TObject);
var
K, iLength : integer;
cOne : char;
sCode : string;
begin
sCode := edtCode.Text;
iLength := Length(sCode);
for K := 1 to iLength do begin
cOne := sCode[K];
memOutput.Lines.Add(cOne);
end;
end;

Delphi stack overflow and access violation error when setting array (of records) length

I am busy building an application in which I am reading data from more two files of "records". I have a very strange error, which pops up depending on the sequence in which I open the files (see code below).
If I click button1 followed by button 2, thus calling the file of "weather data records" followed by the file of "parameters records", all is fine. If I do this the other way around, I get a "stack overflow" followed by "access violation at 0x7c90e898: write of address" error. This happens when I call SetLength for the array in Button1Click.
The weather data file has about 550 records, and the parameters file has about 45 records.
Can anyone see anything obvious wrong with my code? I am not sure how to attach files, or make them available, if anyone wants to use them to test...
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons, ExtCtrls, Grids,FileCtrl,Contnrs;
type
TWeatherData = record
MyDate : TDate;
Rainfall : Double;
Temperature : Double;
end;
TParameters = record
Species : string[50];
ParameterName: string[50];
ParameterValue : double;
end;
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
Var
WeatherDataFile : file of TWeatherData;
j : integer;
WeatherDataArray : array of TWeatherData;
MyFileSize : Integer;
begin
AssignFile(WeatherDataFile,'C:\Test5.cmbwthr') ;
Reset(WeatherDataFile);
MyFileSize := FileSize(WeatherDataFile);
SetLength(WeatherDataArray,MyFileSize);
j := 0;
try
while not Eof(WeatherDataFile) do begin
j := j + 1;
Read (WeatherDataFile, WeatherDataArray[j]) ;
end;
finally
CloseFile(WeatherDataFile) ;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
ParametersFile : file of TParameters;
j : integer;
CurrentParameters : array of TParameters;
MyFileSize : Integer;
begin
AssignFile(ParametersFile,'C:\Test5.cmbpara') ;
Reset(ParametersFile);
Reset(ParametersFile);
MyFileSize := FileSize(ParametersFile);
SetLength(CurrentParameters,MyFileSize);
j := 0;
try
while not Eof(ParametersFile) do begin
j := j + 1;
Read (ParametersFile, CurrentParameters[j]) ;
end;
finally
CloseFile(ParametersFile) ;
end;
end;
end.
You're writing past the ends of the arrays by incrementing the index before writing to the array instead of afterward. Since you're writing into memory that doesn't belong to the array, any number of problems may occur.
AssignFile(ParametersFile, 'C:\Test5.cmbpara');
Reset(ParametersFile);
try // Enter "try" block as soon as the file is opened.
MyFileSize := FileSize(ParametersFile);
SetLength(CurrentParameters, MyFileSize);
j := 0;
while not Eof(ParametersFile) do begin
Read(ParametersFile, CurrentParameters[j]);
Inc(j);
end;
finally
CloseFile(ParametersFile);
end;
if j <> MyFileSize then
raise Exception.CreateFmt('Parameter count mismatch: expected %d but got %d instead.',
[MyFileSize, j]);
You need packed records to save to a file.
type
TWeatherData = packed record
MyDate : TDate;
Rainfall : Double;
Temperature : Double;
end;
TParameters = packed record
Species : string[50];
ParameterName: string[50];
ParameterValue : double;
end;
Take a look at our TDynArray wrapper available in our SynCommons.pas unit. There is serialization feature included.
And you could put regular string inside the records, instead of shortstring: it will use less space on disk, and will be Unicode Ready since Delphi 2009.
type
TWeatherData = record
MyDate : TDate;
Rainfall : Double;
Temperature : Double;
end;
TWeatherDatas = array of TWeatherData;
TParameter = record
Species : string;
ParameterName: string;
ParameterValue : double;
end;
TParameters = array of TParameter;
var
Stream: TMemoryStream;
Params: TParameters;
Weather: TWeatherDatas;
begin
Stream := TMemoryStream.Create;
try
Stream.LoadFromFile('C:\Test5.cmbpara');
DynArray(TypeInfo(TParameters),Params).LoadFromStream(Stream));
Stream.LoadFromFile('C:\Test5.cmbwthr');
DynArray(TypeInfo(TWeatherDatas),Weather).LoadFromStream(Stream));
finally
Stream.Free;
end;
end;
With TDynArray, you can access any dynamic array using TList-like properties and methods, e.g. Count, Add, Insert, Delete, Clear, IndexOf, Find, Sort and some new methods like LoadFromStream, SaveToStream, LoadFrom and SaveTo which allow fast binary serialization of any dynamic array, even containing strings or records - a CreateOrderedIndex method is also available to create individual index according to the dynamic array content. You can also serialize the array content into JSON, if you wish.
For Delphi 6 up to XE.

Resources