3.3 Language Integration

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 integrationthe ability to derive a class from a base that is specified in a totally different language; 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.

Figure 3-1. Polymorphism across languages
figs/nfe3_0301.gif

In the upcoming code example, we can tell a Vehicle to TurnLeft( ) or TurnRight( ), but what turns left or right depends on the target object, whether a Car or a Plane. Unlike the examples in the previous 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.

3.3.1 Vehicle Class in Managed C++

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 turns 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 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 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.

3.3.2 Car Class in VB.NET

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 J#, 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.

3.3.3 Plane Class in C#

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.

3.3.4 Test Driver in J#

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 J#, the next 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 J#, proving that exception handling works across languages:

class TestDrive 
{
  public static void main(  ) 
  {
    Vehicle v = null;  // 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(System.Exception e)
    { 
      System.Console.WriteLine(e.ToString(  ));
    }

  }
}

If you want to test out these features, you can create an EXE using the following command:

vjc /r:vehicle.dll;car.dll;plane.dll /t:exe /out:drive.exe drive.jsl

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. And since we are building an EXE, we need to signal this to the J# 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 that 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 that target the CLR.