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.
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.
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 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:
------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.
什么是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. |
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#
编写Mapping,
The main attributes you need to understand to perform COM mapping are:
注意
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.
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.
After Mapping,使用
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
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.
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 |
Exceptions 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.
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.