Outlook 加载项 :: 无法使用已与其底层 RCW 分离的 COM 对象

Outlook Add-In :: COM object that has been separated from its underlying RCW cannot be used

虽然我在 SO 上发现了这个问题的许多实例,但我实施的 none 解决方案已经解决了我的问题;希望你能帮我解开这个谜。 注意:这是我第一次涉足 COM 对象的世界,所以我的无知既深又广。

作为开始,我使用 Adrian Brown 的 Outlook Add-In code。我不会完全复制他的 CalendarMonitor class;以下是相关部分:

public class CalendarMonitor
{
    private ItemsEvents_ItemAddEventHandler itemAddEventHandler;
    public event EventHandler<EventArgs<AppointmentItem>> AppointmentAdded = delegate { };

    public CalendarMonitor(Explorer explorer)
    {
        _calendarItems = new List<Items>();
        HookupDefaultCalendarEvents(session);
    }

    private void HookupDefaultCalendarEvents(_NameSpace session)
    {
        var folder = session.GetDefaultFolder(OlDefaultFolders.olFolderCalendar);
        if (folder == null) return;

        try
        {
            HookupCalendarEvents(folder);
        }
        finally
        {
            Marshal.ReleaseComObject(folder);
            folder = null;
        }
    }

    private void HookupCalendarEvents(MAPIFolder calendarFolder)
    {
        var items = calendarFolder.Items;

        _calendarItems.Add(items);

        // Add listeners
        itemAddEventHandler = new ItemsEvents_ItemAddEventHandler(CalendarItems_ItemAdd);
        items.ItemAdd += itemAddEventHandler;
    }

    private void CalendarItems_ItemAdd(object obj)
    {
        var appointment = (obj as AppointmentItem);
        if (appointment == null) return;

        try
        {
            AppointmentAdded(this, new EventArgs<AppointmentItem>(appointment));
        }
        finally
        {
            Marshal.ReleaseComObject(appointment);
            appointment = null;
        }
    }

与添加约会无关的部分已被编辑。

我在加载外接程序时实例化 CalendarMonitor class,并在 AppointmentAdded 事件中完成工作,包括将 UserProperty 添加到 AppointmentItem:

private void ThisAddIn_Startup(object sender, EventArgs e)
{
    _calendarMonitor = new CalendarMonitor(Application.ActiveExplorer());
    _calendarMonitor.AppointmentAdded += monitor_AppointmentAdded;
}

private async void monitor_AppointmentAdded(object sender, EventArgs<AppointmentItem> e)
{
    var item = e.Value;

    Debug.Print("Outlook Appointment Added: {0}", item.GlobalAppointmentID);

    try
    {
        var result = await GCalUtils.AddEventAsync(item);

        //store a reference to the GCal Event for later.
        AddUserProperty(item, Resources.GCalId, result.Id);

        Debug.Print("GCal Appointment Added: {0}", result.Id);
    }
    catch (GoogleApiException ex)
    {
        PrintToDebug(ex);
    }
    finally
    {
        Marshal.ReleaseComObject(item);
        item = null;
    }
}

此处抛出错误,我尝试将 UserProperty 添加到 AppointmentItem。我遵循了我能找到的最好的例子:

private void AddUserProperty(AppointmentItem item, string propertyName, object value)
{
    UserProperties userProperties = null;
    UserProperty userProperty = null;

    try
    {
        userProperties = item.UserProperties;
        userProperty = userProperties.Add(propertyName, OlUserPropertyType.olText);
        userProperty.Value = value;
        item.Save();
    }
    catch (Exception ex)
    {
        Debug.Print("Error setting User Properties:");
        PrintToDebug(ex);
    }
    finally
    {
        if (userProperty != null) Marshal.ReleaseComObject(userProperty);
        if (userProperties != null) Marshal.ReleaseComObject(userProperties);
        userProperty = null;
        userProperties = null;
    }
}

...但是当我尝试将 UserProperty 添加到 AppointmentItem 时它会阻塞。我得到了一个非常流行的错误:COM object that has been separated from its underlying RCW cannot be used. 老实说,我不知道我在做什么;所以我拼命地为我的 Padawan 寻找绝地大师。

这里的主要问题是将 Marshal.ReleaseComObject 用于托管运行时在多个地方使用的 RCW。

其实就是这段代码引起了问题。让我们看看 class CalendarMonitor:

    private void CalendarItems_ItemAdd(object obj)
    {
        var appointment = (obj as AppointmentItem);
        if (appointment == null) return;

        try
        {
            AppointmentAdded(this, new EventArgs<AppointmentItem>(appointment));
        }
        finally
        {
            Marshal.ReleaseComObject(appointment);

在事件 returns 之后,它告诉托管运行时释放 COM 对象(从整个托管运行时的角度来看,但仅此而已)。

            appointment = null;
        }
    }

然后,附加一个 async 事件,实际上 return 在使用 appointment 之前,就在 await 行:

private async void monitor_AppointmentAdded(object sender, EventArgs<AppointmentItem> e)
{
    var item = e.Value;

    Debug.Print("Outlook Appointment Added: {0}", item.GlobalAppointmentID);

    try
    {
        var result = await GCalUtils.AddEventAsync(item);

这个方法其实return在这里。 C# 的异步代码生成在 await 点中断 async 方法,为每个处理 awaited 结果的代码块生成 continuation passing style (CPS) 匿名方法。

        //store a reference to the GCal Event for later.
        AddUserProperty(item, Resources.GCalId, result.Id);

        Debug.Print("GCal Appointment Added: {0}", result.Id);
    }
    catch (GoogleApiException ex)
    {
        PrintToDebug(ex);
    }
    finally
    {
        Marshal.ReleaseComObject(item);

看,又在释放COM对象了。没问题,但根本不是最佳选择。这表示不知道使用 ReleaseComObject 会发生什么,除非证明有必要,否则最好避免使用它。

        item = null;
    }
}

本质上ReleaseComObject的使用应该受到以下几点的彻底审查:

  • 我是否真的需要确保托管环境立即释放对象而不是在不确定的时间释放对象?

    偶尔需要释放一些native对象,造成相关的副作用。

    例如,在分布式事务下确保对象提交,但如果您发现需要这样做,那么您可能正在开发服务组件并且您没有正确地在手动事务中登记对象。

    其他时候,您正在迭代大量对象,无论每个对象有多小,您可能需要释放它们,以免导致您的应用程序或远程应用程序宕机。有时,GC 更频繁,切换到 64 位 and/or 添加 RAM 以一种或另一种方式解决问题。

  • 从托管环境的角度来看,我是对象的唯一所有者 of/pointer 吗?

    例如,是我创建的,还是我创建的另一个对象间接提供的对象?

    托管环境中是否没有对该对象或其容器的进一步引用?

  • 我绝对不是ReleaseComObject之后的代码中使用对象,或者在任何其他时间(例如通过制作确保将其存储在字段或闭包中,即使是以迭代器方法或async方法的形式)?

    这是为了避免可怕的断开连接的 RCW 异常。