I have a dictionary crash in the DataSnap client because its FComparer is somehow nil.
Server side code:
TColorNames = TDictionary<integer, string>;
function TServerMethods.DictionaryTest: TColorNames;
begin
result := TColorNames.Create;
result.Add (1, 'Red');
result.Add (2, 'Blue');
end;
Client side code:
procedure TformClientMain.FetchColors;
var
Colors: TColorNames;
begin
Colors := easServer.DictionaryTest;
if Colors.Items[1]<>'Red'
then ShowMessage('Not red');
end;
Colors.Items[1] crashes (as well as other functions that need the FComparer). The crash happens in System.Generics.Collections, when the function tries to access the FComparer.
function TDictionary<TKey,TValue>.Hash(const Key: TKey): Integer;
I do receive all the data in the list, and just looping through it with for color in Colors.Values do ShowMessage(Color); works fine.
When I create a dictionary instance with TColorNames.Create, on client or server side, the FComparer has a value and these issues do not exist. I placed breakpoints in the dictionary constructor and traced the code during the datasnap call - FComparer always gets a value.
What am I (or Delphi) doing wrong?
The answer to "What is Delphi doing wrong" is:
DataSnap uses a TJsonMarshal and TJsonUnmarshal from the unit Data.DBXJSONReflect. Upon unmarshalling, an instance of a TDictionary<X,Y> is created by calling the parameterless constructor. The parameterless constructor here is the one inherited straight from TObject.
When you, however, type TDictionary<X, Y>.Create(); you're calling the "correct" constructor with a default parameter (Create(ACapacity: Integer = 0);). The TJsonUnmarshall class, however, does not since it is looking for a constructor with really no parameters. The one you're usually calling has a parameter, even if you don't have to pass it.
I don't know how DataSnap works, but you should probably be able to pass a custom marshal and unmarshaller to whatever does the serialization.
Since Embarcadero closed all the bug reports I know of (example) as "Works as expected", it's probably a safe bet that generic collections should not be marshalled back and forth and you should probably revert to arrays.
Here is the minimal code to reproduce:
unit Unit1;
interface
uses
System.Generics.Collections,
System.JSON,
Data.DBXJSONReflect;
type
TColorNames = TDictionary<Integer, String>;
procedure p();
implementation
procedure p();
var
original: TColorNames;
marshaller: TJSONMarshal;
unmarshaller: TJSONUnMarshal;
asJson: TJsonValue;
marshalledBack: TColorNames;
begin
original := TColorNames.Create();
marshaller := TJsonMarshal.Create();
asJson := marshaller.Marshal(original);
unmarshaller := TJSONUnMarshal.Create();
marshalledBack := unmarshaller.Unmarshal(asJson) as TColorNames;
marshalledBack.Add(0, ''); // << will crash because FComparer is nil
end;
end.
Related
I want to test a method where an interface is cast to another interface. The cast is valid since the one interface is derived by the other. Unfortunately I receive an error at the marked line. I allready tried to mock QueryInterface but the function was not called and the error was still there. Is there any possibility to handle this within Spring.Mocking?
Tanks and keep healthy
unit Main;
{$M+}
interface
procedure Execute;
implementation
uses
Spring.Mocking,
System.SysUtils;
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;
IYetAnotherIntf = interface(IIntfToMock)
['{95B54D3B-F573-4957-BDB3-367144270C3B}']
end;
IIntfProvider = interface
['{8B3E4B7B-1B2D-4E1F-942D-7E6EB4B9B585}']
function YetAnotherIntfFactory: IYetAnotherIntf;
end;
TClassToTest = class
private
FIntfProvider: IIntfProvider;
public
function MethodeToTest: Boolean;
constructor Create(const AIntfProvider: IIntfProvider);
end;
procedure Execute;
var
IntfMock : Mock<IIntfToMock>;
YetiMock : Mock<IYetAnotherIntf>;
ProvMock : Mock<IIntfProvider>;
Instance : TClassToTest;
OutObj : Pointer;
begin
IntfMock := Mock<IIntfToMock>.Create();
YetiMock := Mock<IYetAnotherIntf>.Create();
YetiMock.Setup.Returns(True).When.DoSth(Arg.IsAny<IHelper>, Arg.IsAny<TRefFunc>());
{
// Just a try. Did not work...
YetiMock.Setup.Executes(
function (const ACallInfo: TCallInfo): TValue
begin
ACallInfo.Args[1].From(IIntfToMock(IntfMock));
Result := TValue.From(True);
end
).When.QueryInterface(IIntfToMock, OutObj);
}
ProvMock := Mock<IIntfProvider>.Create();
ProvMock.Setup.Returns(TValue.From(IYetAnotherIntf(YetiMock))).When.YetAnotherIntfFactory;
Instance := TClassToTest.Create(ProvMock);
if Instance.MethodeToTest then
System.Writeln('everything works fine :)')
else
System.Writeln('that´s bad :(');
end;
{ TClassToTest }
constructor TClassToTest.Create(const AIntfProvider: IIntfProvider);
begin
Self.FIntfProvider := AIntfProvider;
end;
function TClassToTest.MethodeToTest: Boolean;
var
Instance : IIntfToMock;
YetAnother : IYetAnotherIntf;
begin
//
Result := False;
try
Instance := Self.FIntfProvider.YetAnotherIntfFactory;
Instance.DoSth(nil, nil);
YetAnother := Self.FIntfProvider.YetAnotherIntfFactory;
Instance := YetAnother; // works
Instance := IIntfToMock(YetAnother); // works
Instance := YetAnother as IIntfToMock; // BOOM: EIntfCastError
Result := True;
except
end;
end;
end.
Spring Mocks are more powerful than you think.
A mock automatically returns a mock from methods returning a mockable interface (*) - and always the same instance of it. That means for factory mocks you don't need to specify any expectations. You just need to get hold of the mock returned to specify its behavior.
Also found a small bug in there - it tries this on any interface regardless its "mockability" (is that a word? ^^). I will add a check here. Then in case it was not mockable the error will occur later if you really try to grab it as mock.
In order for a mock to also support other interfaces you simply have to tell it. This follows the same behavior as implementing interfaces in objects. If you only implement IYetAnotherIntf in a class and store it in an interface variable of that type but then call as, Supports or QueryInterface on it it will fail.
Here is the entire code - fwiw mocks are auto intialized so you don't have to call Create which nicely reduces the code to its essence: the behavior specification.
Also if you don't care for any of the parameters at all you can write this a litte shorter.
procedure Execute;
var
ProvMock: Mock<IIntfProvider>;
YetiMock: Mock<IYetAnotherIntf>;
Instance: TClassToTest;
begin
// using type inference here - <IYetAnotherIntf> on From not necessary
YetiMock := Mock.From(ProvMock.Instance.YetAnotherIntfFactory);
// lets make the behavior strict here
// so it does not return False when there is no match
YetiMock.Behavior := TMockBehavior.Strict;
YetiMock.Setup.Returns(True).When(Args.Any).DoSth(nil, nil);
// this will internally add the IIntfToMock to the intercepted interfaces
// as it returns a Mock<IIntfToMock> we can also specify its behavior
// more about this particular case below
YetiMock.AsType<IIntfToMock>;
Instance := TClassToTest.Create(ProvMock);
if Instance.MethodeToTest then
System.Writeln('everything works fine :)')
else
System.Writeln('that´s bad :(');
end;
function TClassToTest.MethodeToTest: Boolean;
var
Helper: THelper;
RefFunc: TRefFunc;
Instance: IIntfToMock;
YetAnother: IYetAnotherIntf;
begin
Result := False;
try
// just using some variables for this demo
// to verify that arg matching is working
Helper := THelper.Create;
RefFunc := function: Boolean begin Result := False end;
Instance := FIntfProvider.YetAnotherIntfFactory;
Assert(Instance.DoSth(Helper, RefFunc));
YetAnother := FIntfProvider.YetAnotherIntfFactory;
Assert(YetAnother.DoSth(Helper, RefFunc));
// same as directly assign YetAnotherIntfFactory
Instance := YetAnother;
Assert(Instance.DoSth(Helper, RefFunc));
// same as before, direct assignment no interface cast via QueryInterface
Instance := IIntfToMock(YetAnother);
Assert(Instance.DoSth(Helper, RefFunc));
// QueryInterface "cast" - the interface interceptor internally needs to know
// that it also should handle that interface
Instance := YetAnother as IIntfToMock;
// the following also returns true currently but I think this is a defect
// internally setup for a mock returned via the AsType goes to the same
// interceptor and thus finds the expectation defined on the mock it was
// called on. That means you cannot specify derived behavior on such a mock
// or even worse if they are completely unrelated types but have identical
// methods they would interfer with each other - I will look into this
Assert(Instance.DoSth(Helper, RefFunc));
Result := True;
except
end;
end;
While preparing this answer I found the issue I described as I wanted to demonstrate that you can define a different behavior on the other interface just like you can when implementing interfaces in classes. As I wrote I will look into this any time soon. I think its a general missing feature on the interface interceptor as existing interceptors are just being passed to the additionally handled interface which is not desired here.
Update 12.04.2021: The two mentioned bugs are fixed now:
methods returning an interface will only automatically return a mock when the interface has method info
when supporting other interfaces on a mock each interface will have its own behavior specifications
Working with anonymous functions I found out that sometimes the compiler throws the following error:
E2555 Cannot capture symbol 'Self' when I try to use some field of the object.
I also noticed that this error seems to be related to the fact that a type, the method belongs to, is declared with "object" key word:
MyType = object()
field: integer;
...
end;
MyType.Method1()
begin
p := procedure
begin
// do something with field
end;
end;
However when a type is declared with "class" keyword it seems it works fine.
I know that to prevent the compiler error I can make a local copy of needed fields and use them inside the anonymous functions, but just to be sure - is "object" type cause of the compiler error and what's the reason of that?
Thanks in advance
As David properly analyzed it is because Self in your case is a value and not a reference. It cannot be moved to the internally created class - same is the case with any method arguments that are records. They also cannot be captured for the very same reason.
For arguments I usually copy them to a local variable which is being captured.
The same can be done for capturing Self in a record or object.
However if you capture it as value you get a copy and calling the closure later might have the "wrong" state because it captured a copy. To make it work similar you would have to capture a reference to Self but then for a value type you cannot guarantee that this reference is still valid when you call the closure.
You can see this in the following code:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TProc = reference to procedure;
PRecord = ^TRecord;
TRecord = object
y: Integer;
procedure Foo;
function GetProc: TProc;
end;
procedure TRecord.Foo;
begin
Writeln(y);
end;
function TRecord.GetProc: TProc;
var
this: PRecord;
begin
this := #Self;
Result :=
procedure
begin
this.Foo;
end;
end;
procedure Nested(var p: TProc);
var
r: TRecord;
begin
p := r.GetProc();
r.y := 0;
p();
r.y := 32;
p();
end;
procedure Main;
var
p: TProc;
begin
Nested(p);
p(); // <- wrong value because PRecord not valid anymore
end;
begin
Main;
end.
If you would capture TRecord it would do a local copy that it captures - you can see that it then will print 0 all the time.
Since Turbo Pascal object is long deprecated, it is reasonable for new language features not to have support for object.
There's not really any need to look much further. Since you are maintaining legacy code, I would not expect you to be introducing new language features like anonymous methods. Once you start introducing such language features, this no longer feels like legacy code maintenance and it would be reasonable to re-factor the code away from the legacy language features like object.
Having said that, I do note that the same restriction to capture applies in methods of advanced records.
type
TProc = reference to procedure;
TRecord = record
procedure Foo;
end;
procedure TRecord.Foo;
var
P: TProc;
begin
P :=
procedure
begin
Foo;
end;
end;
This fails to compile with error:
E2555 Cannot capture symbol 'Self'
Why does this code fail, even though advanced records are a fully supported modern feature?
I don't have an explanation for that and the documentation does not make it clear. A plausible explanation is that records are value types. When a local variable is captured, it is hoisted from being a stack allocated variable to a variable owned by an internally created class. That's possible for Self when Self is a reference to an instance of a class. But when Self is a value like a record, it is too late to hoist the record.
Or perhaps it is much more prosaic. Maybe the designers just implemented the most important use case (capturing Self for a class) and omitted the less widely used cases for expediency. It is frustrating that the documentation does not appear to give any rules for what can and cannot be captured.
We would like to share an ADOConnection across a DLL boundary (Delphi to Delphi at the moment, though could also be C# to Delphi in the near future).
As we would like the flexibility to call the DLL from c# in future, we were hoping to be able to define the DLL call using _Connection as a parameter. Something like:
procedure DoStuff (ADOConnection: _Connection)
var
InnerConnection: TADOConnection;
begin
InnerConnection := TADOConnection.create(nil);
try
InnerConnection.ConnectionObject := ADOConnection;
DoMoreStuff(InnerConnection);
finally
InnerConnection.free;
end;
end;
Unfortunately, the TADOConnection destructor code closes the connection passed into it, which is an unwanted side-effect. Adding
InnerConnection.ConnectionObject := nil
prior to the free doesn't do anything, as it's caught by
if Assigned(Value) = nil
in TADOConnection.SetConnectionObject, which results in the call not doing anything.
Is there a better way of achieving this? Passing the connection string is an alternative, but would mean that we would have to deal with username/password issues and encryption across the boundary. Passing the TADOConnection is another option, but that prevents calling from other languages.
Edit: For clarity, the Username/Password of the original TADOConnection object is set using the .Open routine, so these details aren't in the connection string (in fact, the wrong username is usually stored, as it's the name used to 'test connection' in the MS UDL editor)
You can try this way:
type TInit_StFattDLL = procedure( var DataBase:TAdoConnection);
var Init_StFattDLL:TInit_StFattDll;
The caller is:
Function ConnectDll():Boolean;
var
handleDll:THandle;
begin
handleDll := LoadLibrary('mydll.DLL');
#Init_StFattDLL := GetProcAddress(handleDll , 'myConnectFunction');
if #Init_StFattDLL <> nil then
begin
Init_StFattDLL(ADOConnection1);
result:=true;
end
else
result:=false;
end;
into the the dll put the following:
in the project file put the exports:
Exports myConnectFunction;
global section:
var Database:TAdoConnection;
the exported procedure is the following:
procedure myConnectFunction( var MyDataBase:TAdoConnection);export;
begin
Database:=MyDataBase;
end
I am currently testing two external COM components. I have big issue with one of them, but I cannot really find reason behind such behavior. Let me provide some example.
const
CLASS_SomeClas: TGUID = '{SomeGUID}';
type
ISomeInterface = interface(IDispatch)
['{SomeGUID}']
function SomeMethod(const AInput: WideString): WideString; safecall;
end;
TWrappingClass = class(TObject)
strict private
FInstance: ISomeInterface;
procedure CreateInstance;
public
procedure DoYourActualJob;
end;
procedure TWrappingClass.CreateInstance;
begin
FInstance := CreateComObject(CLASS_SomeClass) as ISomeInterface;
dbg(FInstance._AddRef); // Debugs 3
dbg(FInstance._AddRef); // Debugs 4
dbg(FInstance.Release); // Debugs 3
dbg(FInstance._AddRef); // Debugs 4
FInstance.SomeMethod(''); //Runs as expected
end;
procedure TWrappingClass.DoYourActualJob;
begin
CreateInstance;
dbg(FInstance._AddRef); //Debugs -1!
FInstance.SomeMethod(''); //AV
end;
As provided with example instance gets invalid after it leaves CreateInstance method. Component is designed to work with many sequential calls of SomeMethod and it does work when called inside single method.
Could someone give me clue what is actually happening there, why my instance gets invalid? Is it problem with my code, with Delphi or with component's code? When I change the implementation of TWrappingClass to another vendor (that is I change both ISomeInterface and CLASS_SomeClass) then everything works fine.
EDIT:
Behaviour does not change when I don't even call SomeMethod. That is after I leave CreateInstance, call to _AddRef returns -1. Component I am testing is here CadEditorX Probably I am not allowed to attach the OCX without violating its license.
You state clearly in the question that the erroneous behaviour only occurs with one specific COM object. Given this fact, and that Delphi's COM reference counting is known to work correctly, the only reasonable conclusion is that the fault lies in this specific COM object.
Your only recourse of action is to contact the vendor of this COM object and file a bug report with them.
One thing to look at, with a view to a possible work around, is how you are creating the object. You use CreateComObject. This receives a class ID and returns IUnknown. It calls CoCreateInstance passing the class ID, and requesting the IUnknown interface. You then need to query for your interface, ISomeInterface. So your code looks like this:
var
iunk: IUnknown;
intf: ISomeInteface;
....
CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER or CLSCTX_LOCAL_SERVER,
IUnknown, iunk);
iunk.QueryInterface(ISomeInterface, intf);
The fact that you have two interface variables, one IUnknown and one ISomeInterface explains why you see the reference count that you do. Now, you might think that you only have one interface variable, but that's not the case. There are two, only one of them is an implicit local. You can see this by looking at the compiled code and stepping through under the debugger.
This code:
procedure TWrappingClass.CreateInstance;
begin
FInstance := CreateComObject(CLASS_SomeClass) as ISomeInterface;
end;
is compiled as if it were this (ignoring error checking):
procedure TWrappingClass.CreateInstance;
var
iunk: IUnknown;
begin
iunk := CreateComObject(CLASS_SomeClass);
try
FInstance := CreateComObject(CLASS_SomeClass) as ISomeInterface;
finally
iunk := nil;
end;
end;
Perhaps the COM component cannot handle the call to Release made on its IUnknown interface.
So, you could try to work around this by using CoCreateInstance instead of CreateComObject. Pass ISomeInterface as the riid parameter.
OleCheck(CoCreateInstance(CLASS_SomeClass, nil, CLSCTX_INPROC_SERVER
or CLSCTX_LOCAL_SERVER, ISomeInterface, FInstance));
I stumbled upon a case where hard-casting from interface to class fails under certain circumstances.
Consider the following type definitions:
IDummy<T> = interface
end;
TMyRecord = record
Intf:IDummy<Byte>;
end;
TDummy = class(TInterfacedObject, IDummy<Byte>)
public
end;
IThisBreaksIt = IDummy<Byte>; // <== this line triggers the error
And now the simple code that uses the types:
var
ARecord:TMyRecord;
Item:IDummy<Byte>;
ImplWorks,
ImplBroken:TDummy;
begin
ARecord.Intf:=TDummy.Create;
Item:=ARecord.Intf;
ImplWorks:=TDummy(Item);
ImplBroken:=TDummy(ARecord.Intf); // <== resulting instance is broken
end;
So what I am doing is storing an interface reference inside a record. Now I want to cast this back to the implementing class with a hard cast.
Here is the catch: this fails if I define an alias for my interface (IThisBreaksIt = IDummy<Byte>). Comment out this line and the ImplBrokenis not broken anymore. In the broken case the addresses of ImplWorks and ImplBroken are different; instead the addresses of Item and ImplBroken are now the same. It seems like the automagic responsible for hard-casting fails to kick in.
Additional finding: Replacing TDummy(ARecord.Intf) by ARecord.Intf as TDummy fixes it.
This gave me some headache because it was buried in a bunch of code and I wasn't expecting this behavior. Is this normal?
Edit for Cosmin:
Example for working hard cast of interface to object.
Tested in XE: works (the pointers of StreamAdaptIntf and StreamAdaptImpl differ; Assertion succeeds)
Tested in 2009: fails (the pointers of StreamAdaptIntf and StreamAdaptImpl are the same; Assertion fails)
uses ActiveX;
var
Stream:TStream;
StreamAdaptIntf:IStream;
StreamAdaptImpl:TStreamAdapter;
begin
Stream:=TMemoryStream.Create;
StreamAdaptIntf:=TStreamAdapter.Create(Stream, soOwned);
StreamAdaptImpl:=TStreamAdapter(StreamAdaptIntf);
Assert(Integer(StreamAdaptImpl) <> Integer(StreamAdaptIntf));
end;
It's probably no help, this question was asked ages ago, but for anyone checking this out in future...
You've probably just posted test code but your interface should contain a GUID, the GUID uniquely defines the interface to the compiler (Ctrl+Shift+G in Delphi 2009).
IDummy<T> = interface
['{F9EF740B-FF23-465A-A2E0-E2ACD5ABD90F}']
procedure DoSomething;
end;
Hard casting is generally unsafe. A hard cast is only really acceptable where you know that an objects type will be correct. It's preferable when casting to check its type before the cast as follows...
var
lTypeA: TTypeA;
begin
if ObjectA is TTypeA then begin
lTypeA := TTypeA(ObjectA);
end;
Even better I'd perform an "as" cast, which I think will cause an exception if it is invalid. This REALLY is preferable! I've written code that performs a hardcast only to spend hours and hours figuring out that actually my cast was wrong... Delphi won't tell you if you cast to the wrong type, then when you later use the object you end up in a whole debugging nightmare. The as will raise an exception and guide you to the problem in your code
var
lTypeA: TTypeA;
begin
if ObjectA is TTypeA then begin
// Will raise an exception if ObjectA is not TTypeA,
// in this simple case the ObjectA is TTypeA test is redundant
lTypeA := ObjectA as TTypeA;
end;
I'd really only cast between objects in delphi. Delphi has a helpful function "supports" which will determine whether an object implements an interface and give you back an instance of that interface. You can then use the local variable returned to perform whatever function you need from the interface
if Supports(ImplBroken, IThisBreaksIt, lObjInterface) then
begin
lObjInterface.DoSomething;
end;
Code in full...
type
// Generic IDummy interface
IDummy<T> = interface
['{F9EF740B-FF23-465A-A2E0-E2ACD5ABD90F}']
procedure DoSomething;
end;
// Don't alias interfaces if possible
// This is a more specific version of your interface
IThisBreaksIt = interface(IDummy<Byte>)
['{76EFA371-4674-4190-8A4B-06850103C1D8}']
end;
TMyRecord = record
// I would suggest, if you can avoid it don't store interfaces
// in a record, just simple types - just my opinion, delphi doesn't stop you
Intf: IDummy<Byte>;
end;
// Remember this is interfaced, so the object only exists while refcount > 0
TDummy = class(TInterfacedObject, IDummy<Byte>)
protected
{ IDummy<T> }
// interface methods should be protected
// So ideally we refer to the interface of this object,
// not publicly available methods
procedure DoSomething;
public
end;
var
ARecord: TMyRecord;
Item: IDummy<Byte>; // Think this should be IThisBreaksIt
ImplWorks:TDummy;
ImplBroken: IThisBreaksIt;
lObjInterface: IThisBreaksIt;
begin
ARecord.Intf := TDummy.Create;
Item := ARecord.Intf;
// Nasty hard cast here - what if your interfaced object is destroyed
// before you reach this code section?
ImplWorks := TDummy(Item);
// This line compiles, buts it's really not the right way to get the interface
ImplBroken := IThisBreaksIt(ARecord.Intf);
// I believe this is the right way to get the interface for an object
if Supports(ARecord.Intf, IThisBreaksIt, lObjInterface) then
begin
lObjInterface.DoSomething;
end;
end;