I'm trying to fetch an interface using D2010 RTTI.
program rtti_sb_1;
{$APPTYPE CONSOLE}
{$M+}
uses
SysUtils,
Rtti,
mynamespace in 'mynamespace.pas';
var
ctx: TRttiContext;
RType: TRttiType;
MyClass: TMyIntfClass;
begin
ctx := TRttiContext.Create;
MyClass := TMyIntfClass.Create;
// This prints a list of all known types, including some interfaces.
// Unfortunately, IMyPrettyLittleInterface doesn't seem to be one of them.
for RType in ctx.GetTypes do
WriteLn(RType.Name);
// Finding the class implementing the interface is easy.
RType := ctx.FindType('mynamespace.TMyIntfClass');
// Finding the interface itself is not.
RType := ctx.FindType('mynamespace.IMyPrettyLittleInterface');
MyClass.Free;
ReadLn;
end.
Both IMyPrettyLittleInterface and TMyIntfClass = class(TInterfacedObject, IMyPrettyLittleInterface) are declared in mynamespace.pas, in particular
unit mynamespace;
interface
type
IMyPrettyLittleInterface = interface
['{6F89487E-5BB7-42FC-A760-38DA2329E0C5}']
end;
TMyIntfClass = class(TInterfacedObject, IMyPrettyLittleInterface)
end;
//...
Do anyone know why this doesn't work? Is there a way to solve my problem? Thanks in advance!
This is a strange behavior you have found. You can find the type using:
RType := ctx.GetType(TypeInfo(IMyPrettyLittleInterface));
But after you have done this once you can access it by name, so if you need to access it by Name you can do the following to make it work.
Example Program:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils,
Rtti,
TypInfo,
Unit1 in 'Unit1.pas';
var
ctx : TRttiContext;
IType : TRttiType;
begin;
ctx := TRttiContext.Create;
IType := ctx.FindType('Unit1.IExample');
if Assigned(IType) then
begin
writeln('Found');
Writeln(IType.QualifiedName);
end
else
writeln('Not Found');
ReadLn;
end.
Example Unit:
unit Unit1;
interface
type
IExample = interface
['{D61F3245-13FB-44BF-A89D-BB358FAE7D19}']
end;
implementation
uses Rtti;
var
C : TRttiContext;
initialization
C.GetType(TypeInfo(IExample));
end.
The problem is that neither the VMT nor the typeinfo of classes which implement an interface contain any references to the typeinfo of those interfaces. The typeinfo for the interfaces is then eliminated by the linker if not otherwise referenced in the program. To fix this, there would need to be a typeinfo format change for classes to reference the implemented interfaces' typeinfo, or else all interfaces would need to be strongly linked into the executable. Other kinds of fixes, such as strong-linking interfaces that are implemented by linked-in classes without actually including references in the class typeinfo, are problematic owing to how the compiler's integrated smart linker works.
Another way around this issue is to use the directive {$STRONGLINKTYPES ON}. This will cause all types in the EXE, BPL or DLL's root type table (the index that lets the RTL map qualified names to types) to be linked in with strong fixups rather than weak fixups. Symbols that only have weak fixups referencing them are eliminated by the smart linker; if one or more strong fixups references the symbol, then it gets included in the final binary and the weak references don't get nil'ed (actually #PointerToNil) out.
As described in other answers, TypeInfo(ITheInterface) in non-dead code fixes the problem; this is because the TypeInfo() magic function creates a strong fixup to the interface.
Does IMyPrettyLittleInterface have a GUID? I don't think the RTTI system will recognize it if it doesn't.
RTTI is generated for types declared while the $M switch is active. Like all compiler directives, that switch has per-unit scope. You set it in your DPR file, but that setting has no effect in the unit where you declared your types.
Set that switch prior to your interface and class declarations:
type
{$M+}
IMyPrettyLittleInterface = interface
['{6F89487E-5BB7-42FC-A760-38DA2329E0C5}']
end;
TMyIntfClass = class(TInterfacedObject, IMyPrettyLittleInterface)
end;
Related
This is a bit puzzling for me as I'm working on an unit with several dozens of interfaces that are all based on this base interface definition:
type
IDataObject = interface(IInterface)
['{B1B3A532-0E7D-4D4A-8BDC-FD652BFC96B9}']
function This: TDataObject;
end;
ISomeObject = interface(IDataObject)
['{7FFA91DE-EF15-4220-A43F-2C53CBF1077D}']
<Blah>
end;
This means they all have a method 'This' that returns the class behind the interface, which is sometimes needed to put in listviews and stuff, but for this question it doesn't really matter because I want a generic class with additional functions that can be applied to any derived interface. (And any derived interface has their own GUID.) This is the generic class:
type
Cast<T: IDataObject> = class(TDataObject)
class function Has(Data: IDataObject): Boolean;
class function Get(Data: IDataObject): T;
end;
Doesn't look too complex and the use of class methods is because Delphi doesn't support global generic functions, unless they're in a class. So in my code I want to use Cast<ISomeObject>.Has(SomeObject) to check if the objects supports the specific interface. The Get() function is just to return the object as the specific type, if possible. So, next the implementation:
class function Cast<T>.Get(Data: IDataObject): T;
begin
if (Data.QueryInterface(T, Result) <> S_OK) then
Result := nil;
end;
class function Cast<T>.Has(Data: IDataObject): Boolean;
begin
Result := (Data.QueryInterface(T, Result) = S_OK);
end;
And this is where it gets annoying! Elsewhere in my code I use if (Source.QueryInterface(ISomeObject, SomeObject) = 0) then ... and it works just fine. In these generic methods the ISomeObject is replaced by T and should just work. But it refuses to compile and gives this error:
[dcc64 Error] DataInterfaces.pas(684): E2010 Incompatible types:
'TGUID' and 'T'
And that's annoying. I need to fix this but can't find a proper solution without hacking deep into the interface code of the System unit. (Which is the only unit I'm allowed to use in this code as it needs to run on many different platforms!)
The error is correct as QueryInterface expects a TGUID as parameter but it seems to get that from ISomeObject. So why not from T?
I think I'm trying to do the impossible here...
To be a bit more specific: Source.QueryInterface(ISomeObject, SomeObject) works fine without the use of any other unit. So I would expect it to work with a generic type, if that type is limited to interfaces. But that's not the case and I want to know why it won't accept T while it does accept ISomeObject.
Can you explain why it fails with a generic type and not a regular interface type?
QueryInterface() takes a TGUID as input, but an interface type is not a TGUID. The compiler has special handling when assigning an interface type with a declared guid to a TGUID variable, but that doesn't seem to apply inside of a Generic parameter that uses an Interface constraint. So, to do what you are attempting, you will just have to read the interface's RTTI at runtime to extract its actual TGUID (see Is it possible to get the value of a GUID on an interface using RTTI?), eg:
uses
..., TypInfo;
class function Cast<T>.Get(Data: IDataObject): T;
var
IntfIID: TGUID;
begin
IntfIID := GetTypeData(TypeInfo(T))^.GUID;
if (Data.QueryInterface(IntfIID, Result) <> S_OK) then
Result := nil;
end;
class function Cast<T>.Has(Data: IDataObject): Boolean;
begin
Cast<T>.Get(Data) <> nil;
end;
That being said, why are you duplicating functionality that the RTL already provides natively for you?
Your entire Cast class is unnecessary, just use SysUtils.Supports() instead (the SysUtils unit is cross-platform), eg:
uses
..., SysUtils;
//if Cast<ISomeObject>.Has(SomeObject) then
if Supports(SomeObject, ISomeObject) then
begin
...
end;
...
var
Intf: ISomeObject;
//Intf := Cast<ISomeObject>.Get(SomeObject);
if Supports(SomeObject, ISomeObject, Intf) then
begin
...
end;
Also, your IDataObject.This property is completely unnecessary, as you can directly cast an IDataObject interface to its TDataObject implementation object (Delphi has supported such casting since D2010), eg:
var
Intf: IDataObject;
Obj: TDataObject;
Intf := ...;
Obj := TDataObject(Intf);
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) :
A single COM library with both interfaces and CoClasses.
Two COM libraries one with the interfaces and another with the CoClasses
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).
I'm trying to implement Spring 4 Delphi and only program to interfaces instead of classes. However this seems impossible when you want to use a TObjectList.
Consider the following code:
unit Unit1;
interface
uses
Spring.Collections,
Spring.Collections.Lists;
type
IMyObjParent = interface
['{E063AD44-B7F1-443C-B9FE-AEB7395B39DE}']
procedure ParentDoSomething;
end;
IMyObjChild = interface(IMyObjParent)
['{E063AD44-B7F1-443C-B9FE-AEB7395B39DE}']
procedure ChildDoSomething;
end;
implementation
type
TMyObjChild = class(TInterfacedObject, IMyObjChild)
protected
procedure ParentDoSomething;
public
procedure ChildDoSomething;
end;
{ TMyObj }
procedure TMyObjChild.ChildDoSomething;
begin
end;
procedure TMyObjChild.ParentDoSomething;
begin
end;
procedure TestIt;
var
LMyList: IList<IMyObjChild>;
begin
TCollections.CreateObjectList<IMyObjChild>;
//[DCC Error] Unit1.pas(53): E2511 Type parameter 'T' must be a class type
end;
end.
I know I can change IMyObjChild to TMyObjChild in the example above, but if I need that in another unit or a form then how do I do this?
Trying to program only to interfaces seems too hard or impossible as soon as you need a TObjectList.
Grrr... Any ideas or help?
CreateObjectList has a generic constraint that its type parameter is a class:
function CreateObjectList<T: class>(...): IList<T>;
Your type parameter does not meet that constraint since it is an interface. The thing about an object list is that it holds objects. If you take a look at TObjectList in Spring.Collections.Lists you'll see that it also has the generic constraint that its type parameter is a class. And since CreateObjectList is going to create a TObjectList<T>, it must reflect the type constraint.
The raison d'ĂȘtre of TObjectList<T> is to assume ownership of its members through the OwnsObjects. Much in the same way as do the classic Delphi RTL classes of the same name. Of course you are holding interfaces and so you simply do not need this functionality. You should call CreateList instead. A plain TList<T> is what you need, even if you refer to it through the IList<T> interface.
LMyList := TCollections.CreateList<IMyObjChild>;
With RttiContext.FindType('Classes.TStringList') I get RttiType of TStringList with no problem. But with RttiContext.FindType('MyUnit.TMyClass') I always get nil (of course MyUnit is in uses clause). Why, what is wrong?
Example:
unit MyUnit;
interface
uses
Classes;
type
TMyClass = class(TStringList)
end;
implementation
end.
Main unit:
...
uses
MyUnit,
...
var
oCont: TRttiContext;
oType: TRttiType;
begin
oCont := TRttiContext.Create;
try
oType := oCont.FindType('MyUnit.TMyClass'); <== oType = nil !!
...
Probably the class has not included by the delphi linker in the final executable. A fast try can be the following:
Declare a static method on your class. This method should be an empty method, a simple begin end.
Call this static method in the initialization section of this unit.
Ensure that the unit is referenced in your project somewhere.
Now you should see the class with TRttiContext.FindType.
It could be a handful of things. Hard to say without seeing your code, but here are a few suggestions to look at. Is TMyClass a public type in the interface section? Is RTTI generation turned on for that unit? Is MyUnit in a package that hasn't been loaded yet?
The official documentation says they are optional. I know COM interop requires a unique identifier for each interface but every interface example I see has a GUID whether it's used with COM or not? Is there any benefit to including a GUID if its not going to be used with COM?
I've noticed that some methods such as Supports (to determine if a class conforms to a specific interface) require that you define a GUID before you can use them.
This page confirms it with the following information:
Note: The SysUtils unit provides an
overloaded function called Supports
that returns true or false when class
types and instances support a
particular interface represented by a
GUID. The Supports function is used in
the manner of the Delphi is and as
operators. The significant difference
is that the Supports function can take
as the right operand either a GUID or
an interface type associated with a
GUID, whereas is and as take the name
of a type. For more information about
is and as, see Class References.
Here's some interesting information about interfaces, which states:
Why does an interface need to be
uniquely identifiable? The answer is
simple: because Delphi classes can
implement multiple interfaces. When an
application is running, there has to
be a mechanism that will get pointer
to an appropriate interface from an
implementation. The only way to find
out if an object implements an
interface and to get a pointer to
implementation of that interface is
through GUIDs.
Emphasis added in both quotes.
Reading this entire article also makes you realize that QueryInterface (which requires a GUID) is used behind the scenes for reasons such as reference counting.
Only if you need your interface to be compatible with COM.
Unfortunately, that also includes using is, as operators and QueryInterface, Supports functions - the lack of which is rather limiting. So, while not strictly required, it's probably easier to use a GUID. Otherwise, you are left with rather simplistic usage only:
type
ITest = interface
procedure Test;
end;
ITest2 = interface(ITest)
procedure Test2;
end;
TTest = class(TInterfacedObject, ITest, ITest2)
public
procedure Test;
procedure Test2;
end;
procedure TTest.Test;
begin
Writeln('Test');
end;
procedure TTest.Test2;
begin
Writeln('Test2');
end;
procedure DoTest(const Test: ITest);
begin
Test.Test;
end;
procedure DoTest2(const Test: ITest2);
begin
Test.Test;
Test.Test2;
end;
procedure Main;
var
Test: ITest;
Test2: ITest2;
begin
Test := TTest.Create;
DoTest(Test);
Test := nil;
Test2 := TTest.Create;
DoTest(Test2);
DoTest2(Test2);
end;