Is there any Delphi version that can emit RTTI containing tkUnknown? - delphi

Just to make sure I'm not overlooking a strange edge case as I've found yet a case that produce it, but I want to make sure:
Is there any Delphi version that can emit RTTI containing a type that has tkUnknown as TTypeKind?
If so:
any documentation reference?
what type would produce it?
In the current Delphi XE5 RTL, the only place I could find handling tkUnknown is TValue, but I've not found a code path in the RTL that sets up a TValue containing a TypeInfo having tkUnknown as Kind.

The answer is no. Anything else would be a bug in the compiler.
tkUnknown is the indication that there is no type info available which might be the case for discontiguous enumerations and enumerations which don't start at zero (as explained by Barry here) and some types from long ago (like Real48).
It also is returned by TValue.Kind when TValue.IsEmpty is true. (since XE2 afaik before it also could return True in cases where it held a reference type that was nil which was a bug).
When you are retrieving RTTI for something that does not contain type info (like a field, property or parameter of a type that has no type info) your RTTI information is incomplete. TRttiField.FieldType and TRttiProperty.PropertyType return nil in these cases and the array returned by TRttiMethod.GetParameters is incomplete.
While it is possible to call TValue.Make<T> with a type that has no type info you will not be able to do much with this because its TypeInfo will be nil. The compiler obviously works around E2134 and passes nil to TValue.Make. Thus TValue.Kind will say tkUnknown.

Related

Typecast by classType parameter

How can i make a typecast using the object classType parameter?
(obj as obj.classType).items[i]...//obj.classType = TList<myType>
This code is rejected by the compiler. But, moreover, I need to get access to the properties of the object, no matter what class (TList) it possesses:
system.classes.TList
system.generics.collections.TList<T>
In my program there is an object that actually belongs to the class TList<T>, and I am afraid that casting to a system.classes.TList type may cause some errors in my program.
It's impossible to typecast like this. That is because Delphi is a statically typed language.
Imagibe you could write code like this:
(obj as obj.classType).items
Since the value if obj.classType is unknown at compile time, the compiler cannot, at compile time, know whether items even exists, never mind how to access it and so on.
As for your goal regarding list classes, the generic and non-generic list types do not share a common ancestor beyond TObject. What you are attempting is simply impossible.
Whatever your problem is, you'll need to find a different solution.

OLE automation: How to check if a variant references an automation object

I would like to know how can i determine, whether a variant is referencing an OLE automation object, or not.
I'm exporting some Excel graphs to Powerpoint.
I have this code:
var PptFile: Variant;
....
// PptFile _might_ be initialized:
PptFile:=pptApp.Presentations.Open(pptFilename);
// It depends on whether the export has items which need to be exported to
// Powerpoint or not
....
// I would like to determine if PptFile does reference an OLE automated object or not
PptFile.SaveAs(excelFileName+'.pptx');
I know, it could be done by placing the last line of the code (with saveAs) between try...except...end, but i don't feel that approach is good enough.
I was reading about VarIsEmpty, VarIsEmptyParam, Nothing, this question, but i'm not sure about this.
You should use VarIsClear for this test.
Indicates whether the specified variant has an undefined value.
VarIsClear returns true if the given variant's value is undefined. The
value can be undefined for any of several reasons:
The Variant may have had its value set to Unassigned.
The Variant's value may be an interface type that has been set to nil (Delphi) or NULL (C++).
The Variant may be a custom variant that returns true from its IsClear method.
In all other cases, the function result is false.
Note: Do not confuse an unassigned variant with a Null variant. A Null variant is still assigned, but has the value Null. Unlike
unassigned variants, Null variants can be used in expressions and can
be converted to other types of variants.
However, I question whether or not it is needed. How could it be that PptFile was not assigned? That can only happen if the call to pptApp.Presentations.Open() fails, and that would raise an exception. Or am I mis-understanding this? I cannot at the present see any scenario in which you could reach the call to PptFile.SaveAs() for which PptFile had not been assigned.

Why is a Currency variable treated as a constant with FillChar in Delphi?

The following code should compile and does compile with many other types.
However, the compiler reports a "Constant object cannot be passed as var parameter" error - despite the variable quite obviously being a variable.
program CurrencyConstant;
{$APPTYPE CONSOLE}
var
GVar: Currency;
begin
FillChar(GVar, SizeOf(GVar), 0);
end.
Similarly, the same problem occurs with a local variable in a procedure.
procedure TestCurrency;
var
LVar: Currency;
begin
FillChar(LVar, SizeOf(LVar), 0);
end;
I suspect it has something to do with the fact that FillChar is a compiler magic procedure, and that Dest is an untyped var parameter. FillChar is the only routine I've found with this problem.
What causes this problem?
Are any other types affected?
In response to the inevitable "Why would you do that comments": We have a code generator that uses FillChar to generically initialise record structures & primitive types. It works with everything else, but unexpectedly failed with Currency. We do have workarounds, but it would be nice to understand the root cause, and know whether anything else is likely to cause us trouble.
Edit
From Jeroen's answer it is reasonable to conclude that the issue exists in all vesions of Delphi. Furthermore array's of Currency apparently exhibit a similar problem.
David's answer provides some nice workarounds.
One final workaround to consider is, modifying the generator to deal with Currency as a special case and simply set the Value := 0.
What causes the problem?
A compiler bug. Please submit a QC report.
Are any other types affected?
Maybe. Try some to find out.
As for a work around I would write it like this:
FillChar(Pointer(#LVar)^, SizeOf(LVar), 0);
or perhaps like this:
ZeroMemory(#LVar, SizeOf(LVar));
or even like this:
LVar := Default(Currency);
Personally I regard ZeroMemory as being more descriptive than FillChar.
As requested by Craig Young:
It still occurs in Delphi XE4.
Report No: 118866 Status: Reported
Cannot perform FillChar on Currency variables
https://web.archive.org/web/20150322021442/http://qc.embarcadero.com/wc/qcmain.aspx?d=118866
It is similar to
http://qc.embarcadero.com/wc/qcmain.aspx?d=87168 (not archived)
The workaround for this compiler bug for Delphi < 2009: use ZeroMemory or FillMemory from the Windows unit which works just as well as FillChar.
On the Delphi side, ZeroMemory and FillMemory use FillChar underneath which might be inlined as of Delphi 2006.
On the C++ side both use compiler macros.
It might be that this issue only happens with Currency because that is the only numeric compiler type that is scaled.
The issue does not reproduce with ordinal types, regular floating point types, and Comp.
Edit: The issue has been fixed in XE5 Update 2

How can I get the underlying raw Variant value of a Delphi 6 indexed property that accesses that Variant?

I have a Delphi 6 class object that contains an array of 30 Variants, each of which is exposed via a different indexed property. For example:
property responseCode: integer
Index 7 read getIndexedProperty_integer write setIndexedProperty_integer;
I did this to make using the array of Variants easier (helps the IDE's auto-complete) and to provide type safety. It works fine but now I have a wrinkle. The array of Variants are initialized to NULL when the class that wraps it is constructed, so I can tell if a particular variant has ever been instantiated with a value. A consequence of this is if only some of the Variants are instantiated (given valid values), any attempt to access a property that currently represents a NULL Variant will cause a Variant conversion error when Delphi tries to convert the variant to the type declared by the indexed property.
I would much rather not declare an "isValid" property for each indexed property. I was wondering if there was a way to use the TypeInfo library to get the raw value of the underlying Variant without having to access the indexed property directly and thus triggering the conversion Exception. Then I could write code like (using the example property above):
isValidProperty(responseCode);
and that function would return TRUE if the Variant underlying the responseCode property is not NULL and FALSE if it is.
I know I can walk the PPropList property list for the class and access the properties by name, but then I would have to use code like:
isValidProperty('responseCode');
and pass the property name in string form instead of passing in the property directly like the first isValidProperty() above. Is there a way to do this?
So you want "to get the raw value of the underlying Variant without having to access the indexed property directly and thus triggering the conversion Exception". So long as you can access the underlying Variant itself, yes, you can. You will need to change the container class itself most likely.
From the Delphi XE2 help page on variant types:
The standard function VarType returns a variant's type code. The
varTypeMask constant is a bit mask used to extract the code from
VarType's return value, so that, for example,
VarType(V) and varTypeMask = varDouble
returns True if V contains a Double or an
array of Double. (The mask simply hides the first bit, which indicates
whether the variant holds an array.) The TVarData record type defined
in the System unit can be used to typecast variants and gain access to
their internal representation.
You should be able to use a combination of the methods and records mentioned here to find out anything you want about the internal data inside the variant, including if it's a NULL variant, as well as getting direct access to it.
(This system seems slightly dodgy design to me: it doesn't seem a very type safe implementation... see my comment above. I think a design based on the actual types of the values you are expecting might be safer. But, this will let you achieve your goal.)

Is it safe to pass a pointer to a method as a member of a record?

I want to implement a function in a dll that accepts a record as a parameter and this record as a few fields that hold pointers to callback routines. Would this be safe?
Yes, it's perfectly safe to have a pointer to a record that holds other pointers.
Your title mentions methods, though. DLLs rarely request method pointers because method pointers only exist in Delphi and C++ Builder. DLLs written expecting other tools to be able to use them will request ordinary function pointers, so please beware that method pointers are not compatible with pointers to standalone subroutines. The compiler will usually balk if you try to mix them, but type-casting can quell that error message. As a rule of thumb, if you're type-casting a function pointer or method pointer, you're doing something wrong. If your type declarations and your function declarations are correct, you won't need to type-cast.
Likewise, if you're using the # operator to create a function pointer or method pointer, you're probably doing it wrong. In most cases, the compiler can detect and assign compatible code pointers automatically, without you telling it that there's a pointer. Using # may suppress some of Delphi's type checking.
I don't see why not. I think all the usual issues with procedure/method pointers apply, so the object needs to exist if it's a method pointer.
It is safe but there are two issues that you should be aware about :
Records that declared as local variables are stored in the stack and they go away when the function returns. You should consider to allocate/dispose them on the heap with new/dispose functions.
If the DLL will be used by a program developed in other than delphi (or maybe even different versions of delpi), you have to use packed records.

Resources