• Recent Tutorials

  • Tripping The Class Fantastic: Versioned Data Storage

    Supporting Principles

    Now we've got the general idea, lets get into some code. The first thing we need to address is the issue about knowing the parameters and returns types when we finally call our method.

    To handle this, we declare prototypes, just like we do for event handling.

    Code:
    type
      TMyDynamicProcedure = procedure(param1:integer;param2:string) of object;
      TMyDynamicFunction = function(param1:string):boolean of object;
    In this example, we have declared two prototypes. One procedure and one function. As far as I know, these declarations can use any of the standard rules that apply when defining procedures and functions (variable parameters, constant parameters etc.), but I haven't tried every possible permutation so you may find you need to tweak your code if a particular combination isn't acceptable to the compiler.

    Once we have our prototypes, we are pretty much set to start implementing the actual calls. There is just one more rule that we need to be aware of... for this to work, the methods you plan on calling in this way need to be declared as PUBLISHED. This is to ensure that the required information about the methods is available at runtime.

    So lets make a call. The only things we need are a couple of variables that will be used during the process and the prototypes of the methods you want to call.

    The variables...

    Code:
    var
       methodPtr     : pointer;
       method        : TMethod;
    The first (MethodPtr) is used to hold the result from the function that is used to locate the required method. The second (Method) is a base prototype equivalent to 'procedure of object'. It has two fields 'Data' and 'Code'. Code is used to provide the entry point for the required method (from MethodPtr) and Data provides a link to the object to which the method belongs. I've only ever been in a situation where Data is set to Self, so I can't comment any further on whether it works if you call methods in another object. However for the sake of clarity I have used a variable called 'targetObject' which we will for this article assume is set to 'Self'.

    Anyhow, lets get the rest of the code covered. It is essentially quite simple.

    The first thing we need to do is to try and locate the method we want to call. The name of this method is in the string variable 'methName'.

    Code:
    methodPtr:=targetObject.methodAddress(methName);
    If methodPtr is nil, we haven't found the method... the course of action you take here will obviously depend on the application. If methodPtr is not nil, then we've found a method and we can get on and run it like this...

    Code:
      method.data:=targetObject;
      method.code:=methodPtr;
    
      TMethodPrototype(method)(params);
    And that as they say is that. Functions will obviously require a variable to receive the result and the parameter lists will need to match the prototype declarations.

    So, lets put this to some real use... a basic object that can be used as the basis for a versioned data store.

    Comments 2 Comments
    1. Brainer's Avatar
      Brainer -
      Very nice one.

      Regarding TReader and TWriter - in the latest Delphi version you can write class helpers, so it's possible to expand the functionality of TStream and add your own methods for, let's say, storing and reading back a floating-point, while still having the possibility to store a stream in another one.
    1. AthenaOfDelphi's Avatar
      AthenaOfDelphi -
      Thanks Brainer