Delphi OpenTools API get component property - delphi

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);

Related

Using ToString for Variant variables

The following code produces an EVariantInvalidOpError exception:
var
i : Variant;
begin
i := 10;
ShowMessage(i.ToString());
end;
All the following works good but I don't understand why the ToString function raises exception for Variant type variables:
var
i : Variant;
begin
i := 10;
ShowMessage(VarToStr(i));
end;
var
i : Integer;
begin
i := 10;
ShowMessage(i.ToString());
end;
Variants let you store values of various types in them, while the type may be unknown at compile-time. You can write an integer value into single variable of Variant type an later overwrite it with string value. Along with the value variant records stores also the type information in it. Among those values some of them are automatically allocated and/or reference counted. The compiler does a lot of stuff behind the scenes when writing or reading the value from Variant variable.
Variants of type varDispatch get even more special treat from the compiler. varDispatch indicates that the value is of type IDispatch (usually, but not necessarily related to Windows COM technology). Instance of IDispatch provides information about its methods and properties via GetTypeInfoCount and GetTypeInfo methods. You can use its GetIDsOfNames method to query the information by name.
Let's answer the question from your comment first:
Why does Delphi allow me to use the ToString function even if there is no helper implementing such function for the Variant type?
This is how Delphi implements concept called late binding. It allows you to call methods of an object which type is unknown at compile-time. The prerequisite for this to work is that the underlying variant type supports late binding. Delphi has built-in support for late binding of varDispatch and varUnknown variants as can be seen in procedure DispInvokeCore in unit System.Variants.
I don't understand why the ToString function raises exception for Variant type variables.
As discussed above, in run-time your program tries to invoke ToString method on variant value which in your case is of type varByte. Since it doesn't support late binding (as well as further ordinal variant types) you get the exception.
To convert variant value to string use VarToStr.
Here's a simple example of using late binding with Microsoft Speech API:
uses
Winapi.ActiveX,
System.Win.ComObj;
var
Voice: Variant;
begin
CoInitialize(nil);
try
Voice := CreateOleObject('SAPI.SpVoice');
Voice.Speak('Hello, World!');
finally
CoUninitialize;
end;
end.

Use Rtti to set method field

I'm using Delphi XE to write a base class, which will allow descending classes to have dll methods mapped by applying an annotation. However I get a typecasting error, which is understandable.
In essence the base class should look like this:
TWrapperBase = class
public
FLibHandle: THandle;
procedure MapMethods;
end;
procedure TWrapperBase.MapMethods;
var
MyField: TRttiField;
MyAttribute: TCustomAttribute;
pMethod: pointer;
begin
FLibHandle := LoadLibrary(PWideChar(aMCLMCR_dll));
for MyField in TRttiContext.Create.GetType(ClassType).GetFields do
for MyAttribute in MyField.GetAttributes do
if MyAttribute.InheritsFrom(TMyMapperAttribute) then
begin
pMethod := GetProcAddress(FLibHandle, (MyAttribute as TMyMapperAttribute).TargetMethod);
if Assigned(pMethod) then
MyField.SetValue(Self, pMethod); // I get a Typecast error here
end;
And a descending class could look like this:
TDecendant = class(TWrapperBase)
private type
TSomeDLLMethod = procedure(aParam: TSomeType); cdecl;
private
[TMyMapperAttribute('MyDllMethodName')]
FSomeDLLMethod: TSomeDLLMethod;
public
property SomeDLLMethod: TSomeDLLMethod read FSomeDLLMethod;
end;
I could implement this differently, by hard coding the linking for each method in an overriden 'MapMethods'. This would however require each descendant to do so which I'd like to avoid.
I know that the TValue as used in this case will contain a pointer and not of the correct type (procedure(aParam: TSomeType); cdecl; in this case).
My question: Is there a way to pass the pointer from 'GetProcAdress' as the correct type, or to set the field directly (for example by using the field address 'PByte(Self)+MyField.Offset', which you can use to set the value of a record property)?
With the old Rtti, this could be done but only for published properties and without any type checking:
if IsPublishedProp(Self, 'SomeDLLMethod') then
SetMethodProp(Self, 'SomeDLLMethod', GetProcAddress(FLibHandle, 'MethodName');
There are two problems:
First your EInvalidCast is caused by TValue being very strict about type conversions. You are passing in a Pointer and want to set a field of type TSomeDLLMethod. You need to explicitly pass a TValue that has the correct type info.
if Assigned(pMethod) then
begin
TValue.Make(#pMethod, MyField.FieldType.Handle, value);
MyField.SetValue(Self, value);
end;
Now you will run into another EInvalidCast exception which is triggered because of a bug in XE inside the GetInlineSize method of the Rtti.pas which returns 0 for a tkProcedure kind of type. I don't know in what version this got fixed but it does not exist anymore in XE5.
For XE this can be fixed by using a unit I wrote some while ago (and which I just updated to fix this bug): RttiPatch.pas.
I also reported the original issue because Pointer is assignment compatible to a procedure type so TValue should also handle this: http://qc.embarcadero.com/wc/qcmain.aspx?d=124010
You could try something like:
Move(pMethod, PByte(Self) + Field.Offset, SizeOf(Pointer));
or
PPointer(PByte(Self) + Field.Offset)^ := pMethod;

store array of TPoint inside TObjectList

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.

Delphi: problem setting length of a TByteDynArray

Question closed (answer accepted) replaced by Delphi 7: Access violation - TByteDynArray problem)
I have the following delcartion given to me
MyPhoto = class(TRemotable)
private
FhasPhoto: Boolean;
FphotoData: TByteDynArray;
published
property hasPhoto: Boolean read FhasPhoto write FhasPhoto;
property photoData: TByteDynArray read FphotoData write FphotoData;
end;
and I want to
var photo : MyPhoto;
photo := MyPhoto.Create();
SetLength(photo.photoData, 4);
but I get
[Error] mainForm.pas(340): Constant object cannot be passed as var parameter
1) How do I code it correctly, given that I can't change the definition of the class Photo?
2) can I effectively 'cast' any structure to a TByteDynArray just by assigning it?
(as you might have guessed, I'm a BCB guy trying to get into Ddlphi :-)
p.s I will settle for being able to assign each byte of the photo data individually...
This is a flaw in Delphi's implementation of properties. Even if the property can be read and written to, even if both the read and the write point to the same field in the underlying object, you can't pass a property to a var parameter, such as to SetLength. There's no good reason for this, but that's the way it works, for the time being at least.
There's a fairly simple workaround for this issue, though. You need a method on the object that takes an integer and will set the length of FPhotoData internally.
Here is a way :
MyPhoto = class(TRemotable)
...
public
procedure SetSize( aSize : Integer );
end;
procedure MyPhoto.SetSize( aSize : Integer );
begin
SetLength( FphotoData, aSize );
end;
var photo : MyPhoto;
photo := MyPhoto.Create();
MyPhoto.SetSize(4);

How to know what type is a var?

TypeInfo(Type) returns the info about the specified type, is there any way to know the typeinfo of a var?
var
S: string;
Instance: IObjectType;
Obj: TDBGrid;
Info: PTypeInfo;
begin
Info:= TypeInfo(S);
Info:= TypeInfo(Instance);
Info:= TypeInfo(Obj);
end
This code returns:
[DCC Error] Unit1.pas(354): E2133 TYPEINFO standard function expects a type identifier
I know a non instantiated var is only a pointer address.
At compile time, the compiler parses and do the type safety check.
At run time, is there any way to know a little more about a var, only passing its address?
No.
First, there's no such thing as a "non-instantiated variable." You instantiate it by the mere act of typing its name and type into your source file.
Second, you already know all there is to know about a variable by looking at it in your source code. The variable ceases to exist once your program is compiled. After that, it's all just bits.
A pointer only has a type at compile time. At run time, everything that can be done to that address has already been determined. The compiler checks for that, as you already noted. Checking the type of a variable at run time is only useful in languages where a variable's type could change, as in dynamic languages. The closest Delphi comes to that is with its Variant type. The type of the variable is always Variant, but you can store many types of values in it. To find out what it holds, you can use the VarType function.
Any time you could want to use TypeInfo to get the type information of the type associated with a variable, you can also directly name the type you're interested in; if the variable is in scope, then you can go find its declaration and use the declared type in your call to TypeInfo.
If you want to pass an arbitrary address to a function and have that function discover the type information for itself, you're out of luck. You will instead need to pass the PTypeInfo value as an additional parameter. That's what all the built-in Delphi functions do. For example, when you call New on a pointer variable, the compiler inserts an additional parameter that holds the PTypeInfo value for the type you're allocating. When you call SetLength on a dynamic array, the compiler inserts a PTypeInfo value for the array type.
The answer that you gave suggests that you're looking for something other than what you asked for. Given your question, I thought you were looking for a hypothetical function that could satisfy this code:
var
S: string;
Instance: IObjectType;
Obj: TDBGrid;
Info: PTypeInfo;
begin
Info:= GetVariableTypeInfo(#S);
Assert(Info = TypeInfo(string));
Info:= GetVariableTypeInfo(#Instance);
Assert(Info = TypeInfo(IObjectType));
Info:= GetVariableTypeInfo(#Obj);
Assert(Info = TypeInfo(TDBGrid));
end;
Let's use the IsClass and IsObject functions from the JCL to build that function:
function GetVariableTypeInfo(pvar: Pointer): PTypeInfo;
begin
if not Assigned(pvar) then
Result := nil
else if IsClass(PPointer(pvar)^) then
Result := PClass(pvar).ClassInfo
else if IsObject(PPointer(pvar)^) then
Result := PObject(pvar).ClassInfo
else
raise EUnknownResult.Create;
end;
It obviously won't work for S or Instance above, but let's see what happens with Obj:
Info := GetVariableTypeInfo(#Obj);
That should give an access violation. Obj has no value, so IsClass and IsObject both will be reading an unspecified memory address, probably not one that belongs to your process. You asked for a routine that would use a variable's address as its input, but the mere address isn't enough.
Now let's take a closer look at how IsClass and IsObject really behave. Those functions take an arbitrary value and check whether the value looks like it might be a value of the given kind, either object (instance) or class. Use it like this:
// This code will yield no assertion failures.
var
p: Pointer;
o: TObject;
a: array of Integer;
begin
p := TDBGrid;
Assert(IsClass(p));
p := TForm.Create(nil);
Assert(IsObject(p));
// So far, so good. Works just as expected.
// Now things get interesting:
Pointer(a) := p;
Assert(IsObject(a));
Pointer(a) := nil;
// A dynamic array is an object? Hmm.
o := nil;
try
IsObject(o);
Assert(False);
except
on e: TObject do
Assert(e is EAccessViolation);
end;
// The variable is clearly a TObject, but since it
// doesn't hold a reference to an object, IsObject
// can't check whether its class field looks like
// a valid class reference.
end;
Notice that the functions tell you nothing about the variables, only about the values they hold. I wouldn't really consider those functions, then, to answer the question of how to get type information about a variable.
Furthermore, you said that all you know about the variable is its address. The functions you found do not take the address of a variable. They take the value of a variable. Here's a demonstration:
var
c: TClass;
begin
c := TDBGrid;
Assert(IsClass(c));
Assert(not IsClass(#c)); // Address of variable
Assert(IsObject(#c)); // Address of variable is an object?
end;
You might object to how I'm abusing these functions by passing what's obviously garbage into them. But I think that's the only way it makes sense to talk about this topic. If you know you'll never have garbage values, then you don't need the function you're asking for anyway because you already know enough about your program to use real types for your variables.
Overall, you're asking the wrong question. Instead of asking how you determine the type of a variable or the type of a value in memory, you should be asking how you got yourself into the position where you don't already know the types of your variables and your data.
With generics, it is now possible to get the type info without specifying it.
Certain users indicated the following code doesn't compile without errors.
As of Delphi 10 Seattle, version 23.0.20618.2753, it compiles without errors, as seen below in the screenshot.
program TypeInfos;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.TypInfo;
type
TTypeInfo = class
class procedure ShowTypeInfo<T>(const X: T);
end;
{ TTypeInfo }
class procedure TTypeInfo.ShowTypeInfo<T>(const X: T);
var
LTypeInfo: PTypeInfo;
begin
LTypeInfo := TypeInfo(T);
WriteLn(LTypeInfo.Name);
end;
var
L: Exception;
B: Boolean;
begin
// Console output
TTypeInfo.ShowTypeInfo(L); // Exception
TTypeInfo.ShowTypeInfo(B); // Boolean
end.
Not that I know of. You can get RTTI (Run Time Type Information) on published properties of a class, but not for "normal" variables like strings and integers and so forth. The information is simply not there.
Besides, the only way you could pass a var without passing a type is to use either a generic TObject parameter, a generic type (D2008, as in ), or as an untyped parameter. I can't think of another way of passing it that would even compile.

Resources