How to pass arrays around as parameters

jbeh
jbeh used Ask the Experts™
on

I have a record defined as

type
    TJobRecord = record
        AxJobNum: string;
{Lots cut}
    end;

I then have an array of these records

var
    JobRecordArray            : array of TJobrecord;


Further down I have the following to set the array length

    SetLength(jobrecordarray, ADOQyAxJobHeaders.RecordCount);
    OtherInitialProcessing (jobrecordarray);
    OtherInitialProcessing(jobrecordarray);
    OtherInitialProcessing(jobrecordarray);
    OtherInitialProcessing(jobrecordarray);

............and I can continue  - no problem so far

I am going to be running this

What I want to do is wrap this all up in a procedure of the form

Procedure InitialiseThings (<??????> : <????????????> ; qry : TQuery)
begin
{processing}
{processing}
{processing}
end;


My problem is that I cannot work out how to pass this array or any of the other dozens of arrays I'm going to need into the procedure.

I would rather avoid writing them all out longhand if I can avoid it.


Thanks


John











Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Hi,

First way:
----
TJobRecord = record
       AxJobNum: string;
   end;

var
  Form1: TForm1;
  JobRecordArray: array of TJobrecord;

implementation

{$R *.DFM}

procedure ShowArray(var TheArray: array of TJobrecord);
begin
  ShowMessage(TheArray[0].AxJobNum + TheArray[High(TheArray)].AxJobNum); // shows 'aaaccc'
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  SetLength(jobrecordarray,10);
  jobrecordarray[0].AxJobNum := 'aaa';
  jobrecordarray[1].AxJobNum := 'bbb';
  ...
  jobrecordarray[9].AxJobNum := 'ccc';
  ShowArray(jobrecordarray);
end;

Second way: Define a type which is array of record
----
TJobRecord = record
       AxJobNum: string;
   end;

   TJobRecordArray = array of TJobrecord;

var
  Form1: TForm1;
  JobRecordArray: TJobRecordArray;

implementation

{$R *.DFM}

procedure ShowArray(TheArray: TJobRecordArray);
begin
  ShowMessage(TheArray[0].AxJobNum + TheArray[High(TheArray)].AxJobNum); // shows 'aaaccc'
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  SetLength(jobrecordarray,10);
  jobrecordarray[0].AxJobNum := 'aaa';
  jobrecordarray[1].AxJobNum := 'bbb';
  jobrecordarray[9].AxJobNum := 'ccc';
  ShowArray(jobrecordarray);
end;

Regards, Geo
Perhaps the following is a better example:

TJobRecord = record
       AxJobNum: string;
   end;

   TJobRecordArray = array of TJobrecord;

var
  Form1: TForm1;
  JobRecordArray: TJobRecordArray;

implementation

{$R *.DFM}

procedure InitArray(TheArray: TJobRecordArray);
begin
  TheArray[0].AxJobNum := 'aaa';
  TheArray[1].AxJobNum := 'bbb';
  TheArray[9].AxJobNum := 'ccc';
end;

procedure ShowArray(TheArray: TJobRecordArray);
begin
  ShowMessage(TheArray[0].AxJobNum + TheArray[High(TheArray)].AxJobNum); // shows 'aaaccc'
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  SetLength(jobrecordarray,10);
  InitArray(jobrecordarray);
  ShowArray(jobrecordarray);
end;

Regards, Geo
Wim ten BrinkSelf-employed developer

Commented:
Of course Geobul means:

TYPE TJobRecord = record
      AxJobNum: string;
  end;
TYPE TJobRecordArray = array of TJobrecord;

And he is correct. It has to do with type incompatibilities. Every time you declare

var JobRecordArray = array of TJobrecord;

you create a new unnamed data type within your code. The same problem occurs when you create something like:

procedure InitArray(TheArray: array of TJobrecord);

So basically you created two different data types. And while they are the same for you, Delphi considers them as two incompatible data types.
Build an E-Commerce Site with Angular 5

Learn how to build an E-Commerce site with Angular 5, a JavaScript framework used by developers to build web, desktop, and mobile applications.

I've missed TYPE keyword. Thanks, Alex ;-)

Author

Commented:

Hello Geo  

I thought I'd have a go at your 2nd option

but I now also think I have probably explained myself badly in the first place


type
    TJobRecord = record
        AxJobNum: string;
    end;

AND  Lots of other types e.g.

Type TClientRecord = record
     Clientname : String;
  end;

etc etc etc



.............
.............

type
    TJobRecordArray            = array of TJobrecord;
    TClientRecordArray         = array of TClientRecord;

etc etc

.............
.............

var
    JobRecordArray            : array of TJobrecord;
    ClientRecordArray        : array of TClientrecord;

.............
.............


What I now want to do is

     InitialiseArray(ADOQyAxJobHeaders,JobRecordArray);
     InitialiseArray(somequery, clientRecordArray);
     InitialiseArray(someotherquery, someotherarray of records);
     InitialiseArray(someotherquery, someotherarray of records);


and I am trying to right the procedure Initialisearray  // bad name :-(

but still

Procedure InitialiseArray(qry : TADOQuery ; Var Thearray : ????????????) ;
Begin

ConfirmQueryOpen(qry);
qry.first
setlength(Thearray,qry.recordcount);

end;

I suspect what I am trying to do may not be possible but ..................








Interestingly enough



    procedure InitialiseThings(qry: TADOQuery;var TheArray :  TJobRecordArray);
    begin
        showmessage(inttostr(qry.RecordCount));
        SetLength(TheArray, qry.RecordCount);
    end;


     InitialiseArray(ADOQyAxJobHeaders,JobRecordArray);



comes up with an error --- actual and formal var parameters must be identical








Author

Commented:
There seems to be a bit of lag here but never mind

Alex ---  

Thanks for your input


Just to check


TYPE TJobRecordArray = array of TJobrecord;

var JobRecordArray = array of TJobrecord;

Delphi sees these as being DIFFERENT arrays -- Yes ?????



Commented:
Hi,

Consider also working with TList instead of dynamic arrays, I think it's faster too.

- Then you should declare that record as a class:

type
   TJobRecord = class
       AxJobNum: string;
      {whatever...}
   end;

- Create and destroy the TList object:

procedure TForm1.FormCreate(Sender: TObject);
begin
   List := TList.Create;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
   List.Free;
end;

- And use it like this:

procedure TForm1.DoSomething;
var JobRecord: TJobRecord;
begin
   JobRecord := TJobRecord.Create;
   JobRecord.AxJobNum := 999;
   JobRecord.DoWhateverYouWant;
   List.Add(JobRecord);
   ShowThem(List);
end;

procedure ShowThem(List: TList);
begin
   ...
end;

Hope you find this new approach useful.
Self-employed developer
Commented:
yes
Wim ten BrinkSelf-employed developer

Commented:
-> Garca

In my opinion, dynamic arrays have a lot more advantages than simple TLists, especially with the more recent Delphi versions. First of all, speed? I have tested it and don't see any relative speed differences between dynamic arrays and TLists.
Then, your suggestion also changes the data types. Records now become objects and objects need to be created and destroyed. With a record, the memory is already allocated and doesn't need any clean-up.
The use of a dynamic array is also a lot simpler than a TList. No type convertions, no invalid pointers, no need to create and destroy the list and cleaner code. (Besides, if you destroy the list, you first have to walk through it to free all objects it contains.)

While I used to prefer TList in the past too, since Delphi 6 I completely changed my mind and stopped using TLists altogether. Especially if you just want a list of data and no list of objects, the dynamic array is very, very useful. (But I do use it for arrays of records too!)

Commented:
Alex,

I agree with you, I just gave another approarch to solve the problem.

2 considerations:
- To avoid having to free the object in the list, use TObjectList instead of TList. (not so difficult, isn't it?)
- I have allways read about the lack of speed when using non-conventional arrays, so if this has changed since Delphi 6 I just didn't know...
Wim ten BrinkSelf-employed developer

Commented:
Garsa,

Consideration 1, Sure, you could use the TObjectList instead. And there are several othe TList-based classes that can also be used in lots of situations. The basis for a TList is an array with fixed dimensions (MaxInt/16 records) but it just doesn't allocate all this memory immediately. In the background, the TList just has to modify the amount of memory it needs to store all items. Sometimes even move things a bit around.

Now, Delphi handles dynamic arrays in the same way as long strings. So it uses reference counting and memory is dynamically allocated. However, in the past you could only do this with Variant arrays, based on the Variant datatype. But the variant array is indeed slow...
Borland introduced dynamic arrays as a faster alternative. And I get a very good performance when using dynamic arrays. I especially like dynamic arrays because you can pre-allocate the whole array in one time. The best use I had for dynamic arrays was to check an imput-file for duplicate values. The import files were often several hundreds of thousands of records and I measured the speed with and without the duplicate checking. The difference was just a few seconds! So I'm pretty convinced that dynamic arrays can be lightning fast. :-)
Back to the question: I don't know a way which will allow you to do so using arrays of records. I'm sorry.

Regards, Geo
Zlatin ZlatevSenior Technical Architect, Salesforce Commerce Cloud

Commented:
@jbeh, at the moment I do not have Delphi installed to play with, but what about array of variant (TVarRec)?

http://home.t-online.de/home/rvelthuis/articles/articles-openarr.htm

I am not at all sure that it will work - but give it a shot (if you have not already)

HTH
Zlatin

Author

Commented:
Thank you all

Wim ten BrinkSelf-employed developer

Commented:
Array of variant? The Variant is a bit slow datatype, so try to avoid it. Problem of variants is that Delphi has to check the VTable of the variant to find the address of the get/set method to read/write the values of the recordtype.
About the problem of an array with mixed record-types, you might have a problem. Of course, the simple solution could be to create an array of object. Another option might be the use of a variant record. The latter option would actually be a single record type, but it is a bit more flexible. Let me give an example:

type
  TVariantRecordType = (vrtJobNumber, vrtClientRecord, vrtJobHeader);
  TVariantRecord = record
    case VariantRecordType: TVariantRecordType of
      vrtJobNumber: (
        AxJobNum: shortstring
        );
      vrtClientRecord: (
        ClientNumber: Integer;
        ClientName: shortstring
        );
      vrtJobHeader: (
        HeaderID: Integer;
        Jobs: ^TDynamicArray;
        );
  end;

Unfortunately, variant records are a bit limited since they can't handle dynamic variables like long strings and dynamic arrays. But you don't always need these dynamic variables and if required, you might replace dynamic variables by pointers to dynamic variables like I did with Jobs in the vrtJobHeader. Compared to arrays of variants, a variant array is a lot faster in use. Besides, the VariantRecordType in above record tells you exactly what type of data the record contains.

If the variant record cannot be used, an array of object would be an alternative solution but then you have the same problem as with a TList. You will have to create/destroy these objects before/after usage. Personally, I dislike the use of object arrays since objects require a lot more internal memory than records. Above variant record will be about 264 bytes in size, mostly because the shortstring types, which requires 256 bytes. (But since it's a variant, the AxJobNum and ClientName will use the same memory space so if you assign a value to ClientName, the value in AxJobNum becomes something invalid.)

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial