How to cast one interface to another in Delphi 7? - delphi

I am stuck with the following problem:
I have a class, which was generated from an xsd file using the Delphi 7 XML Data binding wizard (New -> Other -> XML Databindng).
I need to find a way to add methods to the generated code:
IXMLGlobeSettingsType = interface(IXMLNode)
['{9A8F5C55-F593-4C70-85D2-68FB97ABA467}']
{ Property Accessors }
function Get_General: IXMLGeneralSettingsType;
function Get_Projector: IXMLProjectorSettingsType;
function Get_LineMode: IXMLLineDrawingSettingsType;
{ Methods & Properties }
property General: IXMLGeneralSettingsType read Get_General;
property Projector: IXMLProjectorSettingsType read Get_Projector;
property LineMode: IXMLLineDrawingSettingsType read Get_LineMode;
//procedure SetDefault; {To be added}
end;
The interface is implemented by a corresponding class, which is also generated by the wizard:
TXMLGlobeSettingsType = class(TXMLNode, IXMLGlobeSettingsType)
protected
{ IXMLGlobeSettingsType }
function Get_General: IXMLGeneralSettingsType;
function Get_Projector: IXMLProjectorSettingsType;
function Get_LineMode: IXMLLineDrawingSettingsType;
public
procedure AfterConstruction; override;
end;
And in order to define my own extensions to the generated code, I have defined the following interface:
IDefaultable = interface
procedure SetDefault;
end;
With the following implementation class:
DefaultableXMLGlobeSettingsType = class(TXMLGlobeSettingsType, IDefaultable)
public
procedure SetDefault;
end;
However, I just realized that Delphi 7 does not let me cast one interface to another (or even from an interface to an object). So the following code will raise an error:
defaultSettings : IDefaultable;
FGlobeSettingsIntf: IXMLGlobeSettingsType; // FGlobeSettingsIntf is in fact a DefaultableXMLGlobeSettingsType
// some code
defaultSettings := FGlobeSettingsIntf as IDefaultable; // error: operator not applicable to this operand type
I am pretty much stuck here. How can get around this error? Is there a way (even an ugly one) in Delphi 7 to cast the Interface to an object and then back to another interface.

defaultSettings := FGlobeSettingsIntf as IDefaultable;
// error: operator not applicable to this operand type
This error indicates that the definition of IDefaultable does not include a GUID. Without a GUID it is not possible to query for an interface, which is what the as operator does in this context.
The as operator, when used with a interface on the right hand side, is implemented by a call to IInterface.QueryInterface. That requires a GUID to be associated with the interface.
Resolve the problem by adding a GUID when you declare IDefaultable.

This is what Supports is for:
if Supports(FGlobeSettingsIntf, IDefaultable, defaultSettings) then begin
defaultSettings.SetDefault;
end;

Related

Delphi property getter function using generics

It would be nice to have generic property getters/setters to perform a common task on each access.
That code gives a compile-time error in Delphi XE2 'E2008 Incompatible types'. A similar code gave an internal error during compilation, but never compiles. Do I make a mistake or it is a compiler limitation?
type TFoo = class
private
function Get<T>: T;
public
property Bar: Integer read Get<Integer>;
end;
function TFoo.Get<T>: T;
begin
Result := 0;
end;
The following things can be generic in the Delphi language:
classes, e.g. TFooClass<T> = class
records, e.g. TFooRecord<T> = record
interfaces, e.g. TFooInterface<T> = interface
procedural types, e.g. TFooProc<T> = procedure
methods, e.g. procedure FooMethod<T>()
Properties cannot be generic themselves, and cannot be implemented using generic getter or setter methods.

Automatically serializing a TObject to JSON using mormot

I am trying to serialize an TObject to JSON using the mORMot framework. Unfortunately, the result is always null.
The class I am trying to serialize is:
type ApmTime = class(TObject)
private
function currentTime() : String;
published
property Current_time: String read currentTime;
public
constructor Create;
end;
constructor ApmTime.Create;
begin
inherited;
end;
function ApmTime.currentTime() : String;
begin
result := TimeToStr(Now);
end;
And the corresponding mORMot method is defined in SynCommons:
currentTime := ApmTime.Create;
Write(ObjectToJSON(currentTime, [woFullExpand]));
This always returns null. After having single-stepped in TTextWriter.WriteObject (located in unit SynCommons), the following piece of code seems to be where the resulting json is set to null:
if not(woFullExpand in Options) or
not(Value.InheritsFrom(TList)
{$ifndef LVCL} or Value.InheritsFrom(TCollection){$endif}) then
Value := nil;
if Value=nil then begin
AddShort('null');
exit;
I am expecting something along the line:
{
"Current_time" : "15:04"
}
Ran into this yesterday and worked out what's going on, so for the benefit of future people stumbling over this problem as well:
If you only add SynCommons.pas to your uses clause, then the default DefaultTextWriterJSONClass is set to TTextWriter which only supports serializing particular class types as you've seen, and doesn't support arbitrary classes/objects. See the following lines in SynCommons.pas where this default is set:
var
DefaultTextWriterJSONClass: TTextWriterClass = TTextWriter;
Now, in order to support serializing arbitrary objects to JSON, this global variable needs to be changed from the default TTextWriter to TJSONSerializer.
This class is defined in mORMot.pas, and in fact, if you add mORMot.pas to your uses clause, its initialization will override the above default and set TJSONSerializer as the new default for you.
This behaviour is in fact documented in SynCommons.pas if you read carefully enough, e.g. see the comments for "SetDEfaultJSONClass()" class method:
// you can use this method to override the default JSON serialization class
// - if only SynCommons.pas is used, it will be TTextWriter
// - but mORMot.pas initialization will call it to use the TJSONSerializer
// instead, which is able to serialize any class as JSON
class procedure SetDefaultJSONClass(aClass: TTextWriterClass);
So in short: To fix your issue, just add mORMot.pas to your uses clause in addition to SynCommons.pas which you should already have.
Try add a write to the published property.
property Current_time: String read currentTime write SetCurrentTime.
A readonly property is not serialized. Also ApmTime should be based on TPersistent
type
ApmTime = class(TPersistent)

Use Rtti to set method field

I'm using Delphi XE to write a base class, which will allow descending classes to have dll methods mapped by applying an annotation. However I get a typecasting error, which is understandable.
In essence the base class should look like this:
TWrapperBase = class
public
FLibHandle: THandle;
procedure MapMethods;
end;
procedure TWrapperBase.MapMethods;
var
MyField: TRttiField;
MyAttribute: TCustomAttribute;
pMethod: pointer;
begin
FLibHandle := LoadLibrary(PWideChar(aMCLMCR_dll));
for MyField in TRttiContext.Create.GetType(ClassType).GetFields do
for MyAttribute in MyField.GetAttributes do
if MyAttribute.InheritsFrom(TMyMapperAttribute) then
begin
pMethod := GetProcAddress(FLibHandle, (MyAttribute as TMyMapperAttribute).TargetMethod);
if Assigned(pMethod) then
MyField.SetValue(Self, pMethod); // I get a Typecast error here
end;
And a descending class could look like this:
TDecendant = class(TWrapperBase)
private type
TSomeDLLMethod = procedure(aParam: TSomeType); cdecl;
private
[TMyMapperAttribute('MyDllMethodName')]
FSomeDLLMethod: TSomeDLLMethod;
public
property SomeDLLMethod: TSomeDLLMethod read FSomeDLLMethod;
end;
I could implement this differently, by hard coding the linking for each method in an overriden 'MapMethods'. This would however require each descendant to do so which I'd like to avoid.
I know that the TValue as used in this case will contain a pointer and not of the correct type (procedure(aParam: TSomeType); cdecl; in this case).
My question: Is there a way to pass the pointer from 'GetProcAdress' as the correct type, or to set the field directly (for example by using the field address 'PByte(Self)+MyField.Offset', which you can use to set the value of a record property)?
With the old Rtti, this could be done but only for published properties and without any type checking:
if IsPublishedProp(Self, 'SomeDLLMethod') then
SetMethodProp(Self, 'SomeDLLMethod', GetProcAddress(FLibHandle, 'MethodName');
There are two problems:
First your EInvalidCast is caused by TValue being very strict about type conversions. You are passing in a Pointer and want to set a field of type TSomeDLLMethod. You need to explicitly pass a TValue that has the correct type info.
if Assigned(pMethod) then
begin
TValue.Make(#pMethod, MyField.FieldType.Handle, value);
MyField.SetValue(Self, value);
end;
Now you will run into another EInvalidCast exception which is triggered because of a bug in XE inside the GetInlineSize method of the Rtti.pas which returns 0 for a tkProcedure kind of type. I don't know in what version this got fixed but it does not exist anymore in XE5.
For XE this can be fixed by using a unit I wrote some while ago (and which I just updated to fix this bug): RttiPatch.pas.
I also reported the original issue because Pointer is assignment compatible to a procedure type so TValue should also handle this: http://qc.embarcadero.com/wc/qcmain.aspx?d=124010
You could try something like:
Move(pMethod, PByte(Self) + Field.Offset, SizeOf(Pointer));
or
PPointer(PByte(Self) + Field.Offset)^ := pMethod;

What does .Add mean when used in a constructor?

I come from a vb/c# background and I am having difficulty understanding the meaning of part of the following code, specifically the bit 'self.fColConsignments.Add'
TConsignment = class(TCollectionItem)
constructor Create(Collection : TCollection); override;
...
function TIFCSUMMsg.AddConsignment: TConsignment;
begin
result := TConsignment(self.fColConsignments.Add);
end;
if you background is C#, don't missinterpret that line:
result := TConsignment(self.fColConsignments.Add);
it's just a type cast and not a constructor call. In C# it would look like:
result = (TConsignment)self.fColConsignments.Add;
Presumably fcolConsignments is a collection owned by the TIFCSUMMsg instance (Self). Add adds a new item to the collection and returns the reference as the result. The result is then cast to a TConsignment to fit the result type of the AddConsignment method.
self.fColConsignments.Add probably adds a new item into fColConsignments, which must be a collection or similar, and returns it. But the declared return type may be more generic than the actual object returned, then a typecast is applied by using TConsignment(object).
The code in your example IS NOT A CONSTRUCTOR.
In C++/C#/Java/(put your C descendant language here), constructors are nameless methods. So:
class TFoo {
TFoo() { // do something }
}
....
{
TFoo myFoo;
myFoo = new TFoo()
.....
}
This a typical construction on such languages. This is NOT how Delphi works.
Constructors in Delphi have names. The convention is that they are called .Create and
they can be static or virtual ones (like any method).
The code above can be converted to:
TFoo = class
constructor Create();
end;
...
constructor TFoo.Create()
begin
// Do something;
end;
....
// Creating an object
var
myFoo: TFoo;
begin
myFoo := TFoo.Create();
...
end;
The code you exemplified were not an constructor but a
kind of typecast.
You can get more information about this (typecasts and constructors)
in the Delphi Language Guide (or Object Pascal Language Guide, depending
on the Delphi version you have available).

How can I pass an instance of a .NET COM object from Delphi to another .NET COM object?

I have a legacy app written in Delphi 7. We are adding new modules to the app. The modules are written in Visual Studio 2010, .NET 4, C#, and exposed to the app through COM.
I have successfully defined a class, registered the assembly, exported the type library, imported the type library into Delphi, created the COM client in Delphi and executed the module. Now comes the tricky part: I want to pass another object (that has been defined-registered-exported-blah-blah-blah as above) as a parameter to a method on the first module.
.NET
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("11111111-1111-1111-1111-AAAAAAAAAAAA")]
public interface IUserMaintenance
{
bool AssignUser(IUserInfo);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
[Guid("11111111-1111-1111-1111-BBBBBBBBBBBB")]
public class UserMaintenance: IUserMaintenance
{
private IUserInfo _UserInfo;
public bool AssignUser(IUserInfo userInfo)
{
_UserInfo = userInfo;
LoadUser();
}
private void LoadUser()
{
//load user data from database using _UserInfo.UserName
}
}
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("22222222-2222-2222-2222-AAAAAAAAAAAA")]
public interface IUserInfo
{
void Initialize(string userName);
string UserName { get; }
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
[Guid("22222222-2222-2222-2222-BBBBBBBBBBBB")]
public class UserInfo: IUserInfo
{
public string UserName { get; private set };
public void Initialize(string userName)
{
UserName = userName;
}
}
Assuming that I have separate classes implementing each interface, and the assemblies containing those classes compile and register successfully, I import the type libraries into Delphi creating UserMaintenance_TLB.pas and UserInfo_TLB.pas. However, I see something unexptected: while the interfaces that I defined in .NET exist (the ones beginning with "I"), Delphi has generated another set of interfaces (the ones beginning with "_"). Delphi does not use the I-interfaces that I declared in .NET at all.
Delphi
UserMaintenance_TLB.pas
// *********************************************************************//
// Forward declaration of types defined in TypeLibrary
// *********************************************************************//
IUserMaintenance = interface;
IUserMaintenanceDisp = dispinterface;
_UserMaintenance = interface;
_UserMaintenanceDisp = dispinterface;
UserInfo_TLB.pas
// *********************************************************************//
// Forward declaration of types defined in TypeLibrary
// *********************************************************************//
IUserInfo = interface;
IUserInfoDisp = dispinterface;
_UserInfo = interface;
_UserInfoDisp = dispinterface;
Delphi has also created the corresponding Delphi types:
// *********************************************************************//
// OLE Server Proxy class declaration
// Server Object : TUserMaintenance
// Help String :
// Default Interface: _UserMaintenance
// Def. Intf. DISP? : No
// Event Interface:
// TypeFlags : (2) CanCreate
// *********************************************************************//
TUserMaintenance = class(TOleServer)
private
FIntf: _UserMaintenance;
function GetDefaultInterface: _UserMaintenance;
protected
procedure InitServerData; override;
function Get_ToString: WideString;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Connect; override;
procedure ConnectTo(svrIntf: _UserMaintenance);
procedure Disconnect; override;
function Equals(obj: OleVariant): WordBool;
function GetHashCode: Integer;
function GetType: _Type;
WordBool AssignUser(IUserInfo userInfo);
property DefaultInterface: _UserMaintenance read GetDefaultInterface;
property ToString: WideString read Get_ToString;
published
end;
What I would like to do is create an instance of TUserMaintenance, and then pass an instance of TUserInfo to it. However, two things are immediately obvious: The Delphi class TUserInfo does NOT implement IUserInfo, and the DefaultInterface is the new interface that Delphi generated, _UserInfo. I cannot declare my UserInfo variable as type IUserInfo because TUserInfo does not implement the interface. Nor can I declare it as type _UserInfo because UserMaintenance.LoadUser expects an IUserInfo instance.
Admittedly, this is a much simplified example of my actual issue, but I think that it sufficiently illustrates the problem.
So my question is this: is there any way for me to force the interface type in Delphi to remain consistent with the interface that is declared in .NET? Or is there another way that I can pass an instance of UserInfo to UserMaintenance?
Part of the problem that in Delphi, Objects may implement interfaces, but they are not in and of themselves interfaced objects.
To understand this distinction, you have to look at the raw implementation of an interface
on a Delphi object, and understand the reference counting mechanism used by COM. Another thing
that has to be understood is that .NET doesn't work the same way, so what appears to be an
interface in .NET for an object IS the same as the ComVisible aspect presented.
TYPE
TMyObject = class(TInterfacedObject, IMyObject, IMyNewInterface)
end;
Given the above, the following things can be said of this object.
You created a new COM object using the Delphi COM object wizard.
The Interface IMyObject is defined as the default interface, and TMyObject is the concrete class
that will implement that interface.
IMyNewInterface is a secondary interface defined somewhere else, that you have indicated your
object implements.
You can do the following with this object
var
I1: IMyObject;
I2: IMyNewInterface;
T: TMyObject;
begin
I1:=TMyObject.Create;
I2:=TMyObject.Create;
T:=TMyObject.Create;
end;
You can do these things because TMyObject IMPLEMENTS these interfaces. Also,
since these are reference counted, you don't have to release them from memory, when
the method scope ends, so does the lifetime of the object - WITH THE EXCEPTION OF THE LAST ONE
Because you are using an OBJECT REFERENCE instead of an INTERFACE REFERENCE, you must free
the OBJECT you created.
Given the code you posted, if you look closely, you will actually find the same situation.
In your case, your object is completely implemented in .NET, so what you are trying to
use is a Delphi code Wrapper - which is an OBJECT, not an INTERFACE.
You notice on the wrapper, that there is no second method to pass an instance
of the IUserInfo object to, and that is because this OBJECT is not implementing the INTERFACE,
(your .NET object was created to do that) - what you need to do in Delphi is "select that interface"
You would accomplish that task by doing the following:
If you have already done a TUserMaintenance.Create(nil) call and have an instance of that object,
get the default interface, then "cast" it to the appropriate implemented interface
var
UMDelphiWrapper: TUserMaintenance;
UIDelphiWrapper: TUserInfo;
UI: IUserInfo;
UM: IUserMaintenance;
begin
//this part creates a Delphi OBJECT reference to the Implementation class -
//this is NOT Reference counted, because it doesn't implement an interface.
UIDelphiWrapper:=TUserInfo.Create(nil);
try
//this is the part where you acquire the interface of the object that actually
//implementes this interface - e.g. your .NET class
//not that this is the INTERFACE reference - which WILL be reference counted
UI:=UIDelphiWrapper.DefaultInterface as IUserInfo;
//UI.<Set some properties of your IUserInfo object>
try
//this part creates a Delphi OBJECT reference to the Implementation class -
//this is NOT Reference counted, because it doesn't implement an interface.
UMDelhpiWrapper:=TUserMaintenance.Create(nil);
try
//this is the part where you acquire the interface of the object that actually
//implementes this interface - e.g. your .NET class
//not that this is the INTERFACE reference - which WILL be reference counted
UM:=UMdelphiWrapper.DefaultInterface as IUserMaintenance;
try
//Here, you have an interface type implemented by your .NET class that you are
//sending to the implementation of your management object (Also a .NET class)
UM.SendUser(UI);
//do whatever else you need to do with your interface and user/management .NET object(s)
finally
//this IS a reference counted COM object - no "free" necessary
//this would naturally happen when the reference goes out of scope in the method
//but for clairity sake is set to NIL to explicitly release your reference
UM:=nil;
end;
finally
//This is a delphi object that is NOT reference counted and must be released
FreeAndNil(UMDelphiWrapper);
end;
finally
//this IS a reference counted COM object - no "free" necessary
//this would naturally happen when the reference goes out of scope in the method
//but for clairity sake is set to NIL to explicitly release your reference
UI:=nil;
end;
Finally
//This is a delphi object that is NOT reference counted and must be released
FreeAndNIl(UIDelphiWrapper);
end;
In addition to actually using the Delphi provided wrappers you could instead directly create references to your .NET objects as long as you know the correct information.
var
UI: IUserInfo;
UM: IUserManager;
begin
UI:=CreateOleObject('YourAssembly.YourImplementationClass') as IUserInfo;
UI.SomeProperty:=SomeValue;
UM:=CreateOleObject('YourAssembly.YourImplementationClass') as IUserManger;
UM.SomeMetohd(UI);
end;
This code is much cleaner - however, you must still have the accurate definition of IUserInfo and IUserMaintenance, as well as know the class friendly name of your objects as they have been registered with COM.
You could do this yourself by typing the code out - this is what the type import function should have done for you when you imported the COM exposed DLL from your assembly. I didn't see the actual implementation in the code you provided, but you should still find this information in your header file. -- If you don't, then you didn't import the correct assembly, or the Delphi 7 import didn't function correctly - or it needs to be refreshed (e.g. you added new methods to your .NET implementation, but didn't re-register (with COM) and re-import the new assembly type info).
type
IUserInfo = interface
['22222222-2222-2222-2222-AAAAAAAAAAAA']
//define your methods
end;
Hope i understand your question correctly. If you want to get rid of the confusing (and unnecessary) _UserInfo in the typelibrary, you should not export your CoClass, only the interface.
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("22222222-2222-2222-2222-AAAAAAAAAAAA")]
public interface IUserInfo
{
}
[ClassInterface(ClassInterfaceType.None)]
[ComVisible(true)]
[Guid("22222222-2222-2222-2222-BBBBBBBBBBBB")]
public class UserInfo: IUserInfo
{
}
Note the first attribute of the CoClass is set to ClassInterfaceType.None, this way DotNet will not reveal the CoClass itself to the typelibrary. Nevertheless you can instantiate your object in Delphi as you always do:
var
pUserInfo: IUserInfo;
begin
pUserInfo := CoUserInfo.Create;

Resources