VSTO C# MailItem.Attachments.Add() 崩溃
VSTO C# MailItem.Attachments.Add() Crashes
我有以下代码
public void SendAttachmentsClick()
{
Microsoft.Office.Interop.Outlook.MailItem oMailItem = HostAddIn.Application.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);
oMailItem.BodyFormat = Microsoft.Office.Interop.Outlook.OlBodyFormat.olFormatHTML;
// //returns strings representing paths to documents I want to attach
List<string> paths = GetAttachmentsPaths();
if (paths.Count > 0)
{
foreach (string itemPath in paths)
{
oMailItem.Attachments.Add(itemPath);
}
if (oMailItem.Attachments.Count > 0)
{
oMailItem.Display(false);
}
}
}
呼叫 1: 第一次呼叫 SendAttachmentsClick()
会打开新电子邮件并正确附加所有附件。
CALL 2: 如果我在这封新电子邮件中单击取消然后再次调用 SendAttachmentsClick()
,我可以跟踪执行直到调用 oMailItemAttachments.Add(itemPath)
上面(我在这段代码中有断点)。但是,一旦在第二次调用中为第一个附件调用此行,整个 VSTO/outlook 就会崩溃。我添加了 try...catch 来尝试捕获异常,但从未输入过,所以我不知道错误是什么。
更新 1:
在 https://www.add-in-express.com/creating-addins-blog/2011/08/10/how-to-add-attachment-to-e-mail-message/?thank=you&t=1467071796#comment-413803 阅读了 Eugene Astafiev 的文章后,我修改了上面的代码以释放 com 对象,现在它看起来像这样,但问题仍然存在
public void SendAttachmentsClick()
{
Microsoft.Office.Interop.Outlook.MailItem oMailItem = HostAddIn.Application.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);
oMailItem.BodyFormat = Microsoft.Office.Interop.Outlook.OlBodyFormat.olFormatHTML;
Selection olSelection = HostAddIn.ActiveExplorer.Selection;
// //returns strings representing paths to documents I want to attach
List<string> paths = GetAttachmentsPaths();
if (paths.Count > 0)
{
try
{
Microsoft.Office.Interop.Outlook.Attachments mailAttachments = oMailItem.Attachments;
foreach (string itemPath in paths)
{
Microsoft.Office.Interop.Outlook.Attachment newAttachment = mailAttachments.Add(itemPath);
oMailItem.Save();
if (newAttachment != null) Marshal.ReleaseComObject(newAttachment);
}
if (oMailItem.Attachments.Count > 0)
{
oMailItem.Display(false);
}
if (mailAttachments != null)
Marshal.ReleaseComObject(mailAttachments);
if (oMailItem.Attachments != null)
Marshal.ReleaseComObject(oMailItem.Attachments);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
if (oMailItem != null)
{
Marshal.ReleaseComObject(oMailItem);
oMailItem = null;
}
Marshal.ReleaseComObject(olSelection);
}
}
}
我建议从立即释放代码中的所有底层 COM 对象开始。为此,您需要使用 System.Runtime.InteropServices.Marshal.ReleaseComObject to release an Outlook object when you have finished using it. And then set a variable to Nothing in Visual Basic (null in C#) to release the reference to the object. Read more about that in the Systematically Releasing Objects 文章。
例如,我注意到以下 属性 链和方法调用:
foreach (string itemPath in paths)
{
oMailItem.Attachments.Add(itemPath);
}
Attachments
属性 的 MailItem
class returns 来自 OOM 的对应 class 的一个实例。而且应该会在之后发布。
Attachments
class returns Attachment 表示新附件的对象的 Add
方法。所以,它也应该发布。
按照建议,我会这样做。您不必像上面那样保存您的邮件项目。请注意,我没有为您提供 GetAttachmentsPaths 方法的逻辑,因为我不知道您是如何做到的,但在我看来,您首先是从某个地方下载文件并且您确认了这一点。但是,我确实提供了非常好的说明,说明如何编写此方法和下载文件的 return 路径。希望对您有所帮助。
public async void SendAttachmentByMailClick()
{
// delete temp directory if it exists, then create brand new one each time
var tempFolder = Path.Combine(Path.GetTempPath(), "MyTempFolder");
if (Directory.Exists(tempFolder))
{
Directory.Delete(tempFolder, true);
}
Directory.CreateDirectory(tempFolder);
// get your list asynchronously
List<string> paths = null;
try
{
// I am doing this asynchronously but awaiting until I get files. I
// would use a TaskCompletionSource (google for it) and once you
// receive each file, store it's path in a List<string> object.
// Check that the List<string> Count is equal to passed in
// selection.Count and when it is, pass the list to TrySetResult()
// of TaskCompletionSource. Wrap that in try...catch and in catch
// block pass the exception to TrySetException method of
// TaskCompletionSource. This method should await and return
// Task object of TaskCompletionSource which will then contain the
// list of paths to your downloaded files. Then you move one with
// logic below to copy them to temp location and attach them from
// there.
paths = await GetAttachmentsPaths(MyAddIn.ActiveExplorer.Selection);
}
catch (Exception e)
{
// handle exceptions
return;
}
// create new message
Microsoft.Office.Interop.Outlook.MailItem oMailItem = MyAddIn.Application.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);
if (paths != null && paths.Count > 0)
{
var attachmentFileName = String.Empty;
try
{
// if list has downloaded files, copy them to tempFolder
List<string> copiedPaths = GetCopiedPaths(tempFolder, paths);
if (copiedPaths.Count > 0)
{
// then attach each from that location
foreach (var itemPath in copiedPaths)
{
FileInfo fInfo = new FileInfo(itemPath);
if (fInfo.Exists)
{
attachmentFileName = fInfo.Name;
oMailItem.Attachments.Add(itemPath, OlAttachmentType.olByValue, 1, fInfo.Name);
}
// delete file once attached to clean up
fInfo.Delete();
}
}
if (oMailItem.Attachments.Count > 0)
{
oMailItem.Display(false);
}
}
catch (Exception ex)
{
// handle exceptions
}
finally
{
// delete temporary folder once done
if (Directory.Exists(tempFolder))
{
Directory.Delete(tempFolder, true);
}
}
}
}
我有以下代码
public void SendAttachmentsClick()
{
Microsoft.Office.Interop.Outlook.MailItem oMailItem = HostAddIn.Application.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);
oMailItem.BodyFormat = Microsoft.Office.Interop.Outlook.OlBodyFormat.olFormatHTML;
// //returns strings representing paths to documents I want to attach
List<string> paths = GetAttachmentsPaths();
if (paths.Count > 0)
{
foreach (string itemPath in paths)
{
oMailItem.Attachments.Add(itemPath);
}
if (oMailItem.Attachments.Count > 0)
{
oMailItem.Display(false);
}
}
}
呼叫 1: 第一次呼叫 SendAttachmentsClick()
会打开新电子邮件并正确附加所有附件。
CALL 2: 如果我在这封新电子邮件中单击取消然后再次调用 SendAttachmentsClick()
,我可以跟踪执行直到调用 oMailItemAttachments.Add(itemPath)
上面(我在这段代码中有断点)。但是,一旦在第二次调用中为第一个附件调用此行,整个 VSTO/outlook 就会崩溃。我添加了 try...catch 来尝试捕获异常,但从未输入过,所以我不知道错误是什么。
更新 1: 在 https://www.add-in-express.com/creating-addins-blog/2011/08/10/how-to-add-attachment-to-e-mail-message/?thank=you&t=1467071796#comment-413803 阅读了 Eugene Astafiev 的文章后,我修改了上面的代码以释放 com 对象,现在它看起来像这样,但问题仍然存在
public void SendAttachmentsClick()
{
Microsoft.Office.Interop.Outlook.MailItem oMailItem = HostAddIn.Application.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);
oMailItem.BodyFormat = Microsoft.Office.Interop.Outlook.OlBodyFormat.olFormatHTML;
Selection olSelection = HostAddIn.ActiveExplorer.Selection;
// //returns strings representing paths to documents I want to attach
List<string> paths = GetAttachmentsPaths();
if (paths.Count > 0)
{
try
{
Microsoft.Office.Interop.Outlook.Attachments mailAttachments = oMailItem.Attachments;
foreach (string itemPath in paths)
{
Microsoft.Office.Interop.Outlook.Attachment newAttachment = mailAttachments.Add(itemPath);
oMailItem.Save();
if (newAttachment != null) Marshal.ReleaseComObject(newAttachment);
}
if (oMailItem.Attachments.Count > 0)
{
oMailItem.Display(false);
}
if (mailAttachments != null)
Marshal.ReleaseComObject(mailAttachments);
if (oMailItem.Attachments != null)
Marshal.ReleaseComObject(oMailItem.Attachments);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
if (oMailItem != null)
{
Marshal.ReleaseComObject(oMailItem);
oMailItem = null;
}
Marshal.ReleaseComObject(olSelection);
}
}
}
我建议从立即释放代码中的所有底层 COM 对象开始。为此,您需要使用 System.Runtime.InteropServices.Marshal.ReleaseComObject to release an Outlook object when you have finished using it. And then set a variable to Nothing in Visual Basic (null in C#) to release the reference to the object. Read more about that in the Systematically Releasing Objects 文章。
例如,我注意到以下 属性 链和方法调用:
foreach (string itemPath in paths)
{
oMailItem.Attachments.Add(itemPath);
}
Attachments
属性 的 MailItem
class returns 来自 OOM 的对应 class 的一个实例。而且应该会在之后发布。
Attachments
class returns Attachment 表示新附件的对象的 Add
方法。所以,它也应该发布。
按照建议,我会这样做。您不必像上面那样保存您的邮件项目。请注意,我没有为您提供 GetAttachmentsPaths 方法的逻辑,因为我不知道您是如何做到的,但在我看来,您首先是从某个地方下载文件并且您确认了这一点。但是,我确实提供了非常好的说明,说明如何编写此方法和下载文件的 return 路径。希望对您有所帮助。
public async void SendAttachmentByMailClick()
{
// delete temp directory if it exists, then create brand new one each time
var tempFolder = Path.Combine(Path.GetTempPath(), "MyTempFolder");
if (Directory.Exists(tempFolder))
{
Directory.Delete(tempFolder, true);
}
Directory.CreateDirectory(tempFolder);
// get your list asynchronously
List<string> paths = null;
try
{
// I am doing this asynchronously but awaiting until I get files. I
// would use a TaskCompletionSource (google for it) and once you
// receive each file, store it's path in a List<string> object.
// Check that the List<string> Count is equal to passed in
// selection.Count and when it is, pass the list to TrySetResult()
// of TaskCompletionSource. Wrap that in try...catch and in catch
// block pass the exception to TrySetException method of
// TaskCompletionSource. This method should await and return
// Task object of TaskCompletionSource which will then contain the
// list of paths to your downloaded files. Then you move one with
// logic below to copy them to temp location and attach them from
// there.
paths = await GetAttachmentsPaths(MyAddIn.ActiveExplorer.Selection);
}
catch (Exception e)
{
// handle exceptions
return;
}
// create new message
Microsoft.Office.Interop.Outlook.MailItem oMailItem = MyAddIn.Application.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);
if (paths != null && paths.Count > 0)
{
var attachmentFileName = String.Empty;
try
{
// if list has downloaded files, copy them to tempFolder
List<string> copiedPaths = GetCopiedPaths(tempFolder, paths);
if (copiedPaths.Count > 0)
{
// then attach each from that location
foreach (var itemPath in copiedPaths)
{
FileInfo fInfo = new FileInfo(itemPath);
if (fInfo.Exists)
{
attachmentFileName = fInfo.Name;
oMailItem.Attachments.Add(itemPath, OlAttachmentType.olByValue, 1, fInfo.Name);
}
// delete file once attached to clean up
fInfo.Delete();
}
}
if (oMailItem.Attachments.Count > 0)
{
oMailItem.Display(false);
}
}
catch (Exception ex)
{
// handle exceptions
}
finally
{
// delete temporary folder once done
if (Directory.Exists(tempFolder))
{
Directory.Delete(tempFolder, true);
}
}
}
}