COM Interop 提供对现有 COM 组件的访问,而不需要修改原始组件。若要将 COM 代码合并到托管应用程序,请通过使用 COM Interop 实用工具 (TlbImp.exe) 导入相关的 COM 类型。一经导入,COM 类型就可以使用。
此外,COM Interop 还使 COM 开发人员能够像访问其他 COM 对象一样轻松访问托管对象。同样,COM Interop 提供了一个专用实用工具 (RegAsm.exe),此工具将托管类型导出到类型库中,并将托管组件注册为传统 COM 组件。
在运行时,公共语言运行库根据需要在 COM 对象和托管对象之间封送数据。
该教程显示如何使用 C# 与 COM 对象交互操作。
COM Interop 第二部分:C# 服务器教程讲述如何将 C# 服务器与 C++ COM 客户端一起使用。有关两个教程的概述,请参见 COM Interop 教程。
教程
C# 使用 .NET Framework 功能执行 COM Interop。C# 支持:
- 创建 COM 对象。
- 确定 COM 接口是否由对象实现。
- 调用 COM 接口上的方法。
- 实现可由 COM 客户端调用的对象和接口。
.NET Framework 使用 COM Interop 处理引用计数问题,因此不必调用或实现 AddRef 和 Release。
本教程阐述以下主题:
- 创建 COM 类包装
- 声明 COM coclass
- 创建 COM 对象
- 声明 COM 接口
- 使用转换而不是 QueryInterface
- 综述
创建 COM 类包装
要使 C# 代码引用 COM 对象和接口,需要在 C# 内部版本中包含 COM 接口的 .NET Framework 定义。完成此操作的最简单方法是使用 TlbImp.exe(类型库导入程序),它是一个包括在 .NET Framework SDK 中的命令行工具。TlbImp 将 COM 类型库转换为 .NET Framework 元数据,从而有效地创建一个可以从任何托管语言调用的托管包装。用 TlbImp 创建的 .NET Framework 元数据可以通过 /R 编译器选项包括在 C# 内部版本中。如果使用 Visual Studio 开发环境,则只需添加对 COM 类型库的引用,将为您自动完成此转换。
TlbImp 执行下列转换:
- COM coclass 转换为具有无参数构造函数的 C# 类。
- COM 结构转换为具有公共字段的 C# 结构。
检查 TlbImp 输出的一种很好的方法是运行 .NET Framework SDK 命令行工具 Ildasm.exe(Microsoft 中间语言反汇编程序)来查看转换结果。
虽然 TlbImp 是将 COM 定义转换为 C# 的首选方法,但也不是任何时候都可以使用它(例如,在没有 COM 定义的类型库时或者 TlbImp 无法处理类型库中的定义时,就不能使用该方法)。在这些情况下,另一种方法是使用 C# 属性在 C# 源代码中手动定义 COM 定义。创建 C# 源映射后,只需编译 C# 源代码就可产生托管包装。
执行 COM 映射需要理解的主要属性包括:
- ComImport,它将类标记为在外部实现的 COM 类。
- Guid,它用于为类或接口指定通用唯一标识符 (UUID)。
- InterfaceType,它指定接口是从 IUnknown 还是从 IDispatch 派生。
- PreserveSig,它指定是否应将本机返回值从 HRESULT 转换为 .NET Framework 异常。
这些属性中的每一个都在本教程的某个实际示例的上下文中进行了显示。
声明 COM coclass
COM coclass 在 C# 中表示为类。这些类必须具有与其关联的 ComImport 属性。下列限制适用于这些类:
- 类不能从任何其他类继承。
- 类不能实现任何接口。
- 类还必须具有为其设置全局唯一标识符 (GUID) 的 Guid 属性。
以下示例在 C# 中声明一个 coclass:
// // declare FilgraphManager as a COM coclass // [ComImport, Guid("E436EBB3-524F-11CE-9F53-0020AF0BA770")] class FilgraphManager{ }
C# 编译器将添加一个无参数构造函数,可以调用此构造函数来创建 COM coclass 的实例。
创建 COM 对象
COM coclass 在 C# 中表示为具有无参数构造函数的类。使用 new 运算符创建该类的实例等效于在 C# 中调用 CoCreateInstance。使用以上定义的类,就可以很容易地实例化此类:
class MainClass { public static void Main() { // // 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(); }}
声明 COM 接口
COM 接口在 C# 中表示为具有 ComImport 和 Guid 属性的接口。它不能在其基接口列表中包含任何接口,而且必须按照方法在 COM 接口中出现的顺序声明接口成员函数。
在 C# 中声明的 COM 接口必须包含其基接口的所有成员的声明,IUnknown 和 IDispatch 的成员除外(.NET Framework 将自动添加这些成员)。从 IDispatch 派生的 COM 接口必须用 InterfaceType 属性予以标记。
从 C# 代码调用 COM 接口方法时,公共语言运行库必须封送与 COM 对象之间传递的参数和返回值。对于每个 .NET Framework 类型均有一个默认类型,公共语言运行库将使用此默认类型在 COM 调用间进行封送处理时封送。例如,C# 字符串值的默认封送处理是封送到本机类型 LPTSTR(指向 TCHAR 字符缓冲区的指针)。可以在 COM 接口的 C# 声明中使用 MarshalAs 属性重写默认封送处理。
在 COM 中,返回成功或失败的常用方法是返回一个 HRESULT,并在 MIDL 中有一个标记为“retval”、用于方法的实际返回值的 out 参数。在 C#(和 .NET Framework)中,指示已经发生错误的标准方法是引发异常。
默认情况下,.NET Framework 为由其调用的 COM 接口方法在两种异常处理类型之间提供自动映射。
- 返回值更改为标记为 retval 的参数的签名(如果方法没有标记为 retval 的参数,则为 void)。
- 标记为 retval 的参数从方法的参数列表中剥离。
任何非成功返回值都将导致引发 System.COMException 异常。
此示例显示用 MIDL 声明的 COM 接口以及用 C# 声明的同一接口(注意这些方法使用 COM 错误处理方法)。
下面是该接口的原始 MIDL 版本:
[ 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(); };
下面是该接口的 C# 等效版本:
using System.Runtime.InteropServices;// Declare IMediaControl as a COM interface which // derives from the IDispatch interface. [Guid("56A868B1-0AD4-11CE-B03A-0020AF0BA770"), InterfaceType(ComInterfaceType.InterfaceIsDual)] interface IMediaControl // cannot list any base interfaces here { // Note that the members of IUnknown and Interface 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(); }
请注意,C# 接口是如何映射错误处理情况的。如果 COM 方法返回错误,则 C# 一方将引发异常。
若要防止 HRESULT 翻译为 COMException,请在 C# 声明中将 PreserveSig(true) 属性附加到方法。有关详细信息,请参见 PreserveSigAttribute 类。
使用转换而不是 QueryInterface
只有在可以访问 C# coclass 实现的接口时,C# coclass 才会非常有用。在 C++ 中,可以使用 IUnknown 接口上的 QueryInterface 方法定位对象接口。在 C# 中,通过将 COM 对象显式转换为所需的 COM 接口,可以做到这一点。如果转换失败,则引发无效转换异常:
// 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();
综述
下面是一个使用 C# 创建 AVI 文件查看器的完整示例。此程序创建 COM coclass 的实例,将其转换为 COM 接口,然后调用 COM 接口上的方法。
本节中的示例代表两种方法:
- 示例 1 使用 TlbImp 创建 .NET Framework 类。
- 示例 2 编写手动执行 COM 映射的 C# 代码。
示例 1:使用 TlbImp
本例显示如何使用 TlbImp 创建 AVI 查看器。程序从命令行读取 AVI 文件名,创建 Quartz COM 对象的实例,然后使用 RenderFile 和 Run 方法显示 AVI 文件。
以下是生成该程序的步骤:
- 在 TLB 上运行 TlbImp。本示例使用的媒体播放机包含在应位于 Windows 系统目录中的 Quartz.dll 中。使用以下命令创建 .NET Framework DLL:
tlbimp c:\winnt\system32\quartz.dll /out:QuartzTypeLib.dll
请注意,得到的 DLL 需要命名为
QuartzTypeLib
,以便 .NET Framework 可以在运行时正确加载包含类型。 - 可以使用 Ildasm 工具查看得到的 DLL。例如,若要显示
QuartzTypeLib.dll
文件的内容,请使用以下命令:Ildasm QuartzTypeLib.dll
- 生成程序时使用 C# 编译器选项 /R 以包含
QuartzTypeLib.dll
文件。
然后就可以使用此程序显示影片(用于测试的一个影片示例是驻留在 Windows 目录中的 Clock.avi)。
// interop1.cs// compile with: /R:QuartzTypeLib.dllusing System;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."); } }
运行示例
若要显示影片示例 Clock.avi,请使用以下命令:
interop1 %windir%\clock.avi
当您按 ENTER 键后,屏幕上将显示影片。
示例 2:C# 代码方法
本示例使用与示例 1 相同的 Main 方法,但它只是使用 C# 映射媒体播放机 COM 对象,而不是运行 TlbImp。
// interop2.csusing 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."); } }
运行示例
若要显示影片示例 Clock.avi,请使用以下命令:
interop2 %windir%\clock.avi
当您按 ENTER 键后,屏幕上将显示影片。
文章来源于领测软件测试网 https://www.ltesting.net/