Getting OleVariant (TClientDataset.Data) from Evaluator - delphi

Is it possible to evaluate an OleVariant of type varArray (Data of TClientDataset) using Delphi evaluator? I'm trying to build a Debugger Visualizer, using ToolsApi, for this kind of type. I work in an application that uses this extensively to transport data from client to server, and this would be really nice! I tried to work with pointers, evaluating TVarData(variable).VType and TVarData(variable).VArray, and then casting then to pointers, but the pointer is not valid in the api.
When I have a TClientDataset variable in the debugging code, this is not necessary, because we already have an extention using ToolApi to visualize the dataset (calling SaveToFile in evaluator). But we have too many code that have only the data variable.
So my question is: is there a way to do this with multiple evaluations, like converting the varArray to string and then unserialize the result in the api (visualizer)?
I saw this answer that could convert the OleVariant to string, but does not say anything about converting back to OleVariant.
I'm using this post as an example of a visualizer.

Related

How can I serialise records containing static arrays (of char) - working around RTTI

I need to be able to pass the same set of structures (basically arrays of different records) over two different interfaces
The first (legacy) which is working requires a pointer to a record and the record size
The second, which I am attempting to develop, is type-safe and requires individual fields to be set using Get/Set methods for each field
Existing code uses records (probably around 100 or so) with memory management being handled in a 3rd party DLL (i.e. we pass the record pointer and size to it and it deals with memory management of new records).
My original thought was to bring the memory management into my app and then copy over the data on the API call. This would be easy enough with the old interface, as I just need to be able to access SizeOf() and the pointer to the record structure held in my internal TList. The problem comes when writing the adapter for the new type-safe interface
As these records are reliant on having a known size, there is heavy use of array 0..n of char static arrays, however as soon as I try to access these via 2010-flavour RTTI I get error messages stating 'Insufficient RTTI information available to support this operation'. Standard Delphi strings work, but old short-strings don't. Unfortunately, fixing string lengths is important for the old-style interface to work properly. I've had a look at 3rd party solutions such as SuperObject and the streaming in MorMot, though they can't do anything out of the box which doesn't give me too much hope of a solution not needing significant re-work.
What I want to be able to do is something like the following (don't have access to my Delphi VM at the moment, so not perfect code, but hopefully you get the gist):
type
RTestRec = record
a : array [0..5] of char;
b : integer;
end;
// hopefully this would be handled by generic <T = record> or passing instance as a pointer
procedure PassToAPI(TypeInfo: (old or new RTTI info); instance: TestRec)
var
Field: RTTIField;
begin
for Field in TypeInfo.Fields do
begin
case Field.FieldType of
ftArray: APICallArray(Field.FieldName, Field.Value);
ftInteger: APICallInteger(Field.FieldName, Field.Value.AsInteger);
...
end;
end;
Called as:
var
MyTestRec: RTestRec;
begin
MyTestRec.a := 'TEST';
MyTestRec.b := 5;
PassToAPI(TypeInfo(TestRec), MyTestRec);
end;
Can the lack of RTTI be forced by a Compiler flag or similar (wishful thinking I feel!)
Can a mixture of old-style and new-style RTTI help?
Can I declare the arrays differently to give RTTI but still having the size constraints needed for old-style streaming?
Would moving from Records to Classes help? (I think I'd need to write my own streaming to an ArrayOfByte to handle the old interface)
Could a hacky solution using Attributes help? Maybe storing some of the missing RTTI information there? Feels like a bit of a long-term maintenance issue, though.

Delphi and COM: Is it possible to pass a TClientDataset to a COM library as a parameter?

I have a Delphi VCL application that manipulates a TClientDataset object. I need to pass this object as a parameter to a custom COM library, also written in Delphi.
I have two questions:
1) Is this possible?
2) If so, how?
No you cannot pass such an object. It is not a valid COM interop type. In fact you cannot even pass such an object between Delphi modules other than runtime packages.
The most obvious solutions are:
Wrap the object with a COM interface and pass that. The interface would have to expose methods to extract the data.
Serialize the data, for instance as JSON, and pass that as text. On the other side you would need to de-serialize.
Use the built in serialization capabilities as offered by the Data and XMLData properties of client data set.
The latter two serialization based options are probably simpler. But more costly in terms of memory. Using an interface requires more work to code, but may result in more efficient runtime performance.

Sending binary data with Indy through TCP\IP, how?

How to send a binary data with Indy components? Which of them is most suitable for this task? I've tried to use TIdTcpClient but it allows only to send strings.
I've found one reponce for that problem here but I don't get it.
It says about method Write(TIdBytes), but the answer is not clear for me. Does he meant Write to some instance of TIdBytes, and how to connect that instance with TIdTcpClient?
Thanks for any help.
The page you cite doesn't reproduce the messages very well. Here's what Remy really wrote:
SendCmd() is designed for textual commands/parameters only. You would have
to send the binary data after SendCmd() exited, and the server would have to
read the binary data after sending a response back to the client. For
example:
--- client ---
begin
IdTCPClient1.SendCmd('DoIt', 200);
// send binary data, such as with Write(TStream) or Write(TIdBytes)...
end;
The Write methods he was talking about are members of the TIdIOHandler class. Your TIdTCPConnection object has an instance of that class in its IOHandler property, and indeed that's what the SendCmd function uses to send its string.
The notation Write(TIdBytes) means to use the Write method that accepts a TIdBytes parameter for its input.
If the binary data is already in a stream or a dynamic array of bytes, then you can pass one of those directly to the Write method. There's also the WriteFile method that will send an entire external file if you provide the file's name. If you use the stream version, then you have the option of including the stream's length automatically.
If you don't have your data in one of those structures already, then you can either write the data piecemeal with the Write methods that accept variously sized integer types, or you can copy your data into a TMemoryStream and then pass that to Write.
Write is a method (or whatever it is called in Delphi) of TCPClient. Here's working code of what you want to do: http://delphi.about.com/od/internetintranet/l/aa012004a.htm. Should get you up & running in no time :)

Delphi : Handling the fact that Strings are not Objects

I am trying to write a function that takes any TList and returns a String representation of all the elements of the TList.
I tried a function like so
function ListToString(list:TList<TObject>):String;
This works fine, except you can't pass a TList<String> to it.
E2010 Incompatible types: 'TList<System.TObject>' and 'TList<System.string>'
In Delphi, a String is not an Object. To solve this, I've written a second function:
function StringListToString(list:TList<string>):String;
Is this the only solution? Are there other ways to treat a String as more 'object-like'?
In a similar vein, I also wanted to write an 'equals' function to compare two TLists. Again I run into the same problem
function AreListsEqual(list1:TList<TObject>; list2:TList<TObject>):boolean;
Is there any way to write this function (perhaps using generics?) so it can also handle a TList<String>? Are there any other tricks or 'best practises' I should know about when trying to create code that handles both Strings and Objects? Or do I just create two versions of every function? Can generics help?
I am from a Java background but now work in Delphi. It seems they are lately adding a lot of things to Delphi from the Java world (or perhaps the C# world, which copied them from Java). Like adding equals() and hashcode() to TObject, and creating a generic Collections framework etc. I'm wondering if these additions are very practical if you can't use Strings with them.
[edit: Someone mentioned TStringList. I've used that up till now, but I'm asking about TList. I'm trying to work out if using TList for everything (including Strings) is a cleaner way to go.]
Your problem isn't that string and TObject are incompatible types, (though they are,) it's that TList<x> and TList<y> are incompatible types, even if x and y themselves are not. The reasons why are complicated, but basically, it goes like this.
Imagine your function accepted a TList<TObject>, and you passed in a TList<TMyObject> and it worked. But then in your function you added a TIncompatibleObject to the list. Since the function signature only knows it's working with a list of TObjects, then that works, and suddenly you've violated an invariant, and when you try to enumerate over that list and use the TMyObject instances inside, something's likely to explode.
If the Delphi team added support for covariance and contravariance on generic types then you'd be able to do something like this safely, but unfortunately they haven't gotten around to it yet. Hopefully we'll see it soon.
But to get back to your original question, if you want to compare a list of strings, there's no need to use generics; Delphi has a specific list-of-strings class called TStringList, found in the Classes unit, which you can use. It has a lot of built-in functionality for string handling, including three ways to concatenate all the strings into a single string: the Text, CommaText and DelimitedText properties.
Although it is far from optimal, you can create string wrapper class, possibly containing some additional useful functions which operate on strings. Here is example class, which should be possibly enhanced to make the memory management easier, for example by using these methods.
I am only suggesting a solution to your problem, I don't agree that consistency for the sake of consistency will make the code better. If you need it, Delphi object pascal might not be the language of choice.
It's not cleaner. It's worse. It's a fundamentally BAD idea to use a TList<String> instead of TStringList.
It's not cleaner to say "I use generics everywhere". In fact, if you want to be consistent, use them Nowhere. Avoid them, like most delphi developers avoid them, like the plague.
All "lists" of strings in the VCL are of type TStringList. Most collections of objects in most delphi apps use TObjectList, instead of templated types.
It is not cleaner and more consistent to be LESS consistent with the entire Delphi platform, and to pick on some odd thing, and standardize on it, when it will be you, and you alone, doing this oddball thing.
In fact, I'm still not sure that generics are safe to use heavily.
If you start using TList you won't be able to copy it cleanly to your Memo.Lines which is a TStringList, and you will have created a type incompatibility, for nothing, plus you will have lost the extra functionality in TStringList. And instead of using TStringList.Text you have to invent that for yourself. You also lose LoadFromFile and SaveToFile, and lots more. Arrays of strings are an ubiquitous thing in Delphi, and they are almost always a TStringList. TList<String> is lame.

Streaming multiple TObjects to a TMemoryStream

I need to store multiple objects (most of them are TObject/non persistent) to a TMemoryStream, save the stream to disk and load it back. The objects need to be streamed one after each other. Some kind of universal container.
At the moment I put all properties/fields/variables of an object into a record and save the record to stream. But I intend to use functions file WriteInterger, WriteString (see below), WriteBoolean, etc functions to save/load data from stream.
StreamReadString(CONST MemStream: TMemoryStream): string;
StreamWriteString(CONST MemStream: TMemoryStream; s: string);
However, it seems that I need to rewrite a lot of code. One of the many examples is TStringList.LoadFromStream that will not work so it needs to be rewritten. This is because TStringList needs to be the last object in the stream (it reads from current position to the end of the stream).
Anybody knows a library that provide basic functionality like this?
I am using Delphi 7 so RTTI is not that great.
See related post here
Btw, Delphi7 also has RTTI support, otherwise your forms (.dfm) could not be loaded :-)
If you use published properties, RTTI will work for you "out of the box".
Otherwise you have to do it yourself with a
procedure DefineProperties(Filer: TFiler); override;
You can take a look at how it's implemented in:
procedure TDataModule.DefineProperties(Filer: TFiler);
These are the only ways for object serialization.
But you could also try records: if you do not use array(strings are also arrays of char) or object properties, you can directly save and load a record to memory (stream, file, etc). I use this in my AsmProfiler to be able to read and write many (small) results very fast (array of record with some integer values can be saved and loaded with one Move/CopyMemory call!).
Which Delphi version? Delphi 2010 has new RTTI functionality, so you can use DeHL which has "Full generic serialization for all included types and collections".
Have you thought about using TReader and TWriter to fill your streams.
Why not use XML?
Write an XSD for the XML that defines the XML.
Generate a Delphi unit form that XSD using the XML Data Binding Wizard.
Put a bunch of your objects into that XML.
Save the XML to disk (or stream it to some other medium).
For more info on XML and the XML Data Binding Wizard see this answer.
Edit:
Just map your objects to the interfaces/objects generated from the XSD; or use the objects/interfaces that have been generated.
That is usually far easier than hooking into the Delphi streaming mechanism (by either writing TPersistent wrappers with published properties around your objects, going the DefineBinaryProperty way, or the TReader/TWriter/DefineProperty way).
--jeroen

Resources