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
相关产品推荐
相关产品推荐

