Sunday, June 25, 2006

.NET 2.0 for Delphi programmers available now

Jon Shemitz’ classic .NET 2.0 for Delphi Programmers book is now available in hardcover and shipping from Amazon and other online and old-fashioned book shops. I don’t have my own copy yet – if you already have yours, you can be the first to write an Amazon review. Read my review of it here. As I’ve mentioned before there is a sample chapter online at Jon’s site.

Highly recommended!

Sunday, June 04, 2006

Simple Interface RTTI

Delphi supports getting RTTI for all interfaces, but it does not include method information for “normal” interfaces.

type
{$M-}
IMyMMInterface = interface
procedure Foo;
end;

By using the built-in TypeInfo function on the interface type, we get a pointer to the RTTI structure the compiler has generated for it, a pointer to a TTypeInfo record. This record is defined in the TypInfo unit and looks like this:

  PPTypeInfo = ^PTypeInfo;
PTypeInfo = ^TTypeInfo;
TTypeInfo = record
Kind: TTypeKind;
Name: ShortString;
{TypeData: TTypeData}
end;

Just following the TTypeInfo record in memory is another variable-length variant record that contains different fields and information depending on the value of the Kind field above. The supported type kinds are defined like this:


type
TTypeKind = (tkUnknown, tkInteger, tkChar, tkEnumeration, tkFloat,
tkString, tkSet, tkClass, tkMethod, tkWChar, tkLString, tkWString,
tkVariant, tkArray, tkRecord, tkInterface, tkInt64, tkDynArray);

For interfaces, we’re only interested in the tkInterface kind. The part of the TTypeData record that encodes the RTTI for normal interfaces look like this:

type
TIntfFlag = (ifHasGuid, ifDispInterface, ifDispatch);
TIntfFlagsBase = set of TIntfFlag;
// …
PTypeData = ^TTypeData;
TTypeData = packed record
case TTypeKind of
// …
tkInterface: (
IntfParent : PPTypeInfo; { ancestor }
IntfFlags : TIntfFlagsBase;
Guid : TGUID;
IntfUnit : ShortStringBase;
{PropData: TPropData});
// …
end;

From this we can se that we have access to the following type information for any interface:



  • A pointer to the parent interface (IntfParent)

  • Flags indicating if this interface has a GUID, if it is a dispintf interface and if it is a IDispatch interface (IntfFlags)

  • The GUID of the interface (if it has one) (Guid)

  • The unit the interface was declared in (IntfUnit)

  • The number of methods in the interface (PropData.Count)
We can write a simple function that dumps this information for a given interface.
type  
PExtraInterfaceData = ^TExtraInterfaceData;
TExtraInterfaceData = packed record
MethodCount: Word; { # methods }
end;

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

procedure DumpSimpleInterface(InterfaceTypeInfo: PTypeInfo);
var
TypeData: PTypeData;
ExtraData: PExtraInterfaceData;
i: integer;
begin
Assert(Assigned(InterfaceTypeInfo));
Assert(InterfaceTypeInfo.Kind = tkInterface);
TypeData := GetTypeData(InterfaceTypeInfo);
ExtraData := SkipPackedShortString(@TypeData.IntfUnit);
writeln('unit ', TypeData.IntfUnit, ';');
writeln('type');
write(' ', InterfaceTypeInfo.Name, ' = ');
if not (ifDispInterface in TypeData.IntfFlags) then
begin
write('interface');
if Assigned(TypeData.IntfParent) then
write(' (', TypeData.IntfParent^.Name, ')');
writeln;
end
else
writeln('dispinterface');
if ifHasGuid in TypeData.IntfFlags then
writeln(' [''', GuidToString(TypeData.Guid), ''']');
for i := 1 to ExtraData.MethodCount do
writeln(' procedure UnknownName',i,';');
writeln(' end;');
writeln;
end;

The function expects a pointer to the type information of an interface. It digs out the generated interface RTTI and tries to write a pseudo interface type declaration inside a unit using the information available. It only knows the number of methods in the interface, so it just outputs some dummy names for them.

To test this we can define some plain vanilla ($M-} interfaces and then use the TypeInfo intrinsic function to get a RTTI pointer for each interface to send to the dumping function above.

program TestSimpleInterfaceRTTI;

{$APPTYPE CONSOLE}

uses
SysUtils,
TypInfo;

// ... Insert the code above here

type
{$M-}
IMyInterface = interface
procedure Foo(A: integer);
procedure Bar(const B: string);
procedure Nada(const C: array of integer; D: TObject);
end;
IMyDispatchInterface = interface(IDispatch)
['{9BC5459B-6C31-4F5B-B733-DCA8FC8C1345}']
procedure Foo; dispid 0;
end;
IMyDispInterface = dispinterface
['{8574E276-4671-49AC-B775-B299E6EF01C5}']
procedure Bar;
end;

begin
DumpSimpleInterface(TypeInfo(IMyInterface));
DumpSimpleInterface(TypeInfo(IMyDispatchInterface));
DumpSimpleInterface(TypeInfo(IMyDispInterface));
readln;
end.

Running this project produces the following output:

unit TestSimpleInterfaceRTTI;
type
IMyInterface = interface (IInterface)
procedure UnknownName1;
procedure UnknownName2;
procedure UnknownName3;
end;

unit TestSimpleInterfaceRTTI;
type
IMyDispatchInterface = interface (IDispatch)
['{9BC5459B-6C31-4F5B-B733-DCA8FC8C1345}']
procedure UnknownName1;
end;

unit TestSimpleInterfaceRTTI;
type
IMyDispInterface = dispinterface
['{8574E276-4671-49AC-B775-B299E6EF01C5}']
procedure UnknownName1;
end;

We are able to pick up the name of the unit (or program as it is in this case) the interface is declared in, the name of the interface and its parent interface and the GUID if it has one. We can also distinguish dispinterfaces that is used in the implementation of Automation servers (for dual interfaces that inherit from IDispatch).

As you can see we are missing the proper names of the interface methods and we don’t have any information about the parameters, return types or calling conventions. Compiling interfaces in the $M+ mode (or inheriting from IInvokable) changes all that – as we shall see in an upcoming article.

Blog feed: atom2rss converter down

As one of my diligent readers made me aware of, the free service I've been using to convert Blogger's Atom-only based XML feed to the more widespread RSS format has stopped working for some time now. The service I used is documented here:

http://www.2rss.com/software.php?page=atom2rss

When I initially created this blog, I included a link of the following format

http://www.2rss.com/atom2rss.php?atom=http://hallvards.blogspot.com/atom.xml

to an XML icon () that used to be listed under the Syndication header in the sidebar. I've decided to comment out that link for now.

So for those of you who are sourcing the RSS feed instead of the Atom feed (and thus will not be able to read this :)), please switch to the Atom feed instead:

http://hallvards.blogspot.com/atom.xml

Sorry for the inconvenience!

Digging into SOAP and WebSnap

In previous articles on this blog we have dissected the internal workings of published methods, and lamented the (assumed) fact that the RTTI for them does not include signature information. We then developed a dirty (and pretty useless) hack to dig out the parameters of a published method by matching it with the RTTI of an event that references the method at runtime. As I briefly mentioned in my last post, David Glassborow pointed me to the extended RTTI that Delphi 6 and later supports.

In Delphi 6, Borland introduced new web services functionality to be able to write SOAP servers and clients. They also released WebSnap – a framework for writing ASP-like web applications with server-side scripting. Both of these frameworks need the functionality to call arbitrary methods with any number and types of parameters dynamically at runtime.

SOAP and method RTTI for interfaces
For SOAP-based Web services, they use invokable interfaces to let a THTTPSoapPascalInvoker component dynamically call methods in a registered interface. An invokable interface is one that inherits from IInvokable, and IInvokable is compiled using the compiler directive $M+ (see System.pas). The compiler generates full RTTI for all the methods in invokable interfaces – including enough parameter information to call the methods dynamically at runtime.

You can read the juicy technical implementation details by browsing the code in IntfInfo.pas, Invoker.pas and RIO.pas. Note that full source for these were not made available in D6 and D7 (there is a RIO.int in the Doc directory, though). They can be found in the Source\Win32\SOAP directory in D2005 and 2006.

WebSnap and method RTTI for classes
For WebSnap, the “new” $METHODINFO ON compiler directive (which was undocumented in D6 and D7) is used to generate extended RTTI for public and published methods. A class declared in this mode (and all classes that inherit from it) will have RTTI generated for all its public and published methods. In addition to the normal $M+ style published method RTTI with name and address, the extended RTTI for METHODINFO includes the name and type of all parameters, the return type and calling convention. This allows the methods (and published properties) to be accessed from the server-side WebSnap script.

In the WebSnap directory you’ll find the source of the following interesting units ObjAuto, ObjComAuto and WebSnapObjs.

Exploring the fascinating and impressive implementation details in these units is left as an exercise for the reader. We might revisit them for a closer examination in the future.

In coming articles, we’ll look into the details of simple interface RTTI, extended interface RTTI and extended class method RTTI. Stay tuned!



Copyright © 2004-2007 by Hallvard Vassbotn