44

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.

Roland Bengtsson
  • 5,058
  • 9
  • 58
  • 99
  • 5
    This is an interesting question. It feels a bit beyond my personal experience. I suspect if you had a demo project that would help any budding investigators. – David Heffernan Apr 26 '13 at 21:50
  • 1
    @DavidHeffernan It's fairly easy to reproduce, all you need is a TComponent descendant with a published property of IInterface type. Register it, install package and drop one on each of two blank forms. Having said that, I could do that myself and let you guys play around with it... –  Apr 26 '13 at 21:55
  • 1
    I think that would help if you did that. Lower the barriers for us. – David Heffernan Apr 26 '13 at 22:00
  • Which version(s) of Delphi are you using? Might be important for any solution / work around? – Marjan Venema Apr 27 '13 at 10:12
  • 1
    Apparently `GetOrdProp` doesn't like properties with a getter function. If you change `property InterfaceReference ... read GetInterfaceReference` to `property InterfaceReference ... read FInterfaceReference` the AV goes away. – Jens Mühlenhoff May 02 '13 at 10:01
  • @JensMühlenhoff Yes, the problem seems to be in GetOrdProp, for interface properties with a getter the ASM code is wrong, maybe it doesn't set up registers correctly before calling the getter, so accessing/setting result in the getter causes an AV. –  May 02 '13 at 10:57
  • @DanielMaurić Maybe the streaming system shouldn't call GetOrdProp in the first place, but my knowledge on that area is too limited. – Jens Mühlenhoff May 02 '13 at 11:56
  • @JensMühlenhoff GetOrdProp works with a field so I'd assume it's correct. Intuition tells me the problem is that whatever mem loc/register is used for result of the getter isn't cleared before calling, so setting the result attemps to decrease refcount on a random mem location. No proof, just guessing.. –  May 02 '13 at 18:21
  • See http://stackoverflow.com/questions/6278381/delphi-rtti-for-interfaces-in-a-generic-context -- this may offer some related insight. – Thomas W May 03 '13 at 02:34

1 Answers1

2

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

  • Is there a QC entry for this? – David Feb 12 '14 at 16:41
  • 1
    That only perpetuates the problem. You can't complain it's not fixed if they don't know about it. You can complain if they do :) (Not that I'm accusing you of complaining, but you get what I mean.) – David Feb 12 '14 at 20:41