How can I return both an error string and error code to VB6 from an ATL activex control? - com

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?

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.

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.

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.

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.

Related

Does the ABI persist any more error information than an HRESULT?

While porting a regular C++ class to a Windows Runtime class, I hit a fairly significant road block. My C++ class reports certain error conditions by throwing custom error objects. This allows clients to conveniently filter on exceptions, documented in the public interface.
I cannot seem to find a reliable way to pass enough information across the ABI to replicate the same fidelity1 using the Windows Runtime. Under the assumption, that an HRESULT is the only generalized error reporting information, I have evaluated the following options:
The 'obvious' choice: Map the exception condition to any of the predefined HRESULT values. While this technically works (presumably), there is no way at the call site to distinguish between errors originating from the implementation, and errors originating from callees of the implementation.
Invent custom HRESULTs. If this layout still applies to the Windows Runtime, I could easily set the Customer bit and go crazy with my 27 bits worth of error code representation. This works, until someone else does the same. I'm not aware of any way to attribute an HRESULT to an interface, which would solve this ambiguity.
Even if either of the above could be made to work as intended, throwing hresult_errors as prescribed, the call site would still be at the mercy of the language projection. While C# seemingly allows to pass any System.Exception(-derived) error object across the ABI, and have them re-thrown at the call site, C++/WinRT only supports some 14 distinct exception types (see throw_hresult).
With neither of these options allowing for sufficiently complete error information to cross the ABI, it seems that an HRESULT simply may not be enough. Does the Windows Runtime have any provisioning to allow for additional (arbitrary) error information to cross the ABI?
1 I'm not strictly interested in passing actual C++ exceptions across. Instead, I'm looking for a way to allow clients to uniquely identify documented error conditions, in a natural way. Passing custom Windows Runtime error types would be fine.
There are a few options here. Our general API guidance for Windows Runtime APIs that have well-defined, expected failure modes is that failure information should be part of the normal parameters and return value. We would normally create a TryDoSomething API in this situation and provide extended error information via either a return or out parameter. This works best for us due to the fact that there's no consistent way to map exceptions across all languages. This is a topic we hope to revisit more in xlang in the future.
HRESULTs are usable with a caveat. HRESULT values can be a nuisance in anything but C++, where you need to redefine them locally because you can't just use the header. They will generate exceptions in most languages, so if this is common, you'll be creating debugger noise for your components' clients.
The last option allows you to transit a language-specific exception stored in a COM object across the ABI boundary (and up the COM logical stack, including across marshalled calls). In practice it will only be usable by C++ code compiled with the same compiler, settings, and type definitions as the component itself. E.g. passing it from a component compiled with VC to a component compiled with Clang could potentially lead to memory corruption.
Assuming I haven't scared you off, you'll want to look at RoOriginateLanguageException. It allows you to wrap the exception in a COM object and store it with other winrt error data in the TLS. We use this in projections to enable exceptions thrown within a callback to propagate to the outer code using the same projection in a controlled way that unwinds safely through other code potentially written using other languages or tools. This is how the support in C# and other languages is implemented.
Thanks,
Ben

Is there an efficient way to avoid instantiating a class with syntax errors?

As you may know, it is pretty easy to have active code of a class containing syntax errors (someone activated the code ignoring syntax warnings or someone changed the signature of a method the class calls, for instance).
This means that also dynamic instantiation of such a class via
CREATE OBJECT my_object TYPE (class_name).
will fail with an apparently uncatchable SYNTAX_ERROR exception. The goal is to write code that does not terminate when this occurs.
Known solutions:
Wrap the CREATE OBJECT statement inside an RFC function module, call the module with destination NONE, then catch the (classic) exception SYSTEM_FAILURE from the RFC call. If the RFC succeeds, actually create the object (you can't pass the created object out of the RFC because RFC function modules can't pass references, and objects cannot be passed other than by reference as far as I know).
This solution is not only inelegant, but impacts performance rather harshly since an entirely new LUW is spawned by the RFC call. Additionally, you're not actually preventing the SYNTAX_ERROR dump, just letting it dump in a thread you don't care about. It will still, annoyingly, show up in ST22.
Before attempting to instantiate the class, call
cl_abap_typedescr=>describe_by_name( class_name )
and catch the class-based exception CX_SY_RTTI_SYNTAX_ERROR it throws when the code it attempts to describe has syntax errors.
This performs much better than the RFC variant, but still seems to add unnecessary overhead - usually, I don't want the type information that describe_by_name returns, I'm solely calling it to get a catchable exception, and when it succeeds, its result is thrown away.
Is there a way to prevent the SYNTAX_ERROR dump without adding such overhead?
Most efficient way we could come up with:
METHODS has_correct_syntax
IMPORTING
class_name TYPE seoclsname
RETURNING
VALUE(result) TYPE abap_bool.
METHOD has_correct_syntax.
DATA(include_name) = cl_oo_classname_service=>get_cs_name( class_name ).
READ REPORT include_name INTO DATA(source_code).
SYNTAX-CHECK FOR source_code MESSAGE DATA(message) LINE DATA(line) WORD DATA(word).
result = xsdbool( sy-subrc = 0 ).
ENDMETHOD.
Still a lot of overhead for loading the program and syntax-checking it. However, at least none additional for compiling descriptors you are not interested in.
We investigated when we produced a dependency manager that wires classes together upon startup and should exclude syntactically wrong candidates.
CS includes don't always exist, so get_cs_name might come back empty. Seems to depend on the NetWeaver version and the editor the developer used.
If you are certain that the syntax errors are caused by the classes’ own code, you might want to consider buffering the results of the syntax checks and only revalidate when the class changed after it was last checked. This does not work if you expect syntax errors to be caused by something outside those classes.

Etiquette of error codes in COM

In a COM object generally there are two ways of indicating that a function failed (that I'm aware of):
return S_OK and have an [out] parameter to give failure info
return a failure HRESULT, and use ICreateErrorInfo to set the info.
Currently what I am doing is using the failure-HRESULT method for failures that are "really bad", i.e. my object will be basically inoperable because this function failed. For example, unable to open its configuration file.
Is this correct, or should failure HRESULTs be reserved only for things like dispatch argument type mismatches?
The short version:
In COM you should use HRESULTs (and strive to use ISupportErrorInfo, etc.) for most/all types of error conditions. The HRESULT mechanism should be viewed as a form of exception throwing. If you are familiar with that, consider "Error conditions" as anything for which you would normally throw an exception in a language that supports them. Use custom return values for things for which you would not normally use exceptions.
For example, use a failure HRESULT for invalid parameters, invalid sequence of operations, network failures, database errors, unexpected conditions such as out-of-memory, etc. On the other hand, use custom out parameters for things like 'polling, data is not ready yet', EOF conditions, maybe 'checked data and it doesn't pass validations'. There is plenty of discussions out there discussing what each should be (e.g. Stroustrup's TC++PL). The specifics will heavily depend on your particular object's semantics.
The longer version:
At a fundamental level, the COM HRESULT mechanism is just an error code mechanism which has been standardized by the infrastructure. This is mostly because COM must support a number of features such as inter-process (DCOM) and inter-threaded (Apartments) execution, system managed services (COM+), etc. The infrastructure has a need to know when something has failed, and it has a need to communicate to both sides its own infrastructure-related errors. Everybody needs to agree on how to communicate errors.
Each language and programmer has a choice of how to present or handle those errors. In C++, we typically handle the HRESULTs as error codes (although you can translate them into exceptions if you prefer error handling that way). In .NET languages, failure HRESULTs are translated into exceptions because that's the preferred error mechanism in .NET.
VB6 supports "either". Now, I know VB6's so-called exception handling has a painful syntax and limited scoping options for handlers, but you don't have to use it if you don't want to. You can always use ON ERROR RESUME NEXT and do it by hand if you think the usage pattern justifies it in a specific situation. It's just that instead of writing something like this:
statusCode = obj.DoSomething(param1)
If IS_FAILURE(statusCode) Then
'handle error
End If
Your write it like this:
ON ERROR RESUME NEXT
...
obj.DoSomething param1
IF Error.Number <> 0 Then
'handle error
End If
VB6 is simply hiding the error code return value from the method call (and allowing the object's programmer to substitute it for a "virtual return value" via [retval]).
If you make up your own error reporting mechanism instead of using HRESULTs, you will:
Spend a lot of time reinventing a rich error reporting mechanism that will probably mirror what ISupportsErrorInfo already gives you (or most likely, not provide any rich error information).
Hide the error status from COM's infrastructure (which might or might not matter).
Force your VB6 clients to make one specific choice out of the two options they have: they must do explicit line-by-line check, or more likely just ignore the error condition by mistake, even if they would prefer an error handler.
Force your (say) C# clients to handle your errors in ways that runs contrary to the natural style of the language (to have to check every method call explicitly and... likely throw an exception by hand).

What type of IEnumerable should INotifyDataErrorInfo.GetErrors return?

It blows my mind that the official document at MSDN doesn't say anything about what the underlying object type of the enumerable that returned by GetErrors of INotifyDataErrorInfo should be: http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifydataerrorinfo.geterrors(v=vs.95).aspx
Options are: System.String, System.Object, MyCustomObject, ISomeOtherShitThatDoesntHaveAnythingToDoWithValidationWhatsoever
Can anybody explain to me how an arbitrary enumerable of object can be OK for notifying about errors without making any assumptions about its structure?
The docs for INotifyDataErrorInfo give more information:
The validation errors returned by the GetErrors method can be of any type. However, if you implement a custom error type, be sure to override the ToString method to return an error message. Silverlight uses this string in its default error reporting.
Custom error objects are useful when you provide custom error reporting in the user interface. For example, you can create a template for the reporting ToolTip that binds to an ErrorLevel property in order to display warnings in yellow and critical errors in red.
There's a link in the Examples section of GetErrors back to that documentation:
For an example of an implementation of this method, see the INotifyDataErrorInfo class overview.
I agree it's less clear than it might be, but the documentation is there...

Proper HRESULT for "this object is not completely initialized"

I'm writing a COM object that provides access to a service that must be explicitly connected before calls can succeed.
Is there a generic HRESULT code that describes that the callee object is in a state where it is unprepared to handle calls, ideally with the implication that this is the caller's fault?
Currently I'm using E_FAIL, which is too generic for my taste; OLE_E_BLANK might be an option, however this is not an OLE object and I'd rather not return a confusing error code.
I would suggest that E_NOT_VALID_STATE would be closest to what you want to convey.
But, as Hans says, implement IErrorInfo to give chapter and verse.