In the previous section, we saw that you can take advantage of .NET object-oriented concepts in any .NET language. In this section, we show that you can take advantage of language integration—the ability to derive a class from a base that is specified in a totally different language, or to catch exceptions thrown by code written in a different language, or to take advantage of polymorphism across different languages, and so forth.
Before we discuss the examples in this section, let’s first understand what we want to accomplish (see Figure 3-1). We will first use Managed C++ to develop a Vehicle class that is an abstract base class. The Vehicle class exposes three polymorphic methods, including TurnLeft( ), TurnRight( ), and ApplyBrakes( ). We will then use VB.NET to develop a Car class that derives from Vehicle and overrides these three virtual methods. In addition, we will use C# to develop the Plane class that derives from Vehicle and overrides these three virtual methods.
In the upcoming code example, we can tell a Vehicle to TurnLeft( ) or TurnRight( ), but what turns left or right depends upon the target object, whether a Car or a Plane. Unlike the examples in the last section, the examples here illustrate that we can inherit classes and call virtual functions from ones that are defined in another language. In addition, we will demonstrate in our test program that exception handling works across different languages.
Let’s use Managed C++ to develop the Vehicle class, which is an abstract base class because ApplyBrakes( ) is a pure virtual function. Vehicle implements the ISteering interface to support turning left and turning right. Since the ApplyBrakes( ) function is a pure virtual function, any concrete derivative of Vehicle must implement this method:
#using<mscorlib.dll> using namespace System; public _ _gc _ _interface ISteering { void TurnLeft( ); void TurnRight( ); }; public _ _gc class Vehicle : public ISteering { public: virtual void TurnLeft( ) { Console::WriteLine("Vehicle turns left."); } virtual void TurnRight( ) { Console::WriteLine("Vehicle turn right."); } virtual void ApplyBrakes( ) = 0; };
Given this abstract base class, we can create a DLL that hosts this
definition. The first command here shows how we use the Managed C++
compiler to compile (as indicated by the /c
option) the vehicle.cpp file, which contains the
previous code. The second command shows how we use the C++ linker to
create a DLL with metadata and IL code:
cl /CLR /c vehicle.cpp link -dll /out:vehicle.dll -noentry vehicle.obj
Given just a few lines of Managed C++ code, we can build a DLL that can be used by another component. Note that there is no need to provide code for the functions IUnknown, DllGetClassObject( ), DllCanUnloadNow( ), DllRegisterServer( ), DllUnregisterServer( ), and so forth. In the old days, you had to provide code for these functions and interfaces for legacy COM DLLs.
Given this abstract Vehicle class, the Car class can derive from it and provide the implementation for the three virtual methods defined by Vehicle. In the following code, note that we’ve overridden and provided the implementation for TurnLeft( ), TurnRight( ), and ApplyBrakes( ). The ApplyBrakes( ) method is special in that it throws an exception, which will be caught by code written in C#, as we’ll see later.
Imports System Public Class Car Inherits Vehicle Overrides Public Sub TurnLeft( ) Console.WriteLine("Car turns left.") End Sub Overrides Public Sub TurnRight( ) Console.WriteLine("Car turns right.") End Sub Overrides Public Sub ApplyBrakes( ) Console.WriteLine("Car trying to stop.") throw new Exception("Brake failure!") End Sub End Class
With this code, we can build a DLL using the command-line VB.NET compiler, as follows:
vbc /r:vehicle.dll /t:library /out:car.dll car.vb
Since we want the VB.NET compiler to generate a DLL, we must signal
this by using the /t:library
option. Also, since
Car derives from Vehicle, the VB.NET compiler must resolve the
references to Vehicle. We can tell the VB.NET compiler the location
of external references using the /r:
option. It is
important to note that you don’t need to have the source code
for the vehicle DLL to reuse its code because all type information
can be obtained from any .NET assembly. In addition, you should note
that from this example, we have proven that you can derive a VB.NET
class from a Managed C++ class.
Now let’s use C# to develop the Plane class, which derives from the Vehicle class written in Managed C++. Similar to the Car class, the Plane class implements the three virtual functions from the Vehicle class. Unlike the Car class, though, the ApplyBrakes( ) method of this class doesn’t throw an exception.
using System; public class Plane : Vehicle { override public void TurnLeft( ) { Console.WriteLine("Plane turns left."); } override public void TurnRight( ) { Console.WriteLine("Plane turns right."); } override public void ApplyBrakes( ) { Console.WriteLine("Air brakes being used."); } }
You can build a DLL from this code using the following command:
csc /r:vehicle.dll /t:library /out:plane.dll plane.cs
Notice that we have used the /r:
option to tell
the C# compiler that Vehicle is defined in
vehicle.dll.
Having developed vehicle.dll, car.dll, and plane.dll, we are now ready to demonstrate that polymorphism and exception handling work across different languages. Written in C#, the upcoming code listing contains a Main( ) method with a Vehicle reference and an exception handler.
Inside the try
block, we
first instantiate a Plane class and refer to this instance using the
local Vehicle reference. Instead of telling the Plane to TurnLeft( )
or ApplyBrakes( ), we tell the Vehicle to do so. Similarly, we
instantiate a Car and refer to this instance using the local Vehicle
reference. Again, instead of telling the Car to TurnLeft( ) or
ApplyBrakes( ), we tell the Vehicle to do so. In both cases, we tell
the Vehicle either to TurnLeft( ) or ApplyBrakes( ), but the actual
vehicle that employs TurnLeft( ) or ApplyBrakes( ) is the Plane
instance in the first case and the Car instance in the second case;
that’s polymorphism, and it works across languages.
You should note that the second call to ApplyBrakes( ) would cause an exception because we threw an exception from Car’s ApplyBrakes( ). Although Car’s ApplyBrakes( ) was written using VB.NET, we could still catch the exception that it’s throwing in C#, proving that exception handling works across languages.
using System; class TestDrive { public static void Main( ) { Vehicle v; // Vehicle reference try { Plane p = new Plane( ); v = p;v.TurnLeft( );
v.ApplyBrakes( );
Car c = new Car( ); v = c;v.TurnLeft( );
v.ApplyBrakes( ); // Exception
} catch(Exception e) { Console.WriteLine(e.ToString( )); } } }
If you want to test out these features, you can create an EXE using the following command:
csc /r:vehicle.dll;car.dll;plane.dll /t:exe /out:drive.exe drive.cs
Since we have used the Vehicle, Car, and Plane classes in this code,
we must include references to vehicle.dll
,
car.dll
, and plane.dll
. In
addition, since we are building an EXE, we need to signify this to
the C# compiler using the /t:exe
option. Once you
have built this EXE and executed it, you get the following output:
Plane turns left. Air brakes being used. Car turns left. Car trying to stop. System.Exception: Brake failure! at Car.ApplyBrakes( ) at TestDrive.Main( )
As expected, the plane first turns left and then uses its air brakes. Then the car turns left, tries to stop, but can’t, so it throws an exception, which is caught in the Main( ) method.
In this simple example, we have shown that you can now take advantage of inheritance, polymorphism, and exception handling across different languages in the .NET Framework.
18.191.61.243