First the question: Why does the removal of const in UnregisterNode() cause failure, but not in RegisterNode().
Now the background: I’m wor
The const directive on a parameter indicates that the procedure/function will not modify the value supplied in that parameter. If the procedure or function wishes to manipulate any const parameter it will first have to copy that value to a local variable.
This allows the compiler to perform some optimisations on such parameters, particularly in the area of reference types such as strings and interfaces etc.
With interfaces specifically, since the parameter is declared const it is impossible for the value of the interface reference passed to be modified during the "lifetime" of the parameter (since the compiler will reject any code that tries to modify the value), thus the compiler is able to eliminate the calls to AddRef() and Release() that would other wise be generated as prolog and epilog in that procedure.
Note however that within the body of the procedure if the reference is assigned to other variables then the reference count could still change. The const optimisation simply eliminates the possible need for one AddRef/Release pair.
This difference in reference counting behaviour between const and non-const parameters is obviously having some side effect or other interaction with the other complexities in your code but now understanding the effect of const you might be able to determine how/where you may have gone wrong elsewhere.
In fact, I can tell you where you have gone wrong. :)
You should never directly cast an interface reference to/from any other type (interface or pointer or otherwise) unless you are very VERY sure of what you are doing. You should always use as or QueryInterface() to cast from one interface type to another:
otherRef := fooRef as IOther;
And you should always use IUnknown (or IInterface) as an 'untyped' interface reference, not a pointer. This ensures that your references are all property accounted for. (there are times when you want an uncounted reference and thus would use a type-cast pointer reference, but that is very advanced voodoo).
In your sample code, the casting to/from pointer type to maintain them in a TList is subverting the reference counting mechanism and in conjunction with the variations in const/non-const parameters is leading to the side effects you are seeing.
To maintain properly counted references to interfaces in a list, use an interface friendly list class such as TList
Footnote:
Also beware: The destruction of an object when the interface reference count drops to zero is not necessarily quite as automatic as you think.
It is an implementation detail of the particular interfaced object class. If you inspect the source of the _Release() implementation on TInterfacedObject you will see how this is possible.
Simply put, the object itself is responsible for destroying itself when it's own reference count reaches zero. In fact, the object is even responsible for implementing the reference count in the first place! It is perfectly possible therefore (and sometimes desirable) for a specialised class to override or replace this behaviour in which case how it responds to a zero reference count (or indeed whether it even bothers to maintain a reference count as such) is entirely up to its own needs.
Having said that, the overwhelming majority of objects that implement interfaces will almost certainly use this form of auto-destruction, but it should not simply be assumed.
What should be safe to assume is that if you are given an interface reference to an object, you would not normally be concerned with how that object will ultimately be destroyed. But that is not the same as saying you can assume it will be destroyed when the interface reference count reaches zero.
I mention this because being aware of how all this apparent "compiler magic" works can be critical to understanding problems such as those you have run into in this case.