5

I'm programming a plugin API interface for an application. The plugins are loaded as shared libraries at run time. They have access to the application API through an interface, such as the following:

class IPluginAPI { public: virtual bool IsPluginsLoaded(void) = 0; virtual bool IsHookingEnabled(void) = 0; // And about 50 more methods }; 

Plugins can request to 'listen' on certain events (such as MouseClick, MouseScroll etc.). These functions make up a total of >300 different events. Normally I would have done something like this:

extern "C" void SetEventHooks(APITable& table) { table.MouseClick = &PluginMouseClickEvent; table.MouseMove = &PluginMouseMoveEvent; } 

Whereas the SetEventHooksfunction resides within the plugin library and is called from the application, and the plugins can listen to functions of interest by pointing to their functions. This is not the method I want to use, but I want to offer some kind of abstraction instead. This is what I had in mind:

// Interface IPluginAPI supplies a 'SetEventHook` method such as void SetEventHook(HookID id, void * callback); 

In this case HookID is a strong typed enum which contains all function IDs:

enum class HookID { MouseClick, MouseMove, // ... }; 

So the plugin would use this function to listen to events:

pluginAPI->SetEventHook(ID::MouseClick, &myCallback); 

The problem with this approach is that it is not type-safe and I cannot use templates directly (since this is done at runtime as libraries). I don't want to expose 300 different functions either for each event (e.gSetHookMouseMove(void (*)(int, int)) and so on). My last idea, is that the plugins have a utility template function which makes this type safe, but I'm not sure how to implement this in a simple way (and without boilerplate code):

template <typename T> SetEventHook(HookID id, T callback) { if(typeof(T) == decltype(/* function type of the ID */)) gPluginAPI->SetEventHook(id, callback); else static_assert("INVALID FUNCTION TYPE"); } 

So to put it simple; how can I enable my plugins to hook to certain events in a dynamic type-safe way without exposing a complete function table and/or >300 methods for each event?

NOTE: I used function pointers for simplification, but I want to use std::function

6
  • Is polymorphism not an option? It seems to me that if you have a base class for arguments and for return values of which there are subclasses for each event, a little use of dynamic_cast, and everything is good. Commented Jul 18, 2012 at 20:08
  • 2
    Nah, that's just gonna make things worse. Exponential argument combinations. Commented Jul 18, 2012 at 20:09
  • What exactly is T supposed to be? You know that function pointers and object pointers are not in general compatible? Commented Jul 18, 2012 at 20:53
  • 3
    Hm, maybe something like template <HookID ID> void SetEventHook(typename CallbackType<ID>::type callback);? Commented Jul 18, 2012 at 22:07
  • 1
    That might just do it! It would be clean and sleek! Only downside is the requirement of a global API object. Could you write that more explanatory and as an answer perhaps? Commented Jul 19, 2012 at 0:00

1 Answer 1

3

As suggested by Kerrek, you can use traits policy to solve your problem. Basically as a part of public API you have to include structures defining callback type for each of your hook id.

// The default traits. If you don't want to have default traits comment body // of this type out (including curly braces). template <HookID id> struct CallbackTraits { typedef void (*CallbackType)(); }; // Traits for MouseClick template <> struct CallbackTraits<HookID::MouseClick> { typedef void (*CallbackType)(int); }; // Traits for MouseDoubleClick are the same template <> struct CallbackTraits<HookID::MouseDoubleClick> : CallbackTraits<HookID::MouseClick> {}; // Traits for MouseMove template <> struct CallbackTraits<HookID::MouseMove> { typedef void (*CallbackType)(int, int); }; // actual hooking function template <HookID id> void SetEventHook(typename CallbackTraits<id>::CallbackType callback) { // do something with id and the callback } 

Now you can use this API following way:

// handlers prototypes void MouseClicked(int button); void MouseMoved(int x, int y); void SomeEvent(); int main() { // compiles ok SetEventHook<HookID::MouseClick>(MouseClicked); SetEventHook<HookID::MouseMove>(MouseMoved); // won't compile - function signature incompatible SetEventHook<HookID::MouseDoubleClick>(MouseMoved); // will compile if you left default traits body uncommented SetEventHook<HookID::HookWithNoTraitsDefined>(SomeEvent); return 0; } 

I've uploaded a working sample here.

Sign up to request clarification or add additional context in comments.

1 Comment

That's what I ended up doing, so I'll accept this as my answer!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.