A site devoted to discussing techniques that promote quality and ethical practices in software development.

Wednesday, November 29, 2006

A Borland feature or bastardation of CLR ?

A recent chance development of a .Net prototype solution took me face to face with a ugly finding of fiddling of .Net/CLR. Fiddling other's structure or framework is not new to Borland as reported elsewhere but this surpasses all I have seen.

Let's describe the structure of my prototype leading to the discovery of this damaging technique.

I have a Delphi.Net VCL Form application, MyApp.exe, which uses a VS/C# assembly in which one finds a factory object. Because of a Delphi 2006 (D2006) internal compiler bug, this .Net assembly cannot reference any Borland assembly directly. If this assembly references any Borland vcl assembly, you cannot directly reference this assembly in a Delphi VCL package (Delphi's term for DLL assemblies). Hence this factory object, FormFactory, uses Activator.CreateInstance() to dynamically creating Delphi VCL.Net forms from a number of Delphi.Net packages directed by some policy. The factory returns System.Object to the caller.

These Delphi packages contain VCL.Net forms and that they declare references to Borland.Vcl.dll which provides the type Borland.Vcl.TCustomForm, the base type of the forms in these packages.

In MyApp.exe, it has a reference to my factory assembly but it does not have a reference to Borland.Vcl.dll and yet I can compile code like this:

obj := FormFactory.CreateInstance( .... );
Debug.Assert( obj is TCustomForm );

I built all the assemblies and they do not have any compilation error. Surprise as I expect this to complain just like their C# builder would. But when it runs, the above assert fail!!

This is totally crazy. In the debugger, I can see the base type of obj is TCustomForm so what's happening. I even do this:

Debug.WriteLine( obj.GetType().BaseType.BaseType.Fullname );

This shows Borland.Vcl.TCustomForm. So what is happening?

Initially I thought Borland must have translated the is operator to something else because their as operator and Pascal cast operator behave exactly opposite to C#. So I fired up Lutz's Reflector and is operator is generated to isinst IL instruction. So that proves the Pascal is operator is being translated to some Borland operator.

The explanation only surfaces when I type out the Type.AssemblyQualifiedName. The outputs of obj.GetType().BaseType.BaseType and TCustomForm from MyApp explain why isinst is failing.

The Delphi compiler took action when it sees that I have not defined a reference to Borland.Vcl.dll and rammed the IL code into MyApp.exe to produce this:

Borland.Vcl.TCustomForm, MyApp, Version=1.0.27277.2828, Culture=neutral, PublicKeyToken=None

The base class from obj is
Borland.Vcl.TCustomForm, Borland.Vcl, Version=10.xxxxxx, .....

In other words, as far as CLR is concerned, these are two distinct types. Borland must mistakenly believing that CLR would simply use namespace qualified type names. How wrong is such a view!

Let's see what the standard says about the above types - are they 2 distinct types or one the same?

With reference to Partition I, section 8.5.2 Assmblies and Scoping:
To fully identify a type, the type name shall be qualified by the scope that includes the type name. A type name is scoped by the assembly that contains the implementation of the type. An assembly is a configured set of loadable code modules and other resources that together implement a unit of functionality. The type name is said to be in the assembly scope of the assembly that implements the type. Assemblies themselves have names that form the basis of the CTS naming hierarchy.

Here you have the unambiguous definition from the standard that says the above encounter resulted in two distinct types.

In fact, Borland is willing to throw away the strongly name attribute of their Borland.Vcl.dll to a non-strongly name one. Gee I am wondering if I can use that to con users that I am a genuine Borland assembly. I will leave that for another day.

After discovering this, a report was dispatched to Borland. As usual, it is not a bug but a 'feature' and 'an advantage'. Oh Yah! Probably the COM bug in their code is also a feature. It is a special feature called static binding. Indeed it is a static binding! If you open the assembly with Lutz's Reflector, you can see all the IL code rammed into your assembly as if they were in Borland.Vcl.dll. The trouble is that they have just allowed you to give their type your version and assembly scope. Even if it is in an executable, you can still load it as if an ordinary DLL assembly.

Incidentally, their compiler does not even have a switch to turn off this 'feature' nor does it generates any warning. So beware!

Now let's consider a number of normal .Net usages in which Borland's 'feature' can wreck havoc to a CLR solution.

Scenario 1
Let's say a gun ho Delphi developer comes up with this special component that he wants other to use. Seeing it is cooler not having to dispatch Borland.Vcl.dll, he uses this static linkage to produces his assembly.

The other developer using factory pattern to load this will have lots of problem.

In fact, if this developer wants to achieve this, he/she would be better to use DILMerge.

Scenario 2
Let say developer creates MyAssem1.dll using static binding, another one produces MyAssem2.dll, etc. (incidentally don't be fool to believe that if you have MyAssem1.exe, MyAssem2.exe, two separate applications will not encounter this madness), soon you have as many distinct TCustomForm types as you have assemblies.

Scenario 3
When Borland finally catches up to .Net 2 and that they support generic, whose syntax is still a mystery, you cannot use List because if you have a number of distinct TCustomForm types as far as CLR is concerned. I pray Borland will not create its own flavour of System.Collections.Generic.List<>? The only way out is to resort back to using ArrayList that support inhomogenous collection.

Clearly Borland must be naively believing type comparison is only performed based on namespace qualified name rather than what is defined by CLI. Perhaps in their VCL32 world this is the case. Well that's fine as that is their own creation and they can be as free as they like because there is no one they have to be compatible with except themselves. Sorry Borland. Someone must have forgotten to tell you that the world has changed!

I would like to agree with Borland that this is a 'feature' or 'advantage' but if this creates more problems, violates CLI specification, misleads developers and has no real benefit at all, I am afraid that my logical thinking does not allow me to agree. So what can I say other than to label this as a bug and an attempt to bastardize CLR or to create Borland's CLR.

Having features to distinguish oneself from others is great but the features should not damage and confuse a framework that promotes interchangeability, cross-language programming and component technology.

What Borland has done is to transplant their VCL32 features to .Net and blindly or rather arrogantly ignoring the need to meet the ECMA CLI standard.

VCL namespace is already non-compatible with the System namespace and this is another Borland's feature to take their product further away from being compatible with .Net. So users be warned and stay clear.


Allen said...

This is a common mistake that many people make, so don't feel bad. Let me explain. When you referenced the Borland.Vcl.Forms unit and did *not* reference the Borland.Vcl.dll assembly, you linked into your application a new and distinct copy of Borland.Vcl.Forms and all the types therein.

This same exact scenario is possible using C# simply by including the same class into to two different assemblies or in an assembly and an application.

Delphi/.NET allows more flexibility when creating applications where you have the option of linking all the code from Borland.Vcl.dll into your application, linking with that assembly.

What happened in your case was that you linked with a C# assembly which was in-turn linked with Borland.Vcl.dll. Simply link your application with the Borland.Vcl.dll assembly in addition to the C# assembly. That way both the C# assembly and the application will be referring to the same type.

Allen Bauer.

L. Mar said...

There are a few comments made by Allan that needs to be further qualified:
"including the same class into to two different assemblies or in an assembly and an application."
By this the "same class" should read the same class source file. Not merely referencing the class, such as System.InvalidException, would pull in the source of that class as well as it ancestor classes. In C# if you fail to define the reference of these classes that you do not have source file defined in your project, it will fail to compile.

Incidentally this is not a C# issue but a CLI standard to ensure componentisation and interoperability. Without strict adherence to the Standard, any perceived marketing 'advantage' could produce incompatible product as demonstrated.

This is distinctly different in BDS, in which it pulls in the source file (IL code) into your project silently without any warning.

"have the option of linking all the code from Borland.Vcl.dll into your application, linking with that assembly."
Please note that BDS does not offer any option to turn this 'feature' off. Like many Borland's product, it makes that assumption for you without even a warning.

Blog Archive