Delphi Berlin 10.1 adds [weak] references. Marco Cantu's Blog has some basics on it.
For my test I created two COM libraries holding two automation object types. The container object holds a list of the content objects while the content objects holds a weak reference to their container.
The following two scenarios were tested and worked correctly (weak references are set null and memory is released) :
However, when I place the coclasses in two separate libraries the code produces "invalid class typecast", the error goes away when removing the [weak] attribute. Please excuse the odd sample, its purpose is simply to make the problem minimal and should not be taken as standard coding practice
Here is the first library .ridl file that defines both interfaces and the CoClass for the container:
[
uuid(E1EE3651-A400-49BF-B5C5-006D9943B9C0),
version(1.0)
]
library DelphiIntfComLib
{
importlib("stdole2.tlb");
interface IMyContainer;
interface IMyContent;
coclass MyContainer;
[
uuid(A7EF86F7-40CD-41EE-9DA1-4D9B7B24F06B),
helpstring("Dispatch interface for MyContainer Object"),
dual,
oleautomation
]
interface IMyContainer: IDispatch
{
[id(0x000000C9)]
HRESULT _stdcall Add([in] IMyContent* AMyContent);
};
[
uuid(BFD6D976-8CEF-4264-B95A-B5DA7817F6B3),
helpstring("Dispatch interface for MyContent Object"),
dual,
oleautomation
]
interface IMyContent: IDispatch
{
[id(0x000000C9)]
HRESULT _stdcall SetWeakReferenceToContainer([in] IMyContainer* AContainer);
};
[
uuid(1F56198B-B1BE-4E11-BC78-0E6FF8E55214)
]
coclass MyContainer
{
[default] interface IMyContainer;
};
};
Here is my container implementation
unit Unit1;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
ComObj, ActiveX, DelphiIntfComLib_TLB, StdVcl, Generics.Collections;
type
TMyContainer = class(TAutoObject, IMyContainer)
private
FList: TList<IMyContent>;
protected
procedure Add(const AMyContent: IMyContent); safecall;
public
Destructor Destroy; override;
procedure Initialize; override;
end;
implementation
uses ComServ;
procedure TMyContainer.Add(const AMyContent: IMyContent);
begin
FList.Add(AMyContent);
AMyContent.SetWeakReferenceToContainer(self);
end;
destructor TMyContainer.Destroy;
begin
FList.Free;
inherited;
end;
procedure TMyContainer.Initialize;
begin
inherited;
FList := TList<IMyContent>.create;
end;
initialization
TAutoObjectFactory.Create(ComServer, TMyContainer, Class_MyContainer,
ciMultiInstance, tmApartment);
end.
My second library reference the first and only contains my content interface's CoClass
[
uuid(65659EE4-1949-4112-88CA-F2D5B5D8DA2C),
version(1.0)
]
library DelphiImplComLib
{
importlib("stdole2.tlb");
importlib("DelphiIntfComLib.dll");
coclass MyContent;
[
uuid(79D1669A-8EB6-4AE6-8F4B-91137E6E6DC1)
]
coclass MyContent
{
[default] interface IMyContent;
};
and its implementation with the weak reference
unit Unit2;
{$WARN SYMBOL_PLATFORM OFF}
interface
uses
ComObj, ActiveX, DelphiImplComLib_TLB, StdVcl, DelphiIntfComLib_TLB;
type
TMyContent = class(TAutoObject, IMyContent)
private
[Weak] //If included will cause "invalid class typecast" error
FContainer : IMyContainer;
protected
procedure SetWeakReferenceToContainer(const AContainer: IMyContainer); safecall;
end;
implementation
uses ComServ;
procedure TMyContent.SetWeakReferenceToContainer(const AContainer: IMyContainer);
begin
FContainer := AContainer;
end;
initialization
TAutoObjectFactory.Create(ComServer, TMyContent, Class_MyContent,
ciMultiInstance, tmApartment);
end.
I tested as follows
program Project13;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
DelphiImplComLib_TLB in 'impl\DelphiImplComLib_TLB.pas',
DelphiIntfComLib_TLB in 'Intf\DelphiIntfComLib_TLB.pas';
var
GMyContainer : IMyContainer;
GMyContent : IMyContent;
begin
GMyContainer := CoMyContainer.Create;
GMyContent := CoMyContent.Create;
GMyContainer.Add(GMyContent);
end.
Why do I get an error when I split my implementations? How can I alleviate this problem?
Do not use [Weak] for COM-interfaces. It is not intended for use with COM. [Weak] should only be used for internal non-exported-COM interfaces.
The reason is that there is no way for a COM-interface implementation, which may not even be implemented by a Delphi class, to properly handle the [weak] references. Additionally, the COM libraries you have aren't sharing the same implementation of the base TObject. You may get away with building everything using the shared rtl package, but even then... you're dancing on a land-mine.
As Allen Bauer explained in his answer, [weak]
does not work with COM interfaces, as they are not guaranteed to be backed by Delphi TObject
-derived classes, which is necessary for [weak]
references to be auto-nil'ed when objects are freed. The RTL keeps track of weak references at runtime, but cannot track weak references across libraries unless a single instance of the RTL library is shared between them (ie if you compile the libraries with Runtime Packages enabled, and then deploy the RTL BPL with your executables).
However, as long as you don't need to use the auto-nil functionality of [weak]
, you can use an untyped Pointer
instead:
type
TMyContent = class(TAutoObject, IMyContent)
private
FContainer : Pointer{IMyContainer};
...
end;
You would just have to typecast FContainer
to IMyContainer
whenever you need to use its methods/properties, eg:
IMyContainer(FContainer).Add(...);
In 10.1 Berlin and later, you can use the [unsafe]
attribute instead:
type
TMyContent = class(TAutoObject, IMyContent)
private
[Unsafe] FContainer : IMyContainer;
...
end;
As mentioned on Marco's blog:
Weak and Unsafe Interface References in Delphi 10.1 Berlin
What if the object has a standard reference count implementation and you want to create an interface reference that is kept out of the total count of references? You can now achieve this by adding the [unsafe] attribute to the interface variable declaration, changing the code above to:
procedure TForm3.Button2Click(Sender: TObject); var [unsafe] one: ISimpleInterface; begin one := TObjectOne.Create; one.DoSomething; end;
Not that this is a good idea, as the code above would cause a memory leak. By disabling the reference counting, when the variable goes out of scope nothing happens. There are some scenarios in which this is beneficial, as you can still use interfaces and not trigger the extra reference. In other words, an unsafe reference is treated just like... a pointer, with no extra compiler support.
Now before you consider using the unsafe attribute for having a reference without increasing the count, consider that in most cases there is another better option, that is the use of weak references. Weak references also avoid increasing the reference count, but they are managed. This means that the system keeps track of weak references, and in case the actual object gets deleted, it will set the weak reference to nil. With an unsafe reference, instead, you have no way to know the status of the target object (a scenario called dangling reference).
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