MSG file cannot be opened twice (It's possible the file is already open)

Add-in Express™ Support Service
That's what is more important than anything else

MSG file cannot be opened twice (It's possible the file is already open)
 
Subscribe
Alex Carter




Posts: 67
Joined: 2019-02-21
Good Morning,
I've come across an issue with my Outlook addin. If I have Outlook open and Open an MSG file from disk it opens correctly the first time, however if I close it down and re-open, I receive the error: "We can't open [FilePath].msg. It's possible the file is already open....."

I have tracked this down to my OutlookItemEventsClass in the ProcessOpen method. If I comment out the TempestaEmail(E) line then the MSG opens correctly the second time.

In my addin module I handle the InspectorActivate and the InspectorClose events to hook up the Item events class & disconnect it, it has the second param set to true to ensure COM objects are released as well.

When reading other posts, I believe this is because I've not released a COM Object correctly somewhere. I've gone over the TempestaEmail method and made sure I'm releasing everything correctly (to the best of my knowledge) and also converted the For Each loop to a For Loop based on the guidance for handling com objects.

Below is my code inside the OutlookItemEventsClass, is there something I have missed here?



Public Overrides Sub ProcessOpen(ByVal E As AddinExpress.MSO.ADXCancelEventArgs)
    TempestaEmail(E)
End Sub

Private Sub TempestaEmail(ByVal E As AddinExpress.MSO.ADXCancelEventArgs)
    Dim MailItem As Outlook.MailItem = Nothing
    Dim OutlookApp As Outlook._Application = Nothing
    Dim MSG As Outlook.MailItem = Nothing

    If TypeOf Me.ItemObj Is Outlook._MailItem Then
        Try
            MailItem = Marshal.GetUniqueObjectForIUnknown(Marshal.GetIUnknownForObject(Me.ItemObj))

            ' Meta tag is set in the email body inside Tempesta generated EML files.
            ' This helps us to identify these emails efficiently. 
            ' Html meta tags do not get carried across to the MSG file.
            If InStr(MailItem.HTMLBody, "<meta name=""tempesta-email"" content=""true"">") = 0 Then
                Exit Sub
            End If

            ' Cancel the original process.
            E.Cancel = True

            OutlookApp = DirectCast(Me.Module, AddinModule).OutlookApp

            ' Create a new MSG MailItem.
            MSG = CType(OutlookApp.CreateItem(Outlook.OlItemType.olMailItem), Outlook.MailItem)

            ' To maintian signature Display() must be called before the contents is applied.
            MSG.Display()

            For i As Integer = 1 To MailItem.Recipients.Count
                Dim Recipient As Outlook.Recipient = MailItem.Recipients(i)
                MSG.Recipients.Add(Recipient.Address).Type = Recipient.Type

                If Recipient IsNot Nothing Then
                    Marshal.ReleaseComObject(Recipient)
                End If
            Next


            MSG.Recipients.ResolveAll()
            MSG.Subject = MailItem.Subject
            MSG.HTMLBody = MailItem.HTMLBody & MSG.HTMLBody

        Finally
            If MailItem.Recipients IsNot Nothing Then
                Marshal.ReleaseComObject(MailItem.Recipients)
            End If

            If MSG IsNot Nothing Then
                If MSG.Recipients IsNot Nothing Then
                    Marshal.ReleaseComObject(MSG.Recipients)
                End If

                Marshal.ReleaseComObject(MSG)
            End If

            If OutlookApp IsNot Nothing Then
                Marshal.ReleaseComObject(OutlookApp)
            End If

            If MailItem IsNot Nothing Then
                Marshal.ReleaseComObject(MailItem)
            End If
        End Try

    End If

End Sub


As always, any help is greatly appreciated!
Thanks,
Alex
Posted 15 May, 2024 09:18:33 Top
Andrei Smolin


Add-in Express team


Posts: 18965
Joined: 2006-05-11
Hello Alex,

A property and some methods of the host's object model can return an object (we won't consider returning a primitive type such as string, number, or Boolean). An object returned in this way has two sides. First off, this is a regular .NET object. The other side of the object is: it has a COM object attached. You can only communicate with the COM object through a specially crafted object called RCW; this is yet another invisible object.

That is, *every time* you call a property/method returning an object from the host's object model or returning of object of the Object type, you create a COM object that .NET associates with the object just returned. Note that casting an object doesn't create a new COM object in .NET; instead, the original variable holding the non-cast object and the new variable holding the cast object will refer to the same COM object (internally, they will use the same RCW).

Here the Recipients COM object is created twice; only the second COM object is released:
Alex Carter writes:
If MSG.Recipients IsNot Nothing Then
Marshal.ReleaseComObject(MSG.Recipients)
End If


To solve this, use the pattern below:

' save MSG.Recipients to a variable
Dim recipients as Outlook.Recipients = MSG.Recipients

' use recipients

'release recipients 
If recipients IsNot Nothing Then
  Marshal.ReleaseComObject(recipients)
End If


Apply this pattern to every COM object you create.


            For i As Integer = 1 To MailItem.Recipients.Count 
                Dim Recipient As Outlook.Recipient = MailItem.Recipients(i) 
                MSG.Recipients.Add(Recipient.Address).Type = Recipient.Type 
 
                If Recipient IsNot Nothing Then 
                    Marshal.ReleaseComObject(Recipient) 
                End If 
            Next 


In the code fragment above, on every iteration of the loop, these COM objects are created:
1. three COM objects of the Outlook.Recipients type
- MailItem.Recipients.Count.
- MailItem.Recipients(i).
- MSG.Recipients.Add.
2. two COM objects of the Outlook.Recipient (not Recipients!) type
- MailItem.Recipients(i); in fact, Recipients(i) is equivalent to Recipients.Item(i), where Item(i) a method of the object model.
- MSG.Recipients.Add; Recipients.Add returns a COM object that you need to release.

Although Me.ItemObj returns a COM object (in fact, this is a .NET object associated with a COM object), the ItemObj property doesn't belong to the host object model; as you know Add-in Express holds this COM object and relies on it. Still, that COM object must be released before you connect the OutlookItemEventsClass to another mail item.

Here's how we use a similar events class; see the sample project at https://www.add-in-express.com/creating-addins-blog/sample-com-addin-projects-outlook-excel-powerpoint-word/:


        ItemsEvents = New OutlookItemsEventsClass1(Me)

        ItemsEvents.ConnectTo(
            folderType:=AddinExpress.MSO.ADXOlDefaultFolders.olFolderInbox,
           eventClassReleasesComObject:=True)


The eventClassReleasesComObject parameter specifies whether the events class will release the COM object when you call {an instance of the events class}.RemoveConnection(). You should call this method to let the events class release the COM object. Or, if the logic requires that you release that variable later, set eventClassReleasesComObject to false. Anyway, the COM object the events class holds in Me.ItemObj should be released as well.

Regards from Poland (GMT+2),

Andrei Smolin
Add-in Express Team Leader
Posted 16 May, 2024 18:04:45 Top
Alex Carter




Posts: 67
Joined: 2019-02-21
Good Morning Andrei,
As always thanks for your insightful and helpful reply!

I've now managed to fix the problem when opening an MSG file using your advice above. However, this has now resulted in another issue. My updated code in the ProcessOpen method is below:


    Public Overrides Sub ProcessOpen(ByVal E As AddinExpress.MSO.ADXCancelEventArgs)
        TempestaEmail(E)
    End Sub

    Private Sub TempestaEmail(ByVal E As AddinExpress.MSO.ADXCancelEventArgs)
        Dim OutlookApp As Outlook._Application = Nothing
        Dim MSG As Outlook.MailItem = Nothing
        Dim mailRecipients As Outlook.Recipients = Nothing
        Dim msgRecipients As Outlook.Recipients = Nothing
        Dim mailItem As Outlook._MailItem = Nothing

        Try
            If TypeOf Me.ItemObj Is Outlook._MailItem Then
                mailItem = Me.ItemObj

                ' meta tag is set in the email body inside tempesta generated eml files.
                ' this helps us to identify these emails efficiently. 
                ' html meta tags do not get carried across to the msg file.
                If InStr(mailItem.HTMLBody, "<meta name=""tempesta-email"" content=""true"">") = 0 Then
                    Exit Sub
                End If

                ' cancel the original process.
                E.Cancel = True

                OutlookApp = DirectCast(Me.Module, AddinModule).OutlookApp

                '' create a new msg mailitem.
                MSG = CType(OutlookApp.CreateItem(Outlook.OlItemType.olMailItem), Outlook.MailItem)
                MSG.Subject = mailItem.Subject
                Dim mailItemBody As String = mailItem.HTMLBody

                mailRecipients = mailItem.Recipients
                msgRecipients = MSG.Recipients

                If mailRecipients.Count > 0 Then
                    For i As Integer = 1 To mailRecipients.Count
                        Dim recipient As Outlook.Recipient = mailRecipients.Item(i)
                        Dim added = msgRecipients.Add(recipient.Address).Type = recipient.Type

                        If recipient IsNot Nothing Then
                            Marshal.ReleaseComObject(recipient)
                            recipient = Nothing
                        End If
                    Next
                End If

                msgRecipients.ResolveAll()

                ''' to maintian signature display() must be called before the contents is applied.
                MSG.Display()

                MSG.HTMLBody = mailItemBody & MSG.HTMLBody
            End If

        Finally
            If mailRecipients IsNot Nothing Then
                Marshal.ReleaseComObject(mailRecipients)
                mailRecipients = Nothing
            End If

            If msgRecipients IsNot Nothing Then
                Marshal.ReleaseComObject(msgRecipients)
                msgRecipients = Nothing
            End If

            If OutlookApp IsNot Nothing Then
                Marshal.ReleaseComObject(OutlookApp)
                OutlookApp = Nothing
            End If
        End Try

    End Sub


As you can see, there's a line that checks for a meta header in the body of the opened email (in this case it's an EML file, we have produced in a separate system). When opening this EML file, the plugin detects this header and should take the content of the EML and produce a new email with that content. The reason for doing this is to add the user's email signature to the bottom of the email. When opening the EML file I now receive several exception pop ups, both of these exceptions are of type InvalidComObjectException and the associated message is: COM object that has been separated from its underlying RCW cannot be used. Stack traces are as follows:


Detailed technical information follows: 
---
Date and Time:         23/May/2024 10:50:32
Machine Name:          3BX6BK3
IP Address:            192.168.1.185
Current User:          HGFcarter

Application Domain:    C:projectseFiles-Office-IntegrationeFileseFilesinDebug
Assembly Codebase:     file:///C:/Windows/Microsoft.NET/Framework/v4.0.30319/mscorlib.dll
Assembly Full Name:    mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Assembly Version:      4.0.0.0
Assembly Build Date:   06/Apr/2024 03:51:18

Exception Source:      mscorlib
Exception Type:        System.Runtime.InteropServices.InvalidComObjectException
Exception Message:     COM object that has been separated from its underlying RCW cannot be used.
Exception Target Site: InvokeDispMethod

---- Stack Trace ----
   System.RuntimeType.InvokeDispMethod(name As String, invokeAttr As BindingFlags, target As Object, args As Object[], byrefModifiers As Boolean[], culture As Int32, namedParameters As String[])
       AddinExpress.OL.2005.dll: N 00000 (0x0) JIT 
   System.RuntimeType.InvokeMember(name As String, bindingFlags As BindingFlags, binder As Binder, target As Object, providedArgs As Object[], modifiers As ParameterModifier[], culture As CultureInfo, namedParams As String[])
       AddinExpress.OL.2005.dll: N 0492 (0x1EC) IL 
   System.Type.InvokeMember(name As String, invokeAttr As BindingFlags, binder As Binder, target As Object, args As Object[])
       AddinExpress.OL.2005.dll: N 0000 (0x0) IL 
  AddinExpress.OL.OutlookUtils.ExplorersCount(OutlookAppObj As Object)
       AddinExpress.OL.2005.dll: N 0040 (0x28) IL 



Detailed technical information follows: 
---
Date and Time:         23/May/2024 10:50:49
Machine Name:          3BX6BK3
IP Address:            192.168.1.185
Current User:          HGFcarter

Application Domain:    C:projectseFiles-Office-IntegrationeFileseFilesinDebug
Assembly Codebase:     file:///C:/Windows/Microsoft.NET/Framework/v4.0.30319/mscorlib.dll
Assembly Full Name:    mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Assembly Version:      4.0.0.0
Assembly Build Date:   06/Apr/2024 03:51:18

Exception Source:      mscorlib
Exception Type:        System.Runtime.InteropServices.InvalidComObjectException
Exception Message:     COM object that has been separated from its underlying RCW cannot be used.
Exception Target Site: InvokeDispMethod

---- Stack Trace ----
   System.RuntimeType.InvokeDispMethod(name As String, invokeAttr As BindingFlags, target As Object, args As Object[], byrefModifiers As Boolean[], culture As Int32, namedParameters As String[])
       AddinExpress.OL.2005.dll: N 00000 (0x0) JIT 
   System.RuntimeType.InvokeMember(name As String, bindingFlags As BindingFlags, binder As Binder, target As Object, providedArgs As Object[], modifiers As ParameterModifier[], culture As CultureInfo, namedParams As String[])
       AddinExpress.OL.2005.dll: N 0492 (0x1EC) IL 
   System.Type.InvokeMember(name As String, invokeAttr As BindingFlags, binder As Binder, target As Object, args As Object[])
       AddinExpress.OL.2005.dll: N 0000 (0x0) IL 
   AddinExpress.OL.OutlookUtils.InspectorsCount(OutlookAppObj As Object)
       AddinExpress.OL.2005.dll: N 0040 (0x28) IL 


The only difference is ExplorersCount vs InspectorsCount in both exceptions. I also recieve the same exceptions when closing the mail message.

I've previously encountered this problem when releasing COM objects too early which I've successfully resolved however am struggling in this instance.

I've also tried to create a new project with just the basics included, AddinModule, OutlookItemEvents and the issue is not repeatable either.

Do you have any advice on how to resolve this issue?
Posted 23 May, 2024 10:02:01 Top
Alex Carter




Posts: 67
Joined: 2019-02-21
Just as a follow up to my previous post. When commenting everything to do with the MSG variable out it still errored. I then commented out line:

OutlookApp = DirectCast(Me.Module, AddinModule).OutlookApp


After this I no longer received an RCW error. Am I going about the wrong way of creating and showing an email? I was following the guidance here: https://www.add-in-express.com/creating-addins-blog/outlook-create-show-message-programmatically/
Posted 23 May, 2024 11:35:13 Top
Andrei Smolin


Add-in Express team


Posts: 18965
Joined: 2006-05-11
Hello Alex,

You can use OutlookApp = DirectCast(Me.Module, AddinModule).OutlookApp. No problem about that. After you do this, you have two variables (OutlookApp and DirectCast(Me.Module, AddinModule).OutlookApp) pointing to the same COM object. That COM object is the *main* COM object: Office supplies it to your add-in at startup and and every COM object you create originates from that COM object. You must *not* release that variable (=the COM object associated with that variable) simply because that COM object isn't created by your code. "COM object that has been separated from its underlying RCW cannot be used" is caused by this COM object getting released.

MSG is still unreleased.
msgRecipients.Add creates and returns a COM object; you should release it.

Regards from Poland (GMT+2),

Andrei Smolin
Add-in Express Team Leader
Posted 23 May, 2024 12:42:03 Top
Alex Carter




Posts: 67
Joined: 2019-02-21
Thanks Andrei,
That worked perfectly!
Posted 23 May, 2024 13:00:39 Top
Andrei Smolin


Add-in Express team


Posts: 18965
Joined: 2006-05-11
Welcome!

Regards from Poland (GMT+2),

Andrei Smolin
Add-in Express Team Leader
Posted 24 May, 2024 08:40:03 Top