Thursday, January 29, 2004

Whither go the RTL

There seems to be some confusion cropping up with Delphi 8 for .NET. It seems that many folks are somewhat befuddled by the difference between a "library" assembly and a "package" assembly. It's really quite simple, but I can certainly understand the confusion. An assembly in .NET/CLR has the .dll extension. Well, for the Delphi programmer that means a "library" project, right? Well... almost... it seems that we inadvertently threw you a curve. An "assembly" in .NET/CLR is a very richly adorned .dll... not unlike a traditional Delphi "package." When we began digging under the hood of the CLR, we discovered that an assembly contains classes, metadata and code that are publically available to other assemblies and applications... Well... a Delphi "package" contains classes, metadata (RTTI) and code that are publically available to other packages or applications. I'm sure you see where I'm going with this. Yep, assembly=package.

What about "library?" It works too... Yes it does, but for different reasons and different purposes. You see, a CLR assembly can normally be called only from other CLR assemblies (or through COM-Interop, for the pedantic ones out there)... or so we thought. When we were looking at the whole of the managed code space, this included educating ourselves about CIL (Common Intermediate Language), CLR, C#, VB.NET and C++ with Managed extensions. It was that last one that sparked our curiosity. It seems that in C++ with managed extensions, you can create an assembly (you know; a .dll) that is directly callable from unmanged code! It can be called using the standard LoadLibrary, GetProcAddress pattern. No, you can't directly construct a managed CLR class, or call instance methods of that class, but you can call static methods. To the caller they simply appear as a flat "C"-style API. Of course you have to tell the compiler/linker which ones to "export" from the assembly and under what name. You also have to make sure the caller passes in the parameters correctly. But the interesing thing here is that all that work that the CLR does to allow P/Invoke (Platform Invoke) calls out to unmanaged APIs from managed code also works in reverse! This is how the whole thing operates. The final kicker in all of this is C# cannot do this.

What does that have to do with Delphi? Originally, the library syntax was used simply as an internal stop-gap measure until we had finished the compiler work to support "packages." The "library" syntax was earmarked for destruction. When we discovered that little tidbit above, the trusty ole' "library" syntax gained a new lease on life. We figured out that by pulling the same little linker tricks that the Managed/C++ compiler/linker do to export flat functions from an assembly, we can do the same thing. Hey, it's the little things that excite us sometimes... So now you can create a managed code assembly using the "library" syntax and then define exports via the tried and true "exports" clause. There is one little caveat to all these machinations. You must mark the library as "unsafe." This means that your managed code assembly no longer will pass the PEVerify test (a utility that is part of the .NET SDK determines if an assembly or managed application violates several key tests). This is due to the fact that the assembly now has true Win32/ia32 entry points. Since unmanged code can only call a dll via a "CALL" machine instruction, there needs to be native machine instructions at that entry point in order to properly transfer control.

So the general guide lines here are you should rarely, if ever use a "library" when you want to build an assembly. Use "package" for that purpose.

Now on to another bit of confusion. What is the deal with Borland.Delphi.dll? If I don't link against that assembly, things don't seem to function correctly. Lets first step back and get a little perspective. CLR/.NET defines a common set of rules, metadata formats, file formats, and data-types designed to allow language interoperability. This allows one to declare and implement a class type in one language then create a derived class type in another language. The CLR only defines those things nessesary for two types defined by different languages to play nice with one another. Languages are different for a reason. Syntax is the most obvious difference one thinks of when comparing two languages. The other differences that actually affect the run-time behavior of language are a little harder to characterize. For, instance CLR/.NET has no notion of a class method. They has static methods, but that is different than a Delphi class method. This is because a CLR static method really has no direct knowleged of the class in which it is executing. A Delphi class method has direct knowledge of the class in which it belongs. It has an implicit Self parameter that is actually a class type reference. This brings up the another difference, class references. Yes, CLR does has a similar entity called the System.Type type. Close, but no cigar. In Delphi you can also declare a class method as virtual and call it polymorphically through a class reference variable.

All these differences require some level of run-time support. There are also certain global variables that the Delphi programmer has come to expect from the RTL. This is the reason there is still the trusty old System unit, which in CLR is renamed to Borland.Delphi.System. This is where all the compiler "magic" functions reside, and all the classic base Delphi types are declared. If you look at other non-C# CLR languages, such as Visual Basic.NET, Visual J#, they also have a language-specific runtime like microsoft.visualbasic.dll. Once you understand that there are types and global data in that unit (and thus in Borland.Delphi.dll), you can see the importance of using packages instead of libraries and also make sure all your packages ultimately link to a common Borland.Delphi.dll. For an explination of what can happen when you mix seemingly identical types from two separate assemblies, check out this blog entry.