stand on the shoulders of giants

COM .NET Interoperability

Ref:
1. http://www.codeproject.com/KB/COM/COM_DOTNET_INTEROP.aspx
2. http://www.csharphelp.com/2007/01/com-interoperability-in-net-framework-part-i/
3. http://msdn.microsoft.com/en-us/library/aa645736(VS.71).aspx#vcwlkcominteroppart1cclienttutorialanchor1


REGSVR32 : This is used for registering a COM based DLL. More information here.
REGASM: This is used to register a .NET Assembly for COM Interop. More info here.

The .NET Framework handles reference-counting issues with COM Interop so there is no need to call or implement AddRef and Release.

Communication between Object and Client

 COM is a binary reusable object which exposes its functionality to other components.

 

In the above figure the IUnknown and IDispatch are the interfaces and QueryInterface, AddRef, Release, etc., are the methods exposed by those interfaces.

The communication between the .NET objects occurs through Objects, there are no such interfaces for communication. Assembly is a collection of types and resources that are built to work together and form a logical unit of functionality.  

 

Calling COM components from .NET Client

The .NET SDK provides Runtime Callable Wrapper (RCW) which wraps the COM components and exposes it into to the .NET client application.

 

创建Wrapper有两种方式:

核心是我们需要.NET Definition for COM interface

  • 方式一:tlbimp

    RCW can be generated by using VS.NET(IDE, 就是添加Reference->COM,实际上VS会帮你做tlbimp) or by the use of TlbImp.exe utility.

tlbimp InteropExample.dll /output:InteropExampleRCW.dll /verbose
csc /r InteropExampleRCW.dll demo.cs

It read the type library and uses System.Runtime.InteropServices.TypeLibConverter class to generate the RCW. This class reads the type library and converts those descriptions into a wrapper (RCW). After generating the RCW, the .NET client should import its namespace. Now the client application can call the RCW object as native calls.

 

------From MSDN-------Begin---
For C# code to reference COM objects and interfaces, you need to include a .NET Framework definition for the COM interfaces in your C# build. The easiest way to do this is to use TlbImp.exe (Type Library Importer), a command-line tool included in the .NET Framework SDK. TlbImp converts a COM type library into .NET Framework metadata — effectively creating a managed wrapper that can be called from any managed language. .NET Framework metadata created with TlbImp can be included in a C# build via the /R compiler option. If you are using the Visual Studio development environment, you only need to add a reference to the COM type library and the conversion is done for you automatically.

tlbimp performs the following conversions:

  • COM coclasses are converted to C# classes with a parameterless constructor.
  • COM structs are converted to C# structs with public fields.

------From MSDN--------End---

When a client calls a function, the call is transferred to the RCW. The RCW internally calls the native COM function coCreateInstance there by creating the COM object that it wraps. The RCW converts each call to the COM calling convention. Once the object has been created successfully, the .NET client application can access the COM objects as like native object calls.

 

tlbimp不是任何情况都适用的,解决方案是手动编写Mapping
Although TlbImp is the preferred method for converting COM definitions to C#, it is not always possible to use it (for example, if there is no typelib for the COM definitions, or if TlbImp cannot handle the definitions in the typelib). In these cases, the alternative is to manually define the COM definitions in C# source code using C# attributes. Once you have created the C# source mapping, you simply compile the C# source code to produce the managed wrapper. 

 

  • 方式二:Mapping

什么是TLB?

Type Library include information about types and objects exposed by an ActiveX application是表述性文件。
如果是VB,编译器会自动生成tlb并把tlb嵌入DLL中。如果是VC++,基于IDL(接口定义文件),编译器生成各自的tlb文件。
BTW:
  如果是.NET 为COM guy写的assembly,用regasm *.dll /tlb: *.tlb 生成tlb文件,COM guy import就可以使用了
     #import “CSharpLib.tlb" no_namespace named_guids

TLB contains only the declarations.
DLL contains definitions or implementations.

TLB is enough to create wrappers.
But when the wrappers are actually called, it will call into the DLL.
Type libraries can be shipped in any of the following forms:

  • A stand-alone binary file. The .tlb (type library) file output by the MIDL utility is a binary file.
    1. Creating the basic IDL file with VB and OLE View, 打开OLE View-->View TypeLib"
    2. Edit the IDL file
    3. Save
    4. midl "%IDLFile%" /tlb "%TLBFile%"
  • A resource in a dynamic link library (DLL). This resource should have the type TypeLib and an integer identifier. It must be declared in the resource (.rc) file as follows:
    1 typelib mylib1.tlb
    2 typelib mylib2.tlb
  • There can be multiple type library resources in a DLL. Application developers should use the resource compiler to add the .tlb file to their own DLL. A DLL with one or more type library resources typically has the file extension .olb (object library).

  • A resource in an .exe file. The .exe file can contain multiple type library resources.

以Com控件Media Player COM object (%windir%\System32\quartz.dll)为例
首先用OLE view看一下IDL文件
 

// Generated .IDL file (by the OLE/COM Object Viewer)
//
// typelib filename: quartz.dll

[

uuid(56A868B0-0AD4-11CE-B03A-0020AF0BA770),

version(1.0),

helpstring("ActiveMovie control type library")

]

library QuartzTypeLib

{

...
}

a COM interface declared in MIDL and the same interface declared in C#

original MIDL of Interface ImediaControl

  odl, 
  uuid(56A868B1-0AD4-11CE-B03A-0020AF0BA770), 
  helpstring(
"IMediaControl interface"), 
  dual, 
  oleautomation 

interface IMediaControl : IDispatch 

  [id(
0x60020000)] 
  HRESULT Run(); 

  [id(
0x60020001)] 
  HRESULT Pause(); 

  [id(
0x60020002)] 
  HRESULT Stop(); 

  [id(
0x60020003)] 
  HRESULT GetState( [in] long msTimeout, [out] long* pfs); 

  [id(
0x60020004)] 
  HRESULT RenderFile([in] BSTR strFilename); 

  [id(
0x60020005)] 
  HRESULT AddSourceFilter( [in] BSTR strFilename, [out] IDispatch** ppUnk);

  [id(
0x60020006), propget] 
  HRESULT FilterCollection([out, retval] IDispatch** ppUnk); 

  [id(
0x60020007), propget] 
  HRESULT RegFilterCollection([out, retval] IDispatch** ppUnk); 

  [id(
0x60020008)] 
  HRESULT StopWhenReady(); 
};

 

编写Mapping,

The main attributes you need to understand to perform COM mapping are:

  • ComImport - Marks a class as an externally implemented COM class.
  • Guid – Used to specify a universally unique identifier (UUID) for a class or an interface.
  • InterfaceType – specifies whether an interface derives from IUnknown or IDispatch.
  • PreserveSig – specifies whether the native return value should be converted from an HRESULT to a .NET Framework exception.

注意
  1. 要列出所有接口,除了IUnknown and IDispatch — the .NET Framework automatically adds these

  2. Marshalling
When calling a COM interface method from C# code, the common language runtime must marshal the parameters and return values to/from the COM object. 对所有.NET framwork的类型,CLR使用默认类型,marshal when marshaling across a COM call. 例如对 C# string values is to the native type LPTSTR (pointer to TCHAR char buffer). 用MarshalAs attribute可以Override默认的映射.
  3. HRESULT and exception

In COM, a common way to return success or failure is to return an HRESULT and have an out parameter marked as "retval" in MIDL for the real return value of the method. In C# (and the .NET Framework), the standard way to indicate an error has occurred is to throw an exception.

By default, the .NET Framework provides an automatic mapping between the two styles of exception handling for COM interface methods called by the .NET Framework.

  • The return value changes to the signature of the parameter marked retval (void if the method has no parameter marked as retval).

    MIDL 
    [id(0x60020000)]
      HRESULT Run();
    C#
     void Run();

  • The parameter marked as retval is left off of the argument list of the method.
    MIDL 
    [id(0x60020006), propget]
      HRESULT FilterCollection([out, retval] IDispatch** ppUnk);
    C#
    [return : MarshalAs(UnmanagedType.Interface)]
     object FilterCollection();

If the COM method returns an error, an exception will be raised on the C# side.

 

the Mapping wrapper
using System;
using System.Runtime.InteropServices;
 
namespace QuartzTypeLib 
{
      
// Declare IMediaControl as a COM interface which 
      
// derives from IDispatch interface:
      [Guid("56A868B1-0AD4-11CE-B03A-0020AF0BA770"), 
      InterfaceType(ComInterfaceType.InterfaceIsDual)] 
      
interface IMediaControl   // Cannot list any base interfaces here 
      { 
            
// Note that IUnknown Interface members are NOT listed here:
 

            
void Run(); 

            
void Pause();
 
            
void Stop();
 
            
void GetState( [In] int msTimeout, [Out] out int pfs);
 
            
void RenderFile(
                  [In, MarshalAs(UnmanagedType.BStr)] 
string strFilename);
 
            
void AddSourceFilter( 
                  [In, MarshalAs(UnmanagedType.BStr)] 
string strFilename, 
                  [Out, MarshalAs(UnmanagedType.Interface)]
                  
out object ppUnk);
 
            [
return: MarshalAs(UnmanagedType.Interface)] 
            
object FilterCollection();
 
            [
return: MarshalAs(UnmanagedType.Interface)] 
            
object RegFilterCollection();
            
            
void StopWhenReady(); 
      }
      
// Declare FilgraphManager as a COM coclass:
      [ComImport, Guid("E436EBB3-524F-11CE-9F53-0020AF0BA770")] 
      
class FilgraphManager   // Cannot have a base class or
            
// interface list here.
      { 
            
// Cannot have any members here 
            
// NOTE that the C# compiler will add a default constructor
            
// for you (no parameters).
      }
}
 
class MainClass 

      
/********************************************************** 
      Abstract: This method collects the file name of an AVI to 
      show then creates an instance of the Quartz COM object.
      To show the AVI, the program calls RenderFile and Run on 
      IMediaControl. Quartz uses its own thread and window to 
      display the AVI.The main thread blocks on a ReadLine until 
      the user presses ENTER.
            Input Parameters: the location of the AVI file it is 
            going to display
            Returns: void
      ************************************************************
*/ 
 
      
public static void Main(string[] args) 
      { 
            
// Check to see if the user passed in a filename:
            if (args.Length != 1
            { 
                  DisplayUsage();
                  
return;
            } 
 
            
if (args[0== "/?"
            { 
                  DisplayUsage(); 
                  
return;
            }
 
            String filename 
= args[0]; 
 
            
// Check to see if the file exists
            if (!System.IO.File.Exists(filename))
            {
                  Console.WriteLine(
"File " + filename + " not found.");
                  DisplayUsage();
                  
return;
            }
 
            
// Create instance of Quartz 
            
// (Calls CoCreateInstance(E436EBB3-524F-11CE-9F53-0020AF0BA770, 
            
//  NULL, CLSCTX_ALL, IID_IUnknown, 
            
//  &graphManager).):
            try
            {
                  QuartzTypeLib.FilgraphManager graphManager 
=
                        
new QuartzTypeLib.FilgraphManager();
 
                  
// QueryInterface for the IMediaControl interface:
                  QuartzTypeLib.IMediaControl mc = 
                        (QuartzTypeLib.IMediaControl)graphManager;
 
                  
// Call some methods on a COM interface.
                  
// Pass in file to RenderFile method on COM object.
                  mc.RenderFile(filename);
        
                  
// Show file. 
                  mc.Run();
            }
            
catch(Exception ex)
            {
                  Console.WriteLine(
"Unexpected COM exception: " + ex.Message);
            }
            
// Wait for completion. 
            Console.WriteLine("Press Enter to continue."); 
            Console.ReadLine();
      }
 
      
private static void DisplayUsage() 
      { 
            
// User did not provide enough parameters. 
            
// Display usage. 
            Console.WriteLine("VideoPlayer: Plays AVI files."); 
            Console.WriteLine(
"Usage: VIDEOPLAYER.EXE filename"); 
            Console.WriteLine(
"where filename is the full path and");
            Console.WriteLine(
"file name of the AVI to display."); 
      } 
}

 

After Mapping,使用
COM coclasses are represented in C# as classes with a parameterless constructor.
// Declare FilgraphManager as a COM coclass:
      [ComImport, Guid("E436EBB3-524F-11CE-9F53-0020AF0BA770")] 
      
class FilgraphManager   // Cannot have a base class or
            
// interface list here.
      { 
            
// Cannot have any members here 
            
// NOTE that the C# compiler will add a default constructor
            
// for you (no parameters).
      }
Creating an instance of this class using the new operator is the C# equivalent of calling CoCreateInstance.
   //
        // Create an instance of a COM coclass - calls
        //
        // CoCreateInstance(E436EBB3-524F-11CE-9F53-0020AF0BA770,
        //                  NULL, CLSCTX_ALL,
        //                  IID_IUnknown, &f)
        //
        // returns null on failure.
        //
        FilgraphManager f = new FilgraphManager();

 

A C# coclass is 是用来干什么的呢? 只有你access an interface that it implements, 它才有用!
In C++ you would navigate an object's interfaces using the QueryInterface method on the IUnknown interface.
In C# you can do the same thing by explicitly casting the COM object to the desired COM interface. If the cast fails, then an invalid cast exception is thrown:

// Create an instance of a COM coclass:
FilgraphManager graphManager = new FilgraphManager();

// See if it supports the IMediaControl COM interface. 
// Note that this will throw a System.InvalidCastException if 
// the cast fails. This is equivalent to QueryInterface for 
// COM objects:
IMediaControl mc = (IMediaControl) graphManager;

// Now you call a method on a COM interface: 
mc.Run();
另外的方式:
一套折中的方案是,自己写一个封装CoCreateInstance的类,.NET端引用这个类,并传入clsid和iid,就可以实现Interopability

 

Calling .NET components from COM Client

When a COM client requests a server, first it searches in the registry entry and then the communication starts. The .NET objects communicate through Objects. But the Object based communication may not be recognized by the COM clients. So, to communicate with the .NET component from the COM component, the .NET component should be wrapped in such a way that the COM client can identify this .NET component.

This wrapper is known as COM Callable Wrapper (CCW). The COM Callable Wrapper (CCW) will be used to wrap the .NET components and used to interact with the COM clients.

CCW就是RegAsm.exe?

CCW will be created by the .NET utility RegAsm.exe. This reads metadata of the .NET component and generates the CCW. This tool will make a registry entry for the .NET components.

Generally COM client instantiates objects through its native method coCreateInstance. While interacting with .NET objects, the COM client creates .NET objects by coCreateInstance through CCW.

底层发生了什么:Internally, when coCreateInstance is called, the call will redirect to the registry entry and the registry will redirect the call to the registered server, mscoree.dll. This mscoree.dll will inspect the requested CLSID and reads the registry to find the .NET class and the assembly that contains the class and rolls a CCW on that .NET class.

When a client makes a call to the .NET object, first the call will go to CCW. The CCW converts all the native COM types to their .NET equivalents and also converts the results back from the .NET to COM.

 

Programming model comparison of .NET-COM interoperability

The following table compares the .NET and COM based component programming models.


.NET

COM

Object based communication

Interface based communication

Garbage Collector to manage memory

Reference count will be used to manage memory,The component's IUnknown interface helps to maintain a reference count of the number of clients using the component. When this count drops down to zero, the component is unloaded.

Type Standard objects

Binary Standard objects

Objects are created by normal new operator

Objects are created by coCreateInstance

Exceptions will be returned

HRESULT will be returned

Object info resides in assembly files

Object info resides in Type library

Before the application starts to communicate, there are some technical constraints associated with this. When an object is transmitted to a receiver which is in a separate machine/process (managed/unmanaged) space, the object may need to undergo a transformation according to the native type to make it suitable for use by the recipient. That is the object will be converted into a recipient readable form. This process of converting an object between types when sending it across contexts is known as marshaling. The next section of the paper will gives an overview of marshalling in .NET.

 

.NET Marshalling

Thus .NET runtime automatically generates code to translate calls between managed code and unmanaged code. While transferring calls between these two codes, .NET handles the data type conversion also. This technique of automatically binding with the server data type to the client data type is known as marshalling. Marshaling occurs between managed heap and unmanaged heap. For example, Fig.4 shows a call from the .NET client to a COM component. This sample call passes a .NET string from the client. The RCW converts this .NET data type into the COM compatible data type. In this case COM compatible data type is BSTR. Thus the RCW converts the .NET string into COM compatible BSTR. This BSTR will be passed to the object and the required calls will be made. The results will be returned to back to the RCW. The RCW converts this COM compatible result to .NET native data type.


posted @ 2010-11-24 16:21  DylanWind  阅读(1822)  评论(0编辑  收藏  举报