【问题标题】:Exposing a type-safe dynamic API with a shared library使用共享库公开类型安全的动态 API
【发布时间】:2012-07-18 19:57:37
【问题描述】:

我正在为应用程序编写插件 API 接口。插件在运行时作为共享库加载。他们可以通过接口访问应用程序 API,例如:

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

插件可以请求“监听”某些事件(例如MouseClickMouseScroll 等)。这些功能总共构成了超过 300 个不同的事件。通常我会做这样的事情:

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

SetEventHooks函数驻留在插件库中并从应用程序中调用,插件可以通过指向它们的函数来监听感兴趣的函数。这不是我想使用的方法,但我想提供某种抽象。这就是我的想法:

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

在这种情况下,HookID 是一个包含所有函数 ID 的强类型枚举:

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

所以插件会使用这个函数来监听事件:

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

这种方法的问题是它不是类型安全的,我不能直接使用模板(因为这是在运行时作为库完成的)。我不想为每个事件公开 300 个不同的函数(例如SetHookMouseMove(void (*)(int, int)) 等等)。我的最后一个想法是,插件有一个实用模板函数,可以使这种类型安全,但我不确定如何以简单的方式实现它(并且没有样板代码):

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");
}

简单地说;如何让我的插件以动态类型安全的方式连接到某些事件,而不为每个事件公开完整的函数表和/或超过 300 个方法?

注意:我使用函数指针进行简化,但我想使用std::function

【问题讨论】:

  • 多态不是一个选项吗?在我看来,如果您有一个用于参数和返回值的基类,每个事件都有其子类,那么使用dynamic_cast,一切都很好。
  • 不,这只会让事情变得更糟。指数参数组合。
  • T 到底应该是什么?你知道函数指针和对象指针一般不兼容吗?
  • 嗯,也许像template &lt;HookID ID&gt; void SetEventHook(typename CallbackType&lt;ID&gt;::type callback);
  • 那可能就行了!这将是干净和时尚!唯一的缺点是需要全局 API 对象。你能写出更多解释性并作为答案吗?

标签: c++ gcc c++11 shared-libraries


【解决方案1】:

正如 Kerrek 所建议的,您可以使用特征策略来解决您的问题。基本上,作为公共 API 的一部分,您必须包含为每个钩子 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
}

现在您可以通过以下方式使用此 API:

// 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;
}

我已经上传了一个工作示例here

【讨论】:

  • 这就是我最终做的,所以我会接受这个作为我的答案!
猜你喜欢
  • 2013-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-17
  • 1970-01-01
  • 2014-05-17
相关资源
最近更新 更多