I defined a Objectlist to store several Polygons as TFPolygon = array of TPoint inside this TObjectList; but with the add function of my objectlist I get an Access violation error
:
type
TFPolygon = array of TPoint;
TFPolygonList = class(TObjectList)
private
procedure SetPolygon(Index: Integer; Value: TFPolygon);
function GetPolygon(Index: Integer): TFPolygon;
public
procedure Add(p: TFPolygon);
property Items[index: Integer]: TFPolygon read GetPolygon write SetPolygon; default;
end;
implementation
procedure TFPolygonList.SetPolygon(Index: Integer; Value: TFPolygon);
begin
inherited Items[Index] := Pointer(Value);
end;
function TFPolygonList.GetPolygon(Index: Integer): TFPolygon;
begin
Result := TFPolygon(inherited Items[Index]);
end;
procedure TFPolygonList.Add(p: TFPolygon);
begin
inherited Add(Pointer(p));
end;
I can not understand the error inside this code sample ? Can I only store classes inside a TObjectList or is my approach to store arrays of TPoints also valid ?
Your approach is not valid. Dynamic arrays are managed types. Their lifetimes are managed by the compiler. For that to work you must not cast away the fact that they are managed types, which is exactly what you did.
You cast the dynamic array to Pointer. At that point you have taken a new reference to the dynamic array, but the compiler is not aware of it because a Pointer is not a managed type.
You've got a few options to solve your problem.
If you are on a modern Delphi then stop using TObjectList. Instead use the generic type safe containers in Generics.Collections. In your case TList<TFPolygon> is what you need. Because this is compile time type safe, all the lifetime of the managed types is taken care of.
If you are on an older Delphi, then you can wrap your dynamic array inside a class. Then add instances of those classes to your TObjectList. Make sure that your list is configured to own its objects. It's perfectly possible for you to do that wrapping purely in the implementation of TFPolygonList which will encapsulate things well.
Related
I'm implementing a package to convert and auto-generate components in the delphi IDE. I'm aware that GExperts has a similar function but I need to customize some specific properties.
Right now I'm stuck on accessing the TADOQuery.SQL property, which is an instance of TStrings:
var
aVal : TValue;
aSqlS : TStrings;
begin
[...]
if (mycomp.GetComponentType = 'TADOQuery') then
if mycomp.GetPropValueByName('SQL', aVal) then
begin
aSqlS := TStrings(aVal.AsClass);
if Assigned(aSqlS) then <----- problem is here
ShowMessage(aSqlS.Text); <----- problem is here
end;
end;
I'm not really sure whether using TValue from RTTI is the correct way to go.
Thanks
Assuming GetPropValueByName() is returning a valid TValue (you did not show that code), then using aVal.AsClass is wrong since the SQL property getter does not return a metaclass type. It returns an object pointer, so use aVal.AsObject instead, or even aVal.AsType<TStrings>.
Update If comp is actually IOTAComponent than TValue is definitely wrong to use at all. The output of IOTAComponent.GetPropValueByName() is an untyped var that receives the raw data of the property value, or an IOTAComponent for TPersistent-derived objects:
var
aVal: IOTAComponent;
aSqlS : TStrings;
begin
[...]
if (mycomp.GetComponentType = 'TADOQuery') then
if mycomp.PropValueByName('SQL', aVal) then
ShowMessage(TStrings(aVal.GetComponentHandle).Text);
end;
However, a better option would be to access the actual TADOQuery object instead:
if (mycomp.GetComponentType = 'TADOQuery') then
ShowMessage(TADOQuery(comp.GetComponentHandle).SQL.Text);
I have a 'Base class' which contain a 'function' that accept parameter of type 'Array of const' as shown below:-
type
TBaseClass = class(TObject)
public
procedure NotifyAll(const AParams: array of const);
end;
procedure TBaseClass.NotifyAll(const AParams: array of const);
begin
// do something
end;
I have another 'Generic class' that is derived from the 'base class' ( defined above )
type
TEventMulticaster<T> = class(TBaseClass)
public
procedure Notify(AUser: T); reintroduce;
end;
procedure TEventMulticaster<T>.Notify(AUser: T);
begin
inherited NotifyAll([AUser]); ERROR HERE
end;
Every time I compile this code it gives error saying:
Bad argument type in variable type array constructor
What does it referring to be wrong?
You cannot pass a Generic argument as a variant open array parameter. The language Generics support simply does not cater for that.
What you can do instead is wrap the Generic argument in a variant type, for instance TValue. Now, you cannot pass TValue instances in a variant open array parameter either, but you can change NotifyAll() to accept an open array of TValue instead:
procedure NotifyAll(const AParams: array of TValue);
Once you have this in place, you can call it from your Generic method like so:
NotifyAll([TValue.From<T>(AUser)]);
Fundamentally, what you are attempting to do here is combine compile-time parameter variance (Generics) with run-time parameter variance. For the latter, there are various options. Variant open array parameters are one such option, but they do not play well with Generics. The alternative that I suggest here, TValue, does have good interop with Generics.
The System.Rtti unit has something exactly for you needs, but not widely known:
TValueArrayToArrayOfConst() and
ArrayOfConstToTValueArray()
So your implementation should be:
procedure TEventMulticaster<T>.Notify(AUser: T);
var
ParametersAsTValueArray: array[1 .. 1] of TValue;
begin
ParametersAsTValueArray[1] := TValue.From<T>(AUser);
NotifyAll(TValueArrayToArrayOfConst(ParametersAsTValueArray));
end;
Notes:
TValueArrayToArrayOfConst()'s result is a non-owning container. It contains memory pointers backed by the source array of the TValue container. ParametersAsTValueArray is alive and not being altered while the array of const is used.
One of these 2 Rtti procedures has a bug with regard to TClass values processing. TClass becomes a Pointer on some stage, and string.Format() breaks because Pointer and TClass are not the same thing. Perform tests on all TVarRec.VType, they are not so many, much less that Variant's VType.
I have some global string variables.
I have to create the function that I could pass & store them in some structure.
Later I need to enumerate them and check their values.
how can this be easily achieved?
(I think I would need some kind of reflection, or store array of pointers).
Anyway, any help will be appreciated.
Thanks!
First of all you can't use Delphi's RTTI for that purpose, because Delphi 7's RTTI only covers published members of classes. Even if you were on Delphi XE, there's still no RTTI for global variables (because RTTI is tied to Types, not to "units").
The only workable solution is to create your own variable registry and register your globals using a name and a pointer to the var itself.
Example:
unit Test;
interface
var SomeGlobal: Integer;
SomeOtherGlobal: string;
implementation
begin
RegisterGlobal('SomeGlobal', SomeGlobal);
RegisterGlobal('SomeOtherGlobal', SomeOtherGlobal);
end.
were the RegisterXXX types would need to be defined somewhere, probably in there own unit, like this:
unit GlobalsRegistrar;
interface
procedure RegisterGlobal(const VarName: string; var V: Integer); overload;
procedure RegisterGlobal(const VarName: string; var V: String); overload;
// other RegisterXXX routines
procedure SetGlobal(const VarName: string; const Value: Integer); overload;
procedure SetGlobal(const VarName:string; const Value:string); overload;
// other SetGlobal variants
function GetGlobalInteger(const VarName: string): Integer;
function GetGlobalString(const VarName:string): string;
// other GetGlobal variants
implementation
// ....
end.
You could also have a global TStringList variable holding a list of name-value pairs.
On Delphi 7, I would follow Cosmin's idea for the interface, and for the implementation, I would use a dictionary type based on Julian Bucknall's excellent data structures code for Delphi, ezDSL.
Later versions of delphi like XE not only have more advanced RTTI they also include a pretty great dictionary type, using generics, so the dictionary can contain any type you like. The esDSL dictionary is pretty easy to use but since it's pointer based, it isnt as type safe as the delphi generics dictionary.
Since what you need to do is look up string "variable names" in very fast time (O(1) we like to call it), what you need is a string-to-variable dictionary. You could have Strings for the keys, and Variants as the values in the dictionary, and just get rid of the original global variables, or you could attempt some rather complex pointers-to-globals logic, but I really think you'd be better off with a simple dictionary of <string,variant> key,value tuples.
I need to understand how to use the generic Delphi 2009 TObjectList. My non-TObjectList attempt looked like
TSomeClass = class(TObject)
private
FList1: Array of TList1;
FList2: Array of TList2;
public
procedure FillArray(var List: Array of TList1; Source: TSource); Overload;
procedure FillArray(var List: Array of TList2; Source: TSource); Overload;
end;
Here, TList1 and TList2 inherits the same constructor constructor TParent.Create(Key: string; Value: string);. However, due to different specialization (e.g. different private fields), they will not be of the same type. So I have to write two nearly identical fill methods:
procedure TSomeClass.FillArray(var List: Array of TList1; Source: TSource);
begin
for i := 0 to Source.List1.Count - 1 do begin
SetLength(List, Length(List) + 1);
List[i] := TList1.Create(Source.List1[i].Key, Source.List1[i].Value);
end;
end;
with FillArray(List: Array of TList2; Source: TSource); being identical, except for the replacement of TList1 with TList2 throughout. As far as I understand, this could be neatly circumvented by using TObjectList and a single fill method; yet, I don't have a clue how to go about this. Do anyone have some good pointers on this? Thanks!
You wouldn't be able to condense that down by using a generic list, since a generic's type is part of the class definition. So a TObjectList<TMyClass1> is different from (and incompatible with) a TObjectList<TMyClass2>. The main benefit of using generic lists over normal TList/TObjectList is improved type safety, with less casts and cleaner code.
Also, if you're using key/value pairs, are you putting them into a list and then retrieving them by searching for a key and returning the associated value? If so, take a look at TDictionary in Generics.Collections. It's a generic key/value hash table that will greatly simplify this process for you.
The Official Embarcadero documentation Wiki on the Generics.Collections.TObjectList contains a simple code example of the TObjectList in action.
I'm not certain exactly what the question is driving at but to address the broad use of a TObjectList, the example initialisation code for a TObjectList might look like this:
var
List: TObjectList<TNewObject>;
Obj: TNewObject;
begin
{ Create a new List. }
List := TObjectList<TNewObject>.Create();
{ Add some items to the List. }
List.Add(TNewObject.Create('One'));
List.Add(TNewObject.Create('Two'));
{ Add a new item, but keep the reference. }
Obj := TNewObject.Create('Three');
List.Add(Obj);
The example code should give you an idea of what the TObjectList can do but If I've understood the question correctly it seems that you would like to be able to add more than one class type to a single instance of the TObjectList? A TObjectList can only be initiated with a single type so it might be better if you initiated the TObjectList with a Interface or Abstract class that is shared by all of the classes you wish to add to it.
One important difference when using a TObjectList compared to creating your own is the existance of the OwnsObjects property which tells the TObjectList whether it owns the objects you add to it and therefore consequently whether it should manage freeing them itself.
Something like this?
TSomeClass = class
private
FList1: TArray<TList1>;
FList2: TArray<TList2>;
public
procedure FillArray<T>(var List: TArray<T>; Source: TSource);
end;
procedure TSomeClass.FillArray<T>(var List: TArray<T>; Source: TSource);
begin
for i := 0 to Source.List1.Count - 1 do begin
SetLength(List, Length(List) + 1);
List[i] := T.Create(Source.List1[i].Key, Source.List1[i].Value);
end;
end;
This, or something like it should do what you want, afaict.
Given a text string containing a type name, is there some way to get the appropriate type itself?
I'm looking to do something like this:
type
TSomeType<T> = class
// yadda yadda
end;
procedure DoSomething;
var
obj : TObject;
begin
o := TSomeType<GetTypeByName('integer')>.Create;
// do stuff with obj
end;
I've looked at several RTTI explanations online and looked through the Delphi units and don't see what I'm looking for. Is this possible?
No, generics are entirely compiletime.
The new RTTI unit in Delphi 2010 has a way of retrieving types declared in the interface section of units. For any given type, represented by a TRttiType instance, the TRttiType.QualifiedName property returns a name that can be used with TRttiContext.FindType later to retrieve the type. The qualified name is the full unit name (including namespaces, if they exist), followed by a '.', followed by the full type name (including outer types if it nested).
So, you could retrieve a representation of the Integer type (in the form of a TRttiType) with context.FindType('System.Integer').
But this mechanism can't be used to retrieve instantiations of generic types that weren't instantiated at compile time; instantiation at runtime requires runtime code generation.
You can always register your types into some sort of registry (managed by a string list or dictionary) and create a factory function to then return the appropriate object. Unfortunately you would have to know in advance what types you were going to need. Something similar to the Delphi functions RegisterClass and FindClass (in the classes unit). My thinking is to put the generic template type into the list directly.
An example of possible usage:
RegisterCustomType('Integer',TSomeType<Integer>);
RegisterCustomType('String',TSomeType<String>);
if FindCustomType('Integer') <> nil then
O := FindCustomType('Integer').Create;
EDIT: Here is a specific simple implementation using a tDictionary from Generics.Collections to handle the registry storage...I'll leave extracting this into useful methods as a simple exercise for the reader.
var
o : TObject;
begin
TypeDict := TDictionary<String,TClass>.Create;
TypeDict.Add('integer',TList<integer>);
if TypeDict.ContainsKey('integer') then
o := TypeDict.Items['integer'].Create;
if Assigned(o) then
ShowMessage(o.ClassName);
end;
Another EDIT: I was giving this some thought last night, and discovered another technique that you can merge into this concept. Interfaces. Here is a quick do nothing example, but can easily be extended:
TYPE
ITest = interface
['{0DD03794-6713-47A0-BBE5-58F4719F494E}']
end;
TIntfList<t> = class(TList<T>,ITest)
public
function QueryInterface(const IID: TGUID; out Obj): HRESULT; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
procedure TForm1.Button7Click(Sender: TObject);
var
o : TObject;
fTestIntf : ITest;
begin
TypeDict := TDictionary<String,TClass>.Create;
TypeDict.Add('integer',TIntfList<integer>);
if TypeDict.ContainsKey('integer') then
o := TypeDict.Items['integer'].Create;
if Assigned(o) and Supports(o,ITest,fTestIntf) then
ShowMessage(o.ClassName);
end;
of course you would have to implement the QueryInterface, _AddRef and _Release methods and extend the interface to do something more useful.
If you forget generics and basic types, the "RegisterClass" function would be helpful. But it doesn't work for generics or basic types.