COM Object and different versions of DLL - delphi

I'm very new to DLL objects and I search everywhere and can't find the right answer.
I doing little addon to Microsoft RMS, it automatically calls function Process from my dll with IDispach parameter passing current session details.
I'm using interface from QSRules.dll (Components > Import > Component > Typed Library ... Add To Project).
It creates TLB file with all references etc.
procedure TRefreshScreenRefreshScreen.Process(const Session: IDispatch);
begin
CodeSite.Send( csmLevel1, '(Session as SessionClass).Cashier.Name', (Session as SessionClass).Cashier.Name );
CodeSite.Send( csmLevel1, '(Session as SessionClass).Cashier.Number', (Session as SessionClass).Cashier.Number );
end;
That works perfectly with software version 2.01 but when trying to use the same function on version 2.02 it crash with "Interface not supported".
The QSRules.dll has updated version and GUID's for all classes are different.
I tried that with fallowing code:
procedure TRefreshScreenRefreshScreen.Process(const Session: IDispatch);
begin
if Supports(Session, QSRules_TLB_2_0_0_151.SessionClass) then
Begin
CodeSite.Send( csmLevel1, '(Session as SessionClass).Cashier.Name', (Session as QSRules_TLB_2_0_0_151.SessionClass).Cashier.Name );
CodeSite.Send( csmLevel1, '(Session as SessionClass).Cashier.Number', (Session as QSRules_TLB_2_0_0_151.SessionClass).Cashier.Number );
end else
if Supports(Session, QSRules_TLB_2_0_0_105.SessionClass) then
Begin
CodeSite.Send( csmLevel1, '(Session as SessionClass).Cashier.Name', (Session as QSRules_TLB_2_0_0_105.SessionClass).Cashier.Name );
CodeSite.Send( csmLevel1, '(Session as SessionClass).Cashier.Number', (Session as QSRules_TLB_2_0_0_151.SessionClass).Cashier.Number );
end
end;
There is 4 or 5 different versions of dll all with different GUID's bu 98% of code is the same between all of them.
Doing that in this way is unnessesary multiplying the code.
Is there any way that I can shorten it ?
I also tried
procedure TRefreshScreenRefreshScreen.Process(const Session: IDispatch);
var
_Session: SessionClass;
begin
if Supports(Session, QSRules_TLB_2_0_0_151.SessionClass) then
_Session = (Session as QSRules_TLB_2_0_0_151.SessionClass)
else if Supports(Session, QSRules_TLB_2_0_0_105.SessionClass) then
_Session = (Session as QSRules_TLB_2_0_0_105.SessionClass);
with _Session do
Begin
CodeSite.Send( csmLevel1, '_Session.Cashier.Name', Cashier.Name );
CodeSite.Send( csmLevel1, '_Session..Cashier.Number', Cashier.Number );
End;
end;
But this not work because variable type can be assigned only from only unit.
Any help appreciated !

You say that the interfaces have different guids in different versions. That is perfectly fine as long as the newer interfaces derive from the older interfaces. Is that actually the case? If they do, then you can simplify your code by casting your Session object to whatever interface actually defines the Cashier member. You do not need to cast it to each individual interface type unless the interfaces do not derive from each other. Can you show the actual interface declarations?

Cashier declaration from v2.0.0.105
_Cashier = interface(IDispatch)
['{AA84B4FB-AA41-4423-A763-59D0723ED52B}']
function Get_Session: _SessionClass; safecall;
function Get_CashDrawer: _CashDrawer; safecall;
function Get_OverShortLimitType: overshortlimitEnum; safecall;
function Get_MaxOverShortAmount: Currency; safecall;
function Get_MaxOverShortPercent: Double; safecall;
function Get_SecurityLevel: Smallint; safecall;
function Get_HasPrivilege(var CashierPrivilege: cashierprivilegesEnum): WordBool; safecall;
function Get_FailedLogOnAttempts: Integer; safecall;
function Get_EmailAddress: WideString; safecall;
function Get_Messages: _CashierMessages; safecall;
function Get_UnreadMessageCount: Integer; safecall;
function Get_Name: WideString; safecall;
function Get_FirstName: WideString; safecall;
function Get_LastName: WideString; safecall;
function Get_ReturnLimit: Currency; safecall;
function Get_FloorLimit: Currency; safecall;
function Get_ID: Integer; safecall;
function Get_CashDrawerNumber: Smallint; safecall;
function Get_Loaded: WordBool; safecall;
function Get_Number: WideString; safecall;
property Session: _SessionClass read Get_Session;
property CashDrawer: _CashDrawer read Get_CashDrawer;
property OverShortLimitType: overshortlimitEnum read Get_OverShortLimitType;
property MaxOverShortAmount: Currency read Get_MaxOverShortAmount;
property MaxOverShortPercent: Double read Get_MaxOverShortPercent;
property SecurityLevel: Smallint read Get_SecurityLevel;
property HasPrivilege[var CashierPrivilege: cashierprivilegesEnum]: WordBool read Get_HasPrivilege;
property FailedLogOnAttempts: Integer read Get_FailedLogOnAttempts;
property EmailAddress: WideString read Get_EmailAddress;
property Messages: _CashierMessages read Get_Messages;
property UnreadMessageCount: Integer read Get_UnreadMessageCount;
property Name: WideString read Get_Name;
property FirstName: WideString read Get_FirstName;
property LastName: WideString read Get_LastName;
property ReturnLimit: Currency read Get_ReturnLimit;
property FloorLimit: Currency read Get_FloorLimit;
property ID: Integer read Get_ID;
property CashDrawerNumber: Smallint read Get_CashDrawerNumber;
property Loaded: WordBool read Get_Loaded;
property Number: WideString read Get_Number;
end;
Cashier declaration from v2.0.0.151
_Cashier = interface(IDispatch)
['{39B2C128-00F1-4834-B1A4-05197C708BD9}']
function Get_Session: _SessionClass; safecall;
function Get_CashDrawer: _CashDrawer; safecall;
function Get_OverShortLimitType: overshortlimitEnum; safecall;
function Get_MaxOverShortAmount: Currency; safecall;
function Get_MaxOverShortPercent: Double; safecall;
function Get_SecurityLevel: Smallint; safecall;
function Get_HasPrivilege(var CashierPrivilege: cashierprivilegesEnum): WordBool; safecall;
function Get_FailedLogOnAttempts: Integer; safecall;
function Get_EmailAddress: WideString; safecall;
function Get_Messages: _CashierMessages; safecall;
function Get_UnreadMessageCount: Integer; safecall;
function Get_Name: WideString; safecall;
function Get_FirstName: WideString; safecall;
function Get_LastName: WideString; safecall;
function Get_ReturnLimit: Currency; safecall;
function Get_FloorLimit: Currency; safecall;
function Get_ID: Integer; safecall;
function Get_CashDrawerNumber: Smallint; safecall;
function Get_Loaded: WordBool; safecall;
function Get_Number: WideString; safecall;
function Get_PasswordAge: Integer; safecall;
function Get_ReminderPeriod: Integer; safecall;
function Get_PasswordResetFlag: WordBool; safecall;
function Get_IsPasswordChanged: WordBool; safecall;
procedure Set_IsPasswordChanged(var Param1: WordBool); safecall;
function Get_TimecardID: Integer; safecall;
procedure Set_TimecardID(var Param1: Integer); safecall;
function ValidatePassword(var Password: WideString): WordBool; safecall;
function IsPwdDuplicated(var CashierNumber: Integer; var Password: WideString): WordBool; safecall;
property Session: _SessionClass read Get_Session;
property CashDrawer: _CashDrawer read Get_CashDrawer;
property OverShortLimitType: overshortlimitEnum read Get_OverShortLimitType;
property MaxOverShortAmount: Currency read Get_MaxOverShortAmount;
property MaxOverShortPercent: Double read Get_MaxOverShortPercent;
property SecurityLevel: Smallint read Get_SecurityLevel;
property HasPrivilege[var CashierPrivilege: cashierprivilegesEnum]: WordBool read Get_HasPrivilege;
property FailedLogOnAttempts: Integer read Get_FailedLogOnAttempts;
property EmailAddress: WideString read Get_EmailAddress;
property Messages: _CashierMessages read Get_Messages;
property UnreadMessageCount: Integer read Get_UnreadMessageCount;
property Name: WideString read Get_Name;
property FirstName: WideString read Get_FirstName;
property LastName: WideString read Get_LastName;
property ReturnLimit: Currency read Get_ReturnLimit;
property FloorLimit: Currency read Get_FloorLimit;
property ID: Integer read Get_ID;
property CashDrawerNumber: Smallint read Get_CashDrawerNumber;
property Loaded: WordBool read Get_Loaded;
property Number: WideString read Get_Number;
property PasswordAge: Integer read Get_PasswordAge;
property ReminderPeriod: Integer read Get_ReminderPeriod;
property PasswordResetFlag: WordBool read Get_PasswordResetFlag;
property IsPasswordChanged: WordBool read Get_IsPasswordChanged write Set_IsPasswordChanged;
property TimecardID: Integer read Get_TimecardID write Set_TimecardID;
end;
As you can see there is few things added in the later version but there is no doubt that I need to check the software version when calling them functions. Cashier that's only one of 25-30 types there so if I have to write the same basic implementation for all versions .... big task, and horrible code for modifications in later stage.

Finally got it sorted ! Just share the answer in case anyone else looking for it.
The key for success is "late binding", that means you don't use interface.
procedure TRefreshScreenRefreshScreen.Process(const Session: IDispatch);
var
_Session: Variant;
begin
_Session := Session;
CodeSite.Send( csmLevel1, '_Session.Cashier.Name', _Session.Cashier.Name );
CodeSite.Send( csmLevel1, '_Session.Cashier.Number', _Session.Cashier.Number );
end;
With variant variable the functions are not check by compiler but in runtime, so you have to make sure that the spelling is correct because intellisense is not checking it.
Works like a dream !
Thanks all of you anyway !

Related

E2134 Error in Turbopower LockBox code when building with runtime type information

I have to build my program with runtime type information, so compiler option Emit runtime type information is checked.
But with this set the LockBox3 unit uTPLb_codecIntf.pas gives error E2134 Type has no type info on the line containing Implementation (see bottom of source).
The LockBox3\run\ (source) folders are in my library path (as specified by the installation).
LockBox DCU's are in d:\LockBox3\packages\Sydney\Delphi\Win32\Release\ and date from the time of installation.
How can I get rid of the error message yet have RTTI available?
Edit FWIW, these are the contents of uTPLb_codecIntf.pas:
interface
uses SysUtils, Classes, uTPLb_StreamCipher, uTPLb_BlockCipher,
uTPLb_CryptographicLibrary;
type
TCodecMode = (cmUnitialized, cmIdle, cmEncrypting, cmDecrypting);
TOnEncDecProgress = function ( Sender: TObject; CountBytesProcessed: int64): boolean of object;
TGenerateAsymetricKeyPairProgress = procedure (
Sender: TObject; CountPrimalityTests: integer;
var doAbort: boolean) of object;
ICodec = interface
['{48B3116A-5681-4E79-9013-8EC89BAC5B35}']
procedure SetStreamCipher( const Value: IStreamCipher);
procedure SetBlockCipher ( const Value: IBlockCipher);
procedure SetChainMode ( const Value: IBlockChainingModel);
function GetMode: TCodecMode;
function GetStreamCipher: IStreamCipher;
function GetBlockCipher : IBlockCipher;
function GetChainMode : IBlockChainingModel;
function GetOnProgress : TOnEncDecProgress;
procedure SetOnProgress( Value: TOnEncDecProgress);
function GetAsymetricKeySizeInBits: cardinal;
procedure SetAsymetricKeySizeInBits( value: cardinal);
function GetAsymGenProgressEvent: TGenerateAsymetricKeyPairProgress;
procedure SetAsymGenProgressEvent( Value: TGenerateAsymetricKeyPairProgress);
function GetKey: TSymetricKey;
function GetCipherDisplayName( Lib: TCryptographicLibrary): string;
procedure Init(const Key: string; AEncoding: TEncoding);
procedure SaveKeyToStream( Store: TStream);
procedure InitFromStream( Store: TStream);
procedure InitFromKey( Key: TSymetricKey); // Transfers ownership.
procedure Reset;
procedure Burn( doIncludeBurnKey: boolean);
// Asymetric support
function isAsymetric: boolean;
procedure InitFromGeneratedAsymetricKeyPair;
procedure Sign(
Document, Signature: TStream;
ProgressSender: TObject;
ProgressEvent: TOnEncDecProgress;
SigningKeys_PrivatePart: TObject; // Must be uTPLb_Asymetric.TAsymtricKeyPart
var wasAborted: boolean);
function VerifySignature(
Document, Signature: TStream;
ProgressSender: TObject;
ProgressEvent: TOnEncDecProgress;
SigningKeys_PublicPart: TObject; // Must be uTPLb_Asymetric.TAsymtricKeyPart
var wasAborted: boolean): boolean;
procedure Begin_EncryptMemory( CipherText{out}: TStream);
procedure EncryptMemory(const Plaintext: TBytes; PlaintextLen: Integer);
procedure End_EncryptMemory;
procedure Begin_DecryptMemory( PlainText{out}: TStream);
procedure DecryptMemory( const CipherText{in}; CiphertextLen: integer);
procedure End_DecryptMemory;
procedure EncryptStream( Plaintext, CipherText: TStream);
procedure DecryptStream( Plaintext, CipherText: TStream);
procedure EncryptFile( const Plaintext_FileName, CipherText_FileName: string);
procedure DecryptFile( const Plaintext_FileName, CipherText_FileName: string);
procedure EncryptString(const Plaintext: string; var CipherText_Base64: string; AEncoding: TEncoding);
procedure DecryptString(var Plaintext: string; const CipherText_Base64: string; AEncoding: TEncoding);
procedure EncryptAnsiString(const Plaintext: string; var CipherText_Base64: string);
procedure DecryptAnsiString(var Plaintext: string; const CipherText_Base64: string);
function GetAborted: boolean;
procedure SetAborted( Value: boolean);
function GetAdvancedOptions2 : TSymetricEncryptionOptionSet;
procedure SetAdvancedOptions2( Value: TSymetricEncryptionOptionSet);
function GetOnSetIV: TSetMemStreamProc;
procedure SetOnSetIV( Value: TSetMemStreamProc);
property Mode: TCodecMode read GetMode;
property Key: TSymetricKey read GetKey;
property StreamCipher: IStreamCipher read GetStreamCipher write SetStreamCipher;
property BlockCipher : IBlockCipher read GetBlockCipher write SetBlockCipher;
property ChainMode : IBlockChainingModel read GetChainMode write SetChainMode;
property OnProgress : TOnEncDecProgress read GetonProgress write SetOnProgress;
property AsymetricKeySizeInBits: cardinal read GetAsymetricKeySizeInBits
write SetAsymetricKeySizeInBits;
property OnAsymGenProgress: TGenerateAsymetricKeyPairProgress
read GetAsymGenProgressEvent write SetAsymGenProgressEvent;
property isUserAborted: boolean read GetAborted write SetAborted;
end;
implementation
end.
The fact that the LockBox writers suggest to place the source folders in your library path, does not mean you have to. It's handy for tracing into their code, but not required.
Just remove them from that list and add the .dcu directory d:\LockBox3\packages\Sydney\Delphi\Win32\Release\ to it.
This makes the error go away, and I don't need RTTI in the LockBox sources.
You need to rebuild the dcus with run time information. The simplest way to do this (which also only includes the dcu in this project, not all projects) is to include the source file uTPLb_codecIntf.pas in you project and build the project. You may need to repeat the process for other source files depending on what RTTI you need.

Invoking object properties in Delphi

I admit I'm not a Delphi expert, so I need some advice.
I have a pre-built class with this definition
TS7Helper = class
private
function GetInt(pval: pointer): smallint;
procedure SetInt(pval: pointer; const Value: smallint);
function GetWord(pval: pointer): word;
procedure SetWord(pval: pointer; const Value: word);
function GetDInt(pval: pointer): longint;
procedure SetDInt(pval: pointer; const Value: longint);
function GetDWord(pval: pointer): longword;
procedure SetDWord(pval: pointer; const Value: longword);
function GetDateTime(pval: pointer): TDateTime;
procedure SetDateTime(pval: pointer; const Value: TDateTime);
function GetReal(pval: pointer): single;
procedure SetReal(pval: pointer; const Value: single);
function GetBit(pval: pointer; BitIndex: integer): boolean;
procedure SetBit(pval: pointer; BitIndex: integer; const Value: boolean);
public
procedure Reverse(pval : pointer; const S7Type : TS7Type);
property ValBit[pval : pointer; BitIndex : integer] : boolean read GetBit write SetBit;
property ValInt[pval : pointer] : smallint read GetInt write SetInt;
property ValDInt[pval : pointer] : longint read GetDInt write SetDInt;
property ValWord[pval : pointer] : word read GetWord write SetWord;
property ValDWord[pval : pointer] : longword read GetDWord write SetDWord;
property ValReal[pval : pointer] : single read GetReal write SetReal;
property ValDateTime[pval : pointer] : TDateTime read GetDateTime write SetDateTime;
end;
Var
S7 : TS7Helper;
procedure TS7Helper.SetInt(pval: pointer; const Value: smallint);
Var
BW : packed array[0..1] of byte absolute value;
begin
pbyte(NativeInt(pval)+1)^:=BW[0];
pbyte(pval)^:=BW[1];
end;
(I cut some code, so don't look for the implementation clause, etc... the helper class is compiling ok....)
Trivially, I want to invoke the SetInt property (as stated in the class documentation)... but the following code gives me an error "Cannot access private symbol TS7Helper.SetInt".
S7.SetInt(#MySnap7Array[i * 2], gaPlcDataScrittura[i]);
What am I doing wrong ?
SetInt and GetInt is the getter and setter for ValInt property as stated in the definition of ValInt. So you shoud use S7.ValInt like
S7.ValInt[#MySnap7Array[i * 2]] := gaPlcDataScrittura[i];
In Delphi,
A private member is invisible outside of the unit or program where its class is declared.
Note: "program" refers to files starting with program keyword (usually the .dpr file), not to the project as a whole.
So you can only call TS7Helper.SetInt from the same unit where TS7Helper class is declared.
Otherwise, #DmLam answer is the correct way to solve it.

Delphi interface reference count mechanism

Indeed there is a lot of stuff online about this but more I read more confuse I am. I have written a component called Combinatorics that does some math probability stuff. The code is pretty short and easy because I don't want it to be complicated. I am doing a little preview here:
//Combinatorio.pas
type
ICombinatorio = interface
function getSoluzioni(): integer; //soluzioni means "Solutions"
function getFormula(): string;
end;
//ImplCombinatorio.pas
type
TCombinazioni = class(TInterfacedObject, ICombinatorio)
private
n, k: integer;
ripetizione: boolean;
function fattoriale(const x: integer): integer;
public
constructor Create(const n, k: integer; const ripetizione: boolean);
function getSoluzioni(): integer;
function getFormula(): string;
end;
TDisposizioni = class(TInterfacedObject, ICombinatorio)
private
n, k: integer;
ripetizione: boolean;
function fattoriale(const x: integer): integer;
public
constructor Create(const n, k: integer; const ripetizione: boolean);
function getSoluzioni(): integer;
function getFormula(): string;
end;
TPermutazioni = class(TInterfacedObject, ICombinatorio)
private
n: integer;
k: string;
ripetizione: boolean;
function fattoriale(const x: integer): integer;
public
constructor Create(const n: integer; const k: string; ripetizione: boolean);
function getSoluzioni(): integer;
function getFormula(): string;
end;
You don't need to see how functions and procedures are implemented, it's not important for the question (and you can easily imagine what they do).
This is my first component ever, I have compiled and installed it and it works. However I cannot understand something.
unit TCombinatorio;
interface
uses
System.SysUtils, System.Classes, Combinatorio, ImplCombinatorio;
type
cCombinatorio = (cNull = 0, cDisposition = 1, cPermutation = 2, cCombination = 3);
type
TCombinatorics = class(TComponent)
strict private
{ Private declarations }
Fn, Fk: integer;
FRep: boolean;
FType: cCombinatorio;
FEngine: ICombinatorio;
procedure Update;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
function getSolution: integer;
function getFormula: string;
published
property n: integer read Fn write Fn;
property k: integer read Fk write Fk;
property kind: cCombinatorio read FType write FType default cNull;
property repetitions: boolean read FRep write FRep;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('RaffaeleComponents', [TCombinatorics]);
end;
{ TCombinatorics }
constructor TCombinatorics.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Fn := 0;
Fk := 0;
FType := cNull;
repetitions := false;
end;
function TCombinatorics.getFormula: string;
begin
Update;
Result := FEngine.getFormula;
end;
function TCombinatorics.getSolution: integer;
begin
Update;
Result := FEngine.getSoluzioni;
end;
procedure TCombinatorics.Update;
begin
case FType of
cDisposition:
FEngine := TDisposizioni.Create(n, k, repetitions);
cPermutation:
FEngine := TPermutazioni.Create(n, '', repetitions);
cCombination:
FEngine := TCombinazioni.Create(n, k, repetitions);
cNull:
raise Exception.Create('You have to select a type.');
end;
end;
end.
Look at the Update; procedure. I have created that because when the user drops the component ( link ) in the form he has to setup in the object inspector (or with the code somewhere) 3 important parameters required in the constructor.
Since FEngine: ICombinatorio I can assign to it a class (TCombinazioni, TDisposizioni or TPermutazioni) without try finally because there is the ref count mechanism. I am not sure if I have coded this properly. Suppose that:
The user selects cDisposition and does a calculation
The user selects cDisposition (different values) and does a calculation
The user selects cPermutation and does a calculation
I am always working on the FEngine. How does the ref count go to zero? Does it go to zero when the form (and the component) destroys? I hope I have explained well what I don't understand. The FEngine is a private variable and I assing to it at runtime different classes (calling the Create). Does the ref count go to 0 when the form destroys or when a new class is assigned?
I coded it like above because nick hodges did that in his book and I trust him of course but I'd like to know what I do.
Based on the code that can be seen, the first time Update is called, a new implementor of ICombinatorio is created and assigned to FEngine; the reference count will be 1. The following times that Update is called, another new instance of ICombinatorio implementor will be created (its reference count will be 1) and is assigned to FEngine. The previous implementor instance that FEngine pointed to will have its reference count decremented; if it is zero, then it will be destroyed. (It probably will be based on your code sample).
Also, when the destructor of the component is called (when the owning Form is destroyed), the implicit instance clean-up code will set FEngine to nil, which will decrement the reference count (and, based on your sample, will be destroyed).
So, based on your code sample, I would expect your code will work properly; cleanly instanciating and destroying the ICombinatorio interfaced objects.

JSONMarshalled not working in Delphi XE10 (again)

I have a class I want to pass to a datasnap server, but the class contains this field Picture which should be a TPicture but for now I use an integer to avoid getting the marshall error "tkPointer currently not supported" :(
I have tried omitting a field/property "Picture" from getting marshalled by adding [JSONMarshalled(False)] but with no luck.
I have added the units as suggested in the thread here
JSONMarshalled not working in Delphi
unit TestObjU;
interface
uses
Classes, System.Generics.Collections, System.SyncObjs, System.SysUtils,
JSON, DBXJsonReflect, REST.JSON,
Data.FireDACJSONReflect, FireDAC.Comp.Client, vcl.ExtCtrls,
pngimage, graphics, variants,
GlobalFunctionsU, GlobalTypesU;
{$M+}
{$RTTI EXPLICIT FIELDS([vcPrivate])}
type
EPerson = class(Exception);
EPersonsList = class(Exception);
TGender = (Female, Male);
TPerson = class(TObject)
private
FFirstName: string;
FLastName: string;
FId: Integer;
FGender: TGender;
FModified : Boolean;
[JSONMarshalled(False)]
FPicture: Integer;
// [JSONMarshalled(False)] FPicture : TPicture;
function GetName: string;
procedure SetFirstName(const Value: string);
procedure SetLastName(const Value: string);
function GetId: Integer;
procedure SetGender(const Value: TGender);
procedure SetModified(const Value: Boolean);
public
property Id : Integer read GetId;
property Name : string read GetName;
property FirstName : string read FFirstName write SetFirstName;
property LastName : string read FLastName write SetLastName;
property Gender : TGender read FGender write SetGender;
property Modified : Boolean read FModified write SetModified;
// property Picture : TPicture read FPicture write FPicture;
[JSONMarshalled(False)]
property Picture : Integer read FPicture write FPicture;
function Update : Boolean;
function Delete : Boolean;
constructor Create(AId : Integer; AFirstName, ALastName : string; AGender : TGender); overload;
constructor Create(AFirstName, ALastName : string; AGender : TGender); overload;
destructor destroy; override;
function ToJsonString: string;
end;
But clearly it has no effect on the marshalling, Picture is still there - what am I missing?
function TPerson.ToJsonString: string;
begin
result := TJson.ObjectToJsonString(self);
end;
08-03-2016 10:26:24 [NORMAL] AddPerson serialized {"firstName":"Donald","lastName":"Duck","id":24,"gender":"Female","modified":false,"picture":92415648}
You are using TJson.ObjectToJsonString from REST.Json unit and that one needs different attribute to skip fields named JSONMarshalledAttribute
You should change your code to [JSONMarshalledAttribute(False)]
Delphi has a bit of mix up between older Data.DBXJsonReflect and newer REST.Json units and you should not mix them together in same code. Pick only one of them.
REST.Json.TJson.ObjectToJsonString
REST.Json.Types.JSONMarshalledAttribute
Data.DBXJSONReflect.JSONMarshalled
Yes - I found the solution, when using DBX (and not REST) you'll need add this unit "Data.DBXJSON" rather than the "REST.JSON" and change the two "from/to" methods for un/marshaling the object something like this.
NOTE. ToJSONString leaks for some reason, I'll have to investigate that more.
function TPerson.ToJsonString: string;
var
JSONMarshal: TJSONMarshal;
begin
result := '';
JSONMarshal := TJSONMarshal.Create(TJSONConverter.Create);
try
Result := JSONMarshal.Marshal(self).ToString;
finally
JSONMarshal.Free;
end;
end;
class function TPerson.FromJsonString(AJSONString: string): TPerson;
var
JSONUnMarshal: TJSONUnMarshal;
begin
JSONUnMarshal := TJSONUnMarshal.Create;
try
Result := JSONUnMarshal.Unmarshal(TJSONObject.ParseJSONValue(AJSONString)) as TPerson;
finally
JSONUnMarshal.Free;
end;
end;

Delphi - Interfaces inside interfaces

I'm an newbee concerning interfaces. I googled a lot but i can't figure out what to do in the following situation.
i created serveral interfaces, which use each other:
IPart = interface(IInterface)
Function getName: string;
procedure setName(aValue: string)
property Name: string read getName write setname;
end;
IOfferLine= interface(iInterface)
Function getPart: IPart;
function getAmount: double;
procedure setPart(aPart: IPart);
procedure setAmount(value: double);
property Amount: double read getAmount write setAmount;
property Part: IPart read GetPart write setPart;
end;
IOffer= interface(iInterface)
function getOffLines: tList<IOfferline>;
procedure setOffLines(aList: tList<IOfferline>);
property OffLines: tList<IOfferlines> read getOffLines write setOfflines;
end;
Now i want to implement those interface.
TPart = class(TInterfacedObject, IPart)
private
_Name: string;
function getName: string;
procedure setName(aValue: string);
public
property Name: string read getName write setName;
end;
TOfferLine = class(TInterfacedObject, IOfferLine)
private
_amount: double;
_part: TPart;
function getAmount: double;
function getPart: tPart;
procedure setAmount(aValue: double);
procedure setPart(aPart: TPart);
public
property Amount: double read getAmount write setAmount;
property Part: TPart read GetPart write SetPart;
end;
TOffer = class(TInterfacedObject, IOffer)
private
_OfferLines: tList<TOfferline>;
function getOffLines: tList<tOfferline>;
procedure setOffLines(aList: tList<tOfferline>);
public
property offLines: tList<TOfferline> read getOffLines write setOffLines;
end;
I have added the implementation.
function TOfferLine.getPart: tPart;
begin
result := _part;
end;
But i still get 'Missing implementation of interface method IOfferline.GetPart;'
And i Can't figure out why.
I dont know what you are trying to to but if you didn't write you code so messy it would be easier to read. But thank God we have a Source formatter.
There are seval problems in you code:
First You have your property declared as property OffLines: TList<IOfferline**s**> while your interface is named IOfferline
Then TOfferline you have a method procedure setPart(aPart: TPart); that should be procedure setPart(aPart: IPart); because thats how you declared your interface. And all the other places where you Use TPart should be IPart.
And the same goes for TOffer
Here is a cleaned up version of your code :
unit Unit20;
interface
uses
Generics.Collections;
type
IPart = interface(IInterface)
function getName: string;
procedure setName(aValue: string);
property Name: string read getName write setName;
end;
IOfferLine = interface(IInterface)
function getPart: IPart;
function getAmount: double;
procedure setPart(aPart: IPart);
procedure setAmount(value: double);
property Amount: double read getAmount write setAmount;
property Part: IPart read getPart write setPart;
end;
IOffer = interface(IInterface)
function getOffLines: TList<IOfferLine>;
procedure setOffLines(aList: TList<IOfferLine>);
property OffLines: TList < IOfferLine > read getOffLines write setOffLines;
end;
TPart = class(TInterfacedObject, IPart)
private
_Name: string;
function getName: string;
procedure setName(aValue: string);
public
property Name: string read getName write setName;
end;
TOfferline = class(TInterfacedObject, IOfferLine)
private
_amount: double;
_part: TPart;
function getAmount: double;
function getPart: IPart;
procedure setAmount(aValue: double);
procedure setPart(aPart: IPart);
public
property Amount: double read getAmount write setAmount;
property Part: IPart read getPart write setPart;
end;
TOffer = class(TInterfacedObject, IOffer)
private
_OfferLines: TList<TOfferline>;
function getOffLines: TList<IOfferLine>;
procedure setOffLines(aList: TList<IOfferLine>);
public
property OffLines: TList < IOfferLine > read getOffLines write setOffLines;
end;
implementation
{ TOfferline }
function TOfferline.getAmount: double;
begin
end;
function TOfferline.getPart: IPart;
begin
end;
procedure TOfferline.setAmount(aValue: double);
begin
end;
procedure TOfferline.setPart(aPart: IPart);
begin
end;
{ TOffer }
function TOffer.getOffLines: TList<IOfferLine>;
begin
end;
procedure TOffer.setOffLines(aList: TList<IOfferLine>);
begin
end;
{ TPart }
function TPart.getName: string;
begin
end;
procedure TPart.setName(aValue: string);
begin
end;
end.
The reason the compiler is saying that the implementation is missing is simply because the implementation is missing.
Your interface for IOfferLine declares this getPart method:
IOfferLine= interface(iInterface)
..
function getPart: IPart;
..
end;
But your implementing class does not provide this method. The getPart method in your class is implemented to return an object reference, not an interface reference:
TOfferLine = class(TInterfacedObject, IOfferLine)
private
..
function getPart: tPart;
..
end;
You need to ensure that your implementing class actually provides the members required by the interfaces that it implements, exactly and precisely:
TOfferLine = class(TInterfacedObject, IOfferLine)
private
..
function getPart: IPart;
..
end;
function TOfferline.getPart: IPart;
begin
result := _part as IPart;
end;
However, since the reference to the Part maintained by the OfferLine object (in the _part variable) is an object reference, then references to that object obtained using interfaces (via the getPart: IPart method) could result in that Part object being destroyed since the object reference in OfferLine is not counted (literally).
You can of course avoid this by making the Part reference held by OfferLine an interface reference itself, but whether this is valid is difficult to say with out a complete picture of your entire object model. If the lifetimes of your objects are ensured by some other mechanism not apparent from the question then it may not be an issue, but if it is not something that has been specifically considered thus far then it probably does need addressing.
Although it is possible to do safely, as a general rule mixing object references and interface references to the same objects is a recipe for problems.

Resources