Adding and removing a class instance to/from the rowTag of a Listbox

Given this code

// we list the files in a listbox
dim theCode() As BasicCode = Basic1.Codes

for each item as FolderItem in Basic1.files
  FIleList.AddFolder(item.name)

// simultaneously add the code from the file to the tag
  dim row as integer = FIleList.LastIndex
  FIleList.RowTag(row) = theCode(row)
next

Fine, and note that BasicCode is a class instance. The removal of folderItems and tags by

   FIleList.DeleteAllRows

or

for i as integer = FileList.ListCount -1  downto 0
  FileList.RowTag(i) = nil
next

or

for i as integer = FileList.ListCount -1  downto 0
  FileList.RemoveRow i
next

does not get the destructor of the BasicCode class called. This means it’s leaking objects. The BasicCodes and FolderItems both reside in arrays. When the arrays are going out of scope (or being unlocked from the plugin), a subsequent call to depopulate the listbox does not release the objects. If the code that populates the Listbox is removed, the folder items and BasicCodes are released. Am bringing it up here to figure if this is already known, or not.

Did Basic1 go away too? If not, those objects still live in its array.

Does each code refer back to Basic1 or some other circular reference?

I very much doubt that your case is related to ListBox.RowTag. As it is not clear from your description what the scope of the arrays are, it is not possible to say more. Do you have a stripped-down version you could upload for us to look at?

I do have a simple project that does not depend on any plugin. It does show the correct behavior. The project that depends on the plugin spitting out arrays has an incorrect behavior. Therefore this issue belongs to the plugin list. But since I am here, I am providing info and it relates to how one should return an array from the plugin to Xojo. Here is the code:

REALarray BassClass_Files(REALobject instance)
{
    ClassData(BasClass, instance, BasData, data);
    REALLockObject((REALobject)data->files);
    return data->files;
}

REALarray BassClass_Codes(REALobject instance)
{
    ClassData(BasClass, instance, BasData, data);
    REALLockObject((REALobject)data->codes);
    return data->files;
}

This code is called when the items are assigned to the Listbox. The Listbox sets a lock on each item. The array also had set a lock on each item. The usual way for a single object is to return it to the user after locking it, the general rule. And therefore by not thinking it over, you would lock the array too when giving it to the user, The notion that the Listbox is doing the right thing led to commenting out the locking of the object-array. Thus:

REALarray BassClass_Files(REALobject instance)
{
    ClassData(BasClass, instance, BasData, data);
    //REALLockObject((REALobject)data->files);
    return data->files;
}

REALarray BassClass_Codes(REALobject instance)
{
    ClassData(BasClass, instance, BasData, data);
    //REALLockObject((REALobject)data->codes);
    return data->files;
}

Apparently, you shouldn’t lock the array when exposed to the user. I just wonder whether this is truly fail-safe.

If I remember correctly you need to use REALLockObject for arrays too unless the array has been created with REALCreateArray, then it is already locked.

  1. If a plugin creates a REALobject and provides this REALobject in a getter then you need to lock this object. If you don’t, and the user uses this object in local scope, the REALobject gets unlocked beyond the local scope.
  2. The plugin generates a number of objects and puts it directly in a REALarray created by REALCreateArray. Then at each insertion into the array, the REALobject is unlocked because the insertion into the array gives the REALobject a lock.
  3. You would think that this for a REALarray, not giving it a lock in a getter would suffer the same fate as pointed out in 1 for a REALobject.
  4. The REALarray is only used in local scope, while the objects in this array are assigned to the Listbox , which locks each object. Out of the local scope the REALarray should have been unlocked. My guess is that the locks of the REALobjects to the ListBox prevents the unlocking of the REALarray when it goes out of scope.

The pure sample project, mimicking the plugin, does not give you this hypothetical insight, but it proved that the Listbox was not at fault.

  1. Correct. Any object you return must be locked. Strings, Objects, Arrays, Windows, etc.
  2. Append/Insert of REALarray will increase lock. So you may need to unlock the object you added in case you just created/locked it.
  3. if you miss to lock, the REALArray is freed too early which can lead to crashes. Windows crashes faster, Mac delays it often.
  4. Objects in array or listbox do not prevent array to unlock.

I bet you have somewhere reference to Basic1 which keeps reference to array, so array is not destroyed.

Correct, that’s what is being done, as I said earlier.

The whole reasoning behind it to lock an array in a getter.

Nope, that was the first thing that was ruled out. You lost your bet. :wink: The initial post states that if the listbox is not populated, a subsequent call to unlock the array in the plugin is releasing the objects. That already rules out a hidden lock. In addition, calling a double unlock after the listbox is populated does not lead to the objects being destroyed too (since their was only one lock). The only way is to not provide locks on an array in a getter and your and mine point 3 in your post is thus moot when it comes to creating an array from the plugin. It also does not lead to crashes.

Does BasicCode have a reference back to Basic1?

Nope.

Maybe check again all locks/unlocks?

[code]typedef struct
{
public:
void *vt;
void *cd;
void *et;
int refCount;
} REALobjectDef;

RBInteger GetObjectLockCountMBS (REALobject obj)
{
if (obj)
{
REALobjectDef d = (REALobjectDef) obj;
return d->refCount-1;
}

return 0;

}[/code]

maybe useful for debugging

Have been doing this 24/7 over the weekend. If you have a plugin that spits out an array, I would suggest to do what I have done. My bet is that you will encounter this too.

Maybe use the code about to output lock count for all relevant objects in console from time to time, so you can see when it goes up and down?

static void _initialize_files_and_codes(REALobject instance)
{
    ClassData(BasClass, instance, BasData, data);
    ScriptPluginLog(cc"in _initialize_files_and_codes because of a reset\
");
    if (!data->didResolveEvents)
        ResolveBasEvents(instance);
    if (data->_resetImportDir)
        data->_resetImportDir(instance);
    
    ScriptPluginLog(cc"LockCount files before = %d\
", Locks((REALobject)data->files));
    REALUnlockObject((REALobject)data->files);
    ScriptPluginLog(cc"LockCount files after = %d\
", Locks((REALobject)data->files));

    data->files = REALCreateArray(kTypeObject, -1);
    ScriptPluginLog(cc"LockCount new empty array files = %d\
", Locks((REALobject)data->files));
    data->fileCount = 0;

    ScriptPluginLog(cc"LockCount codes before = %d\
", Locks((REALobject)data->codes));
    REALUnlockObject((REALobject)data->codes);
    ScriptPluginLog(cc"LockCount codes after = %d\
", Locks((REALobject)data->codes));

    data->codes = REALCreateArray(kTypeObject, -1);
    ScriptPluginLog(cc"LockCount new empty array codes = %d\
", Locks((REALobject)data->codes));
}

I have a similar method to interrogate the locks on an object. If the getters lock the array then
“before” spits out 1;
“after” spits out 0;
“empty” spits out 1;
The “DeleteAllRows” by the Listbox does not free the objects.
If the getters do not lock the arrays then
“before” spits out 0;
“after” spits out 0;
“empty” spits out 1;
The “DeleteAllRows” by the Listbox frees the objects.

Actually, the problem can be reduced to the following:
It the getter locks the array in the plugin and in Xojo a local variable is assigned to it like:

dim theCode() As BasicCode = Basic1.Codes

and nothing is further done with this array, the array goes out of scope and gets unlocked (value = 0). Yet the items in the array have all a lock count of 1. This then explains that there is no equivalent that can be written in pure Xojo.

Further, an array of FolderItems created parallel to the creation of BasicCode items and is called by

dim theFile() As FolderItem = Basic1.Files

while nothing is being done with, goes out of scope, the array is unlocked (value = 0) and all the items have a lock count of 0 and get released.

After exhausting everything, it appears that you never should issue a REALLockObject in a getter or setter in the context of a REALarray, while a REALUnlockObject on a REALarray in a setter (to remove the existing array and replace it with a new one) is a requirement.

This is because a pluginarray accessed in local scope is not being unlocked beyond this scope. This plugin array can be an array of folderItems too.

This has not been documented as far as I can tell, but noting the above and avoiding locking of an array makes the plugin very stable, no leaks, proper disposal of items of the array. So I guess, it is intentional behavior.