Section 2.3. Binary Compatibility


2.3. Binary Compatibility

As explained in Chapter 1, one of the core principles of component-oriented programming is binary compatibility between client and server. Binary compatibility enables binary components because it enforces both sides to abide by a binary contract (typically, an interface). As long as newer versions of the server abide by the original contract between the two, the client isn't affected by changes made to the server. COM was the first component technology to offer true binary compatibility free of DLL Hell (described in the previous chapter), and many developers have come to equate COM's implementation of binary compatibility (and the resulting restrictions, such as immutable COM interfaces) with the principle itself. The .NET approach to binary compatibility is different from that of COM, though, and so are the implications of the programming model. To understand these implications and why they differ from COM's, this section first briefly describes COM's binary-compatibility implementation and then discusses .NET's way of supporting binary compatibility.

2.3.1. COM Binary Compatibility

COM provides binary compatibility by using interface pointers and virtual tables. In COM, the client interacts with the object indirectly via an interface pointer. The interface pointer actually points to another pointer called the virtual table pointer. The virtual table pointer points to a table of function pointers. Each slot in the table points to the location where the corresponding interface's method code resides (see Figure 2-9).

Figure 2-9. COM binary compatibility


When the client uses the interface pointer to call a method (such as the second method of the interface), all the compiler builds into the client's code is an instruction to jump to the address pointed to by the second entry in the virtual table. In effect, this address is given as an offset from the table's starting point. At runtime, the loader patches this jump command to the actual address, because it already knows the table memory location. All the COM client records in its code is the offset from the virtual table start address. At runtime, any server that provides the same in-memory table layout (in which the table entries point to methods with exactly the same signatures) is considered binary-compatible. This is, by the way, exactly the definition of implementing a COM interface. This model yields the famous COM prime directive: Thou Shalt Not Change a Published Interface. Any change to the virtual table layout will break the existing client's code. The virtual table layout is isomorphic to the interface definitionfor example, the second method in the interface is the second entry in the virtual table. The virtual table must have the exact same number of entries as the interface has methods. Any change to the interface's definition must result in the recompiling and redeploying of all clients as well; otherwise, the clients are no longer binary-compatible with the server.

2.3.2. .NET Binary Compatibility

.NET provides binary compatibility using metadata. The high-level client code (such as C# or Visual Basic 2005) is compiled to IL. The client code can't contain any offsets from memory addresses, because those offsets depend on the way the JIT compiler structures the native code. At runtime, the JIT compiler compiles and links the IL to native machine code. The original IL contains only requests to invoke methods or access fields of an object, based on that type's metadatait's as if instead of a traditional method call in native code, the IL contains only a token identifying the method to invoke. Any type that provides these methods or fields in its metadata is binary-compatible, because the actual binary layout in memory of the type is decided at runtime by the JIT compiler, and there is nothing pertaining to this layout in the client's IL.

The main benefit of .NET metadata-based binary compatibility is that every class is a binary component. This, in turn, simplifies developing components tremendously compared with COM. There's no need for complex frameworks such as ATL, because .NET supports components natively. .NET can bridge the skill gap (mentioned in Chapter 1) between what most developers can do and what traditional component technologies demand. If you understand only object-oriented programming, you can do that and still gain some component-oriented programming benefits, relying on .NET to manage binary compatibility and versioning. If you understand the core issues of component-oriented programming, you can maximize your productivity and potential while gaining the full benefits of component-oriented applications. Unlike with COM, with .NET binary compatibility isn't limited to interface-based programming. Any .NET type, be it a class or a struct, is compatible with its clients. Any entry point, be it an instance method, object field, static method, or static field, is binary-compatible. The other main benefit of metadata-based binary compatibility is that it gives you the flexibility of late binding with the safety of early bindingthe code never jumps to the wrong address, and yet you don't bake actual entry-point addresses or offsets into the client's code. For example, consider the following .NET interface:

     public interface IMyInterface     {        void Method1(  );        void Method2(  );     } 

The interface is defined and implemented in a server assembly, and its client resides in a separate assembly. The client is compiled against the interface definition, and the compiler provides type safety by enforcing correct parameter types and return values. However, the same client will function just fine (without recompiling) if you change the order of the methods in the interface:

     public interface IMyInterface     {        void Method2(  );        void Method1(  );     } 

or if you add a new method:

     public interface IMyInterface     {        void Method3(  );        void Method1(  );        void Method2(  );     } 

If the client doesn't use one of the methods, you can remove it, and the client will be unaffected. In the past, this level of flexibility was available only with late-binding scripting languages, which interpreted the code instead of compiling it. All these changes are forbidden in COM interfaces, because they contradict the prime directive of not changing published interfaces. Adding new methods was the main reason for defining new interfaces in COM (which in turn complicated the COM programming model). However, .NET lets you remove unused methods, add new methods (or fields), and change the order of methods (although you can't change method parameters or remove methods that clients expect to use).

2.3.3. Binary Inheritance

An interesting side effect of metadata-based binary compatibility is that it allows binary inheritance of implementation. In traditional object-oriented programming, the subclass developer had to have access to a source file describing the base class in order to derive her class from it. In .NET, types are described using metadata, so the subclass developer needs to access the metadata only of the base class. The compiler reads the metadata from the binary file and knows which methods and fields are available in the base type. In fact, even when both the base class and the subclass are defined in the same project, the compiler still uses the metadata in the project to compile the subclass. This is why in .NET the order in which classes are defined doesn't matter (unlike in C++, which often requires forward type declarations or a particular order of listing the included header files). All the non-sealed .NET Framework base classes are available to you to derive and extend using binary inheritance. Note that inheritance is a double-edged sword, though. Inheritance is a form of white-box reuse: it tends to couple the subclass to the base class, and it requires intimate knowledge of the base class's functionality. Part of the reason why COM didn't allow binary inheritance of implementation (only of interfaces) was because the COM architects were aware of these liabilities. The .NET architects wanted to support inheritance as part of bridging the skill gap. However, I strongly advise against abusing inheritance. Try to keep your class hierarchies as simple and as flat as possible, and use interface-based programming as much as possible.



Programming. NET Components
Programming .NET Components, 2nd Edition
ISBN: 0596102070
EAN: 2147483647
Year: 2003
Pages: 145
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net