You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

VSTO Outlook插件处理收件人时偶发InvalidComObjectException异常排查

问题分析与解决

问题背景

开发VSTO Outlook加载项,在邮件撰写窗口添加按钮移除特定收件人,按钮点击事件及辅助代码如下:

按钮点击事件代码

private void ClickButton(object sender, System.Windows.RoutedEventArgs e)
{
    List<Outlook.Recipient> toList = new List<Outlook.Recipient>();
    List<Outlook.Recipient> CCList = new List<Outlook.Recipient>();
    List<Outlook.Recipient> BCCList = new List<Outlook.Recipient>();

    Outlook.Recipient recipient = null;
    Outlook.Recipients recipients = this.MyMailItem?.Recipients;
    for (int i = recipients?.Count ?? 1; i > 0; i -= 1)
    {
        recipient = recipients[i];
        if (!this.recipientsList.Contains(recipient))
        {
            if (recipient.Type == (int)Outlook.OlMailRecipientType.olTo)
            {
                toList.Add(recipient);
            }
            else if (recipient.Type == (int)Outlook.OlMailRecipientType.olCC)
            {
                CCList.Add(recipient);
            }
            else if (recipient.Type == (int)Outlook.OlMailRecipientType.olBCC)
            {
                BCCList.Add(recipient);
            }
        }

        Marshal.ReleaseComObject(recipient);
        recipient = null;
    }

    this.MyMailItem.To = null;
    this.MyMailItem.CC = null;
    this.MyMailItem.BCC = null;

    if (toList != null && toList.Count > 0)
    {
        MyMailItem.To = string.Join(";", this.GetRecipientsAsString(toList));
    }

    if (CCList != null && CCList.Count > 0)
    {
        MyMailItem.CC = string.Join(";", this.GetRecipientsAsString(CCList));
    }

    if (BCCList != null && BCCList.Count > 0)
    {
        MyMailItem.BCC = string.Join(";", this.GetRecipientsAsString(BCCList));
    }

    this.recipientsList.Clear();
}

辅助方法代码

private List<string> GetRecipientsAsString(List<Outlook.Recipient> recipientsList)
{
    List<string> recList = null;
    if (recipientsList?.Count > 0)
    {
        recList = new List<string>();
        foreach (Outlook.Recipient recipient in recipientsList)
        {
            recList.Add(string.IsNullOrWhiteSpace(recipient.Name) ? recipient.Address : recipient.Name);
        }
    }

    return recList;
}

异常信息

非必现抛出异常:

COM object that has been separated from its underlying RCW cannot be used.

异常触发位置:GetRecipientsAsString方法中访问recipient.Name或recipient.Address的代码行。


错误原因

核心问题是COM对象释放时机错误:

  • 在循环中,你将recipient添加到toList/CCList/BCCList后,立刻调用Marshal.ReleaseComObject(recipient)释放了该COM对象的RCW(Runtime Callable Wrapper)。
  • 后续调用GetRecipientsAsString时,尝试访问已被释放的Recipient对象的属性,此时RCW已与底层COM对象分离,触发异常。
  • 非必现是因为.NET垃圾回收器的时机不确定,偶尔可能还未回收RCW,但大部分场景下释放后无法再访问。

解决方案

方案1:延迟释放COM对象

将Marshal.ReleaseComObject的调用时机延后,直到所有使用Recipient对象的操作完成后,再统一释放列表中的所有对象:

修改后的点击事件代码:

private void ClickButton(object sender, System.Windows.RoutedEventArgs e)
{
    List<Outlook.Recipient> toList = new List<Outlook.Recipient>();
    List<Outlook.Recipient> CCList = new List<Outlook.Recipient>();
    List<Outlook.Recipient> BCCList = new List<Outlook.Recipient>();

    Outlook.Recipient recipient = null;
    Outlook.Recipients recipients = this.MyMailItem?.Recipients;
    if (recipients == null) return;

    for (int i = recipients.Count; i > 0; i -= 1)
    {
        recipient = recipients[i];
        if (!this.recipientsList.Contains(recipient))
        {
            switch ((Outlook.OlMailRecipientType)recipient.Type)
            {
                case Outlook.OlMailRecipientType.olTo:
                    toList.Add(recipient);
                    break;
                case Outlook.OlMailRecipientType.olCC:
                    CCList.Add(recipient);
                    break;
                case Outlook.OlMailRecipientType.olBCC:
                    BCCList.Add(recipient);
                    break;
            }
        }
        // 暂时不释放,保留引用供后续使用
    }

    this.MyMailItem.To = null;
    this.MyMailItem.CC = null;
    this.MyMailItem.BCC = null;

    List<string> toStrings = GetRecipientsAsString(toList);
    List<string> ccStrings = GetRecipientsAsString(CCList);
    List<string> bccStrings = GetRecipientsAsString(BCCList);

    if (toStrings?.Count > 0)
    {
        MyMailItem.To = string.Join(";", toStrings);
    }

    if (ccStrings?.Count > 0)
    {
        MyMailItem.CC = string.Join(";", ccStrings);
    }

    if (bccStrings?.Count > 0)
    {
        MyMailItem.BCC = string.Join(";", bccStrings);
    }

    // 统一释放所有Recipient对象
    foreach (var rec in toList.Concat(CCList).Concat(BCCList))
    {
        Marshal.ReleaseComObject(rec);
    }
    toList.Clear();
    CCList.Clear();
    BCCList.Clear();

    this.recipientsList.Clear();
}

方案2:直接存储字符串(更推荐)

避免持有Recipient COM对象引用,在循环中直接提取需要的名称/地址存入字符串列表,彻底消除COM对象生命周期问题:

修改后的代码(可移除GetRecipientsAsString方法):

private void ClickButton(object sender, System.Windows.RoutedEventArgs e)
{
    List<string> toList = new List<string>();
    List<string> CCList = new List<string>();
    List<string> BCCList = new List<string>();

    Outlook.Recipient recipient = null;
    Outlook.Recipients recipients = this.MyMailItem?.Recipients;
    if (recipients == null) return;

    for (int i = recipients.Count; i > 0; i -= 1)
    {
        recipient = recipients[i];
        try
        {
            if (!this.recipientsList.Contains(recipient))
            {
                // 直接提取字符串,无需持有COM对象引用
                string recStr = string.IsNullOrWhiteSpace(recipient.Name) ? recipient.Address : recipient.Name;
                switch ((Outlook.OlMailRecipientType)recipient.Type)
                {
                    case Outlook.OlMailRecipientType.olTo:
                        toList.Add(recStr);
                        break;
                    case Outlook.OlMailRecipientType.olCC:
                        CCList.Add(recStr);
                        break;
                    case Outlook.OlMailRecipientType.olBCC:
                        BCCList.Add(recStr);
                        break;
                }
            }
        }
        finally
        {
            // 立即释放COM对象,避免内存泄漏
            Marshal.ReleaseComObject(recipient);
            recipient = null;
        }
    }

    this.MyMailItem.To = toList.Count > 0 ? string.Join(";", toList) : null;
    this.MyMailItem.CC = CCList.Count > 0 ? string.Join(";", CCList) : null;
    this.MyMailItem.BCC = BCCList.Count > 0 ? string.Join(";", BCCList) : null;

    this.recipientsList.Clear();
}

内容的提问来源于stack exchange,提问作者Willy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 19:24:20