How to set a record field as 'Procedure of object' before an object exists so that it can run

StackOverflow https://stackoverflow.com/questions/15253864

  •  18-03-2022
  •  | 
  •  

Question

Very un-snappy title I know.

I have a series of text lines that I need to perform certain operations on in a certain order. I have come up with a means of doing this by defining the following record structure:

TProcessOrderRecord = record
  RecordTypes:       TByteSet;
  InitialiseProcedure: TPreScanProc;
  ProcessProcedure:    TProcessRecord;
  FinaliseProcedure:   TEndScanProc;
end;

AProcessOrderArray = array of TProcessOrderRecord;

Initialise tends to call a constructor which will fill a field in the host object.

Process will be a procedure on the object which will be called for each text line that matches one of the record types in RecordTypes.

Finalise will tend to call the destructor and possibly do any checks when it knows that the full set of records has been processed.

The means of processing this array is quite straightforward:

procedure TImport.ScanTransferFile;
var
  i: integer;
  lArrayToProcess: AProcessOrderArray;
begin
  lArrayToProcess := SetUpProcessingOrder(NLPGApp.ImportType);
  for i := low(lArrayToProcess) to high(lArrayToProcess) do
  begin
    ProcessRecordType(lArrayToProcess[i].RecordTypes,     lArrayToProcess[i].InitialiseProcedure, lArrayToProcess[i].ProcessProcedure, lArrayToProcess[i].FinaliseProcedure);
  end;
end;

procedure TImport.ProcessRecordType(const RecordTypesToFind: TByteSet; PreScanProcedure: TPreScanProc; OnFindRecord: TProcessRecord; OnCompleteScan: TEndScanProc);
var
  lLineOfText: string;
  lIntegerRecordID: byte;
begin
  if Assigned(PreScanProcedure) then PreScanProcedure;
  try
    if assigned(OnFindRecord) then
    begin
      Reader.GoToStartOfFile;
      while not Reader.EndOfFile do
      begin
        lLineOfText := Reader.ReadLine;
        lIntegerRecordID := StrToIntDef(GetRecordID(lLineOfText), 0);
        if lIntegerRecordID in RecordTypesToFind then
        begin
          try
            OnFindRecord(lLineOfText);
          except
            on E: MyAppException do
            begin
            // either raise to exit or log and carry on
            end;
          end;
        end;
      end;
    end;
  finally
    // OnCompleteScan usually contains calls to destructors, so ensure it's called
    if Assigned(OnCompleteScan) then OnCompleteScan;
  end;
end;

My problem is that I want to define a record as such:

RecordTypes = [10]
InitialiseProcedure = ProcToCreateFMyObj
ProcessProcedure = FMyObj.do
FinaliseProcedure = ProcToFreeFMyObj

This compiles fine, however when ProcessProcedure is called, as FMyObj was nil when the ProcessProcedure is set, the instance of TMyObj is nil even though FMyObj is now set. Is there any clean way to get the record to point to the instance of FMyObj at the time of calling rather than at the time of first assignment?

At present I have resorted to having 'caller' methods on the host object which can then call the FMyObj instance when needed, but this is creating quite a bloated object with lots of single-line methods.

Edit to clarify/complicate the problem

Sometimes one instance of FObj can handle more than one types of record (usually if they have a master-detail relationship). In this case, InitialiseProcedure of the first record type will create FObj, FinaliseProcedure of the second record will free FObj and each record's ProcessProcedure can reference different procedures of FObj (do1 and do2).

Was it helpful?

Solution

At present I have resorted to having 'caller' methods on the host object which can then call the FMyObj instance when needed, but this is creating quite a bloated object with lots of single-line methods.

That is the right solution. Since the instance is not available at the point of initialisation you have no alternative.

When you use of object you are defining something called a method pointer. When you assign to a variable of method pointer type, the instance is captured at the point of assignment. There is no mechanism for the instance associated with a method pointer to be dynamically resolved. The only way to achieve that is to use runtime delegation, which is what you are currently doing. As is so often the case, another layer of indirection is used to solve a problem!

Your record that contains a number of methods looks awfully like an interface. I suspect that the most elegant solution will involve an interface. Perhaps at the point of calling you can call a function that returns an interface. And that function will using the value of FMyObj at the time of calling to locate the appropriate interface.

OTHER TIPS

Yes it is possible to make additional runtime initialization of your record:

var
  A: TProcessOrderRecord;

begin
  ..
  TMethod(A.ProcessProcedure).Data:= FMyObj;
  ..
end;

though I would prefer a different solution, like the one you already use.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top