Here is the structure that demonstrates the bug.
I have a Delphi.Net (Delphi 2006) package, called PackageA, which contains UnitA that contains an interface IA. It is irrelevant what method IA prescribes but for argument sake, let's say if specifies a method called SayHi() like this.
unit UnitA;
interface
type IA = interface
procedure SayHi();
end;
Then we have a second Delphi.Net package, called PackageB, which contains a UnitB that contains IB as follows:
Everything compiled and linked provided that you have defined the references correctly. Now let's consider PackageA.dll and PackageB.dll as kind of interfaces publishing assemblies and we then implements the following concrete class that implements IB in C#:
unit UnitB;
interface
uses UnitA;
type IB = interface( IA )
procedue Yell();
end;
When I used VS2003 to develop this and pressing the tab key after typing IB the IDE only added the implementation of Yell() method. I was puzzled.
using namespace UnitB;
public class CSharpClass : IB {
public void Yell() { Console.WriteLine( "I am yelling" ); }
};
Even more bizarre is that that assembly not only compiled and built but it also runs!
I then check the PackageB.dll with Lutz's Reflector and to my horror, I cannot see the base interface of IB and this explains why the C# class compiled and built.
Digging this further I disassembly the IB in PackageB and this is the IL code Delphi.Net generates:
No wonder the C# only requires to implement just Yell(). I am wondering will this make any difference if I implement a concrete class in Delphi.Net just like the C# class.
.class interface public abstract auto ansi IB
{
.custom instance void .ctor(string) = ( ...... ) // ...
.method public hidebysig newslot abstract virtual
instance void Yell() cil managed
{
} // end of method IB::Yell
} // end of class IB
To my astonishment, my class
type MyDelphiClass = class( System.Object, IB )
public:
procedure Yell();
end;
did not compiled and was complaining about the need to implement IA.SayHi() and the need to include UnitA.
According to the IL code of PackageB.dll it does not contain any IL code of the base (required interface) of IB, where does Delphi get the information. The only conclusion one can draw is that Delphi.Net is not playing by the rule properly again! It is digging into its Delphi metadata rather than relying purely on CLR metadata.
According to ECMA-335 CLI standard Partition II, 12.1 this is what it says:
Interfaces shall declare that they require the implementation of zero or more other interfaces. If one interface, A, declares that it requires the implementation of another interface, B, then A implicitly declares that it requires the implementation of all interfaces required by B.
Now if PackageB.dll is rewritten in C# or even VB.Net, it would generate the following IL code for the interface IB:
Note that now IB specifies that it requires IA unambiguously.
.class interface public abstract auto ansi IB
implements [UnitA]UnitA.IA
{
} // end of class IB
You may ask what is the big deal when the final concrete class's class IL code is the same. Yes, that is so provided that you are using Delphi.Net throughout and that you are not using Reflection. This is not .Net!
If you use reflection to examine PackageB.dll for IB, you will discover that:
# typeIB.IsInterface is true
# (typeIB.GetInterfaces()).Length = 0
This means Delphi.Net is generating wrong IL code misleading the user of that assemblies.
Digging this further, I discovered that the missing IA from interface IB is caused by the now infamous symbol tossing bug. Delphi.Net has this bug that when it detects no one references a symbol - be it in a package or an executable application, the compiler simply tosses it away.
The only way to keep them is to litter your program with variables of the type you want to keep. This not only promotes dangerous trap for maintenance worker but also being stupid particularly in a package. By definition of the non-.Net world, any public symbols in a package is for someone outside to use. In .Net, executable assembly is being loaded just like a package. So how can the package foretell no one outside the package is using it and then tossing it away.
Either way, it is a compiler bug that does not generate CLI conforming code. Beware when you are planning to migrate to .Net world in Delphi. You could be producing solution that you, in your all Delphi.Net solution, works fine but your customer using your public artifacts to extend finding their assemblies not working.
No comments:
Post a Comment