When is ISomeGenericInterface<T> assignable to ISomeGenericInterface<T>? [duplicate] - delphi

Given the code below, wich is a very trimmed down version of the actual code, I get the following error:
[DCC Error] Unit3.pas(31): E2010 Incompatible types: 'IXList<Unit3.TXList<T>.FindAll.S>' and 'TXList<Unit3.TXList<T>.FindAll.S>'
In the FindAll<S> function.
I can't really see why since there is no problem with the previous very similar function.
Can anyone shed some light on it?
Is it me or is it a bug in the compiler?
unit Unit3;
interface
uses Generics.Collections;
type
IXList<T> = interface
end;
TXList<T: class> = class(TList<T>, IXList<T>)
protected
FRefCount: Integer;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
function Find: IXList<T>;
function FindAll<S>: IXList<S>;
end;
implementation
uses Windows;
function TXList<T>.Find: IXList<T>;
begin
Result := TXList<T>.Create;
end;
function TXList<T>.FindAll<S>: IXList<S>;
begin
Result := TXList<S>.Create; // Error here
end;
function TXList<T>.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
Result := E_NoInterface;
end;
function TXList<T>._AddRef: Integer;
begin
InterlockedIncrement(FRefCount);
end;
function TXList<T>._Release: Integer;
begin
InterlockedDecrement(FRefCount);
if FRefCount = 0 then Self.Destroy;
end;
end.
Thanks for the answers!
It seems like a compiler bug with an acceptable workaround available.
With the interface declared as
IXList<T: class> = interface
function GetEnumerator: TList<T>.TEnumerator;
end;
and findall implemented as
function TXList<T>.FindAll<S>: IXList<S>;
var
lst: TXList<S>;
i: T;
begin
lst := TXList<S>.Create;
for i in Self do
if i.InheritsFrom(S) then lst.Add(S(TObject(i)));
Result := IXList<S>(IUnknown(lst));
end;
I got it working in a simple example.
Doing something like:
var
l: TXList<TAClass>;
i: TASubclassOfTAClass;
begin
.
.
.
for i in l.FindAll<TASubclassOfTAClass> do
begin
// Do something with i
end;

With three minor modification (IInterface, FindAll with "S: class" [Thanks Mason] and the typecasts in FindAll) I got it compiling.
Full code:
unit Unit16;
interface
uses
Generics.Collections;
type
IXList<T> = interface
end;
TXList<T: class> = class(TList<T>, IInterface, IXList<T>)
protected
FRefCount: Integer;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
function Find: IXList<T>;
function FindAll<S: class>: IXList<S>;
end;
implementation
uses Windows;
function TXList<T>.Find: IXList<T>;
begin
Result := TXList<T>.Create;
end;
function TXList<T>.FindAll<S>: IXList<S>;
begin
Result := IXList<S>(IUnknown(TXList<S>.Create));
end;
function TXList<T>.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
Result := E_NoInterface;
end;
function TXList<T>._AddRef: Integer;
begin
InterlockedIncrement(FRefCount);
end;
function TXList<T>._Release: Integer;
begin
InterlockedDecrement(FRefCount);
if FRefCount = 0 then Self.Destroy;
end;
end.

That definitely looks like a compiler error. They're saying how they've focused a lot of effort into improving Generics issues for the next version, Delphi XE. When it gets released, which should be within the next couple weeks, download the preview and see if that will compile now. If not, try filing a bug report with QC.
Also, FindAll<S> should probably be declared as function FindAll<S: class>: IXList<S>;. That doesn't fix the error, but a working compiler would probably give you an error on that.

Related

Receiving MS Word's automation events from a Delphi app

I've been trying to use the technique shown in the answer to this q
Detect when the active element in a TWebBrowser document changes
to implement a DIY version of MS Word's Automation events.
A fuller extract from my app is below, from which you'll be able to see the
declaration of the variables in these methods:
procedure TForm1.StartWord;
var
IU : IUnknown;
begin
IU := CreateComObject(Class_WordApplication);
App := IU as WordApplication;
App.Visible := True;
IEvt := TEventObject.Create(DocumentOpen);
end;
procedure TForm1.OpenDocument;
var
CPC : IConnectionPointContainer;
CP : IConnectionPoint;
Res : Integer;
MSWord : OleVariant;
begin
Cookie := -1;
CPC := App as IConnectionPointContainer;
Res := CPC.FindConnectionPoint(DIID_ApplicationEvents2, CP);
Res := CP.Advise(IEvt, Cookie);
MSWord := App;
WordDoc:= MSWord.Documents.Open('C:\Docs\Test.Docx');
end;
The StartWord routine works fine. The problem is in OpenDocument. The
value of Res returned by Res := CP.Advise(IEvt, Cookie); is $80040200
This isn't present amongst the HResult status codes in Windows.Pas and googling "ole error 80040200"
returns a few hits involving setting up Ado events from Delphi, but nothing
apparently relevant.
Anyway, the upshot of this is that the Invoke method of the EventObject is never
called, so I don't receive notifications of the WordApplication's events.
So, my question is what does this error $80040200 signify and/or how do I avoid it?
Fwiw, I've also tried connecting to the ApplicationEvents2 interface using this code
procedure TForm1.OpenDocument2;
var
MSWord : OleVariant;
II : IInterface;
begin
II := APP as IInterface;
InterfaceConnect(II, IEvt.EventIID, IEvt as IUnknown, Cookie);
MSWord := App;
WordDoc:= MSWord.Documents.Open('C:\Docs\Test.Docx');
end;
That executes without complaint, but again the EventObject's Invoke method is never
called.
If I drop a TWordApplication onto the blank form of a new application, the events
like OnDocumentOpen work fine. I'm mentioning that because it seems to confirm
that Delphi and MS Word (2007) are correctly set up on my machine.
Code:
uses
... Word2000 ...
TForm1 = class(TForm)
btnStart: TButton;
btnOpenDoc: TButton;
procedure FormCreate(Sender: TObject);
procedure btnOpenDocClick(Sender: TObject);
procedure btnStartClick(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure WordApplication1DocumentOpen(ASender: TObject; const Doc: _Document);
private
procedure DocumentOpen(Sender : TObject; DispID : Integer; var Params);
procedure StartWord; // see above for implementation
procedure OpenDocument; // --"--
procedure OpenDocument2; // --"--
public
WordDoc: OleVariant;
IEvt : TEventObject; // see linked question
Cookie : Integer;
App : WordApplication;
[...]
procedure TForm1.WordApplication1DocumentOpen(ASender: TObject; const Doc:
_Document);
begin
//
end;
I could post an MCVE instead, but it would mostly be just the code from the earlier answer.
This had me scratching my head for a while, I can tell you. Anyway, eventually the penny dropped
that the answer must lie in the difference between the way TEventObject is implemented
and TServerEventDispatch in OleServer.Pas.
The key is that TServerEventDispatch implements a custom QueryInterface
function TServerEventDispatch.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
begin
Result := S_OK;
Exit;
end;
if IsEqualIID(IID, FServer.FServerData^.EventIID) then
begin
GetInterface(IDispatch, Obj);
Result := S_OK;
Exit;
end;
Result := E_NOINTERFACE;
end;
whereas TEventObject does not. Once I'd spotted that, it was straightforward to extend
TEventObject to do likewise, and voila! the error returned by "CP.Advise" went away.
For completeness, I've included the complete source
of the updated TEventObject below. It is the
if IsEquallIID then ...
which makes the difference between
Res := CP.Advise(IEvt, Cookie);
returning the $800040200 error and zero for success. With the "if IsEquallIID then ..."
commented out, the RefCount on IEvt is 48 (!) after "CP.Advise ..." returns, by which time
TEventObject.QueryInterface has been called no less than 21 times.
I hadn't realised
previously (because TEventObject didn't previously have its own version to observe)
that when "CP.Advise ..." is executed, the COM system calls "TEventObject.QueryInterface"
with a succession of different IIDs until it returns S_Ok on one of them. When I have some free time, maybe I'll try to look up what these other IIDs are: as it is, the IID for IDispatch is quite a long way down the list of IIDs that are queried for, which seems strangely sub-optimal seeing as I'd have though that would be the one that IConnectionPoint.Advise would be trying to get.
Code for updated TEventObject is below. It includes a rather rough'n ready customization
of its Invoke() which is specific to handling Word's DocumentOpen event.
type
TInvokeEvent = procedure(Sender : TObject; const Doc : _Document) of object;
TEventObject = class(TInterfacedObject, IUnknown, IDispatch)
private
FOnEvent: TInvokeEvent;
FEventIID: TGuid;
protected
function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
function GetIDsOfNames(const IID: TGUID; Names: Pointer;
NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
public
constructor Create(const AnEvent : TInvokeEvent);
property OnEvent: TInvokeEvent read FOnEvent write FOnEvent;
property EventIID : TGuid read FEventIID;
end;
constructor TEventObject.Create(const AnEvent: TInvokeEvent);
begin
inherited Create;
FEventIID := DIID_ApplicationEvents2;
FOnEvent := AnEvent;
end;
function TEventObject.GetIDsOfNames(const IID: TGUID; Names: Pointer;
NameCount, LocaleID: Integer; DispIDs: Pointer): HResult;
begin
Result := E_NOTIMPL;
end;
function TEventObject.GetTypeInfo(Index, LocaleID: Integer;
out TypeInfo): HResult;
begin
Pointer(TypeInfo) := nil;
Result := E_NOTIMPL;
end;
function TEventObject.GetTypeInfoCount(out Count: Integer): HResult;
begin
Count := 0;
Result := E_NOTIMPL;
end;
function TEventObject.Invoke(DispID: Integer; const IID: TGUID;
LocaleID: Integer; Flags: Word; var Params; VarResult, ExcepInfo,
ArgErr: Pointer): HResult;
var
vPDispParams: PDispParams;
tagV : TagVariant;
V : OleVariant;
Doc : _Document;
begin
vPDispParams := PDispParams(#Params);
if (vPDispParams <> Nil) and (vPDispParams^.rgvarg <> Nil) then begin
tagV := vPDispParams^.rgvarg^[0];
V := OleVariant(tagV);
Doc := IDispatch(V) as _Document;
// the DispID for DocumentOpen of Word's ApplicationEvents2 interface is 4
if (DispID = 4) and Assigned(FOnEvent) then
FOnEvent(Self, Doc);
end;
Result := S_OK;
end;
function TEventObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
begin
Result := S_OK;
Exit;
end;
if IsEqualIID(IID, EventIID) then
begin
GetInterface(IDispatch, Obj);
Result := S_OK;
Exit;
end;
Result := E_NOINTERFACE;
end;

Delphi - Drag and Drop .txt Files in TMemo [duplicate]

In Delphi XE can I allow my form to accept file 'drag and drop' but without having to handle bare windows messages?
You don't need to handle messages to implement this. You just need to implement IDropTarget and call RegisterDragDrop/RevokeDragDrop. It's really very very simple. You can actually implement IDropTarget in your form code but I prefer to do it in a helper class that looks like this:
uses
Winapi.Windows,
Winapi.ActiveX,
Winapi.ShellAPI,
System.StrUtils,
Vcl.Forms;
type
IDragDrop = interface
function DropAllowed(const FileNames: array of string): Boolean;
procedure Drop(const FileNames: array of string);
end;
TDropTarget = class(TObject, IInterface, IDropTarget)
private
// IInterface
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
private
// IDropTarget
FHandle: HWND;
FDragDrop: IDragDrop;
FDropAllowed: Boolean;
procedure GetFileNames(const dataObj: IDataObject; var FileNames: TArray<string>);
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; const ADragDrop: IDragDrop);
destructor Destroy; override;
end;
{ TDropTarget }
constructor TDropTarget.Create(AHandle: HWND; const ADragDrop: IDragDrop);
begin
inherited Create;
FHandle := AHandle;
FDragDrop := ADragDrop;
RegisterDragDrop(FHandle, Self)
end;
destructor TDropTarget.Destroy;
begin
RevokeDragDrop(FHandle);
inherited;
end;
function TDropTarget.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then begin
Result := S_OK;
end else begin
Result := E_NOINTERFACE;
end;
end;
function TDropTarget._AddRef: Integer;
begin
Result := -1;
end;
function TDropTarget._Release: Integer;
begin
Result := -1;
end;
procedure TDropTarget.GetFileNames(const dataObj: IDataObject; var FileNames: TArray<string>);
var
i: Integer;
formatetcIn: TFormatEtc;
medium: TStgMedium;
dropHandle: HDROP;
begin
FileNames := nil;
formatetcIn.cfFormat := CF_HDROP;
formatetcIn.ptd := nil;
formatetcIn.dwAspect := DVASPECT_CONTENT;
formatetcIn.lindex := -1;
formatetcIn.tymed := TYMED_HGLOBAL;
if dataObj.GetData(formatetcIn, medium)=S_OK then begin
(* This cast needed because HDROP is incorrectly declared as Longint in ShellAPI.pas. It should be declared as THandle
which is an unsigned integer. Without this fix the routine fails in top-down memory allocation scenarios. *)
dropHandle := HDROP(medium.hGlobal);
SetLength(FileNames, DragQueryFile(dropHandle, $FFFFFFFF, nil, 0));
for i := 0 to high(FileNames) do begin
SetLength(FileNames[i], DragQueryFile(dropHandle, i, nil, 0));
DragQueryFile(dropHandle, i, #FileNames[i][1], Length(FileNames[i])+1);
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
FileNames: TArray<string>;
begin
Result := S_OK;
Try
GetFileNames(dataObj, FileNames);
FDropAllowed := (Length(FileNames)>0) and FDragDrop.DropAllowed(FileNames);
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
FileNames: TArray<string>;
begin
Result := S_OK;
Try
GetFileNames(dataObj, FileNames);
if Length(FileNames)>0 then begin
FDragDrop.Drop(FileNames);
end;
Except
Application.HandleException(Self);
End;
end;
The idea here is to wrap up the complexity of the Windows IDropTarget in TDropTarget. All you need to do is to implement IDragDrop which is much simpler. Anyway, I think this should get you going.
Create the drop target object from your control's CreateWnd. Destroy it in the DestroyWnd method. That point is important because VCL window re-creation means that a control can have its window handle destroyed and re-created during its lifetime.
Note that reference counting on TDropTarget is suppressed. That is because when RegisterDragDrop is called it increments the reference count. This creates a circular reference and this code to suppress reference counting breaks that. This means that you would use this class through a class variable rather than an interface variable, in order to avoid leaking.
The usage would look something like this:
type
TMainForm = class(TForm, IDragDrop)
....
private
FDropTarget: TDropTarget;
// implement IDragDrop
function DropAllowed(const FileNames: array of string): Boolean;
procedure Drop(const FileNames: array of string);
protected
procedure CreateWindowHandle; override;
procedure DestroyWindowHandle; override;
end;
....
procedure TMainForm.CreateWindowHandle;
begin
inherited;
FDropTarget := TDropTarget.Create(WindowHandle, Self);
end;
procedure TMainForm.DestroyWindowHandle;
begin
FreeAndNil(FDropTarget);
inherited;
end;
function TMainForm.DropAllowed(const FileNames: array of string): Boolean;
begin
Result := True;
end;
procedure TMainForm.Drop(const FileNames: array of string);
begin
; // do something with the file names
end;
Here I am using a form as the drop target. But you could use any other windowed control in a similar fashion.
If you don't like pure WinAPI, then you can use components. Drag and Drop Component Suite is free with sources.
No, unless you are about to peruse some custom TForm descendant which have this functionality built-in already.
I used David Heffernan's solution as base for my test application and got 'Invalid pointer operation' on application close.
The solution for that problem was to change the TDropTarget.Create by adding '_Release;'
constructor TDropTarget.Create(AHandle: HWND; const ADragDrop: IDragDrop);
begin
inherited Create;
FHandle := AHandle;
FDragDrop := ADragDrop;
RegisterDragDrop(FHandle, Self);
_Release;
end;
A discussion about this problem you can see on Embarcadero forum.
You have to either write code yourself, or install a 3rd party product like DropMaster, which lets you do drag and drop in much older Delphi versions as well.
--jeroen

Is there a non-reference-counted base class like TInterfacedObject?

I need a base class like TInterfacedObject but without reference counting (so a kind of TNonRefCountedInterfacedObject).
This actually is the nth time I need such a class and somehow I always end up writing (read: copy and pasting) my own again and again. I cannot believe that there is no "official" base class I can use.
Is there a base class somewhere in the RTL implementing IInterface but without reference counting which I can derive my classes from?
In the unit Generics.Defaults there is a class TSingletonImplementation defined. Available in Delphi 2009 and above.
// A non-reference-counted IInterface implementation.
TSingletonImplementation = class(TObject, IInterface)
protected
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
I did this. It can be used in place of TInterfacedObject with or without reference counting. It also has a name property - very useful when debugging.
// TArtInterfacedObject
// =============================================================================
// An object that supports interfaces, allowing naming and optional reference counting
type
TArtInterfacedObject = class( TInterfacedObject )
constructor Create( AReferenceCounted : boolean = True);
PRIVATE
FName : string;
FReferenceCounted : boolean;
PROTECTED
procedure SetName( const AName : string ); virtual;
PUBLIC
property Name : string
read FName
write SetName;
function QueryInterface(const AGUID : TGUID; out Obj): HResult; stdcall;
function SupportsInterface( const AGUID : TGUID ) : boolean;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
// =============================================================================
{ TArtInterfacedObject }
constructor TArtInterfacedObject.Create( AReferenceCounted : boolean = True);
begin
inherited Create;
FName := '';
FReferenceCounted := AReferenceCounted;
end;
function TArtInterfacedObject.QueryInterface(const AGUID: TGUID; out Obj): HResult;
const
E_NOINTERFACE = HResult($80004002);
begin
If FReferenceCounted then
Result := inherited QueryInterface( AGUID, Obj )
else
if GetInterface(AGUID, Obj) then Result := 0 else Result := E_NOINTERFACE;
end;
procedure TArtInterfacedObject.SetName(const AName: string);
begin
FName := AName;
end;
function TArtInterfacedObject.SupportsInterface(
const AGUID: TGUID): boolean;
var
P : TObject;
begin
Result := QueryInterface( AGUID, P ) = S_OK;
end;
function TArtInterfacedObject._AddRef: Integer;
begin
If FReferenceCounted then
Result := inherited _AddRef
else
Result := -1 // -1 indicates no reference counting is taking place
end;
function TArtInterfacedObject._Release: Integer;
begin
If FReferenceCounted then
Result := inherited _Release
else
Result := -1 // -1 indicates no reference counting is taking place
end;
// =============================================================================
You might consider TInterfacedPersistent. If you don't override GetOwner it does no ref-counting.
I don't know of any out-of-the-box base class, so I wrote my own (like you). Just put it in a common utils unit and you are done.
type
TPureInterfacedObject = class(TObject, IInterface)
protected
{ IInterface }
function QueryInterface(const IID: TGUID; out Obj): HResult; virtual; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
{ TPureInterfacedObject }
function TPureInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
Result := E_NOINTERFACE;
end;
function TPureInterfacedObject._AddRef: Integer;
begin
Result := -1;
end;
function TPureInterfacedObject._Release: Integer;
begin
Result := -1;
end;
There is no such class, but you can easily write your own, as others have shown. I do, however, wonder why you would need it. In my experience, there is seldom a real need for such a class, even if you want to mix object and interface references.
Also note that when you use such a class, you'll still have to take care of setting any interface references you have to such an object to nil before they leave scope and before you free the object. Otherwise you might get the situation the runtime tries to call _Release on a freed object, and that tends to cause an invalid pointer exception.
IOW, I would advise against using such a class at all.
As of Delphi 11 Embarcadero added TNoRefCountObject to the System unit. Here's the note from the release notes:
The new class System.TNoRefCountObject is a non-reference-counted
IInterface implementation (replacing the old and oddly named
TSingletonObject)

Delphi - inherit from a class and an interface (adapter pattern)?

I am trying to do the GoF adapter pattern and in the C# example that I am following the Adapter class is inheriting the original class and an adapting interface.
In Delphi (2007), as far as I know, this is not possible, or is it? Cause if a class is inheriting an interface, it needs to inherit from TInterfacedObject and since Delphi doesn't allow multiple class inheritance, that is the end of story. I cannot inherit from a custom class and an interface at the same time.
Am I correct?
Thank you.
I have implemented this pattern on http://delphipatterns.blog.com/2011/02/22/decorator-5/
No that it not correct. You can add an interface to any class you like as follows:
type
IAdapter = interface
procedure DoSomething;
end;
TAdapter = class(TBaseClass, IInterface, IAdapter)
private
FRefCount: Integer;
procedure DoSomething;
protected
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
function TAdapter.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;
function TAdapter._AddRef: Integer;
begin
Result := InterlockedIncrement(FRefCount);
end;
function TAdapter._Release: Integer;
begin
Result := InterlockedDecrement(FRefCount);
if Result = 0 then
Destroy;
end;
procedure TAdapter.DoSomething;
begin
end;

Generic method returning generic interface in Delphi 2010

Given the code below, wich is a very trimmed down version of the actual code, I get the following error:
[DCC Error] Unit3.pas(31): E2010 Incompatible types: 'IXList<Unit3.TXList<T>.FindAll.S>' and 'TXList<Unit3.TXList<T>.FindAll.S>'
In the FindAll<S> function.
I can't really see why since there is no problem with the previous very similar function.
Can anyone shed some light on it?
Is it me or is it a bug in the compiler?
unit Unit3;
interface
uses Generics.Collections;
type
IXList<T> = interface
end;
TXList<T: class> = class(TList<T>, IXList<T>)
protected
FRefCount: Integer;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
function Find: IXList<T>;
function FindAll<S>: IXList<S>;
end;
implementation
uses Windows;
function TXList<T>.Find: IXList<T>;
begin
Result := TXList<T>.Create;
end;
function TXList<T>.FindAll<S>: IXList<S>;
begin
Result := TXList<S>.Create; // Error here
end;
function TXList<T>.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
Result := E_NoInterface;
end;
function TXList<T>._AddRef: Integer;
begin
InterlockedIncrement(FRefCount);
end;
function TXList<T>._Release: Integer;
begin
InterlockedDecrement(FRefCount);
if FRefCount = 0 then Self.Destroy;
end;
end.
Thanks for the answers!
It seems like a compiler bug with an acceptable workaround available.
With the interface declared as
IXList<T: class> = interface
function GetEnumerator: TList<T>.TEnumerator;
end;
and findall implemented as
function TXList<T>.FindAll<S>: IXList<S>;
var
lst: TXList<S>;
i: T;
begin
lst := TXList<S>.Create;
for i in Self do
if i.InheritsFrom(S) then lst.Add(S(TObject(i)));
Result := IXList<S>(IUnknown(lst));
end;
I got it working in a simple example.
Doing something like:
var
l: TXList<TAClass>;
i: TASubclassOfTAClass;
begin
.
.
.
for i in l.FindAll<TASubclassOfTAClass> do
begin
// Do something with i
end;
With three minor modification (IInterface, FindAll with "S: class" [Thanks Mason] and the typecasts in FindAll) I got it compiling.
Full code:
unit Unit16;
interface
uses
Generics.Collections;
type
IXList<T> = interface
end;
TXList<T: class> = class(TList<T>, IInterface, IXList<T>)
protected
FRefCount: Integer;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
function Find: IXList<T>;
function FindAll<S: class>: IXList<S>;
end;
implementation
uses Windows;
function TXList<T>.Find: IXList<T>;
begin
Result := TXList<T>.Create;
end;
function TXList<T>.FindAll<S>: IXList<S>;
begin
Result := IXList<S>(IUnknown(TXList<S>.Create));
end;
function TXList<T>.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
Result := E_NoInterface;
end;
function TXList<T>._AddRef: Integer;
begin
InterlockedIncrement(FRefCount);
end;
function TXList<T>._Release: Integer;
begin
InterlockedDecrement(FRefCount);
if FRefCount = 0 then Self.Destroy;
end;
end.
That definitely looks like a compiler error. They're saying how they've focused a lot of effort into improving Generics issues for the next version, Delphi XE. When it gets released, which should be within the next couple weeks, download the preview and see if that will compile now. If not, try filing a bug report with QC.
Also, FindAll<S> should probably be declared as function FindAll<S: class>: IXList<S>;. That doesn't fix the error, but a working compiler would probably give you an error on that.

Resources