Why is the class instance (TInterfacedObject, IDropTarget) not auto-free?

I implement my IDropTarget based on: How can I allow a form to accept files without processing Windows messages?

The effect of David works great. however, the IDropTarget ( TInterfacedObject ) is not automatically freed, even if it is set to "nil".

Code Part:

 { TDropTarget } constructor TDropTarget.Create(AHandle: HWND; const ADragDrop: IDragDrop); begin inherited Create; FHandle := AHandle; FDragDrop := ADragDrop; OleCheck(RegisterDragDrop(FHandle, Self)); //_Release; end; destructor TDropTarget.Destroy; begin MessageBox(0, 'TDropTarget.Destroy', '', MB_TASKMODAL); RevokeDragDrop(FHandle); inherited; end; ... procedure TForm1.FormShow(Sender: TObject); begin Assert(Panel1.HandleAllocated); FDropTarget := TDropTarget.Create(Panel1.Handle, nil) as IDropTarget; end; procedure TForm1.Button1Click(Sender: TObject); begin FDropTarget := nil; // This should free FDropTarget end; var NeedOleUninitialize: Boolean = False; initialization NeedOleUninitialize := Succeeded(OleInitialize(nil)); finalization if (NeedOleUninitialize) then OleUninitialize; end. 

where is FDropTarget: IDropTarget; .

When I click the button, the MessageBox is not displayed and the object is not destroyed.

If I call _Release; as suggested here at the end of the constructor, FDropTarget destroyed when I click the button or when the program terminates (I doubt this "decision").

If I omit RegisterDragDrop(FHandle, Self) , then the FDropTarget destroyed as expected.

I think link counting is for some reason broken. I'm really confused. How can I make TInterfacedObject free correctly?


EDIT:

Here is the complete code:

 unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, VirtualTrees, ExtCtrls, StdCtrls, ActiveX, ComObj; type TDropTarget = class(TInterfacedObject, IDropTarget) private FHandle: HWND; FDropAllowed: Boolean; function GetTreeFromDataObject(const DataObject: IDataObject): TBaseVirtualTree; procedure SetEffect(var dwEffect: Integer); function DragEnter(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult; stdcall; function DragOver(grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall; function DragLeave: HResult; stdcall; function Drop(const dataObj: IDataObject; grfKeyState: Longint; pt: TPoint; var dwEffect: Longint): HResult; stdcall; public constructor Create(AHandle: HWND); destructor Destroy; override; end; TForm1 = class(TForm) Panel1: TPanel; VirtualStringTree1: TVirtualStringTree; Button1: TButton; procedure FormCreate(Sender: TObject); procedure VirtualStringTree1DragAllowed(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean); procedure Button1Click(Sender: TObject); procedure FormShow(Sender: TObject); private FDropTarget: IDropTarget; public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} { TDropTarget } constructor TDropTarget.Create(AHandle: HWND); begin inherited Create; FHandle := AHandle; OleCheck(RegisterDragDrop(FHandle, Self)); //_Release; end; destructor TDropTarget.Destroy; begin MessageBox(0, 'TDropTarget.Destroy', '', MB_TASKMODAL); RevokeDragDrop(FHandle); inherited; end; function TDropTarget.GetTreeFromDataObject(const DataObject: IDataObject): TBaseVirtualTree; // Returns the owner/sender of the given data object by means of a special clipboard format // or nil if the sender is in another process or no virtual tree at all. var Medium: TStgMedium; Data: PVTReference; formatetcIn: TFormatEtc; begin Result := nil; if Assigned(DataObject) then begin formatetcIn.cfFormat := CF_VTREFERENCE; formatetcIn.ptd := nil; formatetcIn.dwAspect := DVASPECT_CONTENT; formatetcIn.lindex := -1; formatetcIn.tymed := TYMED_ISTREAM or TYMED_HGLOBAL; if DataObject.GetData(formatetcIn, Medium) = S_OK then begin Data := GlobalLock(Medium.hGlobal); if Assigned(Data) then begin if Data.Process = GetCurrentProcessID then Result := Data.Tree; GlobalUnlock(Medium.hGlobal); end; ReleaseStgMedium(Medium); end; end; end; procedure TDropTarget.SetEffect(var dwEffect: Integer); begin if FDropAllowed then begin dwEffect := DROPEFFECT_COPY; end else begin dwEffect := DROPEFFECT_NONE; end; end; function TDropTarget.DragEnter(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult; var Tree: TBaseVirtualTree; begin Result := S_OK; try Tree := GetTreeFromDataObject(dataObj); FDropAllowed := Assigned(Tree); SetEffect(dwEffect); except Result := E_UNEXPECTED; end; end; function TDropTarget.DragLeave: HResult; begin Result := S_OK; end; function TDropTarget.DragOver(grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult; begin Result := S_OK; try SetEffect(dwEffect); except Result := E_UNEXPECTED; end; end; function TDropTarget.Drop(const dataObj: IDataObject; grfKeyState: Integer; pt: TPoint; var dwEffect: Integer): HResult; var Tree: TBaseVirtualTree; begin Result := S_OK; try Tree := GetTreeFromDataObject(dataObj); FDropAllowed := Assigned(Tree); if FDropAllowed then begin Alert(Tree.Name); end; except Application.HandleException(Self); end; end; {----------------------------------------------------------------------------------------------------------------------} procedure TForm1.FormCreate(Sender: TObject); begin VirtualStringTree1.RootNodeCount := 10; end; procedure TForm1.VirtualStringTree1DragAllowed(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean); begin Allowed := True; end; procedure TForm1.FormShow(Sender: TObject); begin Assert(Panel1.HandleAllocated); FDropTarget := TDropTarget.Create(Panel1.Handle) as IDropTarget; end; procedure TForm1.Button1Click(Sender: TObject); begin FDropTarget := nil; // This should free FDropTarget end; var NeedOleUninitialize: Boolean = False; initialization NeedOleUninitialize := Succeeded(OleInitialize(nil)); finalization if (NeedOleUninitialize) then OleUninitialize; end. 

DFM:

 object Form1: TForm1 Left = 192 Top = 114 Width = 567 Height = 268 Caption = 'Form1' Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'MS Shell Dlg 2' Font.Style = [] OldCreateOrder = False OnCreate = FormCreate OnShow = FormShow PixelsPerInch = 96 TextHeight = 13 object Panel1: TPanel Left = 368 Top = 8 Width = 185 Height = 73 Caption = 'Panel1' TabOrder = 0 end object VirtualStringTree1: TVirtualStringTree Left = 8 Top = 8 Width = 200 Height = 217 Header.AutoSizeIndex = 0 Header.Font.Charset = DEFAULT_CHARSET Header.Font.Color = clWindowText Header.Font.Height = -11 Header.Font.Name = 'MS Shell Dlg 2' Header.Font.Style = [] Header.MainColumn = -1 Header.Options = [hoColumnResize, hoDrag] TabOrder = 1 TreeOptions.SelectionOptions = [toMultiSelect] OnDragAllowed = VirtualStringTree1DragAllowed Columns = <> end object Button1: TButton Left = 280 Top = 8 Width = 75 Height = 25 Caption = 'Button1' TabOrder = 2 OnClick = Button1Click end end 

Conclusion: From the documents :

Function

RegisterDragDrop also calls the IUnknown :: AddRef method IDropTarget pointer

Fixed the code in the answer that I linked .

Note that reference counting on TDropTarget is suppressed. That is because when RegisterDragDrop is called, it increases the count link. This creates a circular link and this code to suppress reference counting violates this. This means that you would use this class through a class variable, not an interface variable, in order to avoid leakage.

+6
source share
1 answer

Calling RegisterDragDrop on TDragDrop.Create passes the counted reference to an instance of a new instance of TDragDrop . This increases its reference count. The FDragDrop := Nil instruction decreases the reference counter, but there is still a reference to the object that prevents the destruction of the object. You need to call RevokeDragDrop(FHandle) before to remove the last link to this instance in order to get the reference counter to zero.

In short: the call to RevokeDragDrop inside the destructor is too late.

+8
source

Source: https://habr.com/ru/post/1014216/


All Articles