I am an amator astronomer and I write driver for telescope with Delphi and the standard ASCOM
First a special thank to David Heffernan who has helped me a lot these 2 last months. I have made a lot of progress in the writing of drivers, and there is a judge: 'Conform', a program which test all the compatibilties of a driver. At the beginning it was very bad, and now I am on the good way. The last really unsolved problem is an ArrayList of string.
The documentation of Ascom Telescope driver has a property:
C#
ArrayList SupportedActions { get; }
Visual Basic:
ReadOnly Property SupportedActions As ArrayList
Get
Visual C++
property ArrayList^ SupportedActions {
ArrayList^ get ();
}
Field Value
An ArrayList of strings (SafeArray collection) containing the names of supported actions.An array list collection has been selected as the vehicle for action names in order to make it easier for clients to determine whether a particular action is supported. This is easily done through the Contains method. Since the collection is also ennumerable it is easy to use constructs such as For Each ... to operate on members without having to be concerned about hom many members are in the collection.
Collections have been used in the Telescope specification for a number of years and are known to be compatible with COM. Within .NET the ArrayList is the correct implementation to use as the .NET Generic methods are not compatible with COM.
So I have tried to make an automation object:
And the big judge "Conform" program send me that (sorry it's a translation of an error message in french):
ERROR .NET - Exception: System.InvalidCastException: Impossible to make a cast of a COM object of type 'System.__ComObject' in type of class 'System.Collections.ArrayList'. The instances of type representing COM component, can't be casted in types different from representing COM components; however they can be casted in interfaces until the COM component underlying takes in charge the calls QueryInterfacefor the IID of the interface!!!!!!!!!!!!!!!!??????????????
Chinese or old Greek are more obvious for me.
The only thing I have found is that in regedit:
Thanks again for your help.
For your eyes only, a picture made with my telescope and some home-writed program:
The solution was so simple!!! I found it on a web page. You want an ArrayList from .net?? Make it:
function TTelescope.Get_SupportedActions: OleVariant;
var
capacity: Integer;
item:Variant;
dotNetArrayList:Variant;
begin
{ Create object }
dotNetArrayList := CreateOleObject('System.Collections.ArrayList');
{ Add an element }
dotNetArrayList.Add('Un élément chaîne');
item := dotNetArrayList.Item(0);
Result:=dotNetArrayList;
end;
And it's working perfectly. Nevertheless, thanks a lot for your answers, and perhaps a day I will try to learn C#.
Related
Is there any way to get the names of the parameters of a given method of a class/object in an array/delimited string in Delphi 7? Somehow like this:
var
s : string;
aSL : TStringList;
begin
...
// using rtti calls in TMethodUtility.collectParamNames
s := TMethodUtility.collectParamNames( TStringList.addObject );
// or
TMethodUtility.collectParamNames( TStringList.addObject, aSL );
...
end;
Thanks in advance!
It is technically possible (otherwise the IDE's Form Designer could not generate code for component event handlers at design-time), however, there are important caveats that will hinder your goal (and make the particular example in your question impossible to resolve):
the necessary RTTI is only generated for properties that are declared as published (such as component events). RTTI is not generated for methods themselves (even published ones), or for properties that are not published.
TObject has a public MethodAddress() method for getting the memory address of a published method (the DFM streaming system uses this when hooking up event handlers), however you cannot get the necessary RTTI from a method pointer alone.
IF you can match a given method pointer to the value of a published event, then you can extract the parameter names from the event's RTTI. Obtain a TypInfo.PPropInfo pointer for the event using the TypInfo.GetPropInfo() function, then pass its PropType field value to the TypInfo.GetTypeData() function to get a TypInfo.PTypeData pointer, and then you can iterate through its ParamList field (which is an array of records containing ParamName and TypeName fields).
See the following blog article on this topic for more details:
Getting the parameters of published methods.
For what you are attempting, a general-purpose solution would require Extended RTTI that was introduced in Delphi 2010 and thus is not available in Delphi 7. Extended RTTI is not limited to published items, and is much more detailed than what the old-style RTTI provides.
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.
I have 2 class hierarchies:
1.
TObject
--TAmObject
--TAmField
--TAmFarm
2.
TObjectList<T: TAmObject>
--TAmObjectList<T: TAmObject>
--TAmFieldList
--TAmFarmList
I have some of the of the object linked to TTreeView's nodes.
Then I have a task to check:
if TObject(node.Data) is TAmObject
or
if TObject(node.Data) is TAmObjectList<TAmObject>
First check is good and brings me no problems.
With the second one I have some difficulties.
Pls show me way to make the check.
For example I have TamFarmList linked to node.
Then compiled says me:
TObject(node.Data) is TAmObjectList<TAmObject> = FALSE
but
TObject(node.Data) is TAmObjectList<TAmFarm> = TRUE
But I need common check for all lists.
Pls show me the way. Is it possible?
This is a problem of covariance that Delphi does not support. Since generic types do not provide reflection of their type parameters you have to find some other ways.
One solution I have used over the past is to do some string parsing of the type name and then use RTTI to do what C# reflection for example is capable of. It is not perfect but has done the job pretty well in the past.
Here is some example code using Spring4D for that.
uses
Spring.Helpers,
Spring.Reflection;
...
var
t: TRttiType;
begin
t := TType.GetType(TObject(node.Data).ClassType);
if (t.GetGenericTypeDefinition = 'TAmObjectList<>')
and (t.GetGenericArguments[0].AsInstance.MetaclassType.InheritsFrom(TAmObject)) then
This works if your instance directly is a TAmObjectList<T> where T is TAmObject. If you want to have it work for descendant list classes you will have to walk up the inheritance hierarchy on your instance performing the GetGenericTypeDefinition check on it.
Does anyone know of a TDataset descendant that works with Generics and RTTI, so that I can write code like this, and make use of data-aware components in the GUI? :
...
ds:TDataset<TPerson>;
...
procedure DoStuff;
begin
ds:=TDataset<TPerson>.create;
ds.add(TPerson.Create('A.','Hitler',77));
ds.add(TPerson.Create('O.','Bin Laden',88));
end;
This should be possible. The fielddefs can be created via RTTI because the exact type of the data is known. Values can also be automatically marshalled back and forth, so you can both view and edit data that's in a class or a record.
I hate having to write a lot of useless marshalling code, while the required information for that is available via RTTI already.
Or maybe somebody once wrote some sort of TEnumerable <-> TDataset adapter?
Does something like that exist, or should I start writing one?
...
The closest thing that I could find is an (excellent!) example by Marco Cantu, from Mastering Delphi 7, but the code itself doesn't make use of new language features like generics, the new RTTI system, or attributes, and it doesn't work with Unicode delphi. TDataset has changed since D7 too.
The TAureliusDataSet included in TMS Aurelius comes very close to that.
Take a look at EverClassy Dataset from Inovativa at www.inovativa.com.br/public.
another one is Snap Object Dataset http://digilander.libero.it/snapobject/
DotNet4Delphi by A-Dato Scheduling Technology from the Netherlands is good for you.
Quotes:
From Torry's Delphi
Hook up any collection to your data aware controls.
DotNet4Delphi implements many .Net collection classes, including
generic types like List<> and Dictionary<>. Different from their
Delphi counterpart is that our generic collections also implement the
non-generic interfaces (IList, IDictionary) allowing you to access
your collections in multiple ways. This opens the door to use any
collection as a data source for data aware controls which is exactly
what the (also included) TListDataset component provides.
It targets Delphi XE and XE2.
It's an open source initiative, Delphi rocks !!!
I have found a more relevant resource and can't help sharing it! So relevant that I think it deserves a separate post rather than a mere update in my first answer.
The Dduce library for Delphi XE2-XE6 makes use of TListDataSet<...> a generic dataset component that can be used to expose a generic list as a TDataSet.
The most relevant units pertaining to the implementation of the generic dataset are:
DDuce.Components.VirtualDataSet.pas (The original SO post is itself cited by the author within the source code as a reference among others!!!)
DDuce.Components.ListDataSet.pas
Class hierarchy:
TDataSet <= TCustomVirtualDataset <= TListDataset <= TListDataset<T>
Yes, it inherits lots of features... my only wish is to have at my disposal a version working with a lessen requirement (Delphi XE without most of the other bells and whistles).
Look and feel:
This is a question for the generic collection gurus.
I'm shocked to find that TList does not override equals. Take a look at this example:
list1:=TList<String>.Create;
list2:=TList<String>.Create;
list1.Add('Test');
list2.Add('Test');
Result:=list1.Equals(list2);
"Result" is false, even though the two Lists contain the same data. It is using the default equals() (which just compares the two references for equality).
Looking at the code, it looks like the same is true for all the other generic collection types too.
Is this right, or am I missing something??
It seems like a big problem if trying to use TLists in practice. How do I get around this? Do I create my own TBetterList that extends TList and overrides equals to do something useful?
Or will I run into further complications with Delphi generics...... ?
[edit: I have one answer so far, with a lot of upvotes, but it doesn't really tell me what I want to know. I'll try to rephrase the question]
In Java, I can do this:
List<Person> list1=new ArrayList<Person>();
List<Person> list2=new ArrayList<Person>();
list1.add(person1);
list2.add(person1);
boolean result=list1.equals(list2);
result will be true. I don't have to subclass anything, it just works.
How can I do the equivalent in Delphi?
If I write the same code in Delphi, result will end up false.
If there is a solution that only works with TObjects but not Strings or Integers then that would be very useful too.
Generics aren't directly relevant to the crux of this question: The choice of what constitutes a valid base implementation of an Equals() test is entirely arbitrary. The current implementation of TList.Equals() is at least consistent will (I think) all other similar base classes in the VCL, and by similar I don't just mean collection or generic classes.
For example, TPersistent.Equals() also does a simple reference comparison - it does not compare values of any published properties, which would arguably be the semantic equivalent of the type of equality test you have in mind for TList.
You talk about extending TBetterList and doing something useful in the derived class as if it is a burdensome obligation placed on you, but that is the very essence of Object Oriented software development.
The base classes in the core framework provide things that are by definition of general utility. What you consider to be a valid implementation for Equals() may differ significantly from someone else's needs (or indeed within your own projects from one class derived from that base class to another).
So yes, it is then up to you to implement an extension to the provided base class that will in turn provide a new base class that is useful to you specifically.
But this is not a problem.
It is an opportunity.
:)
You will assuredly run into further problems with generics however, and not just in Delphi. ;)
What it boils down to is this:
In Java (and .NET languages) all types descend from Object. In Delphi integers, strings, etc. do not descend from TObject. They are native types and have no class definition.
The implications of this difference are sometimes subtle. In the case of generic collections Java has the luxury of assuming that any type will have a Equals method. So writing the default implementation of Equals is a simple matter of iterating through both lists and calling the Equals method on each object.
From AbstractList definition in Java 6 Open JDK:
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof List))
return false;
ListIterator<E> e1 = listIterator();
ListIterator e2 = ((List) o).listIterator();
while(e1.hasNext() && e2.hasNext()) {
E o1 = e1.next();
Object o2 = e2.next();
if (!(o1==null ? o2==null : o1.equals(o2)))
return false;
}
return !(e1.hasNext() || e2.hasNext());
}
As you can see the default implementation isn't really all that deep a comparison after all. You would still be overriding Equals for comparison of more complex objects.
In Delphi since the type of T cannot be guaranteed to be an object this default implementation of Equals just won't work. So Delphi's developers, having no alternative left overriding TObject.Equals to the application developer.
I looked around and found a solution in DeHL (an open source Delphi library). DeHL has a Collections library, with its own alternative List implementation. After asking the developer about this, the ability to compare generic TLists was added to the current unstable version of DeHL.
So this code will now give me the results I'm looking for (in Delphi):
list1:=TList<Person>.Create([Person.Create('Test')]);
list2:=TList<Person>.Create([Person.Create('Test')]);
PersonsEqual:=list1.Equals(list2); // equals true
It works for all types, including String and Integer types
stringList1:=TList<string>.Create(['Test']);
stringList2:=TList<string>.Create(['Test']);
StringsEqual:=stringList1.Equals(stringList2); // also equals true
Sweet!
You will need to check out the latest unstable version of DeHL (r497) to get this working. The current stable release (0.8.4) has the same behaviour as the standard Delphi TList.
Be warned, this is a recent change and may not make it into the final API of DeHL (I certainly hope it does).
So perhaps I will use DeHL instead of the standard Delphi collections? Which is a shame, as I prefer using standard platform libraries whenever I can. I will look further into DeHL.