Link to home
Start Free TrialLog in
Avatar of DwayneZandbergen
DwayneZandbergen

asked on

COM Err.Raise to .NET Windows Service Error Text Not Preserved

The manufacturer of a moldy old component we have has insisted that it not be called directly from .NET.  So, it's been wrapped in a VB6 COM component and that component is, in turn, called from a WinApp and from a .NET Windows service.  This component does some sanity checks and raises errors.  As an example, the errors are thrown like:

  (VB6 Component Joe, Class Friday)
 
  Sub DoSomething
    ...
    Err.Raise vbObjectError + 1, "MyComponent", "Parameter X is missing"
    ...
  End Sub

These errors are caught just fine in other VB6 apps and .NET WinApps.  Continuing the example, the following code in a .NET WinApp:

  (VB.NET)
 
  Sub CallDoSomething
    Dim TheErrorMessage as String
    ...
    Try
      Dim jf as new Joe.Friday
      jf.DoSomething
    Catch Ex As Exception
      TheErrorMessage = Ex.Message
    End Try
    ...
  End Sub

will correctly put "Parameter X is missing" in TheErrorMessage.  If, on the other hand, that same bit of .NET code is in a Windows Service, then "Invalid advise flags (Exception from HRESULT: 0x80040001 (OLE_E_ADVF))" will be put in TheErrorMessage.  (note that the Err.Number is correct).
If I change the original Component to send Err.Number = vbObjectError + 2 then, once again the WinApp catches the correct message but now the Windows Service will catch "Can't enumerate any more, because the associated data is missing (Exception from HRESULT: 0x80040002 (OLE_E_ENUM_NOMORE))".  (yet again, the correct Err.Number)

We're using framework 2.0.  The COM component is referenced by adding it to the references of the Windows Service.  It is deployed via a setup project in the same solution as the Windows Service.  I have experimented with changing the component post-install and re-registering.  This allows me to try using the component while it's de-registered (giving the expected exception for any caller) so I'm pretty confident that the same component is being referenced in all cases.

Just to eliminate anything bizarre happening from this old component, I made an extremely simple sandbox with a VB6 COM Component that does nothing but Err.Raise, a .NET WinApp and a .NET Windows Service that each simply instantiate and call the Component.  The results match those found with the full application including the old component.

In a bizarre twist to this, I've found that this happens on 2 of 3 PCs tested.  On the 1 of 3 it catches the same correct message in both the WinApp and the Windows Service.  They're all W2K and, other than that, there's a lot of differences.  The service has to be registered under a user account so that we can access resources over the domain.  On each machine the problem was consistent no matter which user the Windows Service was registered under.  (we have a few we can use)

So, does anyone know how I can doctor either the COM component or the Windows Service to get the error message through?
Avatar of DwayneZandbergen
DwayneZandbergen

ASKER

I've finally have had to use a workaround for this problem because nothing I've done seems to help.  Luckily my COM object only has about a dozen public methods and properties.  This would be a brutal solution should someone have a COM object with a lot of public methods or properties.

To each method I added a pass-by-reference string in which I drop the error message should there be an error.  I then built a wrapper class in .NET that repackages the method calls with this new parameter and, if they find an error has happened, then the error is thrown in .NET.  It required altering each call to each COM member but the change was pretty minor because the call signature remained the same.

Here's a cooked up example (the real one's are a bit involved):

(Old COM Source)

    Public Function SquareRoot(ByVal Value As Double)
        If Value < 0.0 Then
            Err.Raise "SquareRoot called with a negative value"
        End If
        Return Sqr(Value)
    End Function

(New COM Source)

    Public Function SquareRoot(ByVal Value As Double, ByVal ErrText as String)
        If Value < 0.0 Then
            ErrText = "SquareRoot called with a negative value"
        End If
        Return Sqr(Value)
    End Function

(New .NET Wrapper Source - COM instance is in the variable MyComInstance)

    Public Function SquareRoot(ByVal Value as Double)
        Dim ErrMessage As String = Nothing
        Dim Result As Double
   
        Result = MyComInstance.SquareRoot(Value, ErrMessage)
        If Not ErrMessage Is Nothing Then
            Throw New Exception(ErrMessage)
        End If
   
        Return Result
    End Function
ASKER CERTIFIED SOLUTION
Avatar of EE_AutoDeleter
EE_AutoDeleter

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial