Click Here to Install Silverlight*
IndiaChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|How To Buy|Subscribers|My MSDN
 
COM Interoperability in .NET
By G. Gnana Arun Ganesh
 
Article Posted: August 07, 2002
 

Introduction

In this article I cover the area Interoperability issues. There is no doubt that with the help of .NET one can create powerful components and Distributed applications than any other language. But we have to think over about the past reusable components, which were created by many languages such as VB etc.

Is it the usage of those past components is end after evolving of .NET?
No we can use those components in the .NET and the .NET types in the Classic COM clients. Are there any possibilities of communication between managed and unmanaged types? Yes it is possible to make it possible to use existing COM objects (Unmanaged) from within managed applications and expose-managed objects to existing COM (Unmanaged) applications. Now let us see those things in detail.

In the first part of this article Part1, I focus on how .NET types calling c DLLs (Win32 API). In the Part2, I illustrate how you can Build a .NET Server Callable from COM clients and then in the latter Part-3, I will explain you that how you can Build a .NET Client That Uses a COM Server. Confidently, at the end of the article, you'd have achieved enough information to understand how Classic COM and the .NET framework can peacefully co-exist together. Hence if you're geared up, let's take an expedition through travel around how Classic COM can be used in the .NET world.


The .NET and COM Mediator

.NET runtime affords COM Interoperability wrapper for overcoming the differences between .NET and COM environments. For example, runtime make an instance of COM Callable Wrapper (CCW) when a COM client accesses a .NET component. In the same way, an instance of Runtime Callable Wrapper (RCW) is formed when a .NET client accesses a COM component. These wrappers abstract the dissimilarity and provide the faultless incorporation between the two environments.


Part1 - .NET TYPES CALLING C DLLs (Win32 API)


Platform Invoke:

The platform invoke services offers a method to call functions that are exported from an unmanaged DLL. The most distinctive use of PInvoke is to allow .NET components to interact with the Win32 API. PInvoke is also used to access functions exports defined in custom DLLs.

To exemplify the use of PInvoke I create a C# class that makes a call to theWin32 Message Box () function. Before we move into the C# class let us see an example program in VB 6.

The C prototype:

int MessageBox(Hwnd hwnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType);

Parameters
hwnd ---> Handle to the dialog's owner.
lpText ---> The text you wish to display
lpCaption ---> The caption of the message box.
wType ---> The dialog definition. Ex: vbYesNo
Returns ---> Returns the value of the button that was clicked.


VB Declaration:

Private Declare Function MessageBox Lib "user32" Alias "MessageBoxA"_
(ByVal hwnd As Long, ByVal lpText As String, ByVal lpCaption As String,_
ByVal wType As Long) As Long

Private Sub Command1_Click()

   Dim ar As Long
   ar = MessageBox(hwnd, "Msgbox using API", "Arungg", vbInformation)

End Sub


In the above example program, I show you how you can use the Windows API in VB 6.The Visual Basic MsgBox() function actually wrap the MessageBox API. You don't need to use this API; in fact, it would just be unnecessary work. However to understand the usage of API, I show you the above example.

Let us progress to how to call Win32 MessageBox() function from a C# class using PInvoke.

namespace APIExample
{
     using System;
     // Must refernce this library to use PI nvoke types
     using System.Runtime.InteropServices;
     public class PinvokeClient
     {
         [DllImport("user32")]
         public static extern int MessageBox(int hWnd,
         String pText ,
         String pCaption ,
         int uType);
         public static int Main(string[] args)
         {
              String pText = "HELLO INDIA!!";
              String pCaption = "Example by Arungg";
              MessageBox(0,pText,pCaption,0);
              return 0;
         }
     }
}


Explanation:

Before calling a C-style Dll we have to declare the function to call using the static and extern C# keywords. After this you have to specify the name of the raw DLL that contain the function you are attempting to call, as shown here.

[DllImport("user32")]
public static extern int MessageBox(.......);

After declare the DLL Pass the arguments such as pText,pCaption. It should be clear that it does not matter in which order you specify the values.
In the above way one can use .NET types calling any type of raw C DLLs (Win32 API). This comes to an end of Part1 and I think the users now know how to call a raw C DLLs (Win32 API) using PInvoke in .NET. This is an end of Part1.


Part 2 - Build a .NET Server Callable from COM Clients

This article elucidates how to build and install-managed code that will be used from COM applications. A classic COM server is activated using the Service Control Manager (SCM). It looks up numerous information such as CLSIDs, IIDs, ProgIDs etc.

So what is the solution to use the .NET assemblies in the classic COM clients,

These are the steps concerned in the build process are as follows:

  1. Write and compile the managed code.
  2. Generate a COM type library (*.tlb) for the assembly using the tlbexp.exe utility so that allow the COM client to interact with the exposed types.
  3. Install and register the assembly so that COM SCM to locate it.
  4. Write and compile the COM code that references types in the assembly.

For demonstration purposes, I have created a .NET component in C# named Calculator. For client side, I have created a Visual Basic (VB) 6.0-based client.


Writing and Compiling the Managed Code:

To illustrate COM type communication with managed code, Let us see an example in which I create a C# class library which has a class named Calculator which supports three methods named Add(),Subtract()and Hello(). Notice that we define another interface named Imuldiv.

namespace Simpleclasslib
{
     using System;
     using System.Runtime.InteropServices;

     public interface Imuldiv
     {
         int Multiply(int x,int y);
         int Division(int x,int y);
     }

     public class Calculator:Imuldiv
     {
         public Calculator(){}

         public int Add(int x,int y)
         {
             return x+y;
         }

         public int Subtract(int x,int y)
         {
             return x-y;
         }

         int Imuldiv.Multiply(int x,int y)
         {
             return x*y;
         }

             int Imuldiv.Division(int x,int y)
         {
             return x/y;
         }

             public string Hello(string strName)
             {
                  string str ;
                  str = "Hello " + strName ;
                  return str ;
             }
         }
}


Once the managed code is written, the compilation process is the same as it would be for any other piece of managed code.

csc /out:Server.dll /target:library Calculator.cs

Now let us see the Server.dll in the ILDasm.exe.In that you can see the calculator class members and Imuldiv Interface members.


Generating a Type Library and Register the Assembly:

After compiling the project we have to create a type library file. So that only most unmanaged application development tools require a type library before you can make references to external types.

COM states that all server application that is programmed to share their features with other applications is to be register at a common location. The client needs to determine the exposed methods, properties and events. This is done via the libraries. The client reads the registry; determine the properties, methods, and event s of the object. COM requires up information such as CLSIDs, IIDs, and ProgIDs etc. But we know the assemblies are not registered.

.NET framework does not depend on the registry and uses metadata for this information. Hence, we have to produce the COM-compatible registry entries for our managed server so that the COM runtime could instantiate our server.

The .NET framework provides a couple of tools for this. You can use the Type Library Exporter utility (TLBEXP.exe) or the Assembly Registration Utility (Regasm.exe), both of which you'll find in the Bin directory of your .NET SDK installation.

REGASM is a superset of the TLBEXP utility in that it also does much more than generating a type library. It's also used to register the assembly, so that the appropriate registry entries are made to smooth the progress of the COM runtime and the .NET runtime to fastener up the COM aware client to the .NET component.

Usually a type library can be generated from an assembly using the regasm.exe utility. The regasm.exe utility not only registers an assembly and it also creates the required type library file, as shown here.

regasm Server.dll /tlb:Netserver.tlb


After creating the type library file you have to add a reference this to your project. For example, with Visual Basic 6.0, you can reference the .tlb file or dll file from the Project/References dialog. In Visual C++ 6.0, you can use the #import statement to import the type definitions from the type library directly into C++. Once the reference to the type library is added to the project, the types defined within that library can be referenced from unmanaged code.


Installing The Assembly:

In order to actually create managed types from unmanaged code, the assembly needs to be installed in the global assembly cache (GAC) and registered for use from COM.

You can install an assembly in the global assembly cache using gacutil.exe utility. Assemblies can be uninstalled using the /u option.

gacutil /i simpleserver.dll


Writing and Compiling the Unmanaged Code:

Once the assembly is registered and properly installed, the types defined within the assembly can be used from COM as though they were normal COM types.

Now you can create a simple VB 6 Standard EXE project type and as I already said confirm whether you set a reference to the new generated type Library. Place this code in the code section and now you see the usage of our .NET type in Classic COM.

Private Sub Command1_Click()
     Dim a As New Calculator
     MsgBox a.Add(100, 100), vbInformation, "ADDITION (100,100)"
     MsgBox a.GetType, vbInformation, "INFORMATION-TYPE"
     MsgBox a.Hello("India"), vbInformation, "STRING"
     MsgBox a.Subtract(100, 1), vbInformation, "SUBTRACTION(100-1)"
     Dim i As Imuldiv'INTERFACE
     Set i = a
     MsgBox i.Multiply(100, 100), vbInformation, "MULTIPLICATION(100*100)"
     MsgBox i.Division(100, 100), vbInformation, "DIVISION(100/100)"
End Sub



Summary:

A lot of companies have spent a great amount of money and time in COM components. With introduction of .NET people are worried about the future of COM. Microsoft has recognized this and provide means to use classic COM components in .NET code and vice versa.


Part 3 - Build a .NET Client that calls a COM Server


Introduction:

In this article I cover the area how to use a COM server in a .NET client. Existing COM components are precious resources to your managed applications. So now let us observe how you can build a .NET Client that uses a COM Server.

The steps involved in the build process are as follows:

  1. Write and compile the unmanaged code.
  2. Generate an assembly containing definitions of the COM types using the tlbimp.exe utility so that allow the .NET client to interact with the exposed types.
  3. Install the assembly in the global assembly cache. (Optional)
  4. Write and compile the .NET client code that reference the assembly containing the COM type definitions.

1. Write and compile the unmanaged code.

Open an ActiveX DLL project workspace in VB 6.0 and put the below code in code window and name the project as VBServer and Class to Add

Public Function Add (ByVal x As Integer, ByVal y As Integer) As Integer
Add = x + y
End Function


Finally save your workspace and compile the VBServer to VBServer.dll

In the OLE/COM Object Viewer we can see the name of default custom interface(Add) as well as a number of other COM interfaces implemented by VB.


2. Importing the Type Library:

The .NET Framework needs metadata for individual COM types at both compile time and run time. Your choice for generating metadata is the Type Library Importer (TlbImp.exe) utility generates an assembly containing metadata. When you carry out this tool on a type library it generates a standard runtime callable wrapper, based on the contents of the type library.

tlbimp VBServer.dll /out:NetClient.dll

Now we create the NetClient.dll using the Type Library Importer utility.

If you open the NetClient.dll with the ILDasm.exe.
In that You notice the _Add interface and Add coclass are mapped as .NET equivalents.


3. Install the assembly in the global assembly cache. (Optional)


To make assembly to be shared among several applications, it must be installed in the global assembly cache (GAC). Use gacutil.exe to install an assembly in the GAC.

gacutil /i NetClient.dll


4. Write and compile the .NET client code with reference to the assembly

During compilation we have to reference the assembly using the compiler /r switch or we can add reference to the project directly from Visual Studio.NET development tool. Even by using Reflection one can use the COM server (Late Binding). I planned to cover this topic in another article.

csc TestClient.cs /r: NetClient.dll

The .NET client: (Early Binding)

namespace Addvbserver
{
     using System;
     using NetClient;

     public class TestClient
     {
        public static int Main(string[] args)
        {
            Add c = new Add();
            Console.WriteLine(c.Add(2,2));
            return 0;
        }
     }
}


In the above code You notice that all members of a Add interface are accessed directly from an object instance. If you want to explicitly reference the underlying _Add interface you have to write the code as below.

namespace Addvbserver
{
    using System;
    using NetClient;

    public class Client
    {
         public static int Main(string[] args)
         {
             Add c = new Add();
             _Add gg =c;
             Console.WriteLine(gg.Add(2,2));
             return 0;
         }
    }
}


Conclusion:

I hope after reading the three parts regarding Interoperability issues one can gain some good knowledge over communication between managed and unmanaged world and vice versa.


About the Author:

G. Gnana Arun Ganesh is the Administrator and the Founder of ARUN MICRO SYSTEMS (www.arunmicrosystems.netfirms.com). He has been programming in C++, Visual Basic, COM, Java and Microsoft Technologies for 4 years. He has written over 50 articles on .NET published in the top C# websites such as CsharpCorner, CsharpHelp, MSDNAA, Code Project, Developersdex, 411asp.net, ProgrammersHeaven, VB-World, and Devguru etc. He has performed numerous technical reviews for Prentice Hall, Prentice Hall PTR and Sams. He has also created real time projects in Web Services. Currently reviewing a book on .NET Security and put forward a couple of proposals on .NET and in C#. You can contact him for technological support and queries through ggarung@rediffmail.com

 

©2009 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy Statement
Microsoft