I wrote a set of components that link to each other via published interface properties. They are registered and installed in a design package.
Using published interface properties is not that common in Delphi, and thus, unsurprisingly, doesn't seem to work that well.
It works fine when components reside on the same form, however interface property links between components on different forms cause issues.
Unlike object links to components on another form, interface links don't seem to be recognized by IDE. What I mean is best described by an example, when you have 2 forms open in IDE, and have links between components on them, then trying to switch to form view as text (Alt+F12) would cause IDE to correctly complain that:
Module 'UnitXXX.pas' has open descendents or linked modules. Cannot close.
But if the property is an interface then this does not happen, what happens instead is that the link is severed (and that's the best case scenario when you use Notification mechanism to clear references, otherwise you're left with an invalid pointer)
Another problem, likely as a consequence of the same bug is that when you open a project in IDE, the order in which forms will be reopened is undefined, so IDE can try to open a form that contains components that have interface links to components on another form, but that other form is not recreated yet. So this effectively results in either AV or severed links.
Back in 90s while I used Datasets
and Datasources
I remember similar issues with links between forms disappearing, so this is somewhat similar.
As a temp workaround I added duplicate published properties, for each Interface property I added another that is declared as TComponent
. This makes Delphi aware there is a link between forms, but is an ugly workaround to say the least.
So I wonder if there is something I can do to fix this issue ? It's an IDE bug and likely not fixable directly, but perhaps I can override something or otherwise hook in to streaming mechanism to more effectively workaround this bug.
I haven't ever gone so deep into streaming mechanism, but I suspect the Fixup mechanism is supposed to deal with this. There is a csFixups
TComponentState
so I hope a workaround is possible.
Edit: Using D2007.
Update:
New updated reproducible example uploaded to http://www.filedropper.com/fixupbugproject2
Added property ComponentReference: TComponent
so that it's easy to compare and trace interface vs component streaming.
I narrowed the problem down to assembler level which is a bit out of my depth.
In procedure GlobalFixupReferences
in classes
unit it calls:
(GetOrdProp(FInstance, FPropInfo) <> 0)
which eventually executes:
function TInterfacedComponent.GetInterfaceReference: IInterface;
begin
// uncomment the code bellow to avoid exception
{ if (csLoading in ComponentState) and (FInterfaceReference = nil) then
// leave result unassigned to avoid exception
else
}
result := FInterfaceReference; // <----- Exception happens here
end;
As you can see from the comment, the only way I found to avoid the exception is to leave the result unassigned, but that breaks the functionality since comparison above in GlobalFixupReferences
fails due to GetOrdProp <> 0
, which severes the link.
tracing deeper the more exact location of exception is in
procedure _IntfCopy(var Dest: IInterface; const Source: IInterface);
in system
unit
This line in particular raises an read of address 0x80000000
{ Now we're into the less common cases. }
@@NilSource:
MOV ECX, [EAX] // get current value
So, why MOV
fails and what's wrong with ECX
or EAX
I have no idea.
Defining a default implementation for a property in an interface is rare because interfaces may not define instance data fields. In this example, the interface IEmployee has a read-write property, Name, and a read-only property, Counter. The class Employee implements the IEmployee interface and uses these two properties.
Interface properties typically don't have a body. The accessors indicate whether the property is read-write, read-only, or write-only. Unlike in classes and structs, declaring the accessors without a body doesn't declare an auto-implemented property. Beginning with C# 8.0, an interface may define a default implementation for members, ...
Beginning with C# 8.0, an interface may define a default implementation for members, including properties. Defining a default implementation for a property in an interface is rare because interfaces may not define instance data fields.
Interface Properties (C# Programming Guide) Properties can be declared on an interface. The following is an example of an interface property accessor: public interface ISampleInterface { // Property declaration: string Name { get; set; } } The accessor of an interface property does not have a body.
To summarize, the problem happens only with published interface properties that have a getter method, and the property points to component on another form/module (and that form/module is not recreated yet). In such case restoring form DFM causes an AV.
I'm pretty sure the bug is in the ASM code in GetOrdProp
, but it's beyond my ability to fix, so the
easiest workaround is to use a Field instead of a getter method and read it directly in the property. This is, fortunately good enough for my case currently.
Alternatively, you can declare the property as TComponent
instead of interface, then write a TComponentProperty
descendant, override ComponentMayBeSetTo
to filter component that don't support the required interface. And of course register it using RegisterPropertyEditor
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With