首页 > 解决方案 > Outlook VSTO AddIn 如何避免 RaceOnRCWCleanup

问题描述

我要实现的目标是使用 Inspector 上的 AppProperty Change 事件正确处理日历上的拖放事件:

每当用户与界面交互(Explorer.SelectionChange、NewInspector、CloseInspector 等)时,我都会更新 currentAppointmentItem。每当用户与界面交互(SelectionChange、NewInspector、CloseInspector)时,我都会更新 currentInspector 更新意味着我尝试设置/取消设置相应的事件处理程序和 Marshal.ReleaseComObject。最后将引用归零。

但是,当用户只是单击日历中的 AppointmentItem 时,不会创建 Inspector 窗口。因此我无法捕捉 AppPropertyChange 事件。所以我决定在选定的 AppointmentItem 上调用 GetInspector,以防它不为空。我尝试使用它来接收 AppProperty 事件的更改,以便我可以正确处理日历上的拖放事件

问题:从 Microsoft 文档中,我了解到,每当您丢失对 currentAppointmentItem 的引用时,您还应该使用 Marshal.ReleaseComObject ,否则您会冒其他问题的风险。现在我遇到了我无法捕捉到的异常:RaceOnRCWCleanup ...似乎我试图释放一个仍在使用的 COM 对象(可能由 Outlook 使用)。我怎样才能避免这种情况?Marshal.ReleaseComObject(currentAppointmentItem) 是否正确

我注册了 Outlook.Explorer 上的 SelectionChange 事件。在那里,我尝试使用以下方式注册 currentAppointment:

[...]
            log.Info("Selection_Change");
            if (currentExplorer == null)
            {
                return;
            }

            try
            {
                log.Info("Selection_Change: " + currentExplorer.Caption);
                Outlook.MAPIFolder selectedFolder = currentExplorer.CurrentFolder;
                if (currentExplorer.Selection.Count > 0)
                {
                    Object selObject = currentExplorer.Selection[1];
                    if (selObject is Outlook.AppointmentItem)
                    {
                        currentAppointmentItem = (Outlook.AppointmentItem)selObject;
                        Inspectors_NewInspector(currentAppointmentItem.GetInspector);                     
                    }

[...]

请注意:INspectors_NewInspector 也会在 Inspectors 集合中调用。

NewInspector 的代码就像

    void Inspectors_NewInspector(Microsoft.Office.Interop.Outlook.Inspector Inspector)
            {
                try
                {
                    log.Info("Inspectors_NewInspector");
                    //  This function (apparently) gets kicked off whenever a user opens a new or existing item
                    //  in Outlook (Calendar appointment, Email, etc).  
                    //  We can intercept it, modify it's properties, before letting our Ribbon know about its existance.
                    //            
                    if (Inspector != null)
                    {
                        log.Info("Inspectors_NewInspector: " + Inspector.Caption);
                        unregisterCurrentInspector();
                        currentInspector = Inspector;

                        object item = Inspector.CurrentItem;
                        if (item == null)
                            return;

                        if (!(item is Outlook.AppointmentItem))
                            return;
                        unregisterCurrentAppointmentItem();
                        currentAppointmentItem = (Outlook.AppointmentItem)item;
                        currentAppointmentItem.PropertyChange += AppPropertyChanged; // Handle situations where the 
                                                                                     // user tries to convert an appointment w/ an agreedo protocol to a recurring appointment. 
                                                                                     // This needs to be avoided .                
                        currentAppointmentItem.CustomPropertyChange += AppPropertyChanged;
                    }
                    ((Microsoft.Office.Interop.Outlook.InspectorEvents_10_Event)Inspector).Close += Inspector_Close;

                } catch (Exception ex)
                {
                    log.Error(ex.Message);
                }
            }

unregisterCurrentApppointmentItem :

private void unregisterCurrentAppointmentItem()
    {
        try
        {
            log.Info("unregisterCurrentAppointmentItem");
            if (currentAppointmentItem != null)
            {
                currentAppointmentItem.PropertyChange -= AppPropertyChanged; // Handle situations where the 
                currentAppointmentItem.CustomPropertyChange -= AppPropertyChanged;

                Marshal.ReleaseComObject(currentAppointmentItem);
                currentAppointmentItem = null;
            }
        } catch (Exception ex)
        {
            log.Error(ex.Message);
        }
    }

unregisterCurrentInspector:

    private void unregisterCurrentInspector()
            {
                log.Info("unregisterCurrentInspector");
                if (currentInspector != null)
                {
                    ((Microsoft.Office.Interop.Outlook.InspectorEvents_10_Event)currentInspector).Close -= Inspector_Close;
                    Marshal.ReleaseComObject(currentInspector);
                    currentInspector = null;
                }
            }

对此有何建议?

我已经尝试/考虑的内容:

标签: outlookvstooutlook-addin

解决方案


首先,不需要模拟NewInspector事件。相反,您需要正确设置事件处理程序。看来您只需要实现检查器或资源管理器包装器。有关详细信息,请参阅为检查器实现包装器并在每个检查器中跟踪项目级事件

似乎我试图释放一个仍在使用的 COM 对象(可能由 Outlook 使用)。我怎样才能避免这种情况?Marshal.ReleaseComObject(currentAppointmentItem) 是否正确

是的。但是你真的应该对通过调用属性和方法在你的代码中检索到的对象使用这个方法。您不应该释放由 Office 应用程序作为参数传递的对象。查看何时在 .NET 中开发的 Office 加载项中发布 COM 对象一文,该文章解释了可能存在的缺陷并回答了最广泛传播的问题。


推荐阅读