I'm getting a IEnumVariant from a .NET class library and I am trying to use a generic class to convert this to a IEnumerator
There is a compiler error, "Operator not applicable to this operand type" when attempting to cast an IInterface to the generic type T
I've seen workarounds when attempting to type cast to a class, but these don't work for an interface.
Using Supports as suggested by Rob seems to have problems as well as TypeInfo returns nil for the parameterized type.
uses WinApi.ActiveX, Generics.Collections;
type
TDotNetEnum<T: IInterface> = class(TInterfacedObject, IEnumerator<T>)
strict private
FDotNetEnum: IEnumVariant;
FCurrent: T;
function MoveNext: Boolean;
procedure Reset;
function GetCurrent: TObject;
function IEnumerator<T>.GetCurrent = GenericGetCurrent;
function GenericGetCurrent: T;
public
constructor Create(const ADotNetObject: OleVariant);
//// I can get it to work using this constructor
// constructor Create(const ADotNetObject: OleVariant; const AGUID: TGUID);
end;
implementation
uses System.Rtti, SysUtils, mscorlib_TLB, ComObj;
constructor TDotNetEnum<T>.Create(const ADotNetObject: OleVariant);
var
netEnum: IEnumerable;
begin
netEnum := IUnknown(ADotNetObject) as mscorlib_TLB.IEnumerable;
FDotNetEnum := netEnum.GetEnumerator();
end;
function TDotNetEnum<T>.GenericGetCurrent: T;
begin
result := FCurrent;
end;
function TDotNetEnum<T>.GetCurrent: TObject;
begin
result := nil;
end;
function TDotNetEnum<T>.MoveNext: Boolean;
var
rgvar: OleVariant;
fetched: Cardinal;
ti: TypeInfo;
guid: TGUID;
begin
OleCheck(FDotNetEnum.Next(1, rgvar, fetched));
result := fetched = 1;
if not result then
FCurrent := nil
else
begin
FCurrent := IUnknown(rgvar) as T; // <-- Compiler error here
//// Doesn't work using Supports either
// ti := TypeInfo(T); // <-- returns nil
// guid := GetTypeData(#ti)^.Guid;
// Supports(IUnknown(rgvar), guid, FCurrent);
end;
end;
procedure TDotNetEnum<T>.Reset;
begin
OleCheck(FDotNetEnum.Reset);
end;
Am I missing something in order to get that case to the generic interface type to work ?
I do have the alternative constructor which I CAN get the guid from so that
TDotNetEnum<IContact>.Create(vContactList, IContact);
works but the ideal
TDotNetEnum<IContact>.Create(vContactList);
doesn't
Using as to cast interfaces is only valid for interfaces that have GUIDs. The compiler cannot assume that T has a GUID when it's compiling your generic class, so it cannot accept an expression of the form val as T.
This has been covered before, but in reference to the Supports function, which has the same limitation as the as operator.
The solution is to use RTTI to fetch the interface's GUID, and then use that to type-cast the interface value. You could use Supports:
guid := GetTypeData(TypeInfo(T))^.Guid;
success := Supports(IUnknown(rgvar), guid, FCurrent);
Assert(success);
You could also call QueryInterface directly:
guid := GetTypeData(TypeInfo(T))^.Guid;
OleCheck(IUnknown(rgvar).QueryInterface(guid, FCurrent));
Related
program Test;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.Rtti;
function GetPropertyValue(const AObject: TObject; APropertyName: string): TValue;
var
oType: TRttiType;
oProp: TRttiProperty;
begin
oType := TRttiContext.Create.GetType(AObject.ClassType);
if oType <> nil then
begin
oProp := oType.GetProperty(APropertyName);
if oProp <> nil then
Exit(oProp.GetValue(AObject));
end;
Result := TValue.Empty;
end;
function GetAttributePropertyValue(const AClass: TClass; AAttribute: TClass;
AAttributePropertyName: string): TValue;
var
oAttr: TCustomAttribute;
begin
for oAttr in TRttiContext.Create.GetType(AClass).GetAttributes do
if oAttr.InheritsFrom(AAttribute) then
Exit(GetPropertyValue(oAttr, AAttributePropertyName));
Result := nil;
end;
function GetClassAttribute(const AClass: TClass; AAttribute: TClass): TCustomAttribute;
begin
for Result in TRttiContext.Create.GetType(AClass).GetAttributes do
if Result.InheritsFrom(AAttribute) then
Exit;
Result := nil;
end;
type
DescriptionAttribute = class(TCustomAttribute)
private
FDescription: string;
public
constructor Create(const ADescription: string);
property Description: string read FDescription;
end;
constructor DescriptionAttribute.Create(const ADescription: string);
begin
FDescription := ADescription;
end;
type
[Description('MyClass description')]
TMyClass = class(TObject);
var
oAttr: TCustomAttribute;
begin
{ ok, output is 'MyClass description' }
WriteLn(GetAttributePropertyValue(TMyClass, DescriptionAttribute, 'Description').AsString);
{ not ok, output is '' }
oAttr := GetClassAttribute(TMyClass, DescriptionAttribute);
WriteLn(DescriptionAttribute(oAttr).Description);
// WriteLn(oAttr.ClassName); // = 'DescriptionAttribute'
ReadLn;
end.
I need the rtti attribute. I was hoping to get attribute with function GetClassAttribute() but the result is not expected.
Result of function GetAttributePropertyValue() is correct (first WriteLn), but result of function GetClassAttribute() is DescriptionAttribute with empty Description value. Why?
What is correct way to get attribute as function result ?
TIA and best regards
Branko
The problem is that all RTTI related objects created during querying information (including attributes) are being destroyed if the TRttiContext goes out of scope.
You can verify this when you put a destructor on your attribute class.
Recent versions introduced KeepContext and DropContext methods on TRttiContext you can use or just put a global variable somewhere and cause it to trigger the internal creation by calling Create or something. I usually put the TRttiContext variable as class variable into the classes using RTTI.
KeepContext and DropContext can be used in code where you might not have one global TRttiContext instance that ensures its lifetime because you are using other classes, methods and routines that have their own TRttiContext reference - see for instance its use in System.Classes where during BeginGlobalLoading KeepContext is being called and in EndGlobalLoading DropContext.
Ignoring the fact that this uses the Aurelius Framework, this question is more about how I need to re-tweak the code to make the generic constructor injection work for both type:
< string >
and
< TCustomConnection >
Also ignore the fact that the child objects are in the same unit, I would generally put them in their own, but this just makes it easier to post in a question.
I'm trying use the Factory Method pattern to determine what type of connection it should be making at runtime depending on what object I instantiate.
At the moment it is hardcoding the type of link it is expecting on the create.
In the example I want to pass in a TModelDatabaseLink, but want to decide at run time what type of database connection it could be, or whether the database connection comes from a file.
Yes I know I could uncomment FFilename and just use this to hold the file name version, but I am always interested in learning a bit more.
unit Model.Database.Connection;
interface
uses
System.Classes,
Data.DB,
Aurelius.Drivers.Interfaces,
Aurelius.Engine.DatabaseManager;
type
TModelDatabaseLink<T> = class
private
//FFilename: string;
FConnection: T;
FOwnsConnection: boolean;
OwnedComponent: TComponent;
end;
TModelDatabaseConnection = class abstract
private
FDatabaseLink: TModelDatabaseLink<TCustomConnection>;
FDatabaseManager: TDatabaseManager;
FConnection: IDBConnection;
function CreateConnection: IDBConnection; virtual; abstract;
procedure CreateDatabaseManager;
public
constructor Create(ADatabaseLink: TModelDatabaseLink<TCustomConnection>);
destructor Destroy; override;
property Connection: IDBConnection read FConnection;
end;
TSQLLiteConnection = class(TModelDatabaseConnection)
private
function CreateConnection: IDBConnection; override;
end;
TFireDacConnection = class(TModelDatabaseConnection)
private
function CreateConnection: IDBConnection; override;
end;
implementation
uses
System.SysUtils,
Aurelius.Drivers.Base,
Aurelius.Drivers.SQLite,
Aurelius.Drivers.FireDac,
FireDAC.Stan.Intf, FireDAC.Stan.Option,
FireDAC.Stan.Error, FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Def,
FireDAC.Stan.Pool, FireDAC.Stan.Async, FireDAC.Phys, FireDAC.VCLUI.Wait,
FireDAC.Comp.Client;
{ TModelDatabaseConnection }
constructor TModelDatabaseConnection.Create(ADatabaseLink: TModelDatabaseLink<TCustomConnection>);
begin
FDatabaseLink := ADatabaseLink;
FConnection := CreateConnection;
if Assigned(FConnection) then
CreateDatabaseManager
else
raise Exception.Create('Failed to open database');
end;
procedure TModelDatabaseConnection.CreateDatabaseManager;
begin
FDatabaseManager := TDatabaseManager.Create(FConnection);
end;
destructor TModelDatabaseConnection.Destroy;
begin
FDatabaseManager.Free;
FDatabaseLink.Free;
inherited Destroy;
end;
{ TSQLLiteConnection }
function TSQLLiteConnection.CreateConnection: IDBConnection;
var
LFilename: String;
LAdapter: TSQLiteNativeConnectionAdapter;
begin
//LFileName := FDatabaseLink.FConnection; << needs to be type string
LAdapter := TSQLiteNativeConnectionAdapter.Create(LFilename);
LAdapter.DisableForeignKeys;
Result := LAdapter;
end;
{ TFireDacConnection }
function TFireDacConnection.CreateConnection: IDBConnection;
var
LAdapter: TFireDacConnectionAdapter;
begin
if Assigned(FDatabaseLink.OwnedComponent) then
LAdapter := TFireDacConnectionAdapter.Create(FDatabaseLink.FConnection as TFDConnection, FDatabaseLink.OwnedComponent)
else
LAdapter := TFireDacConnectionAdapter.Create(FDatabaseLink.FConnection as TFDConnection, FDatabaseLink.FOwnsConnection);
Result := LAdapter;
end;
end.
One other thing I would like to do if possible, is to change two creations:
LAdapter := TSQLiteNativeConnectionAdapter.Create(LFilename)
LAdapter := TFireDacConnectionAdapter.Create(FDatabaseLink.FConnection as TFDConnection, FDatabaseLink.OwnedComponent)
to use an abstract "GetAdapterClass" type function in the parent TModelDatabaseConnection and just declare the class of adapter in the child to do something like:
LAdapter := GetAdapterClass.Create...
An example of an adapter declaration is
TFireDacConnectionAdapter = class(TDriverConnectionAdapter<TFDConnection>, IDBConnection)
I have done this before when I wrote an abstract layer in case I need to replace Aurelius in my applications. I think the best way to deal with what you want to do is to use interfaces.
I am copying here some parts of my code with adjustments:
TServerType = (stLocal, stFireDac);
IDatabase = interface
function getDatabaseType: TServerType;
property DatabaseType: TServerType read getDatabaseType;
end;
IAurelius = interface (IDatabase)
['{990BB776-2E70-4140-B118-BEFF61FDBDAF}']
function getDatabaseConnection: IDBConnection;
function getDatabaseManager: TDatabaseManager;
property DatabaseConnection: IDBConnection read getDatabaseConnection;
property DatabaseManager: TDatabaseManager read getDatabaseManager;
end;
IAureliusLocal = interface (IAurelius)
['{9F705CC4-6E3B-4706-B54A-F0649CED3A8D}']
function getDatabasePath: string;
property DatabasePath: string read getDatabasePath;
end;
IAureliusFireDac = interface (IAurelius)
// I use this for a memory database but the logic is the same for FireDAC. You need to add the required properties
end;
TAurelius = class (TInterfacedObject, IAurelius)
private
fServerType: TServerType;
fDatabaseConnection: IDBConnection;
fDatabaseManager: TDatabaseManager;
function getDatabaseConnection: IDBConnection;
function getDatabaseManager: TDatabaseManager;
public
constructor Create (const serverType: TServerType);
end;
TAureliusLocal = class (TAurelius, IAureliusLocal)
private
fDatabasePath: string;
function getDatabasePath: string;
public
constructor Create (const databasePath: string);
end;
TAureliusFireDac = class (TAurelius, IAureliusFireDac)
public
constructor Create (const aConnection: TFDConenction); <-- or whatever parameters you need here to initalise the FireDac connection
end;
I am going to skip the code for all the getXXX functions here.
The constructors are these:
constructor TAurelius.Create(const serverType: TServerType);
begin
inherited Create;
fServerType:=serverType;
end;
constructor TAureliusLocal.Create (const databasePath: string);
const
databaseFilename = 'test.sqlite';
begin
inherited Create(stLocal);
fDatabasePath:=Trim(databasePath);
try
fDatabaseConnection:=
TSQLiteNativeConnectionAdapter.Create(
TPath.Combine(fDatabasePath, databaseFilename));
except
raise Exception.Create('stLocal database can''t be created');
end;
end;
constructor TAureliusFireDac.Create (const aConnection: TFDConenction);
begin
inherited Create(stFireDac);
// <-- here you initialise the connection like before but for FireDac
end;
Now, when you want to create an Aurelius database you use the following functions:
function createAureliusDatabase (const serverType: TServerType): IAurelius;
begin
case serverType of
stLocal: result:=TAureliusLocal.Create(path);
stFireDac: result:=TAureliusFireDac.Create(....);
end;
end;
...and you simply call it like this:
var
currentDatabase: IAurelius;
begin
currentDatabase:=createAureliusDatabase(stLocal,'c:\....');
end;
A better way to deal with the creation of the database is to use overloaded functions with different parameters or anonymous methods to avoid the case-end branch.
Anonymous methods are essentially interfaces with an Invoke method:
type
TProc = reference to procedure;
IProc = interface
procedure Invoke;
end;
Now, is there a possibility to assign them to an actual interface variable or pass them as interface parameter?
procedure TakeInterface(const Value: IInterface);
begin
end;
var
P: TProc;
I: IInterface;
begin
I := P; // E2010
TakeInterface(P); // E2010
end;
[DCC32 Error] E2010 Incompatible types: 'IInterface' and 'procedure, untyped pointer or untyped parameter'
Question: What would be the use case for this?
There are a lot of objects out there, that cannot be simply kept alive with an interface reference. Therefore they are wrapped in a closure and get destroyed with it, "Smart Pointers":
type
I<T> = reference to function : T;
TInterfaced<T: class> = class (TInterfacedObject, I<T>)
strict private
FValue: T;
function Invoke: T; // Result := FValue;
public
constructor Create(const Value: T); // FValue := Value;
destructor Destroy; override; // FValue.Free;
end;
IInterfacedDictionary<TKey, TValue> = interface (I<TDictionary<TKey, TValue>>) end;
TKey = String;
TValue = String;
var
Dictionary: IInterfacedDictionary<TKey, TValue>;
begin
Dictionary := TInterfaced<TDictionary<TKey, TValue>>
.Create(TDictionary<TKey, TValue>.Create);
Dictionary.Add('Monday', 'Montag');
end; // FRefCount = 0, closure with object is destroyed
Now, sometimes it is necessary to not only keep one single object alive but also a context with it. Imagine you have a TDictionary<TKey, TValue> and you pull an enumerator out of it: TEnumerator<TKey>, TEnumerator<TValue> or TEnumerator<TPair<TKey, TValue>>. Or the dictionary contains and owns TObjects. Then both, the new object and the dictionary's closure would go into to a new closure, in order to create one single, standalone reference:
type
TInterfaced<IContext: IInterface; T: class> = class (TInterfacedObject, I<T>)
strict private
FContext: IContext;
FValue: T;
FFreeObject: Boolean;
function Invoke: T; // Result := FValue;
public
constructor Create(const Context: IContext; const Value: T; const FreeObject: Boolean = True); // FValue = Value; FFreeObject := FreeObject;
destructor Destroy; override; // if FFreeObject then FValue.Free;
end;
IInterfacedEnumerator<T> = interface (I<TEnumrator<T>>) end;
TValue = TObject; //
var
Dictionary: IInterfacedDictionary<TKey, TValue>;
Enumerator: IInterfacedEnumerator<TKey>;
Obj: I<TObject>;
begin
Dictionary := TInterfaced<TDictionary<TKey, TValue>>
.Create(TObjectDictionary<TKey, TValue>.Create([doOwnsValues]));
Dictionary.Add('Monday', TObject.Create);
Enumerator := TInterfaced<
IInterfacedDictionary<TKey, TValue>,
TEnumerator<TKey>
>.Create(Dictionary, Dictionary.Keys.GetEnumerator);
Obj := TInterfaced<
IInterfacedDictionary<TKey, TValue>,
TObject
>.Create(Dictionary, Dictionary['Monday'], False);
Dictionary := nil; // closure with object still held alive by Enumerator and Obj.
end;
Now the idea is to melt TInterfaced<T> and TInterfaced<IContext, T>, which would make the type parameter for the context obsolete (an interface is enough) and result in these consturctors:
constructor TInterfaced<T: class>.Create(const Value: T; const FreeObject: Boolean = True); overload;
constructor TInterfaced<T: class>.Create(const Context: IInterface; const Value: T; const FreeObject: Boolean = True); overload;
Being a (pure) closure might not be the primary use one would think of when working with anonymous methods. However, their types can be given as an interface of a class whose objects can do cleanup on a closure's destruction, and a TFunc<T> makes it a fluent access to its content. Though, they don't share a common ancestor and it seems values of reference to types cannot be assigned to interface types, which means, there is no unified, safe and futureproof way to refer to all types of closures to keep them alive.
This is super easy. I will show you two ways.
var
P: TProc;
I: IInterface;
begin
I := IInterface(Pointer(#P)^);
TakeInterface(I);
end;
Another way is to declare PInterface
type
PInterface = ^IInterface;
var
P: TProc;
I: IInterface;
begin
I := PInterface(#P)^;
TakeInterface(I);
end;
To the best of my knowledge you cannot do what you need with casting.
You can, I suppose, use Move to make an assignment:
{$APPTYPE CONSOLE}
type
TProc = reference to procedure(const s: string);
IProc = interface
procedure Invoke(const s: string);
end;
procedure Proc(const s: string);
begin
Writeln(s);
end;
var
P: TProc;
I: IProc;
begin
P := Proc;
Move(P, I, SizeOf(I));
I._AddRef;//explicitly take a reference since the compiler cannot do so
I.Invoke('Foo');
end.
I've honestly no idea how robust this is. Will it work on multiple Delphi versions? Is it wise to rely on obscure undocumented implementation details? Only you can determine whether the gains you make outweigh the negatives of relying on implementation details.
The easiest way to cast is the folowing:
IProc((#P)^)
I want to create a TObjectList<T> descendant to handle common functionality between object lists in my app. Then I want to further descend from that new class to introduce additional functionality when needed. I cannot seem to get it working using more than 1 level of inheritance. I probably need to understand generics a little bit more, but I've search high and low for the correct way to do this without success. Here is my code so far:
unit edGenerics;
interface
uses
Generics.Collections;
type
TObjectBase = class
public
procedure SomeBaseFunction;
end;
TObjectBaseList<T: TObjectBase> = class(TObjectList<T>)
public
procedure SomeOtherBaseFunction;
end;
TIndexedObject = class(TObjectBase)
protected
FIndex: Integer;
public
property Index: Integer read FIndex write FIndex;
end;
TIndexedObjectList<T: TIndexedObject> = class(TObjectBaseList<T>)
private
function GetNextAutoIndex: Integer;
public
function Add(AObject: T): Integer;
function ItemByIndex(AIndex: Integer): T;
procedure Insert(AIndex: Integer; AObject: T);
end;
TCatalogueItem = class(TIndexedObject)
private
FID: integer;
public
property ID: integer read FId write FId;
end;
TCatalogueItemList = class(TIndexedObjectList<TCatalogueItem>)
public
function GetRowById(AId: Integer): Integer;
end;
implementation
uses
Math;
{ TObjectBase }
procedure TObjectBase.SomeBaseFunction;
begin
end;
{ TObjectBaseList<T> }
procedure TObjectBaseList<T>.SomeOtherBaseFunction;
begin
end;
{ TIndexedObjectList }
function TIndexedObjectList<T>.Add(AObject: T): Integer;
begin
AObject.Index := GetNextAutoIndex;
Result := inherited Add(AObject);
end;
procedure TIndexedObjectList<T>.Insert(AIndex: Integer; AObject: T);
begin
AObject.Index := GetNextAutoIndex;
inherited Insert(AIndex, AObject);
end;
function TIndexedObjectList<T>.ItemByIndex(AIndex: Integer): T;
var
I: Integer;
begin
Result := Default(T);
while (Count > 0) and (I < Count) and (Result = Default(T)) do
if Items[I].Index = AIndex then
Result := Items[I]
else
Inc(I);
end;
function TIndexedObjectList<T>.GetNextAutoIndex: Integer;
var
I: Integer;
begin
Result := 0;
for I := 0 to Count - 1 do
Result := Max(Result, Items[I].Index);
Inc(Result);
end;
{ TCatalogueItemList }
function TCatalogueItemList.GetRowById(AId: Integer): Integer;
var
I: Integer;
begin
Result := -1;
for I := 0 to Pred(Self.Count) do
if Self.Items[I].Id = AId then
begin
Result := I;
Break;
end;
end;
end.
/////// ERROR HAPPENS HERE ////// ???? why is beyond me
It appears that the following declaration:
>>> TCatalogueItemList = class(TIndexedObjectList<TCatalogueItem>) <<<<
causes the following compiler error:
[DCC Error] edGenerics.pas(106): E2010 Incompatible types:
'TCatalogueItem' and 'TIndexedObject'
However the compiler shows the error at the END of the compiled unit (line 106), not on the declaration itself, which does not make any sense to me...
Basically the idea is that I have a generic list descending from TObjectList that I can extend with new functionality on an as needs basis. Any help with this would be GREAT!!!
I should add, using Delphi 2010.
Thanks.
Your error is in the type casting, and the compiler error is OK (but it fails to locate the correct file in my Delphi XE3).
Your ItemByIndex method is declared:
TIndexedObjectList<T>.ItemByIndex(AIndex: Integer): T;
But then you have the line:
Result := TIndexedObject(nil);
This is fine for the parent class TIndexedObjectList, where the result of the function is of type TIndexedObject, but is not OK for the descendant class TCatalogueItemList, where the result of the function is of the type TCatalogueItem.
As you may know, a TCatalogueItem instance is assignment compatible with a TIndexedObject variable, but the opposite is not true. It translates to something like this:
function TCatalogueItemList.ItemByIndex(AIndex: Integer): TCatalogueItem;
begin
Result := TIndexedObject(nil); //did you see the problem now?
To initialize the result to a nil value, you can call the Default() pseudo-function, like this:
Result := Default(T);
In Delphi XE or greater, the solution is also generic. Rather than type-casting the result as a fixed TIndexedObjectList class, you apply a generic type casting use the T type
Result := T(nil);
//or
Result := T(SomeOtherValue);
But, in this specific case, type-casting a nil constant is not needed, since nil is a special value that is assignment compatible with any reference, so you just have to replace the line with:
Result := nil;
And it will compile, and hopefully work as you expect.
I have a lot of interfaces as a result of importing an type library. So, the interfaces are like this:
ISomeCollection = dispinterface
['{6592E851-3D65-4D04-B5F3-B137667B816A}']
procedure Remove(Identifier: OleVariant); dispid 2;
function Add(Name: OleVariant; DatabaseType_ID: OleVariant): ERSModel; dispid 3;
property _NewEnum: IUnknown readonly dispid -4;
property Item[Identifier: OleVariant]: ERSModel readonly dispid 4;
property _Item[Identifier: OleVariant]: ERSModel readonly dispid 0; default;
property Count: Integer readonly dispid 1;
end;
_NewEnum is a idiom for Visual Basic for-each loop statement consumption ( it's exactly like Delphi's for-in) of COM collection of objects - despite the declaration of being IUnknown, it's really an IEnumVARIANT interface. Since it's the only way to enumerate the collection' items, I got around it with:
{This class have just this class function}
class function TVariantUtils.GetAs<T>(pModeloOleVar: OleVariant): T;
begin
Result := (T(IUnknown(pModeloOleVar)));
end;
Use:
var
EnumColecction: IEnumVariant;
// TEnumeratorObjects: This is a generic class to implement an enumerator over
// an IEnumVARIANT interface
ListOfSubObjects: TEnumaretorObjects;
begin
...
EnumCollection := TVariantUtils.GetAs<IEnumVariant>(Object.SomeCollection._NewEnum);
ListOfSubObects := TEnumeratorObjects<ItemofSomeCollection>.Create(EnumCollection);
...
End;
The constructor receives an IEnumVariant parameter. What I want is create an constructor
that receive IInterface and determine if the ISomeCollection have an _NewEnum property of
IUnknown type - and do the above code once.
I don't know the name or the GUID of the interface on compile-time.
Obs: the delphi-xe tag is because I want to know the mechanism even if works only on Delphi XE
(even if I need to buy an Starter Edition just for this).
I use D2010.
EDIT:
My attempt using RTTI (it compiles but doesn't work):
constructor TEnumeratorVariant<T>.Create(pEnumeraVariante: IInterface);
var
EnumVar: IEnumVariant;
Contexto: TRttiContext;
InfoTipo: TRttiType ;
PropInfo: TRttiProperty;
pTipo: PTypeInfo;
begin
Contexto.Create;
pTipo := TypeInfo(pEnumeraVariante);
InfoTipo := Contexto.GetType(TypInfo(pEnumeraVariante));
PropInfo := InfoTipo.GetProperty('_NewEnum');
if Assigned(PropInfo) then
begin
Supports(PropInfo.GetValue(pEnumeraVariante), IEnumVariant, EnumVar);
Create(EnumVar);
end;
Contexto.Free;
PropInfo.Free;
InfoTipo.Free;
end;
Try the standard IDispatch method (not tested, you may need to tweak it):
function GetEnumerator(const Disp: IDispatch): IEnumVariant;
var
DispParams: TDispParams;
ExcepInfo: TExcepInfo;
Status: Integer;
VarResult: OleVariant;
begin
Result := nil;
FillChar(DispParams, SizeOf(DispParams), 0);
FillChar(ExcepInfo, SizeOf(ExcepInfo), 0);
Status := Disp.Invoke(DISPID_NEWENUM, GUID_NULL, LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET, DispParams, #VarResult, #ExcepInfo, nil);
if Succeeded(Status) then
Result := IUnknown(VarResult) as IEnumVariant
else
DispatchInvokeError(Status, ExcepInfo);
end;
You're on the right track. Delphi's RTTI can find methods of an interface, but the interface has to generate RTTI for those methods. It doesn't do that by default; you have to enable it. Put a {$M+} directive at the top of your type library import unit and it should work.