views:

543

answers:

4

I'm trying to return a detailed error to VB6 using CComCoClass::Error, but it seems I can only return an error code /or/ a message - but not both.

return Error(_T("Not connected"), __uuidof(IMyInterface), HRESULT_FROM_WIN32(ERROR_CONNECTION_INVALID));

results in a generic "Method 'Request' of object 'IMyInterface' failed" error message in Err.Description on the VB6 side (but ERROR_CONNECTION_INVALID in Err.Number), while

return Error(_T("Not connected"));

results in the appropriate error message, but a generic error code in Err.Number. How can I get the best of both worlds?

A: 

Derive the class that implements your COM-exposed interface from ISupportErrorInfoImpl, call SetErrorInfo to set the detailed explanation of the error if any occurs. Don't forget to include ISupportErrorInfo into the COM_MAP of your class.

sharptooth
I have done so - although my code is attributed so the support_error_info declaration is in the attribute block above the class declaration instead. As I indicated above, if I _don't_ pass a specific error code, my error string is passed back, so I don't think that's the problem...
bdonlan
If you pass it as an out parameter - it will surely be passed only if you don't return an error code. That's a convention. You need to call SetErrorInfo to provide the error text.
sharptooth
I have tried using SetErrorInfo, but if I return a HRESULT other than DISP_E_EXCEPTION, the description is ignored by VB6...
bdonlan
That's very strange. We call COM interface methods from VB6 with early binding and have no problems with error descriptions. On error we manually call CreateErrorInfo(), ICreateErrorInfo::SetDescription(), then ICreateErrorInfo::QueryInterface(), then SetErrorInfo() - that;s a bit less than AtlSetErrorInfo does - and the VB6 side is happy. Do you use early binding or late binding on VB6 side?
sharptooth
I'm using it as a control on a form, not declaring it as a variable.
bdonlan
It doesn't matter at all. A control is also a COM object. It should handle the problem situations the same way as any COM objects. How do you call the methods of the control - cast it to the interface?
sharptooth
No, simply invoke them directly. Presumably it's early binding, I guess.
bdonlan
A: 

I'm struggling with this right now too. So far my digging indicates that the error code is really the HRESULT value. VB6 tries to be smart and interpret the HRESULT but it seems to have a fairly limited list of HRESULTs it understands. For the HRESULTs VB6 is not familiar with, it just puts the HRESULT into the Err.Number property and hopes that the developer is smart enough to figure out what to do with it.

The closest I've come to returning an error number is by using MAKE_SCODE to generate an HRESULT with the code field of the HRESULT set to what I want, the severity flag set and what I hope is the right facility.

That in conjunction with CreateErrorInfo and SetErrorInfo get me an error code and an error description in VB6. And that brings us back to VB6 trying to be smart with a limited list of errors.

Corin
What facility are you using? FACILITY_ITF doesn't show the proper error description for me
bdonlan
For the particular error we wanted to send back, I'm using FACILITY_STORAGE. But we tried FACILITY_WIN32 and FACILITY_ITF as well and the error code and message still went through.
Corin
A: 

Checkout this article http://support.microsoft.com/kb/827994. So your object must implement method ISupportsErrorInfo::InterfaceSupportsErrorInfo() which returns S_OK. and then before returning you must call SetErrorInfo with a pointer to a COM object which implements IErrorInfo::GetDescription(). There is an example here: http://msdn.microsoft.com/en-us/library/ms221409.aspx.

If you SetErrorInfo before return, VB will query the GetDescription method of the object pointer you passed to SetErrorInfo.

I am not too deep in the attributed code you are using - I would prefer to test it using more raw COM which is surely always a lot of boilerplate code - but at least it works, then you could use sophisticated wrappers instead of it.

justadreamer
+3  A: 

You can't, this appears to be by design. Details further below, but in short you have three options:

  • Return no message and a VB friendly COM error, i.e. one well known by the VB runtime according to this KB article; the VB runtime will translate this 'COM error' to a VB error plus message.
  • Return an error message and DISP_E_EXCEPTION; the VB runtime will pass through this 'Server error' and your custom error message. This is what's implicitly happening on your second example, see below for details.
  • Return no message and any other COM error, i.e. one not known by the VB runtime; the VB runtime will resort to the raw HRESULT plus the generic message "Method '~' of object '~' failed".
    • Please note that this runtime behavior does also apply, if you do supply an error message here, i.e. your message will simply be ignored! This is what's happening on your first example, see below for details.

For the task at hand it boils down to two choices:

  • If you want to supply contextually correct 'COM errors' for automation clients like VB (and likely you should) you must omit custom error messages.
  • If you want to supply custom error messages for 'Server errors' (i.e. a custom error conditions regarding the functionality within your automation server) your only option is DISP_E_EXCEPTION.


Details

The VB runtime seems to offer only very restricted handling in regard to COM errors. This is likely for historic and/or technical reasons specific to the way VB has been implemented and not of particular interest here (keywords would be IDispatch only vs dual interface and ActiveX as a 'subset' of COM).

While I've been unable to surface an explicit specification for the behavior outlined above one can figure it from digging through other sources:

From the KB article justadreamer pointed out already:

[...] a call is made to the GetErrorInfo method to retrieve the available error information. The runtime then determines whether bstrDescription has a value other than NULL. If the runtime finds a value other than NULL, [...], the raw HRESULT value is used in this scenario. If the runtime finds a NULL value, [...] Visual Basic then uses HRESULT to look up the corresponding Visual Basic error.

This explains the behavior regarding your fist example: you did supply an error message, hence the runtime simply resorts to its generic message "Method '~' of object '~' failed" plus your HRESULT.

The behavior of your second example is also consistent once you look at the definition of the (first listed) constructor for CComCoClass::Error: it has defaults for the non specified parameters, especially 'hRes = 0'. The 'Remarks' section further states that "If hRes is zero, then the first four versions of Error return DISP_E_EXCEPTION.". Consequently this implicitly triggers the 'Server error' pass through behavior.

Finally, for a concrete C++ implementation sample of a VB like automation client behavior see for example paragraphs 'Error handling' and the following 'Exercise 5' in Automating Microsoft Office 97 and Microsoft Office 2000.

Steffen Opel