A TDictionary is assigned to an TObject variable. I can cast the TOBject back to a TDictionary in the same project. However if the cast is done in a referenced bpl project the cast fails depending on how the project is referenced.
The test code:
procedure TestTDefault.Test1;
var
Obj: TObject;
begin
Obj := TDictionary<string, Integer>.Create;
Check(Obj is TDictionary<string, Integer>);
CheckNotNull(Obj as TDictionary<string, Integer>);
// this calls the function in the referenced bpl package.
// Returns True if Obj can be cast back to a TDictionary.
CheckTrue(TestIfDictionary(Obj)); // outcome depends if referenced packge is included in <DCC_UsePackage>
end;
The function in the dependent package:
function TestIfDictionary(aObject: TObject): Boolean;
begin
Result := aObject is TDictionary<string,Integer>;
end;
I have a simple project group with only two packages:
The DUnit Test Runner (Package1Tests.exe)
Package1.bpl
Both packages have the same compiler/linker options set.
What is very odd is that the test works only if Package1 is NOT listed as a runtime package:
However test fails if Package1 is listed a runtime package!!
I am using XE2. As to the purpose, this issue surfaced when dealing with RTTI. I was able to isolate the problem in this simple Test project. Problem has nothing to do with RTTI.
Just as a note, if instead of using a TDictionary I use a TStringList, then test always works. So problem seems to be somehow related to generics.
If needed I can make the simple test project available.
I have spent quite some time tracking down this problem. I am happy that I got to discover what triggers the problem. But unfortunately I can just not understand why this happens.
The problem you have here is related to generic instantiation. Each module is separately instantiating the generic type, and so they have different type identity. This is a basic limitation of the design of generics, and its interaction with runtime packages.
You could instantiate the generic type in a unit in the package, for instance like this:
type
TDictionaryStringInteger = class(TDictionary<string, Integer>);
And then use TDictionaryStringInteger in place of TDictionary<string, Integer>. However, this pretty much cripples your code as it stops you writing generic methods that use generic types.
The other way out of your bind is to stop using runtime packages. Frankly, that seems rather more attractive to me.
You should use type declaration for this generic class.
type
Tx = TDictionary<string, Integer>;
....
Obj := Tx.Create;
Check(Obj is Tx);
RTTI doesn't matter in your case.
Related
I am upgrading Delphi software from Delphi 6 (2001) to Delphi 11 Alexandria.
This software consists of many BPL's, but this code is not working properly. The is command is not returning True, when checking if the component from a BPL is an TIBQuery - although it really is.
procedure LoadDLLAndPassDatabaseConnection(const DLLName: string);
var
PackageHandle: HMODULE;
ServiceModule: TMyServiceModule;
I: Integer;
Component: TComponent;
begin
PackageHandle := LoadPackage(PChar(DLLName));
ServiceModule := TMyServiceModule(GetProcAddress(hInst,'GetServiceModule'));
if Assigned(ServiceModule) then
begin
for I:=0 to ServiceModule.ComponentCount - 1 do
begin
Component := ServiceModule.Components[I];
// This component is declared in another bpl.
// It really is an TIBQuery, but the following if never returns True...
// (Evaluating Component.ClassName results in 'TIBQuery')
if Component is TIBQuery then
begin
// this is never executed...
TIBQuery(Component).Database := GetDatabase;
end;
end;
end;
end;
I already considered to compare classnames, but this does not work for descendants. And we tried toggling project options such as "Emit runtime type information", but that's not making any difference.
How to get this working?
Thank you!
The is operator does not work across BPLs (DLLs) for the following reason:
The class you are inspecting is implemented inside its own unit file.
You build the BPL, link the unit, and a RTTI section is created inside the BPL file.
Then, you build the EXE, link the unit, and a new RTTI section is created inside the EXE file.
Now: the class name is the same for the two modules, but the RTTI, used by the is operator to check equality, are different, so the operator returns FALSE!
Solution: check equality againts class name.
I found this somewhere, but it seems to contradict Antionio's answer a bit.
When you use packages, there is only ever one copy of any unit in
memory. One copy of Forms, one copy of SysUtils, one copy of System
(well, most of it), one copy of StdCtrls, etc.
All class-related operations, such as the "is" and "as" operators, rely
on class references. Class references are actually just addresses. They
point to definitions for the layouts of the classes' internals. (They
point to what's called the virtual-method table, the VMT.) Two classes
are the same if they point to the same VMT -- if the addresses are equal.
When you have a class defined in the EXE's copy of StdCtrls and the same
class defined in a DLL's copy of StdCtrls, those classes will really
have different addresses. The "is" and "as" operators won't work with
cross-module clases. But when you use packages, there is only one copy
of the class, kept in vcl70.bpl, so all modules that reference that
package will share a single class definition.
As Antonio Petricca wrote (thank you!), it's not possible to use the is operator. I now have implemented this by comparing (ancestor) class names - I want to share the code:
function IsSameClassOrAncestor(const ClazzToCheck: TClass; const Clazz: TClass): Boolean;
begin
Result := SameText(ClazzToCheck.ClassName, Clazz.ClassName);
if not Result and not SameText(ClazzToCheck.ClassName, 'TObject') then
begin
Result := IsSameClassOrAncestor(ClazzToCheck.ClassParent, Clazz);
end;
end;
This way, I can check this as follows:
if IsSameClassOrAncestor(Component, TIBQuery)
begin
// The code is now executed correctly, also for descendants
TIBQuery(Component).Database := GetDatabase;
end;
Such a basic question, but I'm stuck...
I'm trying to get started with UI Automation using Delphi 2007 (Win32) on Windows 7. It seems I don't have declarations for some of the methods and types I need to use. I have .NET Framework 4.x installed on this machine, but I imported a type library from C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\v3.0\UIAutomationClient.dll...which in UIAutomationClient_TLB.pas I see is:
UIAutomationClientMajorVersion = 1;
UIAutomationClientMinorVersion = 0;
I'm not sure if that's the problem (wrong version). I can declare the following with no problem:
var
UIAuto: IUIAutomation;
Element: IUIAutomationElement;
But methods of IUIAutomationElement appear to be missing. For instance, there's nothing in the interface declaring the method:
IUIAutomationElement.TryGetCurrentPattern()
...which, according to msdn.microsoft.com is an interface method going back to at least .NET 3.0.
Where / how do I get the necessary interface declarations? Is this possibly a registration problem? If so, what needs to be registered, and how?
Ultimately, I'm wanting to play with retrieving text from controls via UI Automation with something like the following, but technically speaking, I think you have to get code to compile before you can consider it a failure. ;)
var
UIAuto: IUIAutomation;
Element: IUIAutomationElement;
RetVal: HResult;
APattern: AutomationPattern; //not defined!
ValuePattern : ValuePattern; //not defined!
begin
UIAuto := CoCUIAutomation.Create;
Element := UIAuto.GetFocusedElement(RetVal);
if Assigned(Element) then begin
if Element.TryGetCurrentPattern(ValuePattern.Pattern, APattern) then begin //not defined!
Result := ValuePattern.Current.Value; //not defined!
...
end;
It looks like you have imported the .net assembly.
From native code it is best to import the native COM type library. The steps are:
Component | Import Component.
Import a Type Library.
Select UIAutomationClient which is implemented in UIAutomationCore.dll.
This imports a type library and creates a unit named UIAutomationClient_TLB.
The method that you need is IUIAutomationElement.GetCurrentPattern. The TryGetCurrentPattern method in the .net version of the interface is simply a convenience method that indicates failure with a boolean return value rather than by raising an exception. When you call IUIAutomationElement.GetCurrentPattern you will need to check the HRESULT return value to detect failure.
You are importing a .NET class as a COM object. In .NET, classes and methods have to be explicitly declared as COMVisible=True to be accessible via COM. Without seeing how UIAutomationClient.dll actually exports its AutomationElement class, my guess would be that its TryGetCurrentPattern() method is not declared a COM-visible.
The IUIAutomationElement interface exported from UIAutomationCore.dll, on the othe hand, does not have a TryGetCurrentPattern() method.
An example with types with the same identity (TLevel and integer)
unit UnitType;
interface
type
TLevel = integer;
TObj = class
public
procedure Test(Level: TLevel);virtual;
end;
There's another unit where another object inherits from this object (Notice integer instead of TLevel, but this is not a problem since they're not distinct)
unit UnitOther;
interface uses UnitType;
type
TInhObj = class(TObj)
public
procedure Test(Level: integer);override;
end;
Everything compiles as usual.
Now I modify TLevel type to be distinct
TLevel = type integer;
and try to compile, but everything compiles fine.
I go to UnitOther and change something unrelated (even just resave it). Now I have "declaration of Test differs from previous declaration" in unit UnitOther
Is this correct actions or a bug that was fixed in latest versions of Delphi? (mine is 5)
I'm using Delphi XE and it still behaves exactly as you described. One workaround is to periodically run the Build event (i.e. before you commit your code). This isn't a very eloquent solution but at least it will allow the changed typed to be detected without having to touch all the files that use it.
What's the reason this won't compile?
type
IInterfaceA = interface ['{44F93616-0161-4912-9D63-3E8AA140CA0D}']
procedure DoA;
end;
IInterfaceB = interface(IInterfaceA) ['{80CB6D35-E12F-462A-AAA9-E7C0F6FE0982}']
procedure DoB;
end;
TImplementsAB = class(TSingletonImplementation, IInterfaceB)
procedure DoA;
procedure DoB;
end;
var
ImplementsAB: TImplementsAB;
InterfaceA: IInterfaceA;
InterfaceB: IInterfaceB;
begin
ImplementsAB := TImplementsAB.Create;
InterfaceA := ImplementsAB; >> incompatible types
...
end
In contrast this is how I make it work:
InterfaceA := ImplementsAB as InterfaceB;
or
InterfaceA := InterfaceB;
I mean, if IInterfaceB inherits from IInterfaceA and TImplementsAB implements IInterfaceB, it wouldn't be logical to also implement IInterfaceA and be type compatible?
This so because early OLE/COM had a bug and Borland decided to be compatible with it. This is mentioned in this article: New Delphi language feature: Multiple inheritance for interfaces in Delphi for .NET. The solution is to list all ancestor interfaces explicitly in the class as Mikael wrote.
Some quotes from the linked article:
The problem was in COM itself. To load a module, COM would load the DLL, GetProcAddress on a well-known entry point that was supposed to be exported from the DLL, call the DLL function to obtain an IUnknown interface, and then QueryInterface for IClassFactory. The problem was, when Microsoft added support for IClassFactory2, they added the QueryInterface for IClassFactory2 after the existing code that queried for IClassFactory. IClassFactory2 would only be requested if the query for IClassFactory failed.
Thus, COM would never request IClassFactory2 on any COM server that implemented both IClassFactory2 and IClassFactory.
This bug existed in COM for a long time. Microsoft said that they couldn't fix the COM loader with an OS service pack because both Word and Excel (at the time) relied on the buggy behavior. Regardless of whether it's fixed in the latest releases of COM or not, Borland has to provide some way to preserve this behavior in Win32 Delphi for the forseeable future. Suddenly adding all ancestors into an implementing class that weren't there before is very likely to break existing code that unintentionally falls into the same pattern as the COM loader.
Another way to make it work is to include both interfaces in the class declaration.
TImplementsAB = class(TSingletonImplementation, IInterfaceA, IInterfaceB)
procedure DoA;
procedure DoB;
end;
I guess this is what is required for the compiler to realize that TImplementsAB implements both IInterfaceA and IInterfaceB.
Using Delphi 2010...
I have a set of binary properties I want to group together. I have defined it as such...
type
TTableAttributeType = (
tabROOT = 1,
tabONLINE = 2,
tabPARTITIONED = 3,
tabCOMPRESSED = 4,
);
// Make a set of of the Table Attribute types...
type
TTableAttrSet = Set of TTableAttributeType;
In my MAIN.PAS unit, I can create a variable of type TTableAttrSet.
Another Unit, UTILS.PAS needs to understand the TTableAttrSet type as well. That is taken care of by the USES clauses...
Main USES Util...
Util USES Main (The 2nd uses clauses, under implementation section, so I don't get circular reference problems....
So far so good. My problem is that I need to pass a var variable of type TTableAttrSet FROM main to Utils.
In main.pas
procedure TForm1.Button1Click(Sender: TObject);
var
TabAttr : TTableAttrSet;
begin
TestAttr (TabAttr);
end;
and in UTILS.PAS, I have
procedure TestAttr(var Attr: TTableAttrSet);
begin
Attr := [tabROOT, tabCOMPRESSED];
end;
When I try this, I run into several problems...
Problem 1). When I define my procedure definition at the top of utils.pas,
procedure TestAttr(var Attr: TTableAttrSet);
I get the error that TTableAttrSet is an Undeclared Identifier. This makes sense because the definition is in Main.pas, and the 'uses Main.pas' is AFTER my procedure definitions. How do I get around this? For now, I have duplicated the TTableAttrSet type definition at the top of the Utils.pas file as well as Main.pas, but this does not 'seem the right way'.
Problem 2). The bigger issue that I am running into is a compile error. on the calling line in Main.pas
TestAttr(TabAttr);
I get the error 'Types of actual and formal var parameters must be identifical'. To my knowledge they are identical. What is the compiler complaining about?
The simple solution is to move the declaration of TTableAttributeType to the Utils unit. You can't declare it twice because then you have two distinct types. That's no use to you, you need only a single type.
This solution will work so long as the Main unit does not need to reference TTableAttributeType in its interface section. Since the Utils unit clearly needs to do so then that would create a circular dependency between unit interface sections which is illegal.
If both the Main and Utils units need to reference TTableAttributeType in their interface sections then you need to create another unit that just contains type declarations. That unit could be used by both Utils and Main in their interface sections.