0
votes

I'm pondering on using exception handling in some libraries I'm writing. The code base may ultimately be implemented in a number of dlls. I know that using exception handling across dlls is a bad idea - however, would it be safe to do the following?

class IDllSafeException
{
public:
    virtual void AddRef() = 0;
    virtual void Release() = 0;
    virtual int GetErrorCode() const = 0;
    virtual const char * What() const = 0;
};

Assuming this is a valid approach, I'd really like to catch by value, rather than pointer. That way I could have a wrapper class which automatically calls Release().

Thus...

template <typename T>
class SafeExceptionT
{
public:
    SafeExceptionT(T *);            
    SafeExceptionT(SafeExceptionT &);
    SafeExceptionT & operator=(SafeExceptionT &);
    ~SafeExceptionT();

    int GetErrorCode() const;
    const char * What() const;
private:
    T * m_pException;
};

typedef SafeExceptionT<IDllSafeException> SafeException;

Being a template means all catch blocks would generate their own layout and call can m_pException->Release() in the destructor (using AddRef() when copying occurs). Also I could implement different types of exceptions as needed.

My catch block would then look like this...

try
{
    ThrowSomething();
}
catch(SafeException except)
{       
    const char * psz = except.What();
    int nErrorCode = except.GetErrorCode();
}

...

void ThrowSomething()
{   
    // Concrete implementation omitted for brevity
    throw new DllSafeException(1, "something went wrong");
}

I would have thought that would work but my exception handler does not get caught, even though I've a non-explicit constructor for SafeException.

However I can only seem to catch via pointer...

try
{
    ThrowSomething();
}
catch(SafeException except)
{       
    /// IGNORED!
    ...
}
catch(IDllSafeException * pExcept)
{       
    // CAUGHT BY POINTER but we now have to micro-manage.
    const char * psz = pExcept->What();
    int nErrorCode = pExcept->GetErrorCode();
    pExcept->Release();
}

Are there special rules for how types are matched in exception handlers?

1
Using exception handling across dlls is not a bad idea at all. It just requires very careful handling, just like C++ exceptions in general. If exception to be thrown is defined in this dll then you need to import rtti for this class from this dll. If it is defined somewhere else then you need to import rtti for exception class from there. Also you should throw by value and capture by const reference. If you still want to throw a pointer then make virtual destructor instead of COM-style Release and reinventing smart pointers. - user7860670
You CANNOT throw exceptions, particularly class-based exceptions, over the DLL boundary unless both the DLL and the caller are built against the same Runtime Library, share the same RTTI, the same Memory Manager, etc. Just don't do it, it is NOT SAFE. - Remy Lebeau
Thanks for the comments though there seems to be disagreement. I would have thought that the memory management concerns are resolved if using a virtual method and throwing via pointer. Also, any ideas why the exception is not being caught by value as SafeException? I may just go back to using return codes if this is going to be a problem. - nearproc
If it is not getting caught than most likely you didn't import exception class from dll. Also conditions listed above ("both the DLL and the caller are built against the same Runtime Library, share the same RTTI, the same Memory Manager") are actually conditions for safe use of C++ code imported from dlls in general, not just for exceptions. - user7860670
If I made the destructor virtual, wouldn't this cause heap deallocation in the wrong dll? That's why I went with ref counting. Though, happy to be corrected if I'm wrong. - nearproc

1 Answers

0
votes

If anyone is interested, the above code does successfully circumvent the dll boundary exception issue by using abstract base classes and relying on consistent vtable layout across compilers.

I tested this on VS2008 and VS2017. I had a dll built in each environment and a console app in each. The dll raised exceptions and the exception class internally aggregates an object to ensure heap allocation would take place.

I also mixed and matched versions of the C-runtime (static vs dll) between different dlls/console apps. I could successfully raise/catch my exception across the dll divide.

As expected, the vtable layout ensures that the delete/~tor happens in the correct binary.

The only caveat is that you must throw by abstract interface. Which also means ensuring you manually call pEx->Release. This is not pretty I know but I still don't know why my other class does not capture the exception via it's non-explicit constructor.

VS2008 Example Debug Session