Welcome to our new forum
All users of the legacy CODESYS Forums, please create a new account at account.codesys.com. But make sure to use the same E-Mail address as in the old Forum. Then your posts will be matched. Close

OOP: Programmatical finding all objects for __QUERYINTERFACE

Anonymous
2014-10-07
2017-06-07
  • Anonymous - 2014-10-07

    Originally created by: scott_cunningham

    I have a project that will have hundreds of objects (FBs) implementing different interfaces. I am using __QUERYINTERFACE and looping through the objects to call certain methods. Since I didn't find a way to automatically learn all objects and I don't want to rely on remembering to manually assign drive objects to the drive array and pinch objects to the pinch array, etc... I created an AllObjects array and learn from this array. But! I really do not want to MANUALLY add to this array every time I create a new object - it will be too easy to miss one.

    Does anyone know a method to step through var memory and look for objects meeting IDriveClass, IPinchClass, etc?

    Simplified and in small scale, example code:

    VAR CONSTANT
      MAX_OBJ: INT:= 4;
    END_VAR
    VAR
      Roller: ClassRoller;
      Pinch1, Pinch2: ClassPinch;
      Drive1, Drive2: ClassACDrive;
      AllObjects: ARRAY[0..MAX_OBJ] OF IAllObjClass:= [Roller, Pinch1, Pinch2, Drive1, Drive2]; // all object array workaround
      DriveObjects: ARRAY[0..MAX_OBJ] OF IDriveClass;  //array to store drive objects for fast runtime (__queryinterface is slow)
      PinchObjects: ARRAY[0..MAX_OBJ] OF IPinchClass; //array to store pinch objects for fast runtime (__queryinterface is slow)
    END_VAR
    ...init code somewhere...
    // learn drive objects
    ObjIndex:= 0;
    FOR J:= 0 TO MAX_OBJ DO
       IF __QUERYINTERFACE(AllObjects[J], IDriveClass) THEN
          DriveObjects[ObjIndex]:= IDriveClass;
          ObjIndex:= ObjIndex + 1;
       END_IF
    END_FOR
    TotalDriveObjects:= ObjIndex;
    ...main code...
    For Loop:= 0 to TotalDriveObjects DO
      DriveObjects[Loop].Home();
    END_FOR
    etc
    
     
  • singleton - 2014-10-08

    Why do you use a generic interface IAllObjClass when you have to handle the content separetly?

    What is the benefit instead of using specific interfaces for each type?

     
  • Anonymous - 2014-10-08

    Originally created by: scott_cunningham

    There will be quite a number of interfaces - at least 20 (I left all of these out of my sample code) and a large number of objects (many hundreds). To avoid using __QUERYINTERFACE during the actual machine operation (too slow doing this with nearly 1000 objects - results in about 2mS of execution time), I must build ahead of time, specific lookup arrays for each interface. Therefore, I create one single large array of all objects, and then build individual arrays from there (FOR loops for each interface compare against the whole list).

    Additionally, by having IAllObjClass extend __System.IQueryInterface and requiring all objects to extend IAllObjClass, __QUERYINTERFACE is working.

    There is also the possibility that there will be some methods/properties that all objects may have to implement.

     
  • singleton - 2014-10-09

    scott_cunningham hat geschrieben:
    There is also the possibility that there will be some methods/properties that all objects may have to implement.

    In this case I understand the generic interface, but I do not know a good solution for your problem.

    The only thing I could imagine is to have a range inside the array for each specific type, e.g. 1-10 are drives, 11-20 the next type,....

     
  • Anonymous - 2014-10-09

    Originally created by: scott_cunningham

    If I put all of the objects into one large array, then it is quite easy to progmatically generate the smaller, specific arrays for each interface (some objects will belong to multiple interfaces due to their functionality):

    //... main object array already populated....
    //... now build array for IDrive objects....
    // learn drive objects
    ObjIndex:= 0;
    FOR J:= 0 TO MAX_OBJ DO
       IF __QUERYINTERFACE(AllObjects[J], IDriveClass) THEN
          DriveObjects[ObjIndex]:= IDriveClass;
          ObjIndex:= ObjIndex + 1;
       END_IF
    END_FOR
    TotalDriveObjects:= ObjIndex;
    //... now build array for IPinch objects....
    ObjIndex:= 0;
    FOR J:= 0 TO MAX_OBJ DO
       IF __QUERYINTERFACE(AllObjects[J], IPinchClass) THEN
          PinchObjects[ObjIndex]:= IPinchClass;
          ObjIndex:= ObjIndex + 1;
       END_IF
    END_FOR
    TotalPinchObjects:= ObjIndex;
    //... now build array for Ixxxx objects....
    //... etc
    

    This, of course, takes some time, but no problem at machine power on. Just trying to avoid manually building the main array - but so far, it looks like I will have too!

     
  • Anonymous - 2014-10-14

    Originally created by: scott_cunningham

    Posting solution for those who can use it. Solution requires usage of FB_init() for each Class type. In this way, the arrays of objects are already filled at program start:

    ...define some interfaces...

    INTERFACE IAllObjects// absolute base class
       METHOD Fb_init : BOOL
       VAR_INPUT
          bInitRetains : BOOL; // Initialization of the retain variables
          bInCopyCode : BOOL; // Instance moved into copy code
       END_VAR
    
    INTERFACE IConveyor EXTENDS IAllObjects // interface for conveyors
    
    INTERFACE IDrive EXTENDS IAllObjects // interface for drives
    

    ...define global collection arrays...

    GVL_OBJECTS
       VAR_GLOBAL CONSTANT
          MAX_OBJECTS: INT:= 10;
       END_VAR
       VAR_GLOBAL
          CollectionAllObjects: CollectionHandlerAll; // maintains array of all objects
          CollectionDrives: CollectionHandlerDrives; // maintains array of drives
          CollectionConveyors: CollectionHandlerConveyors; // maintains array of conveyors
       END_VAR
    

    ...define FB to handle array collections...

    FUNCTION_BLOCK CollectionHandlerAll
    VAR_INPUT
    END_VAR
    VAR_OUTPUT
       TotalObjects: INT; // total objects found - use for FOR/NEXT loop iterations of methods
    END_VAR
    VAR
       Collection: ARRAY[1..GVL_OBJECTS.MAX_OBJECTS] OF IAllObjects; // object array
    END_VAR
       METHOD AddObject
       VAR_INPUT
          Obj   : IAllObjects;
       END_VAR
       TotalObjects:= TotalObjects + 1;
       Collection[TotalObjects]:= Obj;
    
    FUNCTION_BLOCK CollectionHandlerConveyor
    VAR_INPUT
    END_VAR
    VAR_OUTPUT
       TotalObjects: INT; // total objects found - use for FOR/NEXT loop iterations of methods
    END_VAR
    VAR
       Collection: ARRAY[1..GVL_OBJECTS.MAX_OBJECTS] OF IConveyor; // object array
    END_VAR
       METHOD AddObject
       VAR_INPUT
          Obj   : IConveyor;
       END_VAR
       TotalObjects:= TotalObjects + 1;
       Collection[TotalObjects]:= Obj;
    
    FUNCTION_BLOCK CollectionHandlerDrive
    VAR_INPUT
    END_VAR
    VAR_OUTPUT
       TotalObjects: INT; // total objects found - use for FOR/NEXT loop iterations of methods
    END_VAR
    VAR
       Collection: ARRAY[1..GVL_OBJECTS.MAX_OBJECTS] OF IDrive; // object array
    END_VAR
       METHOD AddObject
       VAR_INPUT
          Obj   : IDrive;
       END_VAR
       TotalObjects:= TotalObjects + 1;
       Collection[TotalObjects]:= Obj;
    

    ...define device FBs...

    FUNCTION_BLOCK ClassConveyor IMPLEMENTS IConveyor // conveyor FB
    VAR_INPUT
    END_VAR
    VAR_OUTPUT
    END_VAR
    VAR
    END_VAR
       METHOD Fb_init : BOOL
       VAR_INPUT
          bInitRetains : BOOL; // Initialization of the retain variables
          bInCopyCode : BOOL; // Instance moved into copy code
       END_VAR
       GVL_OBJECTS.CollectionAllObjects.AddObject(THIS^);   // add instance to all objects collection
       GVL_OBJECTS.CollectionConveyors.AddObject(THIS^);   // add instance to conveyor collection
    
    FUNCTION_BLOCK ClassDrive IMPLEMENTS IDrive // drive FB
    VAR_INPUT
    END_VAR
    VAR_OUTPUT
    END_VAR
    VAR
    END_VAR
       METHOD Fb_init : BOOL
       VAR_INPUT
          bInitRetains : BOOL; // Initialization of the retain variables
          bInCopyCode : BOOL; // Instance moved into copy code
       END_VAR
       GVL_OBJECTS.CollectionAllObjects.AddObject(THIS^);   // add instance to all objects collection
       GVL_OBJECTS.CollectionDrives.AddObject(THIS^);      // add instance to drives collection
    

    ...now main program...

    PROGRAM PLC_PRG
    VAR
       Drive1, Drive2: ClassDrive;
       Conv1, Conv2: ClassConveyor;
    END_VAR
    

    On program start, arrays for all objects, conveyors and drives are automatically built. You must manually code into the ClassXXX.FB_init() which collection arrays you want to have the object, but this is logical. Now it is easy to handle collection method calls as needed. If each drive has a Home() method, a simple FOR loop through the drives collection will call all drives. If all objects have a Reset() method, then a FOR loop through the all objects collection will call all objects. If I add another Conveyor (Conv3) or drive (Drive3) - no change is necessary - automatic object inclusion at project start. All without the slow __QUERYINTERFACE() call during runtime (penalty is RAM usage for holding arrays).

     
  • Kim - 2017-05-09

    Scott, it's been some years but I would like to know how you handle online changes.
    As I understand it, FB_init only executes after a cold reset.
    Doesn't this mean your arrays can be corrupted by online changes? If so, could you make use of fb_exit and fb_reinit somehow?

     
  • Anonymous - 2017-05-10

    Originally created by: scott_cunningham

    Online change has not been a problem when changing the logic. Adding or removing objects is a different story. I never use online change when I add or remove variables/objects.

     
  • Kim - 2017-05-10

    Thanks Scott, I do have one more question because I noticed something strange.

    If I declare the InterfaceHandler in one GVL and Drive1in another GVL, it doesn't work.
    The program runs but it's like the InterfaceHandler code never executes.

    If they are put in the same GVL, it works.
    It also works if Drive1 is declared in a POU (as in your example code).

    I can only assume that this has something to do with the order that Codesys creates the variables and/or initialises them internally.
    Do you have any insight to this matter?

     
  • josepmariarams - 2017-05-11

    I have an ObjectPool too, You can add {attribute 'global_init_slot' := '49995'} at program or GVL init, it ensures that the intialization of the element which has this attribute will be instantiated before all others (The default init slot is 50000).

     
  • hermsen

    hermsen - 2017-05-18

    @Scott,
    Would you be kind enough and post us an archived project?

    Thanks in advance!

     
  • Anonymous - 2017-06-07

    Originally created by: scott_cunningham

    Sorry, the project code is not allowed to be shared. Here is finally how I used the __QUERYINTERFACE to add a qualified object (only showing collection specific info):

    FUNCTION_BLOCK FINAL ClassFaultObjectsCollection
    VAR
       Collection   : ARRAY[1..GVL_COLLECT.MAX_OBJECTS] OF IFault;
       ObjNames   : ARRAY[1..GVL_COLLECT.MAX_OBJECTS] OF STRING;
       _ArrayOver   : BOOL;
       _BadObj      : INT;
       _DupeObj   : INT;
       _OverObj   : INT;
       _TotalObj   : INT;
    END_VAR
    (* Add object to the collection.
    -----------------------------------------------------------------------
    Call this method to add an object to the collection array:
       AddObject(THIS^)
    Found below is typical code required.  'Obj' must be converted from an
    IAll type to the interface type specific to the collection you are
    implementing
    *)
    METHOD FINAL AddObject
    VAR_INPUT
       (* object*)
       Obj   : IAll;
    END_VAR
    VAR
       Cast: IFault;
       J   : INT;
    END_VAR
    IF (__QUERYINTERFACE(Obj, Cast)) THEN
       // make sure haven't already added this object
       FOR J:= 1 TO _TotalObj DO
          IF Collection[J] = Cast THEN
             // object pointer matches - dupe!
             _DupeObj:= _DupeObj + 1;
             RETURN;
          END_IF
       END_FOR
       
       IF _TotalObj < GVL_COLLECT.MAX_OBJECTS THEN
          _TotalObj:= _TotalObj + 1;
          Collection[_TotalObj]:= Cast;
          ObjNames[_TotalObj]:= Obj.Instance;
       ELSE
          _ArrayOver:= TRUE;
          _OverObj:= _OverObj + 1;
       END_IF
       
    ELSE
       _BadObj:= _BadObj + 1;
    END_IF
    

    So if you have a FB that should be added to the collection, it can do it in the FB_INIT of itself (but it must implement the matching interface - or it's a bad object...):

    FUNCTION_BLOCK WantToFault IMPLEMENTS IFault
    ...
    some code
    ...
    METHOD FB_init : bool
    VAR_INPUT
       bInitRetains : BOOL; // Initialization of the retain variables
       bInCopyCode : BOOL;  // Instance moved into copy code
       FaultCollector : REFERENCE TO ClassFaultObjectsCollection;
    END_VAR
    FaultCollector.AddObject(THIS^);
    END_FUNCTION_BLOCK
    

    On instantiation, the instance of WantToFault will call the collector's AddObject() method and pass itself to it. The collector will verify it should be an IFault member, and add's it, if necessary.

     

Log in to post a comment.