« Labour Mythology"Truth in Science" Anything But »

Delphi Pseudo-Generics and Namespaces

07/01/07

  05:13:59 am, by Nimble   , 779 words  
Categories: Thoughts, Programming

Delphi Pseudo-Generics and Namespaces

I have been using Rossen Assenov's pseudo-generics in Delphi for quite a while now to great effect. (See here for downloads)

In an effort to more properly ".NET-ify" things, I have been converting Delphi unit names into namespace-scoped unit names. For example, instead of Guards, I could have Ritchie.Sync.Guards. This is the equivalent of a C# namespace of Ritchie.Sync with a file name of Guards.cs. Essentially, you strip off the last name to get the name of the namespace. So, I could have a Ritchie.Sync.Events and Ritchie.Sync.Waiters and they would all end up in the Ritchie.Sync namespace. I could make a Ritchie.Sync.dll assembly out of that and use it in C# that way.

There was an unintended side effect, though. All of my pseudo-generics blew up... at link time. I cannot remember for the life of me the last time I had a link-time error in Object Pascal. With precious few exceptions, everything gets caught at compile time.

It turns out that the scoping rules are a little different as well when you have namespaces. Before, with just the unit names, including a generic in Guards was just fine because all of the generic types would be scoped as things like Guards._REGISTRY_. This is not the case with namespaces.

In namespaces, with Ritchie.Sync.Guards, the generic items are actually scoped to the whole namespace, so it’s actually getting Ritchie.Sync._REGISTRY_. This interferes with any other generic that has a _REGISTRY_ in it at link time.

So what to do, what to do?

Well, there’s a new feature of the Delphi for .NET versions corresponding to the C# feature with similar capabilities... class-embedded types. You can embed any type within a class, including other classes, which really helps us out here.

As an aside, in .NET, you also no longer need the UUIDs for interfaces, which is good for cutting out a couple of lines for my interface-based generics. Otherwise, you need to define and pass in unique GUIDs for each interface a pseudo-generic implements for you.

So what I do in the interface section of the unit implementing a generic has switched from this:

const
  _FACTORY_IID_ =  '{FD6540F3-EDC3-4ACC-9810-3E04504D8D6A}';
  _REGISTRY_IID_ = '{5A894CD9-EC3F-4BBA-A31A-6AE499642EA3}';
type
  _BUILD_ITEM_ = ICOREGuard;

{$INCLUDE GSimpleFactory.inc}

type
  ICOREGuardFactory = _FACTORY_INTF_;
  ICOREGuardFactoryRegistry = _REGISTRY_INTF_;
  TCOREGuardFactoryRegistry = class(_REGISTRY_)...

...to this...

const
  _FACTORY_IID_ =  '{FD6540F3-EDC3-4ACC-9810-3E04504D8D6A}';
  _REGISTRY_IID_ = '{5A894CD9-EC3F-4BBA-A31A-6AE499642EA3}';
type
  _BUILD_ITEM_ = ICOREGuard;

  TGuardFactoryScope = class
    {$INCLUDE GSimpleFactory.inc}
  end;

type
  ICOREGuardFactory = TGuardFactoryScope._FACTORY_INTF_;
  ICOREGuardFactoryRegistry = TGuardFactoryScope._REGISTRY_INTF_;
  TCOREGuardFactoryRegistry =
    class(TGuardFactoryScope._REGISTRY_)


So this gives me the opportunity to make a “scope class”, which really is all about adding in the scope that is now missing, now that namespaces flatten out the unit scope.

Now, this alone is not enough to make things work. Now that you have the generic classes embedded within another class, this actually changes how you have to do the implementation half.

An inner class in Delphi must have its methods implemented like this:

procedure OuterClass.InnerClass.Method;
begin
  ...
end;

Oh no! What are we going to do? The whole point behind having a scope class is that the scope class can be different from unit to unit, so that you end up with Ritchie.TGuardFactoryScope._REGISTRY_, for example. If the outer class is always the same (e.g. Ritchie.MyScope), we will run smack into the same linker problems complaining of duplicates.

Fortunately, Delphi provides us a rather easy and surprising trick here. We can define a type equivalence and then actually use that type equivalence to implement methods.

So, for example, you can put into the generic’s implementation pass:

function _SIMPLE_FACTORY_SCOPE_._REGISTRY_.Build(
  const AName: String): _BUILD_ITEM_;
var
  Factory : _FACTORY_INTF_;
begin
  Factory := FactoryByName(AName);
  if Assigned(Factory) then
    Result := Factory.Build
  else
    Result := nil;
  if Result=nil then
    raise EFactory.CreateFmt(
      '_REGISTRY_::Build returned no interface for %s',
      [AName]);
end;

Then, it’s a relatively small change in the implementing unit’s code, from:

{$INCLUDE GSimpleFactory.inc}

to

type
  _SIMPLE_FACTORY_SCOPE_ = TGuardFactoryScope;

{$INCLUDE GSimpleFactory.inc}

Note the trick there. We just defined _SIMPLE_FACTORY_SCOPE_ as TGuardFactoryScope, and Delphi was quite happy to let us use _SIMPLE_FACTORY_SCOPE_ as the class name when we are implementing.

Hm!

Well, that technique saved the day. Looking forward to the release of 'Highlander' later this year, which will give us the proper .NET 2.0 generics (and native Win32 ones, too? That's almost what they make it sound like).

No feedback yet