Home > Back-end >  Call rejected by Callee when user is editing a Cell in an Excel workbook
Call rejected by Callee when user is editing a Cell in an Excel workbook

Time:11-26

We have been experiencing this issue for a while now and I have discovered what the problem is. Now all I need to know is: is there any way one can work around it?

Our application references Excel Spreadsheets (.xlsx files) and allows users to open them.

When we do that, we fetch an Excel.Application object (GetOrCreateCOMObject is our proprietary code)

    oXApp = GetOrCreateCOMObject("Excel.Application")

That bit works. However, the next thing we need to do is fetch our Excel Addin:

    oAddin = oXApp.COMAddIns.Item("[OurProprietaryAddinName].AddinModule").Object

And that fails with Call is Rejected by Callee. And the reason this fails is that the user clicked in a Cell in another Excel Spreadsheet, made a change, and stayed there. Didn't tab out, didn't click on another location in the Excel Spreadsheet, just typed something and left it there.

Now I can code a check for this:

    Dim oFocusCheck As Object
    Dim bFocusedInCell As Boolean = False
    Try
        oFocusCheck = oXApp.CommandBars.FindControl(Microsoft.Office.Core.MsoControlType.msoControlButton, 23, System.Reflection.Missing.Value, System.Reflection.Missing.Value)
        Marshal.ReleaseComObject(oFocusCheck)
        oFocusCheck = Nothing
    Catch
        bFocusedInCell = True
    End Try

And I can show a message to the user to tell them they need to exit from whatever cell they are currently editing. But that is one ugly solution, and guaranteed to result in phone calls to our Support team because, face it, when do users ever really read messages presented to them?

So I'm hoping for a solution to stop the Call being rejected by the Callee.

Thank you!

Apologies for the VB.NET code. I know it ain't "cool".

CodePudding user response:

This is a known problem with COM objects. There was an official page about it in MSDN in the past, but it's no longer available. You can get the info here or here.

The VB implementation is:

Imports System.Runtime.InteropServices


''' <summary> Class containing the IOleMessageFilter thread error-handling
''' functions.</summary>
''' <remarks>If you programatically call into Visual Studio automation from an
''' external (out-of-proc), multi-threaded application, you can occasionally get the
''' errors &quot;Application is busy&quot; or &quot;Callee was rejected by
''' caller.&quot; These errors occur due to threading contention issues between
''' external multi-threaded applications and Visual Studio. 
''' <para></para>
''' <para>When your external, multi-threaded application calls into Visual Studio,
''' it goes through a COM interface. COM sometimes has problems dealing properly
''' with threads, especially timing-wise. As a result, occasionally the incoming
''' thread from the external application cannot be handled by Visual Studio at the
''' very moment it arrives, resulting in the aforementioned errors. This does not
''' occur, however, if you are calling from an application that is running inside
''' Visual Studio (in-proc), such as a macro or an add-in.</para>
''' <para></para>
''' <para>To avoid these errors, implement an IOleMessageFilter handler function in
''' your application. When you do this, if your external application thread calls
''' into Visual Studio and is rejected (that is, it returns SERVERCALL_RETRYLATER
''' from the IOleMessageFilter.HandleIncomingCall method), then your application can
''' handle it and either retry or cancel the call. To do this, surround your
''' automation code with the IOleMessageFilter handler.</para>
''' <para></para>
''' <para><b>To fix errors</b></para>
''' <list type="number">
''' <item>
''' <description>Add the following class to your application.</description></item>
''' <item>
''' <description>In the code, create an instance of EnvDTE.</description></item>
''' <item>
''' <description>Call <see
''' cref="MessageFilter.Register">Message.Register</see>
''' to handle thread errors.</description></item>
''' <item>
''' <description>Call your automation code as usual.</description></item>
''' <item>
''' <description>When your automation code is finished, call <see
''' cref="MessageFilter.Revoke">Message.Revoke</see> to
''' remove the thread error handlers.</description></item></list>
''' <para></para>
''' <para>For more details, see
''' http://msdn2.microsoft.com/en-us/library/ms228772(VS.80).aspx.</para></remarks>
Friend Class MessageFilter
    Implements IOleMessageFilter

    '''<summary>Start the filter.</summary>
    Public Shared Sub Register()
        Dim newFilter As IOleMessageFilter = New MessageFilter()
        Dim oldFilter As IOleMessageFilter = Nothing
        CoRegisterMessageFilter(newFilter, oldFilter)
    End Sub

    '''<summary>Done with the filter, close it.</summary>
    Public Shared Sub Revoke()
        Dim oldFilter As IOleMessageFilter = Nothing
        CoRegisterMessageFilter(Nothing, oldFilter)
    End Sub


    ' IOleMessageFilter functions.

    '''<summary>Handle incoming thread requests.</summary>
    Private Function HandleInComingCall(ByVal dwCallType As Integer, ByVal hTaskCaller As System.IntPtr, ByVal dwTickCount As Integer, ByVal lpInterfaceInfo As System.IntPtr) As Integer Implements IOleMessageFilter.HandleInComingCall
        'Return the flag SERVERCALL_ISHANDLED.
        Return 0
    End Function

    '''<summary>Thread call was rejected, so try again.</summary>
    Private Function RetryRejectedCall(ByVal hTaskCallee As System.IntPtr, ByVal dwTickCount As Integer, ByVal dwRejectType As Integer) As Integer Implements IOleMessageFilter.RetryRejectedCall
        If dwRejectType = 2 Then
            ' flag = SERVERCALL_RETRYLATER.
            ' Retry the thread call immediately if return >=0 & 
            ' <100.
            Return 99
        End If
        ' Too busy; cancel call.
        Return -1
    End Function

    Private Function MessagePending(ByVal hTaskCallee As System.IntPtr, ByVal dwTickCount As Integer, ByVal dwPendingType As Integer) As Integer Implements IOleMessageFilter.MessagePending
        'Return the flag PENDINGMSG_WAITDEFPROCESS.
        Return 2
    End Function

    '''<summary>Implement the IOleMessageFilter interface.</summary>
    <DllImport("Ole32.dll")> _
    Private Shared Function CoRegisterMessageFilter(ByVal newFilter As IOleMessageFilter, ByRef oldFilter As IOleMessageFilter) As Integer
    End Function
End Class


<ComImport(), Guid("00000016-0000-0000-C000-000000000046"), InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)> _
Interface IOleMessageFilter
    <PreserveSig()> _
    Function HandleInComingCall(ByVal dwCallType As Integer, ByVal hTaskCaller As IntPtr, ByVal dwTickCount As Integer, ByVal lpInterfaceInfo As IntPtr) As Integer

    <PreserveSig()> _
    Function RetryRejectedCall(ByVal hTaskCallee As IntPtr, ByVal dwTickCount As Integer, ByVal dwRejectType As Integer) As Integer

    <PreserveSig()> _
    Function MessagePending(ByVal hTaskCallee As IntPtr, ByVal dwTickCount As Integer, ByVal dwPendingType As Integer) As Integer
End Interface

I use Visual Studio and EnvDte in the comments, but it applies to any COM object, including Excel.

Just call MessageFilter.Register() before you create the Excel object and MessageFilter.Revoke() after you finish all the work.

  • Related