Friday, February 27, 2026

DFM migrator

Another part of the migration process from Delphi 7/Windows XP/ANSI to Delphi12CE/Windows11/Unicode is the conversion of the definition files (DFM) that each form has; such files contain definitions of the visual elements of a form. Most of the visual components remain as they were, but any database components, primarily queries, have to be converted. I had done a certain amount manually, but this is painstaking work, added to which certain extra definitions have to be changed.

So on Saturday morning I started work on a DFM migrator. At first, this required a simple substitution of TFDQuery for TSQLQuery, but then I discovered more definitions that needed to be changed. Once I had completed (as I thought) the code for TSQLQuery, I thought that I was done, but then I came across a form with the 'trinity' as I call it: a TSqlDataSource connected to a TDataSetProvider connected to a TClientDataSet. There are two major problems handling the trinity: firstly, the components can appear in any order, and secondly some of the required definitions (the SQL statement and the parameters) are in the TSqlDataSource whereas any field definitions (the titles which appear when a field is displayed) are in the TClientDataSet!

How to solve this? I had no idea of where to start, so I turned to my trusty CoPilot who suggested a two pass solution over the DFM file, saving certain data (where?) during the first pass then using the saved data to output the remaining data. This is how some if not most assemblers work as certain data, such as jumps, can only be calculated after the entire file has been read. CP suggested some convoluted data structures in which would be saved the data. By this time (Saturday lunch), I was developing a migraine, not because of the work but because of the changing weather, so I had a sandwich and went to lie down for a few hours.

But I couldn't turn my mind off, and shortly I came up with a much simpler solution to the one that CP suggested: I could build a symbol table in which I would store the component's name, its type and the line in the DFM where its definition starts. This way I wouldn't have to save the text of any component which was already saved in the file.

So when I felt a bit better, I started work on implementing this symbol table. One problem that I had was determining when a component's definition had finished; after all, none of these lines are being sent to the output file. This was easy for the TDataSetProvider as it has only four lines, one of which is the name of the TSqlDataSource, but the other two were more complicated. Eventually I solved this by noting that the penultimate line of both definitions begins with 'Top', so the migrator could 'eat the lines' until the 'Top' line, then read one more line (the 'end' that completes each definition).

Then after all the lines had been read, I could traverse the symbol table, looking for entries with the TClientDataSet type then building from the data the definitions for a TFDQuery component. I'm not 100% sure that I did this properly as there seemed to be all kinds of problems, and I was getting very tired whilst fixing them.

Last night, I revised the DFM migrator, specifically the code that handles TSQLQueries; one file that had ten queries prior to conversion had only one after conversion, so obviously there was some work that needed to be done there! I simplified certain aspects of this, especially the handling of parameters, and after very careful debugging, the TSQLQuery conversion code worked perfectly. Encouraged by this, I will revise the 'trinity conversion': starting anew with a fresh brain will no doubt improve the faulty code that I wrote when over-tired. The problem here appears to be not with my code but with the final 'end' that is written to the DFM before my code can output the built query, so that's an easy fix.

Today I finally fixed the 'trinity' code and I also added code to handle TSimpleDataSets - these should have been easy to fix but the required code was more tricky than I expected - the order of the different sections has to be changed. Now that the migrator is working properly, I probably converted about 30 forms today. Even so, there are some forms that I have to convert manually, as their DFM seems not to be in the correct format, and my migrator would only mangle them more, so much so that the compiler won't be able to read them.

Aside from the DFM migration, the actual program code needs massaging here and there. Last week I wrote1 about indexes; in the end, my original code that called a routine to build the indexes stayed the same, where the actual routine cannibalised the code that I had written for FireDAC. I checked this on the one completed form (Manage38) that I have so far that actually displays data. 

Another construction that requires rewriting - although not very much - involves ad hoc clientdatasets. I build these when data from several queries has to be combined. Thinking about it now, I could use the UNION operator in order to build one query from two unrelated tables, but this is more complicated (in one sense) than saving the results of two or three separate queries into a cliendataset. Original code would be like this

with qShowMailingList do
begin fielddefs.add (field1, ftString, 32, false); fielddefs.add (field2, ftString, 48, false); fielddefs.add (field3, ftString, 24, false); fielddefs.add (field4, ftInteger, 0, false); fielddefs.add (field5, ftString, 8, false); fielddefs.add (field6, ftInteger, 0, false); createdataset; open; for i:= 0 to 3 do begin j:= i * 2; addindex ('idx' + inttostr (j), fieldlist.strings[i], [], '', '', 0); addindex ('idx' + inttostr (j+1), fieldlist.strings[i], [ixDescending], '', '', 0); end; alist:= tstringlist.create; getindexnames (alist); alist.free; close; open; end;

The new code uses the TFDMemTable component instead of the ClientDataSet and looks like this

with qShowMailingList do begin fielddefs.add (field1, ftWideString, 32, false); fielddefs.add (field2, ftWideString, 48, false); fielddefs.add (field3, ftWideString, 24, false); fielddefs.add (field4, ftInteger, 0, false); fielddefs.add (field5, ftString, 8, false); fielddefs.add (field6, ftInteger, 0, false); createdataset; open; for i:= 0 to 3 do begin j:= i * 2; indexdefs.add ('idx' + inttostr (j), fieldlist.strings[i], []); indexdefs.add ('idx' + inttostr (j), fieldlist.strings[i], [ixDescending]); end; close; open; end;

Apart from the change in component, the code is almost the same, the differences being

  1. ftString is replaced by ftWideString, to ensure Unicode
  2. The index definitions are subtlely different and now simpler
  3. The 'getindexnames' hack is no longer required

Internal links
[1] 2079



This day in blog history:

Blog #Date TitleTags
33727/02/2011Michael PalinTV series, Prague, Poland, Michael Palin
45627/02/2012More spooksTV series, MI5
92827/02/2016Chicken breasts in tomato sauceCooking
101327/02/2017What are the benefits of ERP enhancement?ERP, DBA
120327/02/2019Pneumonia againHealth
147627/02/2022More musiciansObituary, King Crimson
158727/02/2023Bone conduction headphones/mp3 playerMP3, Headphones
172527/02/2024The Dublin murder squadKindle, Cormoran Strike, Pedal board, Police procedurals

No comments: