Cross-Language Support


One of the best features of COM is its support for multiple languages. It’s possible to create a COM component with Visual Basic and to make use of it from within a scripting client such as JScript. On the other hand, it’s also possible to create a COM component using C++ that a Visual Basic program cannot make use of. A scripting client has different requirements than a Visual Basic client, and a C++ client is able to use many more COM features than any other client language.

When you write COM components, it’s always necessary to have the client programming language in mind. The server must be developed for a specific client language or for a group of client languages. If you are designing a COM component for a scripting client, this component can also be used from within C++, but then the C++ client has some disadvantages. Many rules must be followed when different clients should be supported, and the compiler can’t help with COM; the COM developer has to know the requirements of the client language and has to create the interfaces accordingly.

How does this compare with the .NET platform? With the Common Type System (CTS), .NET defines how value types and reference types can be defined from a .NET language and the memory layout of such types. But the CTS does not guarantee that a type that is defined from any language can be used from any other language. This is the role of the Common Language Specification (CLS). The CLS defines the minimum requirement of types that must be supported by a .NET language.

The CTS and CLS were briefly mentioned in Chapter 1, “.NET Architecture.” This section goes deeper and explores the following:

  • Both CTS and CLS.

  • Language independence in action by creating a C++, Visual Basic, and a C# class that derive from each other. You look at the MSIL code that’s generated from these compilers.

  • The requirements of the CLS.

The CTS and the CLS

All types are declared with the guidance of the CTS. The CTS defines a set of rules that language compilers must follow to define, reference, use, and store reference and value types. Therefore, if you follow the CTS, objects written in different languages can interact with each other.

However, not all types are available to all programming languages. To build components accessible from all .NET languages, use the CLS. With the CLS, the compiler can check for valid code according to the CLS specification.

Any language that supports .NET isn’t just restricted to the common subset of features that is defined with the CLS; even with .NET, it’s still possible to create components that cannot be used from different languages. Supporting all languages is much easier with .NET than it was with COM. If you do restrict yourself to the CLS, it’s guaranteed that this component can be used from all languages. It is most likely that libraries written by third parties will restrict themselves to the CLS to make the library available to all languages.

The .NET Framework was designed from the ground up to support multiple languages. During the design phase of .NET, Microsoft invited many compiler vendors to build their own .NET languages. Microsoft itself delivers Visual Basic, C++/CLI, C#, J#, and JScript .NET. In addition, more than 50 languages from different vendors, such as COBOL, Smalltalk, Delphi, Perl, and Eiffel, are available. Each of these languages has its specific advantages and many different features. The compilers of all these languages have been extended to support .NET.

Important 

The CLS is the minimum specification of requirements that a language must support. This means that if you restrict your public methods to the CLS, all languages supporting .NET can use your classes!

Most, but not all, of the classes in the .NET Framework are CLS-compliant. The noncompliant classes and methods are specially marked as not compliant in the MSDN documentation. One example is the UInt32 structure in the System namespace. UInt32 represents a 32-bit unsigned integer. Not all languages (for example Java) support unsigned data types; such data types are not CLS-compliant.

Language Independence in Action

This section shows CLS in action. The first assembly created includes a base class with Visual C++. The second assembly has a Visual Basic class that inherits from the C++ class. The third assembly is a C# console application with a class derived from the Visual Basic code, and a Main function that’s using the C# class. The implementation should just show how the languages make use of .NET classes and how they handle numbers, so all these classes have a simple Hello() method where the System.Console class is used, and an Add() method where two numbers are added. Figure 16-9 shows the Visual Studio class diagram with these classes and methods.

image from book
Figure 16-9

Tip 

Chapter 43, “C#, Visual Basic, and C++/CLI,” compares the syntax of C++/CLI, Visual Basic, and C#.

Writing the C++/CLI Class

The first project type you create for this example is a .NET class library, which is created from the Visual C++ Projects project type of Visual Studio, and named HelloCPP (see Figure 16-10). Name the solution LanguageIndependence.

image from book
Figure 16-10

The Application Wizard generates a class Class1 marked with ref to make the class a managed type. Without a special attribute, the class would be a normal unmanaged C++ class generating native code. This class is in the file HelloCPP.h:

  // HelloCPP.h #pragma once using namespace System; namespace HelloCPP {    public ref class Class1    {       // TODO: Add your methods for this class here.    }; } 

For demonstration purposes, change the namespace and class name, and add three methods to the class. The virtual method Hello2() uses a C runtime function, printf(), that demonstrates the use of native code within a managed class. To make this method available the header file, stdio.h must be included. Within the Hello() method, you are using the Console managed class from the System namespace. The C++ using namespace statement is similar to the C# using statement. using namespace System opens the System namespace, so you don’t have to write System::Console::WriteLine(). Mark the method Hello() with the virtual keyword, so that it can be overridden. Hello() will be overridden in the Visual Basic and C# classes. Similarly to C#, C++ member functions are not virtual by default. Add a third method, Add(), which returns the sum of two int arguments, to the class so that you can compare the generated MSIL to the different languages to see how they handle numbers. All three examples use the same namespace: Wrox.ProCSharp.Assemblies.CrossLanguage.

 // HelloCPP.h #pragma once #include <stdio.h>  using namespace System;  namespace Wrox {    namespace ProCSharp    {       namespace Assemblies       {          namespace CrossLanguage          {             public ref class HelloCPP             {             public:                virtual void Hello()                {                   Console::WriteLine("Hello, C++/CLI");                }                 virtual void Hello2()                {                   printf("Hello, calling native code\n");                }                                int Add(int val1, int val2)                {                   return val1 + val2;                }             };          }       }    } } 

To allow all .NET languages to use the class HelloCPP, the assembly is marked CLS-compliant. You can find the assembly attribute [assembly:CLSCompliant(true)] in the file AssemblyInfo.cpp.

To compare the programs with running code, this example uses the release build instead of the debug configuration. With debug configurations you can see some nonoptimized IL code. Looking at the generated DLL using ildasm (see Figure 16-11), in addition to the class HelloCPP with its members you will also find the static method printf(). This method calls a native unmanaged function using pinvoke.

image from book
Figure 16-11

You can verify the IL code with the ildasm tool. The Hello2() method loads the address of the field $ArrayType$$$BY0BM@$$CBD. In line IL_0005, a call to the static printf() method can be seen where a pointer to the string “Hello, calling native code“ is passed.

 .method public hidebysig newslot virtual         instance void  Hello2() cil managed {   // Code size       12 (0xc)   .maxstack  1   IL_0000:  ldsflda    valuetype         '<CppImplementationDetails>'.$ArrayType$$$BY0BM@$$CBD         modopt([mscorlib]System.Runtime.CompilerServices.IsConst)         '??_C@_0BM@POGPABJ@Hello?0?5calling?5native?5code?6?$AA@'   IL_0005:  call       vararg int32         modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl) printf(int8         modopt([mscorlib]System.Runtime.CompilerServices.IsSignUnspecifiedByte)         modopt([mscorlib]System.Runtime.CompilerServices.IsConst)*)   IL_000a:  pop   IL_000b:  ret } // end of method HelloCPP::Hello2

The Hello() method is completely made up of MSIL code; there’s no native code. Also the string “Hello, C++/CLI“ is a managed string written into the assembly and put onto the stack with ldstr. In line IL_0005, you are calling the WriteLine() method of the System.Console class, using the string from the stack.

 .method public hidebysig newslot virtual         instance void  Hello() cil managed {   // Code size       11 (0xb)   .maxstack  1   IL_0000:  ldstr      "Hello, C++/CLI"   IL_0005:  call       void [mscorlib]System.Console::WriteLine(string)   IL_000a:  ret } // end of method HelloCPP::Hello

To demonstrate how numbers are used within C++/CLI, take a look at the MSIL code of the Add() method. The passed arguments are put on the stack with ldarg.1 and ldarg.2, add adds the stack values and puts the result on the stack, and in line IL_0003 the result is returned.

 .method public hidebysig instance int32 Add(int32 val1,       int32 val2) cil managed {   // Code size       4 (0x4)   .maxstack  2   IL_0000:  ldarg.1   IL_0001:  ldarg.2   IL_0002:  add   IL_0003:  ret } // end of method HelloCPP::Add

Important 

What’s the advantage of using C++/CLI compared to C# and other languages of the .NET Framework? C++/CLI makes it easier to make traditional C++ code available to .NET. MSIL code and native code can be mixed easily.

Writing the Visual Basic Class

In this section, you use Visual Basic to create a class. Again, use the Class Library Wizard, and name the project HelloVB (see Figure 16-12).

image from book
Figure 16-12

Change the namespace of the class to Wrox.ProCSharp.Assemblies.CrossLanguage. In a Visual Basic project, you can do this by changing the root namespace of the project in the project properties, as demonstrated in Figure 16-13.

image from book
Figure 16-13

To make it possible to derive the class from HelloCPP, a reference to HelloCPP.dll is needed. Add the reference to the C++ project by selecting Project image from book Add Reference. After you build the assembly, the reference can be seen inside the manifest: .assembly extern HelloCPP (see Figure 16-14). Adding the reference to the project copies the referenced assembly to the output directory of the Visual Basic project, so that it is independent of later changes made to the original referenced assembly.

image from book
Figure 16-14

Create the class HelloVB using the following code. The class HelloVB inherits from HelloMCPP. Visual Basic uses the keyword Inherits to derive from a base class. Inherits must be in the line after the Class statement. Override the Hello() method in the base class. The Visual Basic Overrides keyword does the same thing as the C# override keyword. In the implementation of the Hello() method, the Hello() method of the base class is called using the Visual Basic keyword MyBase. The MyBase keyword is the same as base in C#. The method Add() is implemented so that you can examine the generated MSIL code to see how Visual Basic works with numbers. The Add() method from the base class is not virtual, so it can’t be overridden. Visual Basic uses the keyword Shadows to hide a method of a base class. Shadows is similar to C#’s new modifier. The C# new modifier was introduced in Chapter 4, “Inheritance.”

  Public Class HelloVB    Inherits HelloCPP    Public Overrides Sub Hello()       MyBase.Hello()       Console.WriteLine("Hello, Visual Basic")    End Sub    Public Shadows Function Add(ByVal val1 As Integer, _                                ByVal val2 As Integer) As Integer       Return val1 + val2    End Function End Class 

Now look at the MSIL code generated from the Visual Basic compiler.

The HelloVB.Hello() method first calls the Hello() method of the base class HelloMCPP. In line IL_0006, a string stored in the metadata is pushed on the stack using ldstr.

 .method public strict virtual instance void         Hello() cil managed {   // Code size       17 (0x11)   .maxstack  8   IL_0000:  ldarg.0   IL_0001:  call       instance void      [HelloCPP]Wrox.ProCSharp.Assemblies.CrossLanguage.HelloCPP::Hello()   IL_0006:  ldstr      "Hello, Visual Basic"   IL_000b:  call       void [mscorlib]System.Console::WriteLine(string)   IL_0010:  ret } // end of method HelloVB::Hello

The other method you are looking at is Add(). Visual Basic uses add.ovf instead of the add method that was used in the C++/CLI-generated MSIL code. This is just a single MSIL statement that’s different between C++ and Visual Basic, but the statement add.ovf generates more lines of native code because add.ovf performs overflow checking. If the result of the addition of the two arguments is too large to be represented in the target type, add.ovf generates an exception of type OverflowException. In contrast, add just performs an addition of the two values, whether or not the target fits. In the case where the target is not big enough, the true value of the summation is lost, which results in a wrong number. So, add is faster, but add.ovf is safer.

 .method public instance int32 Add(int32 val1,       int32 val2) cil managed {   // Code size       4 (0x4)   .maxstack  2   .locals init ([0] int32 Add)   IL_0000:  ldarg.1   IL_0001:  ldarg.2   IL_0002:  add.ovf   IL_0003:  ret } // end of method HelloVB::Add

Writing the C# Class

The third class is created using C#, which is the language this book is written about. For this project, create a C# console application called HelloCSharp. Add a reference to the HelloVB and the HelloCPP assembly, because the C# class will be derived from the Visual Basic class.

Use the following code to create the class HelloCSharp. The methods implemented in the C# class are similar to the C++/CLI and the Visual Basic classes. Hello() is an overridden method of the base class; Add() is a new method:

 using System; namespace Wrox.ProCSharp.Assemblies.CrossLanguage {    public class HelloCSharp : HelloVB    {       public HelloCSharp()       {       }       public override void Hello()       {          base.Hello();          Console.WriteLine("Hello, C#");       }       public new int Add(int val1, int val2)       {          return val1 + val2;       }    }    class Program    {       static void Main()       {          HelloCSharp hello = new HelloCSharp();          hello.Hello();       }    } } 

Using ildasm you can see the generated MSIL code for the Hello() method is the same as the MSIL code from the Visual Basic compiler:

 .method public hidebysig virtual instance void         Hello() cil managed {   // Code size       17 (0x11)   .maxstack  8   IL_0000:  ldarg.0   IL_0001:  call       instance void      [HelloVB]Wrox.ProCSharp.Assemblies.CrossLanguage.HelloVB::Hello()   IL_0006:  ldstr      "Hello, C#"   IL_000b:  call       void [mscorlib]System.Console::WriteLine(string)   IL_0010:  ret } // end of method HelloCSharp::Hello

The Add() method differs from, and yet is similar to, the C++/CLI code. When doing calculations, the C# compiler doesn’t use the methods with overflow checking if the default compiler settings are configured in the Visual Studio project. The faster MSIL method add is used instead of add.ovf; but it’s possible to change this option by using the configuration properties of C# and Visual Basic projects. Setting “Check for overflow underflow” to true in a C# project, the MSIL code that the C# compiler generates for this example will be the same as that generated by the Visual Basic compiler. Unlike Visual Basic, with C# it’s also possible to choose this option on an expression-by-expression basis with the checked and unchecked operators. The checked and unchecked operators are discussed in Chapter 6, “Operators and Casts.”

 .method public hidebysig instance int32  Add(int32 val1,        int32 val2) cil managed {   // Code size       4 (0x4)   .maxstack  8   IL_0000:  ldarg.1   IL_0001:  ldarg.2   IL_0002:  add   IL_0003:  ret } // end of method HelloCSharp::Add

Finally, here you can see the console application output:

 Hello, C++/CLI Hello, Visual Basic Hello, C# Press any key to continue . . .

Tip 

Because all the .NET languages generate MSIL code and all the languages make use of the classes in the .NET Framework, it’s often said that there is no difference regarding performance. As you can see, however, small differences are still there. First, some languages support different data types from others. Second, the generated MSIL code can still be different. One example that you’ve seen is that the number calculations are implemented differently: whereas the default configuration of Visual Basic is for safety, the default for C# is for speed. C# is also more flexible.

CLS Requirements

You saw the CLS in action when you looked at cross-language inheritance between C++/CLI, Visual Basic, and C#. Until now you didn’t pay any attention to the CLS requirements when building your project. You were lucky - the methods defined in the base classes were callable from the derived classes. If a method had the System.UInt32 data type as one of its arguments, you wouldn’t be able to use it from Visual Basic. Unsigned data types are not CLS-compliant; for a .NET language, it’s not necessary to support this data type.

The CLS exactly defines the requirements to make a component CLS-compliant, which enables it to be used with different .NET languages. With COM you had to pay attention to language-specific requirements when designing a component. JScript had different requirements from Visual Basic 6, and the requirements of Visual J++ were different again. This is no longer the case with .NET. To design a component that should be used from other languages, you just have to make it CLS-compliant; it’s guaranteed that this component can be used from all .NET languages. If you mark a class as CLS-compliant, the compiler can warn you about noncompliant methods.

All .NET languages must support the CLS. When talking about .NET languages it’s important to differentiate between .NET consumer and .NET extender tools.

A .NET consumer language just uses classes from the .NET Framework - it can’t create .NET classes that can be used from other languages. A consumer tool can use any CLS-compliant class. A .NET extender tool has the requirements of a consumer and in addition can inherit any CLS-compliant .NET class and define new CLS-compliant classes that can be used by consumers. C++, Visual Basic, and C# all are extender tools. With these languages, it’s possible to create CLS-compliant classes.

The CLSCompliant Attribute

With the CLSCompliant attribute, you can mark your assembly as CLS-compliant. Doing this guarantees that the classes in this assembly can be used from all .NET consumer tools. The compiler issues warnings when you are using non-CLS-compliant data types in public and protected methods. The data types you use in the private implementation don’t matter - when using other languages outside of the class, you don’t have direct access to private methods anyway.

To get compiler warnings when a data type is not compliant in public and protected methods, you set the attribute CLSCompliant in the assembly by adding this attribute to the file AssemblyInfo.cs. To make a class CLS-compliant, it is also necessary that the base classes be CLS compliant, so you must apply this attribute not only to the C# class but also to the Visual Basic and C++ base classes.

  [assembly: System.CLSCompliant(true)] 

This way, all the defined types and public methods inside the assembly must be compliant. Using a non-compliant uint type as argument type, you get this error from the compiler:

 error CS3001: Argument type uint is not CLS-compliant 

When you mark an assembly as compliant, it’s still possible to define methods that are not compliant. This can be useful if you want to override some method to make it available with both compliant and noncompliant argument data types. The methods that are not compliant must be marked, within the class, by the CLSCompliant attribute with a value of false. The CLSCompliant attribute can be applied to types, methods, properties, fields, and events:

 [CLSCompliant(false)] void Method(uint i) {    //...

CLS Rules

The requirements for an assembly to be CLS-compliant are:

  • All types appearing in a method prototype must be CLS-compliant.

  • Array elements must have a CLS-compliant element type. Arrays must also be 0-indexed.

  • A CLS-compliant class must inherit from a CLS-compliant class. System.Object is CLS-compliant.

  • Although method names in CLS-compliant classes are not case-sensitive, method names may not only be different in their case.

  • Enumerations must be of type Int16, Int32, or Int64. Enumerations of other types are not compliant.

All the listed requirements only apply to public and protected members. The private implementation itself doesn’t matter - noncompliant types can be used there, and the assembly will still be compliant.

In addition to these naming guidelines to support multiple languages, it’s necessary to pay attention to method names where the type is part of the name. Data type names are language-specific, for example, the C# int, long, and float types are equivalent to the Visual Basic Integer, Long, and Single types.

When a data type name is used in a name of a method, the universal type names - Int32, Int64, and Single - and not the language-specific type names should be used:

  int ReadInt32(); long ReadInt64(); float ReadSingle(); 

Complying with the CLS specs and guidelines, it’s easy to create components that can be used from multiple languages. It’s not necessary to test the component by using all .NET consumer languages.




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

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