Reputation: 63
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?
Upvotes: 3
Views: 207
Reputation: 1155
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.
Upvotes: 6