Thursday, August 31, 2006

DN4DP#1: Getting classy

As most of you will know by now, I was the tech editor of Jon Shemitz’ great .NET 2.0 for Delphi Programmers book and I wrote chapter 10 on The Delphi Language – covering what’s new in the language since Delphi 7. Jon has made one chapter available for download.

To give you some extra teasers from the book, I will in the coming months post a few excerpts of The Delphi Language chapter. Note that I do not get any royalties from the book and I highly recommend that you get your own copy – for instance at Amazon.

"Getting classy

The object model has been extended with static members; class static methods, class constructors, class var fields and class properties. For example, the AppendixDfn\ClassStatic project shows this

type
TFoo = class
strict private
class constructor Create;
private
class var FYank: integer;
class procedure SetYank(const Value: integer); static;
protected
class procedure OldVirtualClassProcedure; virtual;
public
class procedure OldClassProcedure;
class function ClassStaticMethod: integer; static;
class property Yank: integer read FYank write SetYank;
end;


Note that C#-style static methods must be declared as class procedure or class function with a static directive. The reason for this is historic; Delphi also supports class procedures that receive an extra implicit TClass parameter: a reference to the actual class type the call is made on. This can be used to invoke virtual class methods polymorphically, something that is not supported in C# or with the new class static methods in Delphi.

A class var declaration introduces a block of global-lifetime, class-scoped fields. Traditionally, Delphi programmers have used global variables in the implementation section of the unit for this purpose, but declaring class var fields directly in the class is undeniably much cleaner and clearer. Note that to be consistent, normal instance fields can now also optionally be declared in a var block.


The availability of class fields also opened the path for class properties[1]. These are declared like normal instance properties, but use a class property prefix. The read and write accessors can be class fields or class static methods (but not the older class methods).


Finally, a class’ single class constructor is guaranteed to run exactly once before any members of the class are referenced. It should be declared strict private[2] and cannot be referenced directly from user code[3] – it is always invoked by the CLR when it deems it necessary. Often, code in the initialization section of the unit would benefit from being moved to a class constructor – then you would not incur the overhead unless you actually use the class.


Tip Note that class constructors are not supported in Win32. To emulate a class constructor in Win32, put the code in a strict private class static method and call the method from the initialization section of the unit.





[1] In Delphi 7 and earlier, you could actually declare instance properties that referenced class methods as the read and write accessors, but this was a compiler quirk and didn’t actually work correctly at run-time (using the implicit Self: TClass parameter such as calling a virtual class method would crash, for instance). Also while Delphi 8 allowed you to declare class properties, there was no intuitive way of accessing them (you had to access them via an instance reference, not a class reference). This issue has been fixed in Delphi 2006.

[2] While Delphi 2006 does allow declaring non-private class constructors, you can’t call them and the generated IL declares them private anyway, so it is cleaner to declare them as such in the Delphi code as well.

[3] Hackers and compilers can ensure it has been called by using the RuntimeHelpers.RunClassConstructor method from the System.Runtime.CompilerServices namespace."

[Note: This text differs slightly from the final printed version]

Tuesday, August 29, 2006

Nordic Delphi meetup groups

Last week I had a very nice meeting and dinner with DevCo’s Nordic representatives Fredrik Haglund (Developer Relations and Evangelist) and Dan Nygren (Account Manager). We had some interesting discussions about everything from nuclear power plants to Delphi and DevCo. These guys are very dedicated and on a mission to make DevCo and Delphi succeed even better than under the Borland umbrella.

One of their goals is to encourage the formation of more technical (and less marketing-speak driven) meetings with and between local Delphi developers in cities throughout the Nordic area. Fredrik has already started a Delphi meetup.com group for Stockholm – they have their next meeting September 14th. In Gothenburg, Magnus Flysjö is running another Delphi group that will have their next meeting September 1st.

As far as I know there are no active Delphi meeting groups in Oslo (or Norway?) for the time being. There has been the (give or take) annual official Delphi launch meetings, but no user-driven events. Once upon a long time ago, there was a smartly named group – NoBUG (for Norwegian Borland User Group) – with their own Usenet group (no.org.nobug.diverse). It was pioneered by our own CTO, Morten Lindeman. To quote from the bottom of this page:

“NoBUG
The Norwegian Borland User Group recently announced their formation on several newsgroups. Their aim is to promote and support the Norwegian community of Borland product users. In addition to Borland C++, the group covers Pascal, Delphi, OWL etc. For more information, send an email request to: nobug__at__falcon.no
Alas, it ceased to exist several years ago. If there is enough interest, I’ll be glad to contribute to a new Delphi meetup group in Oslo. You can state your interest in a comment here, or even better by indicating your interest at meetup.com. Now with the upcoming free and inexpensive Turbo Delphi Explorer and Pro versions, it should be easier to get hobbyists and students interested, too.

A quick search reveals that there is some activity to try and start a group in Bergen (by Tom Reiertsen). He has registered the domain ndug.no (Norwegian Delphi User Group). Great initiative, Tom!

Tuesday, August 15, 2006

Extended Interface RTTI

To support the most basic mechanisms of its SOAP architecture, Delphi has supported extended interface RTTI since version 7. As we saw in the previous article, all interfaces support basic RTTI information such as the name of the interface, its GUID, unit, parent interface and number of methods.

To enable extended RTTI on an interface, compile it with {$M+}, {$TYPINFO ON} or {$METHODINFO ON} defined. Alternatively, you can have your interface inherit from IInvokable (defined in System with $M+ enabled). This will extend the generated RTTI for the interface with information about each method’s signature.

Both the client and server side support code for SOAP in Delphi uses the extended interface RTTI structures. Some of the most basic routines can be found in the IntfInfo unit (source is shipped with D2005 and D2006, but not in D7, it seems), for instance take a look at the FillMethodArray and GetIntfMetaData routines.

There is also code to generate WSDL (Web Service Description Language) from the list of registered interfaces in a web service (see WSDLPub.pas) and to dynamically generate an interface method table (IVT) with pointers to dynamically generated thunks that will eventually call TRIO.Generic. This method is responsible for packing up the client-side method call including parameters into an XML formatted SOAP message, execute it by sending it to the server and waiting for the return information, decoding the XML formatted SOAP reply message and updating out and var parameters (in OPToSOAPDomConv’s TOPToSoapDomConvert.ProcessSuccess method) and the result value. Pretty advanced stuff! Note that TRIO does not support the register calling convention for remoted interface calls – the recommendation is to use stdcall.

That was the background information about how the extended interface information is used and where you can find the implementation code that utilizes it. While there are low-level access routines in the IntfInfo unit (exported when DEVELOPERS is defined), we want to get our hands dirty and implement this ourselves from first principles.

As usual the RTTI structures for interface methods contain numerous packed shortstrings – meaning it is impossible to write correct Pascal declarations for them. By digging around in Borland’s extended interface RTTI consuming SOAP code, stepping through the code in the debugger and dumping out raw RTTI memory contents as an array of characters and deducing the (dynamic) field lengths I was able to reverse engineer and write some pseudo Pascal structures to map into the RTTI information. For example here is an ASCII dump from a single interface method’s RTTI that I manually tagged with probable field declarations:

{ MethodCount:1; HasMethodRTTI:1; 
Test:(
Name: #3, 'F', 'o', 'o',
Kind: #0,
CallConv: #0,
ParamCount: #3,
Flags: #8,
ParamName: #4, 'S', 'e', 'l', 'f',
TypeName: #14, 'I', 'M', 'y', 'M', 'P', 'I', 'n', 't', 'e', 'r', 'f', 'a', 'c', 'e',
TypeInfo: #24, 'T', 'O', #0,
Flags: #0,
Name: #1, 'A',
TypeName: #7, 'I', 'n', 't', 'e', 'g', 'e', 'r', }

At the outermost level we start with the record that follows the IntfUnit field of the tkInterface part of the TTypeData variant record from TypInfo.

  PExtraInterfaceData = ^TExtraInterfaceData;
TExtraInterfaceData = packed record
MethodCount: Word; // #methods
HasMethodRTTI: Word; // $FFFF if no method RTTI,
// #methods again if has RTTI
Methods: packed array[0..High(Word)-1] of
TInterfaceMethodRTTI;
end;

For all interfaces, the MethodCount field contains the number of methods in the interface. For “normal” interfaces (compiled with $METHODINFO OFF) the HasMethodRTTI field will be $FFFF indicating that there are no more RTTI for this interface. Extended RTTI interfaces (compiled with $METHODINFO ON) the HasMethodRTTI field will equal the MethodCount field and there will be a packed array of information about each method following it.

  PInterfaceMethodRTTI = ^TInterfaceMethodRTTI;
TInterfaceMethodRTTI = packed record
Name: TPackedShortString;
Kind: TMethodKind; // mkProcedure or mkFunction
CallConv: TCallConv;
ParamCount: byte; // including Self
Parameters: packed array[0..High(byte)-1] of TInterfaceParameterRTTI;
case TMethodKind of
mkFunction:
(Result: TInterfaceResultRTTI);
end;

The RTTI for a single interface method contains the name of the method, the kind of method (procedure or function), the calling convention, the number of parameters (including the implicit Self parameter), and a packed array of details about each parameter. If the method is a function, the parameter array is followed about information about the result type.

  PInterfaceParameterRTTI = ^TInterfaceParameterRTTI;
TInterfaceParameterRTTI = packed record
Flags: TParamFlags;
ParamName: TPackedShortString;
TypeName: TPackedShortString;
TypeInfo: PPTypeInfo;
end;

The record definition for a parameter contains flags (indicating var, const, out or value parameter, array parameters and references) , the parameter name, the string name of the parameter type and a pointer to a PTypeInfo with the RTTI for the parameter type (if it has RTTI).

  PInterfaceResultRTTI = ^TInterfaceResultRTTI;
TInterfaceResultRTTI = packed record
Name: TPackedShortString;
TypeInfo: PPTypeInfo;
end;

Finally, we have the record definition for a function result.

  PInterfaceResultRTTI = ^TInterfaceResultRTTI;
TInterfaceResultRTTI = packed record
Name: TPackedShortString;
TypeInfo: PPTypeInfo;
end;

Again we have the string name of the result type and a pointer to a PTypeInfo for the RTTI of the return type.

The TExtraInterfaceData structures above approximate the physical layout of the raw RTTI information generated by the compiler. For external code we want to translate this into some preprocessed and easier to use structures. Note that this is very similar to what we did for published methods. Both interface and published methods have signatures with parameter and return type information. So I decided to refactor out the signature related definitions and helper routines from the HVPublishedMethodParams unit into a separate HVMethodSignature unit.

unit HVMethodSignature;

interface

uses Classes, SysUtils, TypInfo, HVVMT;

type
TCallConv = (ccReg, ccCdecl, ccPascal, ccStdCall, ccSafeCall);
PMethodParam = ^TMethodParam;
TMethodParam = record
Flags: TParamFlags;
ParamName: PShortString;
TypeName: PShortString;
TypeInfo: PTypeInfo;
end;
TMethodParamList = array of TMethodParam;
PMethodSignature = ^TMethodSignature;
TMethodSignature = record
Name: PShortString;
MethodKind: TMethodKind;
CallConv: TCallConv;
ParamCount: Byte;
Parameters: TMethodParamList;
ResultTypeName: PShortString;
ResultTypeInfo: PTypeInfo;
end;

function MethodKindString(MethodKind: TMethodKind): string;

function MethodParamString(const MethodParam: TMethodParam;
ExcoticFlags: boolean = False): string;

function MethodParametesString(const MethodSignature: TMethodSignature;
SkipSelf: boolean = True): string;

function MethodSignatureToString(const Name: string;
const MethodSignature: TMethodSignature): string; overload;

function MethodSignatureToString(
const MethodSignature: TMethodSignature): string; overload;

implementation

function MethodKindString(MethodKind: TMethodKind): string;
begin
case MethodKind of
mkSafeProcedure,
mkProcedure : Result := 'procedure';
mkSafeFunction,
mkFunction : Result := 'function';
mkConstructor : Result := 'constructor';
mkDestructor : Result := 'destructor';
mkClassProcedure: Result := 'class procedure';
mkClassFunction : Result := 'class function';
end;
end;

function MethodParamString(const MethodParam: TMethodParam;
ExcoticFlags: boolean = False): string;
begin
if pfVar in MethodParam.Flags then Result := 'var '
else if pfConst in MethodParam.Flags then Result := 'const '
else if pfOut in MethodParam.Flags then Result := 'out '
else Result := '';
if ExcoticFlags then
begin
if pfAddress in MethodParam.Flags then
Result := '{addr} ' + Result;
if pfReference in MethodParam.Flags then
Result := '{ref} ' + Result;
end;

Result := Result + MethodParam.ParamName^ + ': ';
if pfArray in MethodParam.Flags then
Result := Result + 'array of ';
Result := Result + MethodParam.TypeName^;
if Assigned(MethodParam.TypeInfo) then
Result := Result + ' {' + MethodParam.TypeInfo.Name + '} ';
end;

function MethodParametesString(
const MethodSignature: TMethodSignature;
SkipSelf: boolean = True): string;
var
i: integer;
MethodParam: PMethodParam;
begin
Result := '';
for i := 0 to MethodSignature.ParamCount-1 do
begin
MethodParam := @MethodSignature.Parameters[i];
// Skip the implicit Self parameter for class and interface methods
// Note that Self is not included in event types
if SkipSelf and
(i = 0) and
(MethodParam.Flags = [pfAddress]) and
(MethodParam.ParamName^ = 'Self') and
(MethodParam.TypeInfo.Kind in [tkInterface, tkClass]) then
Continue;
Result := Result + MethodParamString(MethodParam^);
if i < MethodSignature.ParamCount-1 then
Result := Result + '; ';
end;
end;

function CallingConventionToString(CallConv: TCallConv): string;
begin
case CallConv of
ccReg : Result := 'register';
ccCdecl : Result := 'cdecl';
ccPascal : Result := 'pascal';
ccStdCall : Result := 'stdcall';
ccSafeCall: Result := 'safecall';
else Result := 'TCallConv('+IntToStr(Ord(CallConv))+')';
end;
end;

function MethodSignatureToString(const Name: string;
const MethodSignature: TMethodSignature): string; overload;
begin
Result := Format('%s %s(%s)',
[MethodKindString(MethodSignature.MethodKind),
Name,
MethodParametesString(MethodSignature)]);
if MethodSignature.MethodKind = mkFunction then
begin
Result := Result + ': ' + MethodSignature.ResultTypeName^;
if Assigned(MethodSignature.ResultTypeInfo) then
Result := Result + ' {' + MethodSignature.ResultTypeInfo.Name + '} ';
end;
Result := Result + ';' ;
if MethodSignature.CallConv <> ccReg then
Result := Result + ' ' +
CallingConventionToString(MethodSignature.CallConv) + ';';
end;

function MethodSignatureToString(
const MethodSignature: TMethodSignature): string; overload;
begin
Result := MethodSignatureToString(MethodSignature.Name^,
MethodSignature);
end;

end.

This code is a simple extension of the code we saw back in the article about the hack of getting published method parameters via an event. Extended interface method RTTI contains more detailed information than an event, so we have extended the structures with fields for PTypeInfo of the parameter and return types, calling convention and method name. The routines are straightforward conversions of the logical method information into their corresponding pseudo-code string representations. We covered these in the previous article.

The only piece we are missing now is the code in the middle to translate from the internal raw RTTI structure to the externally useful structures. Here is the additional code from the HVInterfaceMethods unit that also contains the RTTI structures.

unit HVInterfaceMethods;

interface

uses TypInfo, HVMethodSignature;

type
// Easy-to-use fixed size structure
PInterfaceInfo = ^TInterfaceInfo;
TInterfaceInfo = record
UnitName: string;
Name: string;
Flags: TIntfFlags;
ParentInterface: PTypeInfo;
Guid: TGUID;
MethodCount: Word;
HasMethodRTTI: boolean;
Methods: array of TMethodSignature;
end;

procedure GetInterfaceInfo(InterfaceTypeInfo: PTypeInfo;
var InterfaceInfo: TInterfaceInfo);

implementation

type
// … TExtraInterfaceData type definitions goes here …

function Skip(Value: PShortstring): pointer; overload;
begin
Result := Value;
Inc(PChar(Result), SizeOf(Value^[0]) + Length(Value^));
end;

function Skip(Value: PPackedShortString;
var NextField{: Pointer}): PShortString; overload;
begin
Result := PShortString(Value);
Inc(PChar(NextField), SizeOf(Char) + Length(Result^)
- SizeOf(TPackedShortString));
end;

function Skip(CurrField: pointer; FieldSize: integer)
: pointer; overload;
begin
Result := PChar(Currfield) + FieldSize;
end;

function Dereference(P: PPTypeInfo): PTypeInfo;
begin
if Assigned(P)
then Result := P^
else Result := nil;
end;

procedure GetInterfaceInfo(InterfaceTypeInfo: PTypeInfo;
var InterfaceInfo: TInterfaceInfo);
// Converts from raw RTTI structures to user-friendly Info structures
var
TypeData: PTypeData;
ExtraData: PExtraInterfaceData;
i, j: integer;
MethodInfo: PMethodSignature;
MethodRTTI: PInterfaceMethodRTTI;
ParameterInfo: PMethodParam;
ParameterRTTI: PInterfaceParameterRTTI;
InterfaceResultRTTI: PInterfaceResultRTTI;
begin
Assert(Assigned(InterfaceTypeInfo));
Assert(InterfaceTypeInfo.Kind = tkInterface);
TypeData := GetTypeData(InterfaceTypeInfo);
ExtraData := Skip(@TypeData.IntfUnit);

// Interface
InterfaceInfo.UnitName := TypeData.IntfUnit;
InterfaceInfo.Name := InterfaceTypeInfo.Name;
InterfaceInfo.Flags := TypeData.IntfFlags;
InterfaceInfo.ParentInterface := Dereference(TypeData.IntfParent);
InterfaceInfo.Guid := TypeData.Guid;
InterfaceInfo.MethodCount := ExtraData.MethodCount;
InterfaceInfo.HasMethodRTTI :=
(ExtraData.HasMethodRTTI = ExtraData.MethodCount);
if InterfaceInfo.HasMethodRTTI
then SetLength(InterfaceInfo.Methods, InterfaceInfo.MethodCount)
else SetLength(InterfaceInfo.Methods, 0);

// Methods
MethodRTTI := @ExtraData.Methods[0];
for i := Low(InterfaceInfo.Methods) to
High(InterfaceInfo.Methods) do
begin
MethodInfo := @InterfaceInfo.Methods[i];
MethodInfo.Name := Skip(@MethodRTTI.Name, MethodRTTI);
MethodInfo.MethodKind := MethodRTTI.Kind;
MethodInfo.CallConv := MethodRTTI.CallConv;
MethodInfo.ParamCount := MethodRTTI.ParamCount;
SetLength(MethodInfo.Parameters, MethodInfo.ParamCount);

// Parameters
ParameterRTTI := @MethodRTTI.Parameters;
for j := Low(MethodInfo.Parameters) to
High(MethodInfo.Parameters) do
begin
ParameterInfo := @MethodInfo.Parameters[j];
ParameterInfo.Flags := ParameterRTTI.Flags;
ParameterInfo.ParamName :=
Skip(@ParameterRTTI.ParamName, ParameterRTTI);
ParameterInfo.TypeName :=
Skip(@ParameterRTTI.TypeName, ParameterRTTI);
ParameterInfo.TypeInfo :=
Dereference(ParameterRTTI.TypeInfo);
ParameterRTTI := Skip(@ParameterRTTI.TypeInfo, SizeOf(ParameterRTTI.TypeInfo));
end;

// Function result
if MethodInfo.MethodKind = mkFunction then
begin
InterfaceResultRTTI := Pointer(ParameterRTTI);
MethodInfo.ResultTypeName :=
Skip(@InterfaceResultRTTI.Name, InterfaceResultRTTI);
MethodInfo.ResultTypeInfo :=
Dereference(InterfaceResultRTTI.TypeInfo);
MethodRTTI := Skip(@InterfaceResultRTTI.TypeInfo,
SizeOf(InterfaceResultRTTI.TypeInfo));
end
else
MethodRTTI := Pointer(ParameterRTTI);
end;
end;

end.

The code is a little tricky and hard to follow because of the need to skip over the variable length shortstring fields. The low-level code in IntfInfo is different, using a ReadString, ReadByte, ReadWord , ReadLong model. I like the self-documenting aspects of pseudo-record definitions and wanted to use them in the access code as well. Note that at some points, only one field in a record structure is aligned correctly and can be read.

With all the foundations in place, now we can write a little dumping routine that will print out a pseudo source code representation of an interface.

procedure DumpInterface(InterfaceTypeInfo: PTypeInfo);
var
InterfaceInfo: TInterfaceInfo;
i: integer;
begin
GetInterfaceInfo(InterfaceTypeInfo, InterfaceInfo);

writeln('unit ', InterfaceInfo.UnitName, ';');
writeln('type');
write(' ', InterfaceInfo.Name, ' = ');
if not (ifDispInterface in InterfaceInfo.Flags) then
begin
write('interface');
if Assigned(InterfaceInfo.ParentInterface) then
write(' (', InterfaceInfo.ParentInterface.Name, ')');
writeln;
end
else
writeln('dispinterface');
if ifHasGuid in InterfaceInfo.Flags then
writeln(' [''', GuidToString(InterfaceInfo.Guid), ''']');
if InterfaceInfo.HasMethodRTTI then
for i := Low(InterfaceInfo.Methods) to
High(InterfaceInfo.Methods) do
writeln(' ', MethodSignatureToString(
InterfaceInfo.Methods[i]))
else
for i := 1 to InterfaceInfo.MethodCount do
writeln(' procedure UnknownName',i,';');
writeln(' end;');
writeln;
end;

And finally we can write some test code to try it all out.

type
TNumber = integer;
TNewNumber = type integer;
TIntegerArray = array of integer;
TNormalClass = class
end;
TPersistentClass = class(TPersistent)
end;
TSetOfByte = set of byte;
TEnum = (enOne, enTwo, enThree);
type
{.$M+} {.$TYPEINFO ON}
// With regards to interface RTTI, METHODINFO
// has the same effect as $M and $TYPEINFO
{$METHODINFO ON}
IMyMPInterface = interface
['{AA503475-0187-4108-8E27-41475F4EF818}']
procedure TestRegister(A: integer; var B: string); register;
procedure TestStdCall(LongParaName: TObject;
const B: string; var C: integer; out D: byte); stdcall;
procedure TestSafeCall(out R: integer); safecall;
function Number: TNumber; cdecl;
function NewNumber: TNewNumber; cdecl;
function AsString: string; pascal;
function AsString2: string; safecall;
// Return types that are supported
procedure A2(const A: TIntegerArray);
procedure OkParam1(Value: TSetOfByte);
procedure OkParam2(Value: TSetOfByte);
procedure OkParam3(Value: Variant);
procedure OkParam4(Value: TNormalClass);
function OkReturn1: shortstring;
function OkReturn2: TObject;
function OkReturn3: IInterface;
function OkReturn4: TSetOfByte;
function OkReturn5: TNormalClass;
function OkReturn6: TEnum;
function OkReturn7: TClass;
function OkReturn8: Pointer;
function OkReturn9: PChar;
function OkReturn10: TIntegerArray;
end;
{$M-}

{$WARN SYMBOL_PLATFORM OFF}
procedure Test;
begin
DumpInterface(TypeInfo(IMyMPInterface));
end;

begin
try
Test;
except
on E:Exception do
writeln(E.Message);
end;
readln;
end.

And the output is:

unit TestExtendedInterfaceRTTI;
type
IMyMPInterface = interface (IInterface)
['{AA503475-0187-4108-8E27-41475F4EF818}']
procedure TestRegister(A: Integer {Integer} ; var B: String {String} );
procedure TestStdCall(LongParaName: TObject {TObject} ;
const B: String {String} ; var C: Integer {Integer} ;
out D: Byte {Byte} ); stdcall;
procedure TestSafeCall(out R: Integer {Integer} ); safecall;
function Number(): Integer {Integer} ; cdecl;
function NewNumber(): TNewNumber {TNewNumber} ; cdecl;
function AsString(): String {String} ; pascal;
function AsString2(): String {String} ; safecall;
procedure A2(const A: TIntegerArray {TIntegerArray} );
procedure OkParam1(Value: TSetOfByte {TSetOfByte} );
procedure OkParam2(Value: TSetOfByte {TSetOfByte} );
procedure OkParam3(Value: Variant {Variant} );
procedure OkParam4(Value: TNormalClass {TNormalClass} );
function OkReturn1(): ShortString {ShortString} ;
function OkReturn2(): TObject {TObject} ;
function OkReturn3(): IInterface {IInterface} ;
function OkReturn4(): TSetOfByte {TSetOfByte} ;
function OkReturn5(): TNormalClass {TNormalClass} ;
function OkReturn6(): TEnum {TEnum} ;
function OkReturn7(): TClass;
function OkReturn8(): Pointer;
function OkReturn9(): PAnsiChar;
function OkReturn10(): TIntegerArray {TIntegerArray} ;
end;

The code including the utility units and the test code for this and most of my other RTTI articles can be found in CodeCentral here. The test code also contains some conditional code to test the support of additional parameter types. My experiments indicates that the following parameter types are not supported in interface methods with extra RTTI:



  • All pointer types

  • open array parameters (array of Type), named dynamic array is ok

  • class references (such as TClass)

  • record types (such as TRect)

  • untyped var and out parameters

If the compiler encounters one of these parameter types in a $METHODINFO ON interface method it generates a compiler time error like “[Error] : Type '%s' has no type info” at the end statement of the interface (i.e. it does not indicate what method is the culprit).

Due to my summer holidays and general laxness, this article was brought to you a litter later than originally planned. Hopefully I will be able to follow up with the next article on extended $METHODINFO ON RTTI for public and published methods of a class within a reasonable time. This technology is what the Websnap scripting is based on.


Updated (27. Oct 2007): $METHODINFO was first available in Delphi 7, not Delphi 6.



Copyright © 2004-2007 by Hallvard Vassbotn