Tuesday, August 29, 2006

Interfaces in Delphi / Win32

Craig Stuntz comments on my remarks about interfaces in Delphi / Win32:
Regarding interfaces, I'd argue that Delphi Win32 interfaces are more similar to .NET interfaces than COM interfaces are to COM+ 2.0 (a.k.a. .NET) interfaces insofar as they don't completely dictate lifetime management. In Delphi Win32 I can (and frequently do) override reference counting and use interfaces to provide multiple interface inheritance of classes without reference-counted lifetime management of instances.
Sure. Delphi/Win32's interfaces are a superset of COM interfaces; they include support for native Delphi types (such as AnsiString), and when one is working in single-language / single-vendor (taking BCB into account) environment, it can be useful to discard the language-neutral COM conventions. That doesn't stop the Delphi compiler from performing all the refcounting operations anyway, though.
I can't cast from an interface to an object, but in other respects it's pretty similar to what I do with interfaces in .NET. The underlying mechanisms are very different, yes (e.g., GUIDs as IIDs), but in the end I find myself doing similar things in code.

When you say interfaces are different in Delphi Win32 and .NET you seem to be talking about lifetime management.

I'm talking about slightly more than that. I'm talking about the basic services that IUnknown provides: lifetime management (AddRef and Release) and interface negotiation (QueryInterface). All Delphi/Win32 interfaces derive from an interface that supports these three operations (whether it's IInterface or IUnknown depending on version). Delphi/Win32 implements interface casting (in so far as it does at all, excluding Supports() etc.) through calls to QueryInterface, via 'as'. Delphi/Win32 doesn't support 'as' casting unless the interface has a GUID, a COM-ism. Delphi/Win32 classes can dynamically change which interfaces they support (within or without COM rules) by explicitly implementing QueryInterface() - that won't work in .NET (I use IServiceProvider for this). All COM interfaces consumed in Delphi in the orthodox way (ignoring the old Delphi 2 stuff) are also Delphi/Win32 interfaces. Delphi/Win32 interfaces and COM interfaces are basically made of the same "stuff", with the same binary format.

.NET interfaces are entirely different; they're a runtime feature, and they aren't a binary standard. They're not ref-counted. They don't have the same layout. Casting is defined by the implementing class, and can't be dynamically changed (RealProxy etc. aside). They don't require GUIDs for full support. COM interfaces get wrapped in runtime callable wrapper (RCW), rather than being actual .NET interfaces.

But the lifetime management features in Delphi Win32 are something I generally try to work around because (1) I mostly don't use them for COM and (2) I find having two wildly different lifetime management schemes coexisting inconvenient. When I work in .NET I use the GC; I would find doing a mixture of GC and explicit allocation / disposal messy. Similarly, I only want my object instances reference counted in Win32 when I can't avoid it, like when I must use COM. Otherwise I want to do things explicitly, for the sake of consistency.
Sure - but you have to do this yourself! Delphi's TInterfacedObject, the normal base for a class supporting interfaces, implements COM rules. I think it can even be useful to work within the COM rules too, when restricted to Win32-only code, since the automated ref-counted GC supports shared ownership fairly well.

No comments: