65.9K
CodeProject 正在变化。 阅读更多。
Home

接收来自晚期绑定 COM 服务器的事件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.98/5 (27投票s)

2005年5月2日

CPOL

6分钟阅读

viewsIcon

162089

downloadIcon

1106

一篇演示如何使用 Microsoft Word 作为示例 COM 服务器从后期绑定 COM 服务器接收事件的文章。

Microsoft Office Word 2003

引言

(最新变更请参阅下方 历史记录 部分)

"后期绑定" 这个术语指的是一种技术,它通过 IDispatch 接口在运行时调用 COM 方法和属性并发现可用方法,而不是在编译时链接到 COM 方法和属性,后者称为“早期绑定”。

通常,我会在 Office 自动化中结合使用后期绑定,当我不希望针对特定的 Microsoft Office 版本(例如 Microsoft Office 2003 或 Microsoft Office XP)但希望编写能与不同版本的 Microsoft Office 兼容的代码时。在这种情况下,您不能使用“早期绑定”(即在编译时链接到 COM 方法和属性),因为这总是会针对单个 Microsoft Office 版本,并且在不同 Microsoft Office 版本之间的接口发生变化时可能无法正常工作。

本文重点介绍从 C# .NET 应用程序进行后期绑定。

虽然调用方法和属性的文档比较完善,但我花了整整两天时间搜索如何从 COM 服务器获取某些事件的通知。因此,我写了这篇文章来记录我开发的解决方案,并希望帮助其他开发人员更快地解决类似问题。

使用后期绑定调用方法

从 .NET 应用程序调用 COM 服务器的方法和属性的文档已经相当完善。以下代码片段摘自本文附带的示例应用程序。

// The type of the Word application. Used for dynamically
// creating.
Type wordType = Type.GetTypeFromProgID( "Word.Application" );
 
// The main Word application object.
object wordApplication = Activator.CreateInstance( wordType );

您首先需要获取 COM 服务器的类型,然后创建该类型的实例。这类似于 VBScript 中的 CreateObject 函数。在此示例中,我创建了 Microsoft Office Word 的实例。

要调用方法或属性,您必须了解刚刚创建的 COM 服务器的对象模型。对于 Microsoft Office,MSDN 上有在线 参考手册。例如,您可以使主窗口可见。

// Show Word.
wordType.InvokeMember(
    "Visible",
    BindingFlags.SetProperty,
    null,
    wordApplication,
    new object[]
    {
        true
    } );

接收事件 - 背景知识

在 COM 世界中,事件通过 IConnectionPointContainerIConnectionPoint 接口进行传输。有关更多详细信息,请参阅 MSDN 上 有关 COM 事件的基本解释

对我来说,难点在于将其以后期绑定的方式转移到 .NET 世界。在 Google 上搜索了几个小时后,我找到了一位 指明了方向 的人,尽管他并没有成功。

然后我想到一个过去曾多次帮助过我的想法:看看 .NET 本身是如何做的,然后使用 Lutz Roeder 的 .NET Reflector 并检查反编译代码。这次也帮了我。以下部分将解释我采取的步骤。

从非托管 COM DLL 到 .NET 反编译的步骤

首先,您必须通过 Visual Studio .NET 2003 添加对 Microsoft Office Word COM 对象的引用

这会生成一个 COM 互操作包装器,并将其添加到 Visual Studio .NET 2003 中解决方案的引用列表中

然后打开该引用的属性(按 F4),它会告诉您生成的包装器库的路径

在此示例中,路径将是“C:\Inetpub\wwwroot\Bodycote\BodySales\ WebWordCommunicationTest\obj\Interop.Word.dll”。

现在,在 .NET Reflector 应用程序中打开此文件并检查其属性

从那里开始,您可以检查接口、类等。

用于接收的示例接口:IApplicationEvents2

在本节中,我将向您展示如何后期绑定给定的事件源(Microsoft Office)。我选择的接口是 (I)ApplicationEvents2 接口。我不明白为什么 .NET Reflector 会显示两个接口,一个是 IApplicationEvents2,另一个是 ApplicationEvents2。我只使用了后者(尽管在附加的示例中,我将我的接口命名为带有前导“I”)。所选接口是任意接口。您可以使用任何您喜欢的事件源接口。

.NET Reflector 中该接口的定义如下所示

粗体方法是从 Microsoft Word 在 COM 客户端中发生事件时调用的方法。因此,您必须在 COM 客户端应用程序中实现此接口,并将其连接到 Microsoft Word,以便 Microsoft Word 可以调用您的方法。

定义和实现接口

由于我决定进行后期绑定,因此无法引用生成的 COM 互操作包装器,您必须将此接口的定义复制到您的 COM 客户端应用程序中。我是这样做的:

[ComImport, Guid("000209FE-0000-0000-C000-000000000046"), 
    TypeLibType((short) 0x10d0)]
public interface ApplicationEvents2
{
    [MethodImpl(MethodImplOptions.InternalCall,
        MethodCodeType=MethodCodeType.Runtime), 
        DispId(1), TypeLibFunc((short) 0x41)]
    void Startup();

    [MethodImpl(MethodImplOptions.InternalCall, 
        MethodCodeType=MethodCodeType.Runtime), 
        DispId(2)]
    void Quit();

    [MethodImpl(MethodImplOptions.InternalCall, 
        MethodCodeType=MethodCodeType.Runtime),  
        DispId(3)]
    void DocumentChange();

    [MethodImpl(MethodImplOptions.InternalCall, 
        MethodCodeType=MethodCodeType.Runtime),  
        DispId(4)]
    void DocumentOpen( 
        [In, MarshalAs(UnmanagedType.Interface)] object Doc);

    [MethodImpl(MethodImplOptions.InternalCall, 
        MethodCodeType=MethodCodeType.Runtime),  
        DispId(6)]
    void DocumentBeforeClose( 
        [In, MarshalAs(UnmanagedType.Interface)] object Doc,  
        [In] ref bool Cancel);

    [MethodImpl(MethodImplOptions.InternalCall, 
        MethodCodeType=MethodCodeType.Runtime),  
        DispId(7)]
    void DocumentBeforePrint( 
        [In, MarshalAs(UnmanagedType.Interface)] object Doc,  
        [In] ref bool Cancel);

    [MethodImpl(MethodImplOptions.InternalCall, 
        MethodCodeType=MethodCodeType.Runtime),  
        DispId(8)]
    void DocumentBeforeSave( 
        [In, MarshalAs(UnmanagedType.Interface)] object Doc,  
        [In] ref bool SaveAsUI,  
        [In] ref bool Cancel);

    [MethodImpl(MethodImplOptions.InternalCall, 
        MethodCodeType=MethodCodeType.Runtime),  
        DispId(9)]
    void NewDocument( 
        [In, MarshalAs(UnmanagedType.Interface)] object Doc);

    [MethodImpl(MethodImplOptions.InternalCall, 
        MethodCodeType=MethodCodeType.Runtime),  
        DispId(10)]
    void WindowActivate( 
        [In, MarshalAs(UnmanagedType.Interface)] object Doc,  
        [In, MarshalAs(UnmanagedType.Interface)] object Wn);

    [MethodImpl(MethodImplOptions.InternalCall, 
        MethodCodeType=MethodCodeType.Runtime),  
        DispId(11)]
    void WindowDeactivate( 
        [In, MarshalAs(UnmanagedType.Interface)] object Doc,  
        [In, MarshalAs(UnmanagedType.Interface)] object Wn);

    [MethodImpl(MethodImplOptions.InternalCall, 
        MethodCodeType=MethodCodeType.Runtime),  
        DispId(12)]
    void WindowSelectionChange( 
        [In, MarshalAs(UnmanagedType.Interface)] object Sel);

    [MethodImpl(MethodImplOptions.InternalCall, 
        MethodCodeType=MethodCodeType.Runtime),  
        DispId(13)]
    void WindowBeforeRightClick( 
        [In, MarshalAs(UnmanagedType.Interface)]  
        object Sel,  
        [In] ref bool Cancel);

    [MethodImpl(MethodImplOptions.InternalCall, 
        MethodCodeType=MethodCodeType.Runtime),  
        DispId(14)]
    void WindowBeforeDoubleClick( 
        [In, MarshalAs(UnmanagedType.Interface)] object Sel,  
        [In] ref bool Cancel);
}

如您所见,它与 .NET Reflector 中的原始反编译代码略有不同。具体来说,我将函数中强类型参数(例如,传递的 Document 对象,在 COM 互操作包装器中其他地方定义)更改为无类型的 object 对象。我之所以这样做,是因为我只关心被调用的方法,而不关心类型。如果您想再次获取对象的类型,您需要像本文开头所述那样后期调用方法和属性。

自动生成的 COM 互操作包装器生成了多个类、委托和事件,以便以标准 .NET 方式方便地接收事件。在我的示例中,我将仅限于上述接口的方法。在我需要此代码的具体项目中,这已经足够了。

还需要第二个类来实际实现接口。稍后,您会将此类连接到 Microsoft Word 实例以接收有关事件的通知。此类如下所示(仅摘录,有关完整代码请参阅附加示例)

[ClassInterface(ClassInterfaceType.None)]
public sealed class ApplicationEvents2_SinkHelper : 
    IApplicationEvents2
{
    public void Startup() {}
    public void Quit() {}
    public void DocumentChange() {}
    public void DocumentOpen( object Doc ) {}
    public void DocumentBeforeClose( object Doc, ref bool Cancel ) {}
    public void DocumentBeforePrint( object Doc, ref bool Cancel ) {}
    public void DocumentBeforeSave( object Doc, 
         ref bool SaveAsUI, ref bool Cancel)  {}
    public void NewDocument( object Doc ) {}
    public void WindowActivate( object Doc, object Wn ) {}
    public void WindowDeactivate( object Doc, object Wn ) {}
    public void WindowSelectionChange( object Sel ) {}
    public void WindowBeforeRightClick( object Sel, ref bool Cancel ) {}
    public void WindowBeforeDoubleClick( object Sel, ref bool Cancel ) {}
}

现在,辅助类已完成。您需要将它们连接到 Microsoft Word。

连接到 Microsoft Word

如本文开头示例所示,您需要创建一个 Microsoft Word 的后期绑定实例

// The type of the Word application. Used for dynamically
// creating.
Type wordType = Type.GetTypeFromProgID( "Word.Application" );
 
// The main Word application object.
object wordApplication = Activator.CreateInstance( wordType );

创建此实例后,您可以直接通过调用以下方式查询连接点容器:

// Get the connection point container.
UCOMIConnectionPointContainer connectionPointContainer =
    wordApplication as UCOMIConnectionPointContainer;

然后,您必须查询返回的连接点容器引用,以获取要实现的接口的实际连接点

// The GUID of the connection point to query for.
// This is the same as the GUID of the IApplicationEvents2.
Guid guid = new Guid( "000209FE-0000-0000-C000-000000000046" );

// Get the connection point of the given GUID.
UCOMIConnectionPoint connectionPoint;
connectionPointContainer.FindConnectionPoint( 
    ref guid, 
    out connectionPoint );

如果找到了连接点,它将被返回。现在您可以创建已实现的事件接收器接口的实例,并通过调用 Advise 方法并传递您的类的引用来告知 Microsoft Word 将事件发送到此接收器

// Create the actual object to receive the event
// notifications.
ApplicationEvents2_SinkHelper sink = 
    new ApplicationEvents2_SinkHelper();

// Connect the sink.
int sinkCookie;
connectionPoint.Advise( sink, out sinkCookie );

现在您完成了!现在您可以调用后期绑定方法和属性,并在事件发生时收到通知。

完成代码后,您必须指示 Microsoft Word 停止向您的对象发送通知。这可以通过调用 Unadvise 方法来实现,如下面的代码所示

// Disconnect the sink.
connectionPoint.Unadvise( sinkCookie );

// Release the com object.
Marshal.ReleaseComObject( connectionPoint );

结论

在本文中,我向您展示了如何在无法使用早期绑定的情况下将事件接收器连接到 COM 服务器。您需要做的主要工作是自己定义事件接收器的接口。一旦您定义了接口并在类中实现了该接口,您只需将接口连接到 COM 服务器,并在完成后断开连接。

希望本文能帮助您在 .NET 世界中处理 COM 互操作。

如有任何问题、意见和建议,请使用本文底部的评论区。

参考文献

除了文章中的链接之外,以下参考资料可能也很有用

  1. 维基百科上的“后期绑定”。
  2. CodeProject 上的“理解 COM 事件处理”。
  3. MSDN 上的参考手册“UCOMIConnectionPoint 接口”。
  4. MSDN 上的参考手册“UCOMIConnectionPointContainer 接口”。

历史

  • 2005-05-02

    创建文章的第一个版本。

© . All rights reserved.