Outlook加载项::已与基础RCW分离的COM对象无法使用

时间:2015-09-17 07:09:29

标签: com outlook outlook-addin

虽然我在SO上找到了很多关于这个问题的实例,但我实施的解决方案都没有解决我的问题。希望你能帮助我解决这个谜题。 注意:这是我第一次涉足COM对象的世界,所以我的无知就像它的宽度一样深。

首先,我正在使用阿德里安·布朗的Outlook Add-In code。我完全没有复制他的CalendarMonitor课程;以下是相关部分:

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类,并在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寻找绝地大师。

1 个答案:

答案 0 :(得分:1)

这里的主要问题是将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);

事件返回后,它会告知托管运行时释放COM对象(从整个托管运行时的角度来看,但没有更进一步)。

            appointment = null;
        }
    }

然后,附加async事件,该事件将在使用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);

此方法实际上返回此处。 C#的异步代码生成会在async个点处中断await个方法,为处理await ed结果的每个代码块生成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的使用应该对以下几点进行全面审查:

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

    有时,需要释放一些原生对象以引起相关的副作用。

    例如,在分布式事务下确保对象提交,但如果你发现需要这样做,那么也许你正在开发一个服务组件而你没有正确地在手动事务中使用对象。

    其他时候,无论每个对象有多小,您都会迭代大量对象,并且您可能需要释放它们,以免将应用程序或远程应用程序关闭。有时,更频繁地使用GC,切换到64位和/或添加RAM可以通过某种方式解决问题。

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

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

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

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

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