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
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:
VARCONSTANT
 MAX_OBJ: INT:=4;END_VARVAR
 Roller: ClassRoller;
 Pinch1, Pinch2: ClassPinch;
 Drive1, Drive2: ClassACDrive;
 AllObjects: ARRAY[0..MAX_OBJ] OFIAllObjClass:= [Roller, Pinch1, Pinch2, Drive1, Drive2]; // all object array workaround
 DriveObjects: ARRAY[0..MAX_OBJ] OFIDriveClass; //array to store drive objects for fast runtime (__queryinterface is slow)
 PinchObjects: ARRAY[0..MAX_OBJ] OFIPinchClass; //array to store pinch objects for fast runtime (__queryinterface is slow)END_VAR
...initcodesomewhere...
//learndriveobjectsObjIndex:=0;FORJ:=0TOMAX_OBJDO
  IF__QUERYINTERFACE(AllObjects[J], IDriveClass)THEN
    DriveObjects[ObjIndex]:=IDriveClass;
    ObjIndex:=ObjIndex+1;
  END_IFEND_FORTotalDriveObjects:=ObjIndex;
...maincode...
ForLoop:=0toTotalDriveObjectsDO
 DriveObjects[Loop].Home();END_FORetc
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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?
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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,....
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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):
//... mainobjectarrayalreadypopulated....
//... nowbuildarrayforIDriveobjects....
//learndriveobjectsObjIndex:=0;FORJ:=0TOMAX_OBJDO
  IF__QUERYINTERFACE(AllObjects[J], IDriveClass)THEN
   DriveObjects[ObjIndex]:=IDriveClass;
   ObjIndex:=ObjIndex+1;
  END_IFEND_FORTotalDriveObjects:=ObjIndex;//... nowbuildarrayforIPinchobjects....
ObjIndex:=0;FORJ:=0TOMAX_OBJDO
  IF__QUERYINTERFACE(AllObjects[J], IPinchClass)THEN
   PinchObjects[ObjIndex]:=IPinchClass;
   ObjIndex:=ObjIndex+1;
  END_IFEND_FORTotalPinchObjects:=ObjIndex;//... nowbuildarrayforIxxxxobjects....
//... 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!
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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...
INTERFACEIAllObjects//absolutebaseclass  METHODFb_init:BOOL  VAR_INPUT    bInitRetains:BOOL;//Initializationoftheretainvariables    bInCopyCode:BOOL;//Instancemovedintocopycode  END_VAR
GVL_OBJECTS  VAR_GLOBALCONSTANT    MAX_OBJECTS:INT:=10;  END_VAR  VAR_GLOBAL    CollectionAllObjects:CollectionHandlerAll;//maintainsarrayofallobjects    CollectionDrives:CollectionHandlerDrives;//maintainsarrayofdrives    CollectionConveyors:CollectionHandlerConveyors;//maintainsarrayofconveyors  END_VAR
...define FB to handle array collections...
FUNCTION_BLOCKCollectionHandlerAllVAR_INPUTEND_VARVAR_OUTPUT
  TotalObjects: INT; // total objects found - use for FOR/NEXT loop iterations of methodsEND_VARVAR
  Collection: ARRAY[1..GVL_OBJECTS.MAX_OBJECTS] OFIAllObjects; // object arrayEND_VAR
  METHODAddObject
  VAR_INPUT
    Obj  : IAllObjects;
  END_VAR
  TotalObjects:=TotalObjects+1;
  Collection[TotalObjects]:=Obj;
FUNCTION_BLOCKCollectionHandlerConveyorVAR_INPUTEND_VARVAR_OUTPUT
  TotalObjects: INT; // total objects found - use for FOR/NEXT loop iterations of methodsEND_VARVAR
  Collection: ARRAY[1..GVL_OBJECTS.MAX_OBJECTS] OFIConveyor; // object arrayEND_VAR
  METHODAddObject
  VAR_INPUT
    Obj  : IConveyor;
  END_VAR
  TotalObjects:=TotalObjects+1;
  Collection[TotalObjects]:=Obj;
FUNCTION_BLOCKCollectionHandlerDriveVAR_INPUTEND_VARVAR_OUTPUT
  TotalObjects: INT; // total objects found - use for FOR/NEXT loop iterations of methodsEND_VARVAR
  Collection: ARRAY[1..GVL_OBJECTS.MAX_OBJECTS] OFIDrive; // object arrayEND_VAR
  METHODAddObject
  VAR_INPUT
    Obj  : IDrive;
  END_VAR
  TotalObjects:=TotalObjects+1;
  Collection[TotalObjects]:=Obj;
...define device FBs...
FUNCTION_BLOCKClassConveyorIMPLEMENTSIConveyor//conveyorFBVAR_INPUTEND_VARVAR_OUTPUTEND_VARVAREND_VAR  METHODFb_init:BOOL  VAR_INPUT    bInitRetains:BOOL;//Initializationoftheretainvariables    bInCopyCode:BOOL;//Instancemovedintocopycode  END_VAR  GVL_OBJECTS.CollectionAllObjects.AddObject(THIS^);  //addinstancetoallobjectscollection  GVL_OBJECTS.CollectionConveyors.AddObject(THIS^);  //addinstancetoconveyorcollection
FUNCTION_BLOCKClassDriveIMPLEMENTSIDrive//driveFBVAR_INPUTEND_VARVAR_OUTPUTEND_VARVAREND_VAR  METHODFb_init:BOOL  VAR_INPUT    bInitRetains:BOOL;//Initializationoftheretainvariables    bInCopyCode:BOOL;//Instancemovedintocopycode  END_VAR  GVL_OBJECTS.CollectionAllObjects.AddObject(THIS^);  //addinstancetoallobjectscollection  GVL_OBJECTS.CollectionDrives.AddObject(THIS^);    //addinstancetodrivescollection
...now main program...
PROGRAMPLC_PRGVARÂ Â 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).
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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?
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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?
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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).
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
@Scott,
Would you be kind enough and post us an archived project?
Thanks in advance!
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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_BLOCKFINALClassFaultObjectsCollectionVAR
  Collection  : ARRAY[1..GVL_COLLECT.MAX_OBJECTS] OFIFault;
  ObjNames  : ARRAY[1..GVL_COLLECT.MAX_OBJECTS] OFSTRING;
  _ArrayOver  : BOOL;
  _BadObj    : INT;
  _DupeObj  : INT;
  _OverObj  : INT;
  _TotalObj  : INT;END_VAR(*Addobjecttothecollection.
-----------------------------------------------------------------------Callthismethodtoaddanobjecttothecollectionarray:
  AddObject(THIS^)Foundbelowistypicalcoderequired. 'Obj'mustbeconvertedfromanIAlltypetotheinterfacetypespecifictothecollectionyouareimplementing*)METHODFINALAddObjectVAR_INPUT
  (*object*)
  Obj  : IAll;END_VARVAR
  Cast: IFault;
  J  : INT;END_VARIF(__QUERYINTERFACE(Obj, Cast))THEN
  //makesurehaven't already added this object
  FORJ:=1TO_TotalObjDO
    IFCollection[J] =CastTHEN
      //objectpointermatches-dupe!
      _DupeObj:=_DupeObj+1;
      RETURN;
    END_IF
  END_FOR
 Â
  IF_TotalObj<GVL_COLLECT.MAX_OBJECTSTHEN
    _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_BLOCKWantToFaultIMPLEMENTSIFault...somecode...METHODFB_init:boolVAR_INPUT  bInitRetains:BOOL;//Initializationoftheretainvariables  bInCopyCode:BOOL; //Instancemovedintocopycode  FaultCollector:REFERENCETOClassFaultObjectsCollection;END_VARFaultCollector.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.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
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:
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?
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.
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,....
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):
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!
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...
...define global collection arrays...
...define FB to handle array collections...
...define device FBs...
...now main program...
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).
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?
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.
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?
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).
@Scott,
Would you be kind enough and post us an archived project?
Thanks in advance!
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):
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...):
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.