Hi
I am about to expose a service written in C# to a legacy C++ application using COM. What is the best approach to report errors to the unmanaged client? Throwing exceptions or simply return an error value?
Thanks, Stefano
Hi
I am about to expose a service written in C# to a legacy C++ application using COM. What is the best approach to report errors to the unmanaged client? Throwing exceptions or simply return an error value?
Thanks, Stefano
I think that depends on how the legacy application will react. If it understands error return values then go with that approach. If it doesn't, then you'll have to throw exceptions and hope it handles them appropriately.
Also, if it's a bad reference (null reference for example) or other critical error, I would always throw an exception. Exceptions should, however, be avoided for things the consumer cannot check beforehand (e.g. a search that comes up empty shouldn't throw an exception).
If your COM application supports the IErrorInfo interfaces when calling into your C# service and it's an entirely internal project then throwing exceptions is likely the best bet as it captures the most information. However COM has traditionally relied on HR results to communicate status results and may be better if the service is to be published to other sources.
EDIT: I like Joe's answer better.
The best judge whether you are to use exceptions or return values would be YOU. "Throwing exceptions" outweighs "returning values" in several ways. BUT in some cases returning values would be enough.
I agree with the others that this is not a "yes or no" answer without knowing your project intimately.
It will depend on a number of factors such as:
Here's a good blog post that discusses a number of subtle points about exception processing.
The author recommends either one of two approaches:
Either:
or:
Personally, I think you should avoid tightly coupling your C# service with your C++ application. In other words, write your C# service so that it could theoretically be used by any consumer. Likewise, your C++ code should be written so that it doesn't rely on the internal workings of the C# service, so changes or additions to the exceptions (or error codes) do not break the consumer.
You should throw Exceptions. Exceptions are mapped to HRESULTS by the Framework, and HRESULTs are the standard way to return errors to COM clients, so this is the way to go.
Each Exception type has an HResult property. When managed code called from a COM Client throws an exception, the runtime passes the HResult to the COM client. If you want application-specific HRESULT codes, you can create your own custom Exception types and set the Exception.HResult property.
One point to note is that the call stack information will be lost when an Exception is thrown to a COM client. It can therefore be a good idea to log exceptions before propagating to the COM client.
One technique I sometimes use is the following: explicitly implement a ComVisible interface for COM clients that logs and rethrows exceptions. COM clients use the ComVisible interface that logs exceptions before propagating them. .NET clients use the concrete class and are expected to make their own arrangements for exception handling. It's a bit long-winded to write but can be helpful when you're subsequently troubleshooting.
Another advantage of this approach is that you can have an API tailored to the restrictions of COM for COM clients, and a more standard API for standard .NET clients. For example, COM clients are restricted to passing arrays by reference, whereas passing by reference is discouraged for .NET clients.
Example:
[
ComVisible(true),
GuidAttribute("..."),
Description("...")
]
public interface IMyComVisibleClass
{
// Text from the Description attribute will be exported to the COM type library.
[Description("...")]
MyResult MyMethod(...);
[Description("...")]
MyOtherResult MyArrayMethod([In] ref int[] ids,...);
}
...
[
ComVisible(true),
GuidAttribute("..."),
ProgId("..."),
ClassInterface(ClassInterfaceType.None),
Description("...")
]
public class MyComVisibleClass : IMyComVisibleClass
{
public MyResult MyMethod(...)
{
... implementation without exception handling ...
}
public MyOtherResult MyArrayMethod(int[] ids,...)
{
... input parameter does not use ref keyword for .NET clients ...
... implementation without exception handling ...
}
MyResult IMyComVisibleClass.MyMethod(...)
{
// intended for COM clients only
try
{
return this.MyMethod(...);
}
catch(Exception ex)
{
... log exception ...
throw; // Optionally wrap in a custom exception type
}
}
MyOtherResult IMyComVisibleClass.MyArrayMethod(ref int[] ids, ...)
{
// intended for COM clients only
try
{
// Array is passed without ref keyword
return this.MyArrayMethod(ids, ...);
}
catch(Exception ex)
{
... log exception ...
throw; // Optionally wrap in a custom exception type
}
}
}