views:

44

answers:

4

I have several dozen objects exposed through COM interfaces, each of which with many methods, totaling a few hundred methods. These interfaces expose business objects from my app to a scripting engine.

I have been given the task of protecting every single one of these methods from exceptions being thrown (to catch them and return an error using COM's Error() function, which incidentally I can find no documentation on because it's impossible to google). To my understanding, this requires that I add a try/catch around the guts of each one of these methods. The catch blocks are going to be similar or identical for each and every one of these hundreds of methods, which strongly smells of a problem (massively violates the DRY principle), but I can't think of any way to avoid changing every method. As far as I can tell, these methods are invoked directly by COM, with no intervening code that I can hook into to catch the exceptions. My current best idea is to make a macro for the catch block, but that has it's own sort of code-smell. Can anyone come up with a better approach?

BTW, my app's exceptions do not derive from std::exception, so if there is some way of COM automatically handling standard exceptions, it won't help. And I sadly cannot change the existing exceptions to derive from std::exception.

A: 

Comet is one way to greatly simplify the effort involved.

Kyle Alons
that looks interesting, but for this legacy system I think it would be an impossibly huge amount of work to start using. I'll keep it in mind if I ever have the misfortune of designing a new system that uses COM.
rmeador
A: 

Given that all COM interface methods use the __stdcall convention and assuming you can hook into creating the COM objects yourself, you can "simply" create a wrapper COM object that returns a wrapped COM interface through QueryInterface. This wrapped COM interface should be a COM interface where every vtable entry is replaced by a thunk that wraps the original vtable function with try/catch.

I don't actually recommend doing this, but this is one way you can insulate the runtime implementing the COM object from external systems.

MSN
do you have a link or example code of how to do this? Does C++ allow you to modify the vtable in this way, or would you have to use some direct memory manipulation? And wouldn't it be architecture and OS dependent (which is probably going to elicit a "no" from my boss)?
rmeador
+1  A: 

No happy answers here. It is illegal to let a C++ or SEH exception terminate a COM method. You must catch them and translate them to an appropriate HRESULT. Your coclass must implement the ISupportsErrorInfo and IErrorInfo interfaces so that the client can get exception information back. The CLR will readily do so to produce a customized exception message.

The Error() method you speak of is most likely the ATL CComCoClass::Error() method. It sets up the exception info that IErrorInfo will return.

There is no mechanism to inject a single try/catch block to catch all possible exceptions, COM methods are called directly from the client. You have to do so for each individual COM method that calls C++ code that can throw an exception. Unpleasant, but this should have been done when the code was written. You probably need to wrap every single one of them since finding out if it is calling code that may throw is difficult right now.

Technically, you could change the class factory to create a wrapper instead, one that implements each method of the interface and delegates to the actual method. With boilerplate try/catch handlers. Very mechanical, you can probably come up with some macros to lessen the blow.

Hans Passant
Or perhaps SetErrorInfo(), which is most likely what the ATL method wraps. http://msdn.microsoft.com/en-us/library/aa910593(v=MSDN.10).aspx
jeffamaphone
A: 

The most reliable C++ way is to use macros here. I' ready to accept downvotes for saying this, but we've been using this solution for years and haven't seen any serious problems so far.

Define a "begin method" macro for clearing IErrorInfo and try { and "end method" for } catch and the error handling. If you design the macros right - put all except the most necessary error handling code into helper functions it will be a tolerable and reliable solution with clean-looking moderately maintainable code.

Yes, that doesn't look good at all, but at least it is reliable and standard-compliant way of achieving what you want.

sharptooth