Tuesday, December 12, 2006

The history of the CodeGear name

A while back, Michael Swindell (Borland CodeGear), posted the story about how the CodeGear name was picked to the delphi.nontech newsgroup. First it was established that Michael had recently bought the domain name codegear.com. Then someone asked:

Can you tell us how much cost you that ?

And Michael's reply was an interesting read of the story behind the name:

one BILLION dollars.... <g> heh... no, actually it was a bargain for several thousand dollars... a great deal IMO.... I was really just blown away that  it was even available for purchase. When I thought of the name, Allen [Bauer -ed] was sitting across the table and I told him, "but of course there's no way the domain will be available"... and sure enough it was. I have go back and research who the original suggestion actually came from, but the name stemmed from one of the names submitted by customers from my Blog post - DelphiGear.... we were browsing thru hundreds of submissions (most were ummm.... how shall I put it... "Intersting" is the word I'm looking for <g>) but I really liked the idea of DelphiGear. Like Climbing Gear or Photo Gear but for Delphi programmers. Of course Delphi in the name is just way too narrow a focus for our new company name, and I just swapped Delphi with Code... and wahlah... there it is :o) funny how those things work sometimes. So thanks to the Delphi developer who submitted DelphiGear as a suggestion - you are a part of history! -m

Thanks for the nice story Michael! And sorry if you intended to blog about this yourself - not intending to steal your thunder! :-)

Don't miss the Oslo Delphi Day, Dec 15th 2006!

In a joined operation between Alfacode AS (and Thomas Fjeld), CodeGear Stockholm (represented by Fredrik Haglund) and the Oslo Delphi Club, we are arranging the Oslo Delphi Day on Friday December 15th.

The key attraction is three presentations held by tech Speaker Extraordinaire, Chad Z. Hower (aka Kudzu) of Indy and IntraWeb fame. In addition there will be few sessions by Fredrik and myself. Thomas has just made the agenda available here.

If you are reading this and are within travelling distance of Oslo, make sure to sign up for the event here!

See you there!

Thursday, November 23, 2006

DN4DP#3: Nesting habits

This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book. Last time we learned about the new class member visibility specifiers that are available, abstract classes and final methods. This time we will look at the syntax and semantics of nested classes.

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.

"Nesting habits

The compiler now allows you to declare nested types (including classes) and constants inside another class or record declaration. The implementation of a nested class method must have its name preceded by both the outer and inner class names, like this

type
TOuter = class
public
const
MeaningOfLife = 42;
type
TResult = integer;
TInner = class
public
function Method: TResult;
end;
end;

implementation

function TOuter.TInner.Method;
begin
// or just MeaningOfLife

Result := TOuter.MeaningOfLife;
end;

procedure Test;
var
Inner: TOuter.TInner;
Result: TOuter.TResult;
begin
Inner := TOuter.TInner.Create;
Result := Inner.Method;
end;

Note that a nested class can reference all types and constants nested within its parent class – the TOuter prefix in the above example is optional. Any code outside of a class must use an explicit type prefix – for example, the external test code in the Chapter10\NestingHabits project must use the TOuter prefix to get at the types and constants declared in the TOuter class.

Remember, while inner classes can see all types declared in their outer class(es), they have no special access to their outer class’ fields, methods, or properties. (Similarly, outer classes cannot see private or protected members of their inner types). The main purpose of nested classes is to reduce namespace clutter by keeping (often private) helper classes within the public classes that use them. This also makes it easier to hide implementation details. (See Chapter 2’s Nested Classes section.)

Tip   Delphi’s global level constants are not CLS-compliant (or rather, the name of the class they end up in is an implementation detail generated by the compiler, and should not be relied upon), so you should declare constants you want to export to other languages inside a class or record declaration. The same advice goes for global routines and variables - declare them as static methods and fields."

Borland and CodeGear

As most of you will already know, Borland has now moved its developer tools group into a separate company called CodeGear. CodeGear, which will still be wholly owned by Borland, will continue to develop, sell and support developer tools like Delphi, C++Builder, JBuilder and Interbase. There are also indications that they are moving into the webspace with support for languages such as PHP, Pyhon, Ruby and Ajax technology.

While some people have been waiting anxiously for a complete separation from Borland in a operation to sell the IDE group to a separate entity, I think that the current situation is better for the stability and long-term viability of CodeGear in general and the future of Delphi in particular.

The CodeGear management will be in a position to grow the IDE business and pour the revenue stream back into the development of new versions and products. I think this is good news, but as always we should keep our heads calm and keep our options open ;).

Good thing we called our user group Oslo Delphi Club and not NoBUG (Norwegian Borland User Group) or something similar ;)).

Friday, September 29, 2006

Hack#13: Access globals faster ($ImportedData)

There is an aura of magic around Delphi packages. Packages allow you to share Delphi code at a much higher level than is possible with plain old DLLs. Writing a DLL-based library API involves writing flat non-OOP global routines and avoiding any data types more advanced than integers, doubles, static arrays, PChar and records. You cannot share or exchange classes, objects, global variables or let alone strings (unless the client is a Delphi app and both the DLL and client use ShareMem).

Delphi Package Magic
Enter the magic of Delphi packages. Now you no longer have to worry about what you can share between module boundaries, because you can share everything! Packages is just a way of dividing up the logical code into physical deployment modules. Everything just works - somehow. The somehow is the (mainly undocumented) magic.

We're going to look at a small part of that magic - how global variables can be accessed across the application/package boundary. Lets start with the simplest possible case first .

Lets say we have a Unit1 that defines a GlobalVar: integer and some code in the same unit that sets it. Here is the generated assembly and machine code.

Unit1.GlobalVar := 13;
004081B1 C705989240000D00 mov [GlobalVar],$0000000d

That is a single assembly instruction with no indirections or pointer access going on. Notice that both the address of the global variable and the immediate value 13 (encoded as a 16-bit shortint, $000D) is encoded directly inside the machine code instruction. When I evaluate the address of the global variable in the Ctrl+F7 evaluator, @GlobalVar, I get $409298. This is the big-endian value I have highlighted in yellow above.


Then let's look at the package magic situation. If we have a PackageA containing a Unit1 that defines a GlobalVar: integer and a DemoApplication that links to PackageA and with code that access the GlobalVar, the compiler will compile the code into something like this:

Unit1.GlobalVar := 42;
00403389 A1A4404000 mov eax,[$004040a4]
0040338E C7002A000000 mov [eax],$0000002a

Notice the indirection there? Global variables are accessed through a pointer that is fixed up to point to the right address inside the package when the package is loaded. That is cool and a good thing. The global variable itself resides inside the package/DLL module, while the application has a magic, "invisible" global pointer variable that points to the actual package variable. This pointer is fixed up during the magic static loading of packages (it's value is probably set to the result of a GetProcAddress API call).


Global variables in standalone apps
Now lets step back a little and look at another common situation. We have looked at the intra-unit situation and the inter-module situation - the two most extreme conditions. But what happens when you access a global variable between units inside the same application? Surely, then the indirection should not be needed? Lets test it.


We write a simple console application consisting of two units, Unit1 and Unit2, and the main program.

unit Unit1;

interface

var
GlobalVar: integer = 42;

procedure SetGlobalLocally;

implementation

procedure SetGlobalLocally;
begin
asm int 3 end; // will break here, look in CPU view
Unit1.GlobalVar := 13;
end;

end.
unit Unit2;

interface

procedure SetGlobalIndirect;

implementation

uses Unit1;

procedure SetGlobalIndirect;
begin
asm int 3 end; // will break here, look in CPU view
Unit1.GlobalVar := 42;
end;

end.
program TestImportedData;
{$APPTYPE CONSOLE}
uses
Unit1 in 'Unit1.pas',
Unit2 in 'Unit2.pas';
begin
SetGlobalLocally;
SetGlobalInDirect;
end.

Notice the cute little hand-coded breakpoints? Debugger breakpoints are actually implemented by overwriting a byte of your code with the software breakpoint instruction (int 3) which is encoded in a machine code byte $CC. When you run this program with debugging enabled from the IDE, it will break on the two int 3 instructions. Neat, huh? ;)


The first breakpoint is inside Unit1 just above the code that modifies the global variable. This is the case we showed first above. If you run it and follow the instructions to look at the code in the CPU view, you will see that there is no indirection.

Unit1.GlobalVar := 13;
004081B1 C705989240000D00 mov [GlobalVar],$0000000d

The second breakpoint is in Unit2 where we are modifying the global variable from an "external" unit (from a different unit than were the global was declared). Notice that we are still in a single EXE file here, so there should be no need for indirection, as it was in the package case. Run the code again and when it hits the second breakpoint, look at the machine code in the CPU again.

Unit1.GlobalVar := 42;
00403389 A1A4404000 mov eax,[$004040a4]
0040338E C7002A000000 mov [eax],$0000002a

Looks like we still have indirection here...! What is going on? The reason for this (small) inefficiency is that a unit can be moved into or out of a package without being recompiled. So the compiler has to be conservative and generate the unit's code to support the "worst" case that it will be exported for use from a package. The important thing to learn from this is:



By default, all cross-unit global variable access is performed indirectly via a pointer


The implication is that if you have performance critical code that access a global variable, you should cache it in a local variable inside the loop. You can only do this if you do not need to "see" updates to the global variable while the loop is running (think multithreaded code).


For standalone applications that do not use packages at all, it is a bit irritating to know that the compiler generates sub-optimal code for global variable access. While both the performance and size overhead of the indirection should be negligible in most situations, it would still be nice to force the compiler to get rid of this indirection.


$ImportedData Off
And as it happens there is a compiler directive that does exactly this. Enter the $ImportedData (aka $G) directive. This is what the Delphi help file has to say:



"Type Switch
Syntax {$G+} or {$G-}
{$IMPORTEDDATA ON} or {$IMPORTEDDATA OFF}
Default {$G+} {$IMPORTEDDATA ON}
Scope Local
Remarks


The {$G-} directive disables creation of imported data references. Using {$G-} increases memory-access efficiency, but prevents a packaged unit where it occurs from referencing variables in other packages."


By default the setting is {$ImportedData On} - this enables the pointer indirection in code that access unit-external global variables. By using the {$ImportedData On} directive in a unit, you turn off this indirection, resulting in tighter faster code for global variable access. Notice that you have to use it in every unit referencing global variables, not just in the unit declaring it.


Lets add a third unit to our test bench program.

unit Unit3;

interface

procedure SetGlobalDirect;

implementation

uses Unit1;

{$IMPORTEDDATA OFF}

procedure SetGlobalDirect;
begin
asm int 3 end; // will break here, look in CPU view
Unit1.GlobalVar := 42;
end;

end.

The code here is identical to the code in Unit2, but now we have included the $ImportedData Off directive to tell the compiler generate optimized code for globals. Run the program again and when it stops at the breakpoint in Unit3, look in the CPU view.

Unit1.GlobalVar := 42; 
004033D1 C705A04040002A00 mov [GlobalVar],$0000002a

Hurrah! We got rid of the indirection.



The {$ImportedData Off} compiler directive forces the compiler to generate efficient global variable access


This works fine in Delphi 6 and 7. As it happens it no longer works in Delphi 2006 (and probably not 2005). Looks like a bug has sneaked into the compiler here. The directive is effectively ignored, still producing the indirection as we saw for Unit2 above. Hopefully Borland/DevCo will be able to fix this little issue in a future version of the compiler[1].


IDE Compiler Options
Knowing that $ImportedData Off generates better code for global variable access in a standalone application, we would of course like to set it for all the units in the application as a whole. Unfortunately that is not so easy to do. You see the Project | Options | Compiler Options dialog page does not give access to this option... :(.


Because of this issue you have to use one of the following solutions:



  • Manually insert {$IMPORTEDDATA OFF} at the top of all units in the project
  • Manually insert an {$ MyAppSettings.Inc} file in all units. The .inc file would contain settings such as {$IMPORTEDDATA OFF}
  • Compile using Dcc32.exe setting the option from the command-line

In other words it is a bit awkward to enable this for a given application.



It is hard to set {$ImportedData Off} from the IDE.


I hope that they will be able to make this option available from the compiler options in the IDE in a future version. In fact, I went ahead and reported this as a enhancement request in QC - (please vote for it!):



"Can we please have the $G switch (AKA $IMPORTEDDATA) promoted to appear in the Project Options | Compiler dialog? It makes it easier to switch it off globally for those of us who never use run-time packages."


While it can be argued that introducing this IDE option runs the danger of users producing unusable packages, I don't think this is reason to deny the large number of people writing standalone applications easy access to this option.

The solution could be to only enable this option for projects that are compiled with run-time packages turned off (on the Project Options, Packages page).

Still, if a unit is compiled with IMPORTEDDATA OFF and then moved to a package, it has to be recompiled (i.e. the package project must be rebuilt).


To get around this problem you could:


a) Have the compiler detect this case and recompile all units with the wrong IMPROTEDDATA setting

b) Document the issue and require the user to rebuild himself

c) Combined with b) make the option less obvious and make the IDE compiler options future proof by adding a free-text field for additional compiler options. The user must manually type IMPORTEDDATA OFF in this field, supposedly this makes him qualified to know what he is doing.


The following Dcc32.exe compiler options are currently unavailable from the IDE Compiler Options:


G+ Use imported data references
M- Runtime type info
W- Generate stack frames
Z1 Minimum size of enum types"


If you think this is worthwhile to implement, please vote on QC#34650 here!


Score: Delphi 7 vs. Delphi 2006: 1-1 ;)


[1] I found that this issue has indeed been discovered independently and reported by Frederic Vanmol in Quality Central:



Report No: 33464 Status: Open
{$IMPORTEDDATA OFF} does not seem to work
http://qc.borland.com/wc/qcmain.aspx?d=33464


PS. Just as I had finished this piece, I started experimenting some more...;) In Delphi 7 it is actually possible (but still awkward) to compile an application with $ImportedData Off for all units without messing with each unit or the command line compiler.


You only need to



  • close the project
  • manually edit the project's .dof file
  • change "-G+" to "-G-"
  • save and close the .dof file
  • reopen the project
  • rebuild the application

If you compile from the command line you can update the .cfg file in a similar manner.


It is probably possible to do the same in Delphi 2006 by manually editing the XML in the .bdsproj file, changing



<Compiler Name="G">1</Compiler>


to



<Compiler Name="G">0</Compiler>


But it is a little hard to check, because of the Delphi 2006 bug of ignoring the directive. In both cases, the manually set compiler options are preserved even when changing compiler options from the IDE.

Sunday, September 24, 2006

Hack#12: Create smaller .EXE files ($SetPEFlags)

No, this post is not about so-called EXE-compressors - I don't believe in using them. And it is not a pure hack in the sense that we're breaking any rules - its just about documenting an undocumented and unknown feature of the Delphi 2006 Win32 compiler (it is not implemented in D7 - I don't know about D2005 yet as I don't have it installed on this laptop anymore).

Background info
When you compile a DLL (or a package which is a Delphi-specific DLL in disguise), the linker includes what is known as a relocation table. This table includes information about what addresses must be fixed up by the OS loader in the (likely) event that the DLL must be loaded at a different address than its intended-at-compile/link-time base address. You see all DLLs come with a base address that is the "ideal" loading address of that module. The OS will try to load the DLL at this address to avoid the overhead of runtime rebasing (patching the in-memory pages of the DLL forces it to be paged in from disk and prevents cross-process sharing of the DLL pages). That's why you should set the Image base option in the Linker page of the project options of DLL and package projects. The default Image base that Delphi uses is $00400000 for both applications, DLLs and packages - and thus by default all DLLs and packages will need to be rebased - as that address is reserved for the process' EXE file.

The implication is that an EXE file will always be loaded at the fixed virtual address $00400000 and that it will never need to be rebased. Alas, it doesn't really need its relocation table and we can safely remove it, shrinking the size of the .EXE without affecting its behavior or performance. It is basically a free lunch!

Up until now Delphi users have had to resort to external stripping tools like Jordan Russell's excellent StripReloc tool. It basically takes the name of an .EXE file as a parameter and removes the relocation table from it. I normally don't bother to strip the reloc table during development, but I do run Jordan's tool before deploying our application. This strips a hefty 350 kB from our 7 MB EXE file - a nice, free 5% reduction right there.

As far as I know, most Microsoft tools produces reloc-free EXE files, while Delphi always includes it for both DLLs and EXEs. Up until now. Delphi 2006 actually has a unknown, undocumented compiler directive feature that allows you to turn off the relocation table for EXE files.

To test it out I first opened the ResXplor project from the Demos\DelphiWin32\VCLWin32\ResXplor folder (a pretty neat application, btw). I compiled it as-is. Project | Information told me that the size of the EXE file was 614400 bytes (and Windows Explorer agreed). Then I added the following line to the top of the project file:

{$SetPEFlags 1}

(We'll look at the source of the mysterious value 1 in a moment).


Then I recompiled the project (there is no need to rebuild it, as the unit files does not need to be recompiled - only the linker must be reinvoked). And the size of the EXE file had shrunk to 577536 bytes - that is a reduction of 36864 bytes or 6 %. Not bad for a one-liner, eh? ;).


The Delphi 2006 help file has the following to say about the $SetPEFlags compiler directive:



"Type: Flag
Syntax: {$SetPEFlags <integer expression>} {$SetPEOptFlags <integer expression>}
Scope: Local


Microsoft relies on PE (portable executable) header flags to allow an application to indicate compatiblity with OS services or request advanced OS services. These directives provide powerful options for tuning your applications on high-end NT systems.


These directives allow you to set flag bits in the PE file header Characteristics field and PE file optional header DLLCharacteristics field, respectively. Most of the Characteristics flags, set using $SetPEFlags, are specifically for object files and libraries. DLLCharacteristics, set using $SetPEOptFlags, are flags that describe when to call a DLL's entry point.


The <integer expression> in these directives can include Delphi constant identifiers, such as the IMAGE_FILE_xxxx constants defined in Windows.pas. Multiple constants should be OR'd together."


So it (and the related SetOptPEFlags directive) is a way of informing the OS about the certain aspects of the application. For instance, it can be used to tell the OS that it is ready and compatible with getting access to virtual memory addresses above the 2 GB boundary. Lets look at the IMAGE_FILE constants defined in the Windows.pas unit.

const
{ Relocation info stripped from file. }
IMAGE_FILE_RELOCS_STRIPPED = $0001;
{ File is executable (i.e. no unresolved externel references)}
IMAGE_FILE_EXECUTABLE_IMAGE = $0002;
{ Line nunbers stripped from file. }
IMAGE_FILE_LINE_NUMS_STRIPPED = $0004;
{ Local symbols stripped from file. }
IMAGE_FILE_LOCAL_SYMS_STRIPPED = $0008;
{ Agressively trim working set }
IMAGE_FILE_AGGRESIVE_WS_TRIM = $0010;
{ App can handle >2gb addresses }
IMAGE_FILE_LARGE_ADDRESS_AWARE = $0020;
{ Bytes of machine word are reversed. }
IMAGE_FILE_BYTES_REVERSED_LO = $0080;
{ 32 bit word machine. }
IMAGE_FILE_32BIT_MACHINE = $0100;
{ Debugging info stripped from file in .DBG file }
IMAGE_FILE_DEBUG_STRIPPED = $0200;
{ If Image is on removable media, copy and run from the swap file}
IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = $0400;
{ If Image is on Net, copy and run from the swap file. }
IMAGE_FILE_NET_RUN_FROM_SWAP = $0800;
{ System File. }
IMAGE_FILE_SYSTEM = $1000;
{ File is a DLL. }
IMAGE_FILE_DLL = $2000;
{ File should only be run on a UP machine }
IMAGE_FILE_UP_SYSTEM_ONLY = $4000;
{ Bytes of machine word are reversed. }
IMAGE_FILE_BYTES_REVERSED_HI = $8000;

That's a lot of values, but for the purposes of this article, we're really only interested in the first one.

const
{ Relocation info stripped from file. }
IMAGE_FILE_RELOCS_STRIPPED = $0001;

That's where our magic number 1 comes from. If we add Windows to the project uses clause, and move the SetPEFlags directive below the uses clause, we can use this more self-documenting directive.

{$SetPEFlags IMAGE_FILE_RELOCS_STRIPPED}

So this tells the OS that the file does not contain any relocation information, and thus it cannot be rebased at runtime. In addition, the Delphi compiler now also picks up on this information and uses it as an instruction to do as you say - leaving out reloc information from the generated EXE file!


Note: The compiler allows using the same directive to remove reloc info from packages and DLLs, but this is not a recommended practice. It will generate slightly smaller DLLs, but the DLL will fail to load unless it can be loaded at its given base address.


Score: Delphi 7 vs. Delphi 2006: 0 - 1 ;)

Thursday, September 21, 2006

ODC#2: 1st Oslo Delphi Club event was great!

Unbelievable, but true - only two weeks after the Oslo Delphi Club was created we have 48 members and counting! And on Wednesday September 20th we gathered not less than 21 members to the first ever meeting at the local Peppe's Pizza joint to share good food, beverages, background stories and enthusiasm of Delphi, DevCo and the Oslo Delphi Club.

The room I had booked was packed to the last seat. After getting our first cold glass in hand, we took turns telling about ourselves, our Delphi/ Turbo/Borland Pascal background, our employers/companies, our projects and our ideas, hopes and possible contribution to the future our new-founded club.

Members varied greatly in age, experience and project types, but in common we all had the love in a product and phenomena as splendid as Delphi. We raved about the ingeniousness of a development tool that has followers who will select a product based on the language (read: Delphi) in which it is programmed.

I think that this enthusiasm caters well for the future of Delphi in general and the Oslo Delphi Club in general. If you haven't already, and you're close to Oslo, Norway - be sure to sign up as a member here.

Thursday, September 14, 2006

ODC#1: First ever Oslo Delphi Club meeting!

The Oslo Delphi Meetup group has now been created and the first meeting is scheduled to be Wednesday September 20th, at a local pizza place. If you are a Delphi programmer situated in or close to Oslo, make sure you join the group and signup for the meeting.

 

PS. IMO, the Oslo Delphi group hasn't finalized its name yet. The archetypical English name that meetup.com uses is The Oslo Delphi Meetup Group. The current Norwegian name is the awkward "Brukergruppen for Delphi i Oslo" (literally "The user group for Delphi in Oslo) and I'm not satisfied with it. Perhaps "Oslo Delphi Club" could be a swifter, more natural name - it works in both Norwegian and English. We'll probably discuss it at the first meeting - be there if you can!

Tuesday, September 12, 2006

DN4DP#2: Protecting your privates

This post continues the series of The Delphi Language Chapter teasers from Jon Shemitz’ .NET 2.0 for Delphi Programmers book. Last time we learned about the new kinds of class members that have become available; class fields, class static methods, class properties and class constructors. This time we will look at the new class member visibility specifiers that are available, abstract classes and final methods.

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.

"Protecting your privates

Native Delphi already had four class member visibility levels; private, protected, public and published[1]. One quirk with these is that private and protected members are fully visible to all the code in the unit they are declared in[2], not just the class they are part of (almost like an implicit version of the C++ friend concept). To match .NET's concept of truly private and protected, two new access levels named strict private and strict protected, were introduced. The AppendixDfn\PrivateParts project demonstrates this

type
TFoo = class
strict private
FCantTouchMe: integer;
FAnyOneAndDelphiRTTI: integer;
private
FClassAndUnit: integer;
strict protected
FClassAndDescendants: integer;
protected
FClassDescendantsAndUnit: integer;
public
FAnyOne: integer;
constructor Create(Report: boolean = True);
published
property AnyOneAndDelphiRTTI: integer read FAnyOneAndDelphiRTTI
write FAnyOneAndDelphiRTTI;
end;

Delphi classes can now be explicitly sealed and abstract. The syntax here has the mildly surprising order class sealed and class abstract. The main reason for this is to avoid reserving more language keywords than absolutely necessary – sealed and abstract are directives that only have special meaning after the class reserved word. This means that existing code that already use these identifiers will not break. A sealed class cannot be inherited from and an abstract class cannot be instantiated (even if it does not contain any abstract methods).

Finally, a virtual method that you override can now be marked final, preventing derived classes from overriding that method.

  TAbstractClass = class abstract
public
procedure Bar; virtual;
end;

TSealedClass = class sealed(TAbstractClass)
public
procedure Bar; override;
end;

TFinalMethodClass = class(TAbstractClass)
public
procedure Bar; override; final;
end;




[1] There is also the obsolete automated section from Delphi 2 – but that is not supported in .NET.

[2] One native Delphi trick is to declare a local descendant of a class in the current unit. Then you hard-cast an object instance into this local class. Now you have access to all the protected members of the object. This hack is so common that the .NET compiler has special logic to handle it too. It does work as long as the original class code is in the same assembly as the hacking code. This is because Delphi’s protected access maps to the .NET family or assembly access level. Similarly, Delphi’s private access maps to the .NET assembly access level, but the Delphi compiler itself enforces the cross-unit privateness."

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

Monday, September 11, 2006

Hack#11: Get the GUID of an interface reference

Recently Randy Magruder approached me with a very interesting sounding project he is working on:

"What I'd like to do is effectively add/remove new interfaces to a class at runtime, and invoke them. I have done enough that I can use the OTAServices model in Delphi to pass in additional interfaces, add them to an internal list, and override the QueryInterface behavior to return something in a contained interface list rather than having to declare it on the object."

That really sounds like a very cool (and complicated) project! He continues:

"But if the caller to the object only knows the GUID of the interface he wants, and wants to get back the appropriate interface, and invoke a method on it by name, how would I go about doing that?  Is the extended RTTI with IInvoker the way to go? "

Well, I'm not sure how that can be solved. AFAIK there is no easy mapping from an object implementing interfaces to the typeinfo of those interfaces. One solution might be to implement a registry mapping interface GUIDs to interface typeinfos.

"Also, do you know a way to extract a GUID from an interface instance at runtime, ..."

That should be possible, albeit a little complex. You may recall the little utility routine I wrote to convert a Delphi Win32 interface reference back into the object reference that implements it. It should be possible to morph that function into another function that returns the offset in the object that contains the
interface method table pointer. Combining this with a call to TObject.GetInterfaceTable to get a PInterfaceTable that contains a TInterfaceEntry record for each implemented interface. Compare the
calculated offset with the IOffset field - when you have a match you have the GUID in the IID field.

From System.pas:

 PInterfaceEntry = ^TInterfaceEntry;
TInterfaceEntry = packed record
IID: TGUID;
VTable: Pointer;
IOffset: Integer;
ImplGetter: Integer;
end;
PInterfaceTable = ^TInterfaceTable;
TInterfaceTable = packed record
EntryCount: Integer;
Entries: array[0..9999] of TInterfaceEntry;
end;
TObject = class
class function GetInterfaceTable: PInterfaceTable;

But that will only work for interfaces that are declared on the class at compile time, of course.


Randy continues his sentence above:



"...even if it's passed in as an IUnknown?"


Eh - you keep making it complicated, aren't you, Randy? ;) The GUID of IUnknown is fixed of course (it's {00000000-0000-0000-C000-000000000046}). And I don't think there is any trace of the interface/GUID that the IUnknown reference was converted from . OTOH, all interfaces inherit from IUnknown so if the IUnknown reference you have really is a sub-interface, it could work [it does -  as we will see in a moment].


After answering Randy's email, I took up the challenge and tried to implement my suggestion solution - getting the GUID of an interface reference. The starting point is the code for Hack#7: Interface to Object - which is able to convert an interface reference back into an object reference of the object that implements the interface (say that fast 5 times ;)).

function GetImplementingObject(const I: IInterface): TObject; 
const
AddByte = $04244483;
AddLong = $04244481;
type
PAdjustSelfThunk = ^TAdjustSelfThunk;
TAdjustSelfThunk = packed record
case AddInstruction: longint of
AddByte : (AdjustmentByte: shortint);
AddLong : (AdjustmentLong: longint);
end;
PInterfaceMT = ^TInterfaceMT;
TInterfaceMT = packed record
QueryInterfaceThunk: PAdjustSelfThunk;
end;
TInterfaceRef = ^PInterfaceMT;
var
QueryInterfaceThunk: PAdjustSelfThunk;
begin
Result := Pointer(I);
if Assigned(Result) then
try
QueryInterfaceThunk := TInterfaceRef(I)^. QueryInterfaceThunk;
case QueryInterfaceThunk.AddInstruction of
AddByte: Inc(PChar(Result), QueryInterfaceThunk.AdjustmentByte);
AddLong: Inc(PChar(Result), QueryInterfaceThunk.AdjustmentLong);
else Result := nil;
end;
except
Result := nil;
end;
end;

It does look like a lot of gobbledygook, but most of it is actually constant and type declarations to make the code more self-documenting and clear (yeah, right!). The code peaks into the code thunks generated by the compiler to convert an the Self: IInterface call-side parameter into the expected Self: TObject callee-side parameter.


While an object reference points to the first byte of the memory block allocated to hold the object instance fields, an interface reference points inside somewhere in the middle of the object memory block. So the only difference of the two types of reference pointers is a small offset. Our task is to find that offset. As it happens the compiler must fixup the Self reference of a interface method call - and it does this simply by adding a negative offset to the Self pointer before jumping to the actual method implementation. The code above reaches out into the QueryInterface code thunk and uses the offset there to convert the interface reference to an object reference.


To achieve the goal of finding the GUID of an interface reference, we should return the actual offset instead of just using it to generate an object reference. Lets rewrite the function above to do that.

function GetPIMTOffset(const I: IInterface): integer;
// PIMT = Pointer to Interface Method Table
const
AddByte = $04244483; // opcode for ADD DWORD PTR [ESP+4], Shortint
AddLong = $04244481; // opcode for ADD DWORD PTR [ESP+4], Longint
type
PAdjustSelfThunk = ^TAdjustSelfThunk;
TAdjustSelfThunk = packed record
case AddInstruction: longint of
AddByte : (AdjustmentByte: shortint);
AddLong : (AdjustmentLong: longint);
end;
PInterfaceMT = ^TInterfaceMT;
TInterfaceMT = packed record
QueryInterfaceThunk: PAdjustSelfThunk;
end;
TInterfaceRef = ^PInterfaceMT;
var
QueryInterfaceThunk: PAdjustSelfThunk;
begin
Result := -1;
if Assigned(Pointer(I)) then
try
QueryInterfaceThunk := TInterfaceRef(I)^.QueryInterfaceThunk;
case QueryInterfaceThunk.AddInstruction of
AddByte: Result := -QueryInterfaceThunk.AdjustmentByte;
AddLong: Result := -QueryInterfaceThunk.AdjustmentLong;
end;
except
// Protect against non-Delphi or invalid interface references
end;
end;

As you can see, this is basically the same function as above, but it returns an integer instead of a TObject. The function name is a little geeky - PIMT is short for pointer to interface method table. This is a special compiler generated "field" that is added to an object instance by the compiler when you declare that the class implements an interface. The "field" is a pointer to a kind of virtual method table for the methods declared on the interface. The function returns the offset of this field. Note that the compiler uses an ADD assembly instruction to adjust the Self parameter - but the value added is actually negative. That's why we return the negated value of the adjustment offset.


Now we can rewrite the original GetImplementingObject function in terms of this new, more basic function.

function GetImplementingObject(const I: IInterface): TObject;
var
Offset: integer;
begin
Offset := GetPIMTOffset(I);
if Offset > 0
then Result := TObject(PChar(I) - Offset)
else Result := nil;
end;

A neat little function - nice to get rid of the duplication. Note that PChar is the only pointer type that allows pointer arithmetic and we cast to it to turn the interface and offset into an object reference.


We're still only the first step towards getting the interface GUID (or IID which is the formally correct name). Now we're able to get the PIMT offset, but the offset by itself isn't very useful. What makes it useful is that we can use the offset to compare it with the offsets stored as part of the InterfaceEntry records the compiler generates for all the interfaces a class implements. As indicated above, we can use the TObject class function called GetInterfaceTable to get a pointer to this table. With that knowledge, let's write a function that tries to find the InterfaceEntry of an interface reference.

function GetInterfaceEntry(const I: IInterface): PInterfaceEntry;
var
Offset: integer;
Instance: TObject;
InterfaceTable: PInterfaceTable;
j: integer;
CurrentClass: TClass;
begin
Offset := GetPIMTOffset(I);
Instance := GetImplementingObject(I);
if (Offset >= 0) and Assigned(Instance) then
begin
CurrentClass := Instance.ClassType;
while Assigned(CurrentClass) do
begin
InterfaceTable := CurrentClass.GetInterfaceTable;
if Assigned(InterfaceTable) then
for j := 0 to InterfaceTable.EntryCount-1 do
begin
Result := @InterfaceTable.Entries[j];
if Result.IOffset = Offset then
Exit;
end;
CurrentClass := CurrentClass.ClassParent
end;
end;
Result := nil;
end;

First we use the the utility functions above to get both the object instance and the offset of the PIMT field. Then we loop across this class and all parent classes looking for an InterfaceEntry that has the same offset as our PIMT field. When we find a match, we return a pointer to the InterfaceEntry record. This record contains both the PIMT offset and the IID. Let's write a simple wrapper function to extract the IID.

function GetInterfaceIID(const I: IInterface; var IID: TGUID): boolean;
var
InterfaceEntry: PInterfaceEntry;
begin
InterfaceEntry := GetInterfaceEntry(I);
Result := Assigned(InterfaceEntry);
if Result then
IID := InterfaceEntry.IID;
end;

There - that was a no-brainer. Ok, now we have all the functions and utility punitions in place - now we just need to test that they actually work. Here is my simple test program.

program TestInterfaceGUID;

{$APPTYPE CONSOLE}

uses
SysUtils,
HVInterfaceGUID in 'HVInterfaceGUID.pas';

type
IMyInterface = interface
['{ABDA7685-DB67-43C1-947F-4B9535142355}']
procedure Foo;
end;
TMyObject = class(TInterfacedObject, IMyInterface)
procedure Foo;
end;

procedure TMyObject.Foo;
begin
end;

var
MyInterface: IMyInterface;
Unknown: IUnknown;
Instance: TObject;
IID: TGUID;
begin
MyInterface := TMyObject.Create;
Instance := GetImplementingObject(MyInterface);
Writeln(Instance.ClassName);
if GetInterfaceIID(MyInterface, IID) then
writeln('MyInterface IID = ', GUIDToString(IID));
Unknown := MyInterface;
if GetInterfaceIID(Unknown, IID) then
writeln('Dereived IUnknown IID = ', GUIDToString(IID));
Unknown := TMyObject.Create;
if GetInterfaceIID(Unknown, IID) then
writeln('Pure IUnknown IID = ', GUIDToString(IID));
readln;
end.

This program declares an interface with a method Foo and a class that implements it. Then it creates an instance of the class - assigning it directly to an interface reference. First we test the GetImplementingObject function and write out the name of the implementing class. Then we call GetInterfaceIID three times and print out the resulting GUID. In the first call we use the actual interface directly - if our code is correct, this should work fine. In the second call we have transferred the interface reference into an IUnknown reference. Depending on how the compiler implements interface assignment between assignment compatible interfaces, this may or may not work. We'll see. In the final call we assign a new IUnknown reference a new instance of the class. In this case, we expect the GUID of IUnknown to be returned.


When we run the code we get the following output:

TMyObject
MyInterface IID = {ABDA7685-DB67-43C1-947F-4B9535142355}
Derived IUnknown IID = {ABDA7685-DB67-43C1-947F-4B9535142355}
Pure IUnknown IID = {00000000-0000-0000-C000-000000000046}

Looks like the code works ;). We get the expected results for the class name and the first interface IID. It is also interesting (and useful) that the second "Derived IUnknown IID" returns the IID of the original interface. Unsurprisingly the IID of IUnknown is the one defined by Microsoft. The reason the second IID is preserved is that the Unknown reference is a pure copy of the MyInterface reference. This is the assembly code the compiler generates for the assignment.

  Unknown := MyInterface;  
mov eax,$0040a7a4
mov edx,[MyInterface]
call @IntfCopy

This code calls the RTL routine to copy interfaces, System._IntfCopy, which handles the reference counting of the source and destination interfaces references. So the actual reference stays the same - it only affects the reference count (by calling _AddRef). That's why we get the desirable result with the IMyInterface GUID instead of the IUnknown GUID in the second case.


If the IUnknown interface is assigned using an as-cast, the result it different.

  Unknown := MyInterface as IUnknown;  
if GetInterfaceIID(Unknown, IID) then
writeln('As IUnknown IID = ', GUIDToString(IID));

In this case the output is

  As IUnknown IID = {00000000-0000-0000-C000-000000000046} 

We get the IUnknown GUID, not the IMyInterface GUID. The reason is that the compiler generates the as-cast and assignment like this:

  Unknown := MyInterface as IUnknown;  
mov eax,$0040a7a4
mov edx,[MyInterface]
mov ecx,$00408b0c
call @IntfCast

This version uses System._IntfCast to do the cast and assignment - and looking at the source it uses QueryInterface to perform the conversion. QueryInterface will return another new interface reference (the PIMT field, remember) that TInterfacedObject added for the IUnknown (aka IInterface) interface. This interface has its own IID of course, so we don't get the original IID of IMyInterface that Randy wanted in his original question.


The long string of hex numbers that a GUID consists of isn't very human readable. Some interfaces (particularly COM interfaces), have their IIDs and corresponding name string registered in the registry. For instance, HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Interface\{00000000-0000-0000-C000-000000000046} = IUnknown. It is possible to lookup a GUID in the registry to try and find the name string. This is left as an exercise for the reader ;).

Thursday, September 07, 2006

Oslo Delphi Meetup Group started!

We have now started the Oslo Delphi Meetup Group (or "Brukergruppen for Delphi i Oslo" as it is called in Norwegian). If you have a chance to participate in upcoming meetings in Oslo and is interested in Delphi (I assume you are - why else would you be here;)) please sign up as a member here.

Membership is free, but using the meetup.com site isn't (there is a monthly fee). For the time being DevCo's Fredrik Haglund is the Organizer (and thus the guy paying the bills) while I'm a Assistant Organizer (so I can manage parts of the site).

When we have reached a minimal membership count, we'll try to organize the first actual meeting somewhere in Oslo. Until then, sign up, answer the first Poll and feel free to start threads in the message board with comments, ideas or questions.

Wednesday, September 06, 2006

Extended Class RTTI

As has been mentioned earlier, Delphi (since version 7) supports extended RTTI on the methods of a class - by compiling the class with $METHODINFO ON defined. This RTTI includes the signature information of the public and published methods. Delphi uses this to implement scripting of Delphi code in the WebSnap framework - see ObjAuto and friends for the details.

I have now been able to write my own types and routines to dig down and get hold of the extended class RTTI into a format that can easily be used for external consumption. As usual my sample app simply dumps out the source code declaration of a sample class.

While writing the HVMethodInfoClasses unit I refactored some of the earlier code and structures so that I could use as much common code with HVIntefaceMethods and HVMethodSignature.

We're getting used to this RTTI digging now, so lets go through the new code quickly. First there are the additional implementation-detail structures defining the approximate layout of the RTTI the compiler generates - gleaned from the "official" source in ObjAuto.

type
PReturnInfo = ^TReturnInfo;
TReturnInfo = packed record
Version: Byte;
CallingConvention: TCallConv;
ReturnType: PPTypeInfo;
ParamSize: Word;
end;
PParamInfo = ^TParamInfo;
TParamInfo = packed record
Flags: TParamFlags;
ParamType: PPTypeInfo;
Access: Word;
Name: ShortString;
end;

Exactly how to find where these structures start is a little subtle. Remember back to the Under the hood of published methods article? At the time I wrote the following (ignorant to the existence of the $MethodInfo directive and extended class RTTI):



"As you can see above the published method table now has the type PPmt. It points to a record that contains the number of published methods in this class followed by a packed array of TPublishedMethod records. Each record contains a size (used to find the start of the next record), a pointer to the address of the method and a packed shortstring containing the name of the method.
Notice that it appears that the Size field would have been unnecessary. In all my testing the value of Size has always been equal to the expression:

  Size :=  SizeOf(Size) + SizeOf(Address) + SizeOf(Name[0]) + Length(Name);
In other words, the next TPublishedMethod record starts just after the last byte of the method name. I’m not sure why Borland decided to add the Size field, but one possible reason might be to be able to extend the contents of the TPublishedMethod record in the future. One natural extension would be to include information about the parameters and calling convention of the method. Then Size would be adjusted accordingly and old code unaware of the new fields would still work fine"

It turns out that the Size field is indeed used to pack up the TReturnInfo and TParamInfo records just following the Name field of the TPublishedMethod record.

type
PPublishedMethod = ^TPublishedMethod;
TPublishedMethod = packed record
Size: word;
Address: Pointer;
Name: {packed} Shortstring;
end;

To find and decode the method's signature we have to collect the number of extra bytes as indicated by the Size field. We'll see the code for that shortly.


First, here is the easy to use structure that will hold the decoded RTTI for a single class, including all public/published methods with all their parameters and return types.

type
// Easy-to-use fixed size structure
PClassInfo = ^TClassInfo;
TClassInfo = record
UnitName: string;
Name: string;
ClassType: TClass;
ParentClass: TClass;
MethodCount: Word;
Methods: array of TMethodSignature;
end;

That should be largely self-documenting. As you can see have have reused the same TMethodSignature record as we used for interfaces. Who said you have to write OOP to do re-use ;)? All right, now we're more or less ready to write the actual code to convert a typeinfo of a class to the TClassInfo structure above. This implies getting our hands dirty by iterating over all public/published methods and over all extra RTTI info for each method containing signature info. After a little trial-and-error and a couple of peeks into ObjAuto, I ended up with the following code.

function ClassOfTypeInfo(P: PPTypeInfo): TClass;
begin
Result := nil;
if Assigned(P) and (P^.Kind = tkClass) then
Result := GetTypeData(P^).ClassType;
end;

procedure GetClassInfo(ClassTypeInfo: PTypeInfo;
var ClassInfo: TClassInfo);
// Converts from raw RTTI structures to user-friendly Info structures
var
TypeData: PTypeData;
i, j: integer;
MethodInfo: PMethodSignature;
PublishedMethod: PPublishedMethod;
MethodParam: PMethodParam;
ReturnRTTI: PReturnInfo;
ParameterRTTI: PParamInfo;
SignatureEnd: Pointer;
begin
Assert(Assigned(ClassTypeInfo));
Assert(ClassTypeInfo.Kind = tkClass);
// Class
TypeData := GetTypeData(ClassTypeInfo);
ClassInfo.UnitName := TypeData.UnitName;
ClassInfo.ClassType := TypeData.ClassType;
ClassInfo.Name := TypeData.ClassType.ClassName;
ClassInfo.ParentClass := ClassOfTypeInfo(TypeData.ParentInfo);
ClassInfo.MethodCount := GetPublishedMethodCount(ClassInfo.ClassType);
SetLength(ClassInfo.Methods, ClassInfo.MethodCount);
// Methods
PublishedMethod := GetFirstPublishedMethod(ClassInfo.ClassType);
for i := Low(ClassInfo.Methods) to High(ClassInfo.Methods) do
begin
// Method
MethodInfo := @ClassInfo.Methods[i];
MethodInfo.Name := PublishedMethod.Name;
MethodInfo.Address := PublishedMethod.Address;
MethodInfo.MethodKind := mkProcedure; // Assume procedure by default

// Return info and calling convention
ReturnRTTI := Skip(@PublishedMethod.Name);
SignatureEnd := Pointer(Cardinal(PublishedMethod)
+ PublishedMethod.Size);
if Cardinal(ReturnRTTI) >= Cardinal(SignatureEnd) then
begin
MethodInfo.CallConv := ccReg; // Assume register calling convention
MethodInfo.HasSignatureRTTI := False;
end
else
begin
MethodInfo.ResultTypeInfo := Dereference(ReturnRTTI.ReturnType);
if Assigned(MethodInfo.ResultTypeInfo) then
begin
MethodInfo.MethodKind := mkFunction;
MethodInfo.ResultTypeName := MethodInfo.ResultTypeInfo.Name;
end
else
MethodInfo.MethodKind := mkProcedure;
MethodInfo.CallConv := ReturnRTTI.CallingConvention;
MethodInfo.HasSignatureRTTI := True;
// Count parameters
ParameterRTTI := Pointer(Cardinal(ReturnRTTI) + SizeOf(ReturnRTTI^));
MethodInfo.ParamCount := 0;
while Cardinal(ParameterRTTI) < Cardinal(SignatureEnd) do
begin
Inc(MethodInfo.ParamCount); // Assume less than 255 parameters ;)!
ParameterRTTI := Skip(@ParameterRTTI.Name);
end;
// Read parameter info
ParameterRTTI := Pointer(Cardinal(ReturnRTTI) + SizeOf(ReturnRTTI^));
SetLength(MethodInfo.Parameters, MethodInfo.ParamCount);
for j := Low(MethodInfo.Parameters) to High(MethodInfo.Parameters) do
begin
MethodParam := @MethodInfo.Parameters[j];
MethodParam.Flags := ParameterRTTI.Flags;
if pfResult in MethodParam.Flags
then MethodParam.ParamName := 'Result'
else MethodParam.ParamName := ParameterRTTI.Name;
MethodParam.TypeInfo := Dereference(ParameterRTTI.ParamType);
if Assigned(MethodParam.TypeInfo) then
MethodParam.TypeName := MethodParam.TypeInfo.Name;
MethodParam.Location := TParamLocation(ParameterRTTI.Access);
ParameterRTTI := Skip(@ParameterRTTI.Name);
end;
end;
PublishedMethod := GetNextPublishedMethod(ClassInfo.ClassType,
PublishedMethod);
end;
end;

As usual we test the code by defining some silly code and use RTTI to reconstruct the source of a class declaration. Here is the simplified test project.

program TestHVMethodInfoClasses;

{$APPTYPE CONSOLE}

uses
SysUtils,
TypInfo,
HVMethodSignature in 'HVMethodSignature.pas',
HVMethodInfoClasses in 'HVMethodInfoClasses.pas';

procedure DumpClass(ClassTypeInfo: PTypeInfo);
var
ClassInfo: TClassInfo;
i: integer;
begin
GetClassInfo(ClassTypeInfo, ClassInfo);
writeln('unit ', ClassInfo.UnitName, ';');
writeln('type');
write(' ', ClassInfo.Name, ' = ');
write('class');
if Assigned(ClassInfo.ParentClass) then
write(' (', ClassInfo.ParentClass.ClassName, ')');
writeln;
for i := Low(ClassInfo.Methods) to High(ClassInfo.Methods) do
writeln(' ', MethodSignatureToString(ClassInfo.Methods[i]));
writeln(' end;');
writeln;
end;

type
{$METHODINFO OFF}
TNormalClass = class
end;
TSetOfByte = set of byte;
TEnum = (enOne, enTwo, enThree);
type
{$METHODINFO ON}
TMyClass = class
public
function Test1(const A: string): string;
function Test2(const A: string): byte;
procedure Test3(R: integer);
procedure Test4(R: TObject);
procedure Test5(R: TNormalClass);
procedure Test6(R: TSetOfByte);
procedure Test7(R: shortstring);
procedure Test8(R: openstring);
procedure Test9(R: TEnum);
function Test10: TNormalClass;
function Test11: integer;
function Test18: shortstring;
function Test19: TObject;
function Test20: IInterface;
function Test21: TSetOfByte;
function Test22: TEnum;
end;

//... Dummy implementations of TMyClass methods left out...

procedure Test;
begin
DumpClass(TypeInfo(TMyClass));
end;

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

 And the output of the program is:

unit TestHVMethodInfoClasses;
type
TMyClass = class (TObject)
function Test1(A: String): String;
function Test2(A: String): Byte;
procedure Test3(R: Integer);
procedure Test4(R: TObject);
procedure Test5(R: TNormalClass);
procedure Test6(R: TSetOfByte);
procedure Test7(R: ShortString);
procedure Test8(R: ShortString);
procedure Test9(R: TEnum);
function Test10(): TNormalClass;
function Test11(): Integer;
function Test18(): ShortString;
function Test19(): TObject;
function Test20(): IInterface;
function Test21(): TSetOfByte;
function Test22(): TEnum;
end;

The full code is available at CodeCentral.


As my diligent reader, Ralf, pointed out the output of this program is not a verbatim copy of the source code. Aside from my sloppiness of not omitting the empty parens in the functions, the A: string parameters are not declared const. This is because the RTTI for those parameters does not include pfConst (duh!). I think the reason is that the method and parameter RTTI is optimized to achieve dynamic run-time calling of methods and a const modifier does not affect the caller - it only influences the code the compiler generates in the implementation of the method (omitting string ref-counting and a try-finally clause).


In fact, I've been (unsuccessfully so far) lobbying Borland DevCo CodeGear to ease up the compiler and allow having const in the implementation section and non-const in the interface. This might sound like a sloppy request, but it would allow changing the const-ness of a parameter without affecting the interface. Oh well, a story for some other time, perhaps.


[PS. This blog post was edited in Windows Live Writer - I like it, although I goofed up and posted this article too early, probably by accidentally pressing Ctrl+P = publish post ;)].


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

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