mercredi 21 janvier 2015

Using std::error_category across DLL boundaries

std::error_category is a peculiar beast:



error_category objects are passed by reference, and two such objects are equal if they have the same address. This means that applications using custom error_category types should create a single object of each such type. [syserr.errcat.overview]



So, essentially they are singletons and equality between error_categories is determined by pointer-equality (see also error_category::operator== in syserr.errcat.nonvirtuals).


In our project, we use custom error_categories for reporting errors via std::system_error and ran into a nasty problem when linking several components together.


We have a custom error category defined in a static library CommonBase:



// internal singleton class
class foo_category_T : public std::error_category { /* ... */ };

// public accessor for singleton instance of foo_category_T
const error_category& foo_category();


We have a dynamic library DynLib that provides an asynchronous API for doing stuff:



typedef std::function<void(std::error_code const&)> result_callback;
void doStuffAsync(result_callback const& user_callback);


This is your ordinary asynchronous interface as you would find it for example in Boost.Asio.


DynLib statically links to CommonBase internally and uses the foo_category from CommonBase as the category for the error codes given to the callback in case of errors.


We now have an application that also statically links to CommonBase internally. This application may or may not decide to load DynLib as a plugin at runtime.


If the application does load DynLib we get into trouble, since now both components will end up with their own instance of the foo_category. Even though both instances were created by CommonBase, they are not considered equal as they refer to different objects in memory.


I was wondering if this is a problem that was considered when error_category was originally designed. So far I could see the following solutions to this problem, none of which sounds particularly appealing:



  • Don't do it. error_category (and thus error_code) should not pass over dll boundaries.

  • Provide an accessor for the DynLib instance of the error_category on the interface of the dynamic library. This seems a little silly, as the application already knows the error_category type, it simply does not have access to the pointer.

  • Inject the category instance created by the application when loading DynLib. This requires changing CommonBase in a way that a category is either created upon request, or injected before the first call to the foo_category function. This works fine for custom error categories, but what about builtin categories like std::system_category?


I hope you can shed some light on this issue and give some advice for best practices.


Aucun commentaire:

Enregistrer un commentaire