通常,通过提供共享(动态)库并在标头中提供纯虚拟接口以及一些外部 C 入口点(用于交叉编译器兼容性,如 C++),您将获得最佳(轻松)可实现的二进制兼容性每个编译器都会以不同的方式修改名称)。
这篇文章可能是一个很好的起点:http://chadaustin.me/cppinterface.html - 它主要针对 Windows,但也可以应用于 Linux。
在设计共享库(在 Windows 和 Linux 中工作)时,我实际上已将其用作起点,但我放弃了自定义运算符 delete 以支持直接调用 destroy 方法(实际上是通过自定义智能指针) .
在Linux下,也建议使用编译器的“可见性”标志,默认隐藏所有内容(“-fvisibility=hidden”),只将需要导出的函数标记为__attribute__ ((visibility ("default")))(注意即只需要导出extern "C" 入口点,不需要导出纯虚拟接口)。
为了更好的二进制兼容性,您甚至需要避免使用虚拟方法并实现自己的虚拟表(与用户可能使用的每个编译器兼容),但纯虚拟接口实际上足够兼容。
对于静态库,您可能会遇到问题,因为您可能需要为用户可能使用的每个编译器(有时甚至是同一编译器的不同版本)提供一个静态库。
例如,界面可能如下所示:
class Interface {
public:
virtual void destroy() = 0;
protected:
// prevent to call delete directly
// - needs to be done in every public interface class
~Interface() {}
};
class IGameComponent: public Interface {
public:
virtual int32_t someMethod() const = 0;
protected:
~IGameComponent() {}
};
class IGameEngine: public Interface {
public:
// call component->destroy() when done with the component
virtual IGameComponent * createComponent() const = 0;
protected:
~IGameComponent() {}
};
extern "C"
__attribute__ ((visibility ("default")))
IGameEngine * createEngine();
实现如下所示:
// CRTP to avoid having to implement destroy() in every implementation
template< class INTERFACE_T >
class InterfaceImpl: public INTERFACE_T {
public:
virtual void destroy() { delete this; }
virtual ~InterfaceImpl() {}
};
class GameComponentImpl: public InterfaceImpl<IGameComponent> {
public:
virtual int32_t someMethod() const
{ return 5; }
};
class GameEngineImpl: public InterfaceImpl<IGameEngine> {
public:
virtual IGameComponent * createComponent() const
{
try {
return new GameComponentImpl;
} catch (...) {
// log error
return NULL;
}
}
};
extern "C"
IGameEngine * createEngine()
{
try {
return new GameEngineImpl;
} catch (...) {
// log error
return NULL;
}
}
这是我实现库接口的原理。建议将分配的对象包装在一个智能 ptr 中,但要对其进行定制,以便它调用 Interface::destroy() 而不是 delete。
还要注意 int32_t 的使用——一般来说,如果你希望接口尽可能地与交叉编译器兼容,你应该使用固定大小的类型(即不是例如 size_t,这也适用于 bool 和 enums,它们都高度依赖于编译器,但对于 int、short、long 等也是如此)。
进一步注意 try/catch 保护的使用,一般来说,如果您希望 API 可能在不同的编译器中使用(或者有时甚至在调试/非调试版本的相同的编译器,但更适用于 Windows;但是,当库与太多不同的版本(例如 GCC 编译器)一起使用时,仍然可能会出现问题。