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.
Related
Two simple COM IDL file questions I can't seem to find answers for, even with searching MSDN and the general internet:
Is there an interface attribute which lets me specify my interface is to be implemented by STA objects only, or is this a detail for my documentation alone? I already have [object, local] which I think is correct for non-remoting (in-process) COM objects.
Do I need void in the parentheses of my method declarations (like in C) to specify no arguments? MSDN is inconsistent about this; so are header files. My own personal implementations of this interface will be in C.
Thanks.
You are talking about the threading model you want to specify for your COM component. No, you cannot put that in the IDL, it is far too important. A client doesn't have to use your IDL, a scripting language like Javascript never will for example. It must go in the registry, in the CLSID key for your component. You want ThreadingModel = "Apartment" to request the client to provide an STA thread. If it is missing then COM assumes that by default.
Do keep in mind that this does not force the client programmer to provide one. If he favors MTA for some reason then COM will provide the STA thread to give your component as safe home. If your proxy makes it too slow to be usable then you do have a documentation requirement.
No HRESULT Method(void) in the IDL is not necessary, using HRESULT Method() is sufficient. Midl.exe doesn't care what language you use.
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...
I am building an automation interface for an existing application. After implementing a DLL server and an EXE server (mainly for getting familiar with the basics of COM) I am now at the point where I generate a type library from an IDL file and can, for example, basically automate my application from VBScript:
Set oApp = CreateObject("MyApp.1")
oApp.ShowAboutBox()
This simple call to a function that takes no parameters works. The next step I want to take is call a function that takes a parameter.
The signature of the function in the IDL file is
HRESULT CreateSomeChildWindow([out, retval] MyChildWindow** ppChildWindow);
and in VBScript I assume it would be
Dim oWnd As MyChildWindow
oWnd = oApp.CreateSomeChildWindow()
This call already works in C++ although MyChildWindow is not currently registered as a COM object in the registry. The reason MyChildWindow doesn't need to be registered is that CreateSomeChildWindow simply returns the interface pointer to the created MyChildWindow object in a parameter. And the reason it isn't registered is that I want to avoid redundancy, and also I don't want MyChildWindow to be instantiated directly (e.g. by calling CreateObject in VBScript).
Question:
Now I'm trying to find out whether it will be necessary to register MyChildWindow after all. Is my assumption correct that in order to call CreateSomeChildWindow in VBScript
I need to write Dim oWnd As MyChildWindow
For this to work, MyChildWindow must be registered
And if the answer to that is yes, hopefully clients still can't MyChildWindow directly, since I don't implement a class object for it? Or will I have to implement a class object?
Your out/retval is not an object (on the scripting side), it is an interface pointer. And since the method CreateSomeChildWindow is on IDL, in type library, in registered type library - scripting/automation is aware of interface definition, such as methods etc, because the whole type library is already registered. You are already well set, no additional registration required.
When caller receives an interface pointer, it does not care what object the pointer belongs to. Interface pointer alone is good enough, and scripting/automation environment known how to deal with it.
On the callee side however, you need to return an interface pointer, and you are dealing with objects. So you need some class which implements this interface and you return this object's interface.
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.
What does the ISupportErrorInfo interface mean? I'm at a bit of a loss to understand it. From MSDN:
This interface ensures that error
information can be propagated up the
call chain correctly. Automation
objects that use the error handling
interfaces must implement
ISupportErrorInfo.
This method indicates whether or not
an interface supports the IErrorInfo
interface.
HRESULT InterfaceSupportsErrorInfo(
REFIID riid
);
What does it mean to return S_OK in InterfaceSupportsErrorInfo? Should you return S_OK for all interfaces? Just some?
My understanding of it (based on some related MSDN pages) is that by implementing ISupportErrorInfo, you are indicating that one or more interfaces on your class returns error information by calling SetErrorInfo, as opposed to just returning a failure HRESULT.
To that end, your implementation of ISuportErrorInfo::InterfaceSupportsErrorInfo should return S_OK only for those interfaces on your class that actually use SetErrorInfo to return error information to the caller, and only those interfaces.
For example, say you have a class that implements an interface you wrote called IFoo that has a DoSomething method. If someone else creates an instance of your class and calls IFoo::DoSomething, they are supposed to do the following if DoSomething returns a failure HRESULT (paraphrasing from various MSDN pages, but I started from here: http://msdn.microsoft.com/en-us/library/ms221510.aspx):
Call QueryInterface on the IFoo pointer to get the ISupportErrorInfo interface for the object that is implementing IFoo
If the called object doesn't implement ISupportErrorInfo,
then the caller will have
to handle the error based on the
HRESULT value, or pass it up the call stack.
If the called object does implement ISupportErrorInfo, then the caller should call ISupportErrorInfo::InterfaceSupportsErrorInfo, passing in a REFIID for the interface that returned the error. In this case, the DoSomething method of the IFoo interface returned an error, so you would pass REFIID_IFoo (assuming it's defined) to InterfaceSupportsErrorInfo.
If InterfaceSupportsErrorInfo
returns S_OK, then the caller
knows at this point that it can
retrieve more detailed information
about the error by calling
GetErrorInfo. If InterfaceSupportsErrorInfo returns S_FALSE, the caller can assume the called interface doesn't supply detailed error information, and will have to rely on the returned HRESULT to figure out what happened.
The reason for this somewhat confusing/convoluted error-handling API seems to be for flexibility (as far I as I can tell anyway. This is COM after all ;). With this design, a class can support multiple interfaces, but not every interface is required to use SetErrorInfo to return error information from its methods. You can have certain, select interfaces on your class return detailed error information via SetErrorInfo, while other interfaces can continue to use normal HRESULTs to indicate errors.
In summary, the ISupportErrorInfo interface is a way to inform the calling code that at least one of the interfaces your class implements can return detailed error information, and the InterfaceSupportsErrorInfo method tells the caller whether a given interface is one of those interfaces. If so, then the caller can retrieve the detailed error information by calling GetErrorInfo.