I have to connect several measurement devices to my app (ie. caliper, weight scale, ...), not being tied to a specific brand nor model, so on client side I use interfaces with generic methods (QueryValue). Devices are connected on COM port and accessed on an asynchronous way:
Ask for a value (= send a specific character sequence on
COM port)
Wait for a response
On 'business' side my components use TComPort internally, which data reception event is TComPort.OnRxChar. I wonder how I could fire this event through an interface? Here is what I've done so far:
IDevice = interface
procedure QueryValue;
function GetValue: Double;
end;
TDevice = class(TInterfacedObject, IDevice)
private
FComPort: TComPort;
FValue: Double;
protected
procedure ComPortRxChar;
public
constructor Create;
procedure QueryValue;
function GetValue: Double;
end;
constructor TDevice.Create;
begin
FComPort := TComPort.Create;
FComPort.OnRxChar := ComPortRxChar;
end;
// COM port receiving data
procedure TDevice.ComPortRxChar;
begin
FValue := ...
end;
procedure TDevice.GetValue;
begin
Result := FValue;
end;
But I need an event to know when to call GetValue on client side. What's the usual way to perform that kind of data flow?
You can add event property to interface
IDevice = interface
function GetValue: Double;
procedure SetMyEvent(const Value: TNotifyEvent);
function GetMyEvent: TNotifyEvent;
property MyEvent: TNotifyEvent read GetMyEvent write SetMyEvent;
end;
and realize it in TDevice class
TDevice = class(TInterfacedObject, IDevice)
private
FMyEvent: TNotifyEvent;
procedure SetMyEvent(const Value: TNotifyEvent);
function GetMyEvent: TNotifyEvent;
public
function GetValue: Double;
procedure EmulChar;
end;
Then as usually call FMyEvent handler (if assigned) in the end of ComPortRxChar.
Tform1...
procedure EventHandler(Sender: TObject);
procedure TForm1.EventHandler(Sender: TObject);
var
d: Integer;
i: IDevice;
begin
i := TDevice(Sender) as IDevice;
d := Round(i.GetValue);
ShowMessage(Format('The answer is %d...', [d]));
end;
procedure TForm1.Button1Click(Sender: TObject);
var
id: IDevice;
begin
id:= TDevice.Create;
id.MyEvent := EventHandler;
(id as TDevice).EmulChar; //emulate rxchar arrival
end;
procedure TDevice.EmulChar;
begin
if Assigned(FMyEvent) then
FMyEvent(Self);
end;
function TDevice.GetMyEvent: TNotifyEvent;
begin
Result := FMyEvent;
end;
function TDevice.GetValue: Double;
begin
Result := 42;
end;
procedure TDevice.SetMyEvent(const Value: TNotifyEvent);
begin
FMyEvent := Value;
end;
Related
I am using Delphi Pro 10.2.3 Tokyo. I want to create a TDataset wrapper class which I can use to enumerate through a list of IData descendants with a for-in loop. When I try to compile the code below, I get the following error message.
[dcc32 Error] Core.Data.DatasetAdapter.pas(25): E2291 Missing implementation of interface method IEnumerator.GetCurrent
Clearly, GetCurrent is implemented. Any idea how to fix this?
unit Core.Data.DatasetAdapter;
interface
uses
Data.Db
;
type
IData = interface
['{15D1CF4F-B9E1-4525-B035-24B9A6584325}']
end;
IDataList<T: IData> = interface
['{9FEE9BB1-A983-4FEA-AEBF-4D3AF5219444}']
function GetCount: Integer;
function GetCurrent: T;
procedure Load;
procedure Unload;
property Count: Integer read GetCount;
property Current: T read GetCurrent;
end;
TDatasetAdapter<T: IData> = class(
TInterfacedObject
, IData, IDataList<T>
, IEnumerator<T>
)
private
FBof: Boolean;
FDataset: TDataset;
FIntf: T;
function GetCount: Integer;
function GetCurrent: T;
function GetEof: Boolean;
function GetInterface: T;
function MoveNext: Boolean;
procedure Reset;
protected
function FieldByName(const FieldName: string): TField;
procedure MapFields; virtual;
property Dataset: TDataset read FDataset;
public
constructor Create(ADataset: TDataset); virtual;
function GetEnumerator: IEnumerator<T>;
procedure Cancel;
procedure Close;
procedure Delete;
procedure Edit;
procedure First;
procedure Insert;
procedure Load;
procedure Next;
procedure Open;
procedure Post;
procedure UnLoad;
property Count: Integer read GetCount;
property Eof: Boolean read GetEof;
end;
implementation
uses
System.SysUtils
, System.TypInfo
;
{ TDatasetAdapter<T> }
{
****************************** TDatasetAdapter<T> ******************************
}
constructor TDatasetAdapter<T>.Create(ADataset: TDataset);
begin
FDataset := ADataset;
FIntf := GetInterface;
end;
procedure TDatasetAdapter<T>.Cancel;
begin
FDataset.Cancel;
end;
procedure TDatasetAdapter<T>.Close;
begin
FDataset.Close;
end;
procedure TDatasetAdapter<T>.Delete;
begin
FDataset.Delete;
end;
procedure TDatasetAdapter<T>.Edit;
begin
FDataset.Edit;
end;
function TDatasetAdapter<T>.FieldByName(const FieldName: string): TField;
begin
Result := FDataset.FieldByName(FieldName);
end;
procedure TDatasetAdapter<T>.First;
begin
FDataset.First;
end;
function TDatasetAdapter<T>.GetCount: Integer;
begin
Result := FDataset.RecordCount;
end;
function TDatasetAdapter<T>.GetCurrent: T;
begin
Result := FIntf;
end;
function TDatasetAdapter<T>.GetEnumerator: IEnumerator<T>;
begin
Reset;
Result := Self;
end;
function TDatasetAdapter<T>.GetEof: Boolean;
begin
Result := FDataset.Eof;
end;
function TDatasetAdapter<T>.GetInterface: T;
var
LGuid: TGuid;
begin
LGuid := GetTypeData(TypeInfo(T))^.Guid;
if not Supports(Self, LGuid, Result) then
Result := nil;
end;
procedure TDatasetAdapter<T>.Insert;
begin
FDataset.Insert;
end;
procedure TDatasetAdapter<T>.Load;
begin
Open;
MapFields;
end;
procedure TDatasetAdapter<T>.MapFields;
begin
//Stub procedure
end;
function TDatasetAdapter<T>.MoveNext: Boolean;
begin
if FBof then FBof := False
else Next;
Result := not Eof;
end;
procedure TDatasetAdapter<T>.Next;
begin
FDataset.Next;
end;
procedure TDatasetAdapter<T>.Open;
begin
FDataset.Open;
end;
procedure TDatasetAdapter<T>.Post;
begin
FDataset.Post;
end;
procedure TDatasetAdapter<T>.Reset;
begin
FBof := True;
First;
end;
procedure TDatasetAdapter<T>.UnLoad;
begin
Close;
end;
end.
You need to resolve function GetCurrent: T twice: for IDataList<T> and for Enumerator<T>. But you also need one for the non-generic ancestor of IEnumerator<T>: IEnumerator. Apparently that is not hidden by the GetCurrent method of IEnumerator<T>.
Try method resolution clauses:
function GetGenericCurrent: T; // implement this
function IDataList<T>.GetCurrent = GetGenericCurrent;
function IEnumerator<T>.GetCurrent = GetGenericCurrent;
function GetCurrent: TObject; // implement this -- can return nil.
The implementation of both can be the same, but you will have to make two methods. The one for the non-generic IEnumerator can return nil.
Update
I had to modify the code above. Now it should work. It is not necessary to have two implementations for GetCurrent returning T, but you must have one returning TObject.
I have class with 2 events: OnConnect and OnDisconnect:
type
TEvent = reference to procedure;
TConnection = class
private
fOnConnect: TEvent;
fOnDisconnect: TEvent;
public
procedure SomeBehavior(aChoice: Boolean);
property OnConnect: TEvent read fOnConnect write fOnConnect;
property OnDisconnect: TEvent read fOnDisconnect write fOnDisconnect;
end;
implementation
{ TConnection }
procedure TConnection.SomeBehavior(aChoice: Boolean);
begin
if aChoice then
fOnConnect
else
fOnDisconnect;
//im not cheacking Assign(Events) to make example simple
end;
now I would like to do same thing but in more object style.
I mean use interfaces and observer pattern from String4D. And i made this:
interface
uses
Spring.DesignPatterns;
type
IObserver = interface
procedure ReactToConnect(aText: String);
procedure ReactToDisconnect(aTimeoutInMs: Integer);
end;
IConnection<T> = interface(IObservable<IObserver>)
procedure SomeBehavior(aChoice: Boolean);
end;
implementation
uses
System.SysUtils;
type
TConnection = class(TObservable<IObserver>, IConnection<IObserver>)
public
procedure SomeBehavior(aChoice: Boolean);
end;
{ TConnection }
procedure TConnection.SomeBehavior(aChoice: Boolean);
var
procOnConnect: TProc<IObserver>;
procOnDisconnect: TProc<IObserver>; // what if i want no parameters?
someText: String;
someNumber: Integer;
begin
someText := RandomText;
procOnConnect := procedure(aObserver: IObserver)
begin
aObserver.ReactToConnect(someText);
end;
someNumber := RandomInt;
procOnDisconnect := procedure(aObserver: IObserver)
begin
aObserver.ReactToDisconnect(someNumber);
end;
if aChoice then
Self.NotifyListeners(procOnConnect)
else
Self.NotifyListeners(procOnDisconnect);
end;
im doing it fisrt time and just want to ask if its proper way? or im doing somethink heretical here?
Delphi support generic for IInterface. I have the follow construct using generic IInterface:
type
IVisitor<T> = interface
['{9C353AD4-6A3A-44FD-B924-39B86A4CB14D}']
procedure Visit(o: T);
end;
TMyVisitor = class(TInterfacedObject, IVisitor<TButton>, IVisitor<TEdit>)
procedure Visit(o: TButton); overload;
procedure Visit(o: TEdit); overload;
end;
implementation
procedure TMyVisitor.Visit(o: TButton);
begin
ShowMessage('Expected: TButton, Actual: ' + o.ClassName);
end;
procedure TMyVisitor.Visit(o: TEdit);
begin
ShowMessage('Expected: TEdit, Actual: ' + o.ClassName);
end;
TMyVisitor class implement two interface: IVisitor<TButton> and IVisitor<TEdit>.
I attempt invoke the methods:
procedure TForm6.Button1Click(Sender: TObject);
var V: IInterface;
begin
V := TMyVisitor.Create;
(V as IVisitor<TButton>).Visit(Button1);
(V as IVisitor<TEdit>).Visit(Edit1);
end;
The output I have is:
Expected: TEdit, Actual: TButton
Expected: TEdit, Actual: TEdit
Apparently, the code doesn't invoke procedure TMyVisitor.Visit(o: TButton) when execute (V as IVisitor<TButton>).Visit(Button1).
Is this a bug in Delphi or I should avoid implement multiple generic IInterface? All above codes have test in Delphi XE6.
as operator requires interface GUID to be able to tell which interface you are referring to. Since generic interfaces share same GUID as operator will not work with them. Basically, compiler cannot tell the difference between IVisitor<TButton> and IVisitor<TEdit> interfaces.
However, you can solve your problem using enhanced RTTI:
type
TCustomVisitor = class(TObject)
public
procedure Visit(Instance: TObject);
end;
TVisitor = class(TCustomVisitor)
public
procedure VisitButton(Instance: TButton); overload;
procedure VisitEdit(Instance: TEdit); overload;
end;
procedure TCustomVisitor.Visit(Instance: TObject);
var
Context: TRttiContext;
CurrentClass: TClass;
Params: TArray<TRttiParameter>;
ParamType: TRttiType;
SelfMethod: TRttiMethod;
s: string;
begin
Context := TRttiContext.Create;
CurrentClass := Instance.ClassType;
repeat
s := CurrentClass.ClassName;
Delete(s, 1, 1); // remove "T"
for SelfMethod in Context.GetType(Self.ClassType).GetMethods('Visit' + s) do
begin
Params := SelfMethod.GetParameters;
if (Length(Params) = 1) then
begin
ParamType := Params[0].ParamType;
if ParamType.IsInstance and (ParamType.AsInstance.MetaclassType = CurrentClass) then
begin
SelfMethod.Invoke(Self, [Instance]);
Exit;
end;
end;
end;
CurrentClass := CurrentClass.ClassParent;
until CurrentClass = nil;
end;
If you need to have Visitor interface you can change declarations to
type
IVisitor = interface
['{9C353AD4-6A3A-44FD-B924-39B86A4CB14D}']
procedure Visit(Instance: TObject);
end;
TCustomVisitor = class(TInterfacedObject, IVisitor)
public
procedure Visit(Instance: TObject);
end;
You can then use that in following manner, just by calling Visit and appropriate Visit method will be called.
procedure TForm6.Button1Click(Sender: TObject);
var V: IVisitor;
begin
V := TMyVisitor.Create;
V.Visit(Button1);
V.Visit(Edit1);
end;
Above code is based on Uwe Raabe's code and you can read more http://www.uweraabe.de/Blog/?s=visitor
And here is extended visitor interface and class that can operate on non-class types. I have implemented only calls for string, but implementation for other types consists only of copy-paste code with different typecast.
IVisitor = interface
['{9C353AD4-6A3A-44FD-B924-39B86A4CB14D}']
procedure Visit(const Instance; InstanceType: PTypeInfo);
procedure VisitObject(Instance: TObject);
end;
TCustomVisitor = class(TInterfacedObject, IVisitor)
public
procedure Visit(const Instance; InstanceType: PTypeInfo);
procedure VisitObject(Instance: TObject);
end;
procedure TCustomVisitor.Visit(const Instance; InstanceType: PTypeInfo);
var
Context: TRttiContext;
Params: TArray<TRttiParameter>;
ParamType: TRttiType;
SelfMethod: TRttiMethod;
begin
Context := TRttiContext.Create;
case InstanceType.Kind of
tkClass : VisitObject(TObject(Instance));
// template how to implement calls for non-class types
tkUString :
begin
for SelfMethod in Context.GetType(Self.ClassType).GetMethods('VisitString') do
begin
Params := SelfMethod.GetParameters;
if (Length(Params) = 1) then
begin
ParamType := Params[0].ParamType;
if ParamType.TypeKind = tkUString then
begin
SelfMethod.Invoke(Self, [string(Instance)]);
Exit;
end;
end;
end;
end;
end;
end;
procedure TCustomVisitor.VisitObject(Instance: TObject);
var
Context: TRttiContext;
CurrentClass: TClass;
Params: TArray<TRttiParameter>;
ParamType: TRttiType;
SelfMethod: TRttiMethod;
s: string;
begin
Context := TRttiContext.Create;
CurrentClass := Instance.ClassType;
repeat
s := CurrentClass.ClassName;
Delete(s, 1, 1); // remove "T"
for SelfMethod in Context.GetType(Self.ClassType).GetMethods('Visit' + s) do
begin
Params := SelfMethod.GetParameters;
if (Length(Params) = 1) then
begin
ParamType := Params[0].ParamType;
if ParamType.IsInstance and (ParamType.AsInstance.MetaclassType = CurrentClass) then
begin
SelfMethod.Invoke(Self, [Instance]);
Exit;
end;
end;
end;
CurrentClass := CurrentClass.ClassParent;
until CurrentClass = nil;
end;
Enhanced Visitor can be used like this:
TVisitor = class(TCustomVisitor)
public
procedure VisitButton(Instance: TButton); overload;
procedure VisitEdit(Instance: TEdit); overload;
procedure VisitString(Instance: string); overload;
end;
var
v: IVisitor;
s: string;
begin
s := 'this is string';
v := TVisitor.Create;
// class instances can be visited directly via VisitObject
v.VisitObject(Button1);
v.Visit(Edit1, TypeInfo(TEdit));
v.Visit(s, TypeInfo(string));
end;
This is a well known problem with generic interfaces. Here is yours:
type
IVisitor<T> = interface
['{9C353AD4-6A3A-44FD-B924-39B86A4CB14D}']
procedure Visit(o: T);
end;
Now, the as operator is implemented on top of the GUID that you specify for the interface. When you write:
(V as IVisitor<TButton>).Visit(Button1);
(V as IVisitor<TEdit>).Visit(Edit1);
how can the as operator distinguish between IVisitor<TButton> and IVisitor<TEdit>? You have only specified a single GUID. In fact when this happens, all instantiated types based on this generic interface share the same GUID. And so whilst the as operator compiles, and the code executes, the runtime behaviour is ill-defined. In effect you are defining multiple interfaces and giving them all the same GUID.
So, the fundamental issue here is that the as operator is not compatible with generic interfaces. You will have to find some other way to implement this. You might consider looking at the Spring4D project for inspiration.
After reading many post on StackOverflow about the cons of using automatic reference counting for Interfaces, I started trying to manually reference counting each interface instantiation.
After trying for a full afternoon I give up!
Why do I get Access Violation when I call FreeAndNil(p)?
What follow is a complete listing of my simple unit.
unit fMainForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm4 = class(TForm)
btn1: TButton;
procedure FormCreate(Sender: TObject);
procedure btn1Click(Sender: TObject);
end;
type
IPersona = interface(IInterface)
['{44483AA7-2A22-41E6-BA98-F3380184ACD7}']
function GetNome: string;
procedure SetNome(const Value: string);
property Nome: string read GetNome write SetNome;
end;
type
TPersona = class(TObject, IPersona)
strict private
FNome: string;
function GetNome: string;
procedure SetNome(const Value: string);
protected
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
public
constructor Create(const ANome: string);
destructor Destroy; override;
end;
var
Form4: TForm4;
implementation
{$R *.dfm}
procedure TForm4.FormCreate(Sender: TObject);
begin
ReportMemoryLeaksOnShutdown := True;
end;
procedure TForm4.btn1Click(Sender: TObject);
var
p: IPersona;
begin
p := TPersona.Create('Fabio');
try
ShowMessage(p.Nome);
finally
FreeAndNil(p);
end;
end;
constructor TPersona.Create(const ANome: string);
begin
inherited Create;
FNome := ANome;
end;
destructor TPersona.Destroy;
begin
inherited Destroy;
end;
function TPersona._AddRef: Integer;
begin
Result := -1
end;
function TPersona._Release: Integer;
begin
Result := -1
end;
function TPersona.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := S_OK
else
Result := E_NOINTERFACE;
end;
function TPersona.GetNome: string;
begin
Result := FNome;
end;
procedure TPersona.SetNome(const Value: string);
begin
FNome := Value;
end;
end.
The access violation occurs because FreeAndNil receives an untyped var parameter that is expected to be an object reference. You are passing an interface reference which does not meet the requirement. Unfortunately you only find out at runtime. This is, in my view, the strongest point against the use of FreeAndNil.
Your reference counting disables lifetime management by the interface reference counting mechanism. In order to destroy an object you need to call its destructor. And in order to do that you must have access to the destructor. Your interface doesn't expose the destructor (and it should not). So, we can deduce that, in order to destroy the object, you need to have an object reference.
Here are some options:
var
obj: TPersona;
intf: IPersona;
....
obj := TPersona.Create('Fabio');
try
intf := obj;
//do stuff with intf
finally
obj.Free;
// or FreeAndNil(obj) if you prefer
end;
Or you can do it like this
var
intf: IPersona;
....
intf := TPersona.Create('Fabio');
try
//do stuff with intf
finally
(intf as TObject).Free;
end;
You cannot use FreeAndNil() with an interface reference, only an objct reference. Had you left the interface's reference count enabled, you would simply assign nil to the interface reference (or just let it go out of scope) to free the object correctly, eg:
type
TPersona = class(TInterfacedObject, IPersona)
strict private
FNome: string;
function GetNome: string;
procedure SetNome(const Value: string);
public
constructor Create(const ANome: string);
destructor Destroy; override;
end;
procedure TForm4.btn1Click(Sender: TObject);
var
p: IPersona;
begin
p := TPersona.Create('Fabio');
try
ShowMessage(p.Nome);
finally
p := nil;
end;
end;
But since you have disabled the reference count on the interface, you need to go back to using normal object reference variables in your code, eg:
procedure TForm4.btn1Click(Sender: TObject);
var
p: TPersona;
intf: IPersona;
begin
p := TPersona.Create('Fabio');
try
if Supports(p, IPersona, intf) then
ShowMessage(intf.Nome);
finally
FreeAndNil(p);
end;
end;
I try to do list of procedures this way:
type
TProc = procedure of object;
TMyClass=class
private
fList:Tlist;
function getItem(index:integer):TProc;
{....}
public
{....}
end;
implementation
{....}
function TMyClass.getItem(index: Integer): TProc;
begin
Result:= TProc(flist[index]);// <--- error is here!
end;
{....}
end.
and get error:
E2089 Invalid typecast
How can I fix it?
As I see, I can make a fake class with only one property Proc:TProc; and make list of it. But I feel that it's a bad way, isn't it?
PS: project have to be delphi-7-compatible.
The typecast is invalid because you can not fit a method pointer to a pointer, a method pointer is in fact two pointers first being the address of the method and the second being a reference to the object that the method belongs. See Procedural Types in the documentation. This will not work in any version of Delphi.
Sertac has explained why your code doesn't work. In order to implement a list of such things in Delphi 7 you can do something like this.
type
PProc = ^TProc;
TProc = procedure of object;
TProcList = class(TList)
private
FList: TList;
function GetCount: Integer;
function GetItem(Index: Integer): TProc;
procedure SetItem(Index: Integer; const Item: TProc);
public
constructor Create;
destructor Destroy; override;
property Count: Integer read GetCount;
property Items[Index: Integer]: TProc read GetItem write SetItem; default;
function Add(const Item: TProc): Integer;
procedure Delete(Index: Integer);
procedure Clear;
end;
type
TProcListContainer = class(TList)
protected
procedure Notify(Ptr: Pointer; Action: TListNotification); override;
end;
procedure TProcListContainer.Notify(Ptr: Pointer; Action: TListNotification);
begin
inherited;
case Action of
lnDeleted:
Dispose(Ptr);
end;
end;
constructor TProcList.Create;
begin
inherited;
FList := TProcListContainer.Create;
end;
destructor TProcList.Destroy;
begin
FList.Free;
inherited;
end;
function TProcList.GetCount: Integer;
begin
Result := FList.Count;
end;
function TProcList.GetItem(Index: Integer): TProc;
begin
Result := PProc(FList[Index])^;
end;
procedure TProcList.SetItem(Index: Integer; const Item: TProc);
var
P: PProc;
begin
New(P);
P^ := Item;
FList[Index] := P;
end;
function TProcList.Add(const Item: TProc): Integer;
var
P: PProc;
begin
New(P);
P^ := Item;
Result := FList.Add(P);
end;
procedure TProcList.Delete(Index: Integer);
begin
FList.Delete(Index);
end;
procedure TProcList.Clear;
begin
FList.Clear;
end;
Disclaimer: completely untested code, use at your own risk.