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

无法使用已与其基础RCW分离的Outlook加载项::COM对象,com,outlook,outlook-addin,Com,Outlook,Outlook Addin,虽然我发现这个问题有很多这样的例子,但我实施的任何解决方案都没有解决我的问题;希望你能帮我解开这个谜。注意:这是我第一次涉足COM对象的世界,所以我的无知既深又广 首先,我用的是阿德里安·布朗的。我不会完全复制他的日历监视器类;以下是相关部分: public class CalendarMonitor { private ItemsEvents_ItemAddEventHandler itemAddEventHandler; public event EventHandler&l

虽然我发现这个问题有很多这样的例子,但我实施的任何解决方案都没有解决我的问题;希望你能帮我解开这个谜。注意:这是我第一次涉足COM对象的世界,所以我的无知既深又广

首先,我用的是阿德里安·布朗的。我不会完全复制他的
日历监视器
类;以下是相关部分:

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;
        }
    }
这里抛出了一个错误,我试图向AppointmentItem添加一个UserProperty。我遵循了我能找到的最好的例子:

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对象已经与其底层RCW分离,无法使用。
老实说,我不知道我在做什么;所以我正在拼命地为我的徒弟寻找一位绝地大师。

这里的主要问题是使用
Marshal.ReleaseComObject
来管理运行时在多个地方使用的RCW

事实上,这个代码引发了这个问题。让我们看看
类日历监视器

    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);

然后,会附加一个
async
事件,该事件实际上会在使用
约会
之前返回,就在
等待
行:

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);
看,它又在释放COM对象了。没问题,但根本不是最优的。这是使用
ReleaseComObject
不知道发生了什么的一个指标,除非证明有必要,否则最好避免使用它

        item = null;
    }
}

本质上,
ReleaseComObject
的使用应经过以下几点的彻底审查:

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

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

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

    其他时候,无论每个对象有多小,您都在迭代一组庞大的对象,您可能需要释放它们,以避免应用程序或远程应用程序宕机。有时,GC’ing更常见,切换到64位和/或添加RAM以某种方式解决问题

  • 从托管环境的角度来看,我是否是该对象的唯一所有者/指针

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

    在托管环境中是否不再引用此对象或其容器

  • 我是否明确地
    ReleaseComObject
    之后、在其后的代码中或在任何其他时间使用对象(例如,确保将其存储在字段或闭包中,甚至以迭代器方法或
    异步
    方法的形式)

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


Marshal.ReleaseComObject()是一种非常难看的手动内存管理。一个C程序员必须写的那种,而且永远都会出错。发明垃圾收集的目的是为了防止一直出错。在这里也一样,您在未创建的对象上调用它。代码无意中会继续使用一个被破坏的对象,kaboom。别再帮忙了。了解垃圾收集器的工作方式并学会信任它。
当您处理定期约会项目时,您应该释放所有以前的引用,在访问或修改该项目之前获取对定期约会项目的新引用,并在完成并保存更改后尽快释放这些引用。此实践适用于Recurrence AppointItem对象以及任何异常或RecurrencePattern对象。
-只是尝试遵循该指导。相信我。。所有这些
Marshal.ReleaseComObject()
都不是我自己选择的。@HansPassant-我读了你非常优秀的帖子,但我还是有点模糊。您能从上面选择一个方法并正确地重新编写它作为答案吗?只需删除所有ReleaseComObject()调用。使用真实数据测试加载项时,请观察Outlook的内存使用情况。如果您真的有问题,那么只需计算操作数并以某个值调用GC.Collect()。这段代码并不重要。我的朋友,你需要重新发布这段代码作为答案,这样我才能接受它。删除所有的
ReleaseComObject()
调用后,它不仅修复了这个问题,还修复了另一个问题。
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;
    }
}