I have an interface and I want to mock a function of this interface with an argument which is a reference to a function. See code exmple:
unit Main;
interface
procedure Execute;
implementation
uses
Spring.Mocking;
type
TRefFunc = reference to function: Boolean;
IHelper = interface
['{7950E166-1C93-47E4-8575-6B2CCEE05304}']
end;
IIntfToMock = interface
['{8D85A1CD-51E6-4135-B0E9-3E732400BA25}']
function DoSth(const AHelper: IHelper; const ARef: TRefFunc): Boolean;
end;
procedure Execute;
var
IntfMock : Mock<IIntfToMock>;
begin
IntfMock := Mock<IIntfToMock>.Create();
IntfMock.Setup.Returns(True).When.DoSth(Arg.IsAny<IHelper>, Arg.IsAny<TRefFunc>);
end;
end.
Unfortunately I receive a compile error:
[dcc32 Error] Main.pas(29): E2010 incompatible types: 'TRefFunc' and 'Spring.Mocking.Matching.TArg.IsAny<Main.TRefFunc>'
I understand why passing a callback as an argument to a mocked method is not a good idea if the method will be mocked. The best solution is to refactor the code and remove the callback argument from the method. But I was wondering if it is possible to pass an argument, which is a callback, via the Arg.IsAny<T> syntax?
Thanks and keep healthy
When passing something that is invokable to a function reference parameter the compiler tries to build a closure on it to then pass it to the parameter. This happens also for variables:
var
f: TRefFunc;
begin
f := Arg.IsAny<TRefFunc>; // boom, E2010
This is one of the few cases where Delphi needs the () on a call to understand that you actually want to invoke the rhs and assign its result.
Related
This is a bit puzzling for me as I'm working on an unit with several dozens of interfaces that are all based on this base interface definition:
type
IDataObject = interface(IInterface)
['{B1B3A532-0E7D-4D4A-8BDC-FD652BFC96B9}']
function This: TDataObject;
end;
ISomeObject = interface(IDataObject)
['{7FFA91DE-EF15-4220-A43F-2C53CBF1077D}']
<Blah>
end;
This means they all have a method 'This' that returns the class behind the interface, which is sometimes needed to put in listviews and stuff, but for this question it doesn't really matter because I want a generic class with additional functions that can be applied to any derived interface. (And any derived interface has their own GUID.) This is the generic class:
type
Cast<T: IDataObject> = class(TDataObject)
class function Has(Data: IDataObject): Boolean;
class function Get(Data: IDataObject): T;
end;
Doesn't look too complex and the use of class methods is because Delphi doesn't support global generic functions, unless they're in a class. So in my code I want to use Cast<ISomeObject>.Has(SomeObject) to check if the objects supports the specific interface. The Get() function is just to return the object as the specific type, if possible. So, next the implementation:
class function Cast<T>.Get(Data: IDataObject): T;
begin
if (Data.QueryInterface(T, Result) <> S_OK) then
Result := nil;
end;
class function Cast<T>.Has(Data: IDataObject): Boolean;
begin
Result := (Data.QueryInterface(T, Result) = S_OK);
end;
And this is where it gets annoying! Elsewhere in my code I use if (Source.QueryInterface(ISomeObject, SomeObject) = 0) then ... and it works just fine. In these generic methods the ISomeObject is replaced by T and should just work. But it refuses to compile and gives this error:
[dcc64 Error] DataInterfaces.pas(684): E2010 Incompatible types:
'TGUID' and 'T'
And that's annoying. I need to fix this but can't find a proper solution without hacking deep into the interface code of the System unit. (Which is the only unit I'm allowed to use in this code as it needs to run on many different platforms!)
The error is correct as QueryInterface expects a TGUID as parameter but it seems to get that from ISomeObject. So why not from T?
I think I'm trying to do the impossible here...
To be a bit more specific: Source.QueryInterface(ISomeObject, SomeObject) works fine without the use of any other unit. So I would expect it to work with a generic type, if that type is limited to interfaces. But that's not the case and I want to know why it won't accept T while it does accept ISomeObject.
Can you explain why it fails with a generic type and not a regular interface type?
QueryInterface() takes a TGUID as input, but an interface type is not a TGUID. The compiler has special handling when assigning an interface type with a declared guid to a TGUID variable, but that doesn't seem to apply inside of a Generic parameter that uses an Interface constraint. So, to do what you are attempting, you will just have to read the interface's RTTI at runtime to extract its actual TGUID (see Is it possible to get the value of a GUID on an interface using RTTI?), eg:
uses
..., TypInfo;
class function Cast<T>.Get(Data: IDataObject): T;
var
IntfIID: TGUID;
begin
IntfIID := GetTypeData(TypeInfo(T))^.GUID;
if (Data.QueryInterface(IntfIID, Result) <> S_OK) then
Result := nil;
end;
class function Cast<T>.Has(Data: IDataObject): Boolean;
begin
Cast<T>.Get(Data) <> nil;
end;
That being said, why are you duplicating functionality that the RTL already provides natively for you?
Your entire Cast class is unnecessary, just use SysUtils.Supports() instead (the SysUtils unit is cross-platform), eg:
uses
..., SysUtils;
//if Cast<ISomeObject>.Has(SomeObject) then
if Supports(SomeObject, ISomeObject) then
begin
...
end;
...
var
Intf: ISomeObject;
//Intf := Cast<ISomeObject>.Get(SomeObject);
if Supports(SomeObject, ISomeObject, Intf) then
begin
...
end;
Also, your IDataObject.This property is completely unnecessary, as you can directly cast an IDataObject interface to its TDataObject implementation object (Delphi has supported such casting since D2010), eg:
var
Intf: IDataObject;
Obj: TDataObject;
Intf := ...;
Obj := TDataObject(Intf);
Using Delphi 7 here. When I take the address of a procedure (with the purpose of sending this method address to an external C++ DLL as a callback) the Delphi 7 compiler reports Variable required. Why? How do you take the address of a method with or without a parameter list?
Here's my simplified code which shows the compiler error.
// ...
type
PTProcedureCallback = ^TProcedureCallback;
TProcedureCallback = procedure() of object;
// ...
TTestCallback = class
constructor Create();
procedure MyCallback();
end;
//...
implementation
constructor TTestCallback.Create();
var
pCallback: PTProcedureCallback;
begin
// Constructor
inherited;
// Test callback
pCallback := #MyCallback; // <- [Error] Variable required
end;
procedure TTestCallback.MyCallback();
begin
// Do something
end;
end;
You don't need PTProcedureCallback at all, as TProcedureCallback is already a pointer type.
constructor TTestCallback.Create();
var
pCallback: TProcedureCallback;
begin
// Constructor
inherited;
// Test callback
pCallback := MyCallback;
end;
That being said, you can't use a procedure of object as a C/C++ callback, unless the C/C++ code was written in C++Builder specifically, and is actually expecting a procedure of object via the __closure compiler extension. If not, you will not be able to use a non-static class method as the callback. However, if the callback allows you to pass in a user-defined value, you can use that to pass in your object's Self pointer so your callback can access its non-static members.
Also, your TProcedureCallback is using Delphi's default register calling convention (__fastcall in C++Builder), which does not exist in non-C++Builder compilers. Only cdecl and stdcall are portable calling conventions.
I am trying to compile a project but I guess this error "Variable Required"
function ReadInteger(SomeTStream:TStream):integer;
begin
SomeTStream.Read(Result, SizeOf(Result));
end;
Top:=ReadInteger(SomeTStream);
Left:=ReadInteger(SomeTStream);
Height:=ReadInteger(SomeTStream);
Width:=ReadInteger(SomeTStream);
Then when it tries to write it stops in Top and Left.
SomeTStream.Write(Top,SizeOf(Top));
^
SomeTStream.Write(Left,SizeOf(Top));}
^
E2036 Variable required
I read about the problem in here
But still I have no idea what should I do to fix it.
It's exactly as the error says. The parameter must be a variable. But you are passing a property that is implemented using a getter function. Copy the property into a variable and pass that to the function.
var
Value: Integer;
....
Value := Top;
Stream.Write(Value, SizeOf(Value));
Some helper methods would be most useful here to avoid drowning in boiler-plate. In fact you already added one for ReadInteger and you just need a matching one for WriteInteger.
procedure WriteInteger(Stream: TStream; Value: Integer);
begin
Stream.Write(Value, SizeOf(Value));
end;
What's going on here is that the Write method of TStream is declared like so:
function Write(const Buffer; Count: Longint): Longint; virtual; abstract;
Because this function receives an untyped parameter, it cannot be passed by value. The address of the parameter is actually passed, and so the caller needs to supply something with an address. Your property does not fit the bill.
Incidentally, usually, Write and Read are the wrong methods to call on TStream. They do not perform any error checking. They leave that to you. The Write and Read are abstract methods that are used by specialized classes to provide the stream implementation. You are generally expected to call WriteBuffer and ReadBuffer.
Or perhaps a binary writer would help.
var
bw: TBinaryWriter;
....
bw := TBinaryWriter.Create(Stream);
try
bw.Write(Top);
bw.Write(Left);
// etc.
finally
bw.Free;
end;
I have the following superclass:
unit DlgDefaultForm;
type
TDefaultFormDlg = class(TForm)
published
constructor Create(AOwner: TComponent); reintroduce; virtual;
end;
FormCreateFunc=function(AOwner: TComponent):TDefaultFormDlg;
which is descended by a bunch of forms as follows:
unit Form1
type
TForm1 = class(TDefaultFormDlg)
published
constructor Create(AOwner: TComponent); override;
end;
and created as follows:
unit MainForm;
procedure ShowForm(FormCreate:FormCreateFunc);
begin
(do some stuff)
FormCreate(ScrollBox1);
end;
When I run
ShowForm(#TForm1.Create);
two things happen:
When I step into TForm1.Create, AOwner=nil, even when it didn't in ShowForm.
I get an EAbstractError at the following line:
unit Forms;
(...)
constructor TCustomForm.Create(AOwner: TComponent);
begin
(...)
InitializeNewForm; //EAbstractError
(...)
end;
What am I doing wrong?
EDIT: This of course isn't my exact code.
Delphi constructors take a hidden extra parameter which indicates two things: whether NewInstance needs to be called, and what the type of the implicit first parameter (Self) is. When you call a constructor from a class or class reference, you actually want to construct a new object, and the type of the Self parameter will be the actual class type. When you call a constructor from another constructor, or when you're calling the inherited constructor, then the object instance has already been created and is passed as the Self parameter. The hidden extra parameter acts as a Boolean flag which is True for allocating a new instance but False for method-style calls of the constructor.
Because of this, you can't simply store a constructor in a method pointer[1] location and expect it to work; calling the method pointer won't pass the correct value for the hidden extra parameter, and it will break. You can get around it by declaring the parameter explicitly, and doing some typecasting. But usually it is more desirable and less error-prone to use metaclasses (class references) directly.
[1] That's another problem with your code. You're trying to store a method pointer in a function pointer location. You could do that and still make it work, but you'd need to put the declaration of Self in explicitly then, and you'd also need to pass the metaclass as the first parameter when allocating (as well as passing True for the implicit flag). Method pointers bake in the first parameter and pass it automatically. To make it all explicit, the function pointer equivalent to TComponent.Create is something like:
TComponentCreate = function(Self: Pointer; AOwner: TComponent; DoAlloc: Boolean): Pointer;
Self is a pointer here because it could be of TComponentClass type, or TComponent type, depending on whether DoAlloc is true or false.
You're not using virtual constructors correctly. Try it like this:
type
TDefaultFormDlgClass = class of TDefaultFormDlg;
function Show(FormClass: TDefaultFormDlgClass; AOwner: TComponent): TDefaultFormDlg;
begin
Result := FormClass.Create(AOwner);
end;
...
var
FormClass: TTDefaultFormDlgClass;
...
FormClass := ???;//this is where you specify the class at runtime, e.g. TForm1
MyForm := Show(FormClass, MainForm);
As an aside I do not think you need to reintroduce the constructor in the code you listed.
Based on the information from Barry I tested this code. TSample is a simple class with a paramless constructor. All you need is the pointer to the constructor (#TSample.Create) and the type of the class (TSample). If you've a hashmap key=TClass, value=Pointer ctor you can create any registered type.
type
TObjectCreate = function(Self: TClass; DoAlloc: Boolean): TObject;
var
aSample : TSample;
begin
aSample := TSample(TObjectCreate(#TSample.Create)(TSample, true));
I have to pass an argument of the type Pointer to a function from an external DLL.
How do I create a pointer to a procedure which I can then pass to the function?
Can I also pass a pointer to a class member function to the external function, or will that not work?
Just use #MyProcedure for that.
Beware that it has to have the right calling convention (probably stdcall).
You usually can't use a member function, because it has a hidden SELF parameter.
A class static method acts like a usual procedure/function though.
http://docwiki.embarcadero.com/RADStudio/en/Methods
Create this type if procedure (or function) is method
type
TMyProc = Procedure(x:Integer;y:Integer) of Object;
or this
type
TMyProc = Procedure(x:Integer;y:Integer);
if procedure is stand alone.
Usage:
//Some class method
Procedure TfrmMain.Add(x:Integer;y:Integer);
begin
...
end;
//Another class method that uses procedure as parameter
procedure Name(proc : TMyProc);
begin
...
end;
//Call with:
Name(Add);