为我的 C++ 应用程序提供 SDK
Providing SDK for my C++ Application
假设我正在用 C++ 创建一个游戏引擎,我只想提供一些 headers 而不是提供整个源代码,而创建新游戏将需要这些 headers例如,提供脚本 class,提供游戏 object class 和组件,数学等..
是的,很明显我想为我的游戏引擎提供 SDK,但该怎么做,如何只提供一些 public headers 并仅隐藏源文件和引擎 headers?如何 link 那些 headers 到源的其余部分?
我在 Linux 平台上使用 Eclipse CDT。
通常,通过提供共享(动态)库并在 headers 中提供纯虚拟接口以及一些外部 C 入口点(对于 cross-compiler 兼容性,因为每个编译器对 C++ 名称的处理方式不同)。
这篇文章可能是一个很好的起点:http://chadaustin.me/cppinterface.html - 它主要针对 Windows,但它也可以应用于 Linux。
实际上我在设计共享库时以此为起点(在 Windows 和 Linux 中工作),但我放弃了自定义运算符 delete 以调用 destroy直接方法(实际上是通过自定义的智能指针)。
在Linux下,还建议使用编译器的"visibility"标志,使所有内容默认隐藏(“-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;
}
}
这就是我实现库接口的原则。建议将分配的 objects 包装在智能指针中,但要进行自定义,以便它调用 Interface::destroy() 而不是 delete.
还要注意 int32_t 的使用 - 一般来说,如果您希望界面尽可能 cross-compiler 兼容,您应该使用固定大小的类型(即不是 size_t,这也适用于布尔和枚举,它们都高度依赖于编译器,但对于 int、short、long 等也是如此)。
进一步注意 try/catch 守卫的使用,如果您预计 API 可能会被不同的编译器使用,通常您不应允许异常通过 API 边界(有时甚至在同一编译器的 debug/non-debug 版本之间,但这更适用于 Windows;但是当库与太多不同版本(例如 GCC 编译器)一起使用时仍然可能存在问题)。 =14=]
这是您想要的视频 -> 在 eclipse 中创建静态库 CDT
https://www.youtube.com/watch?v=kw3UD_YCoEk
您也可以创建一个动态库,但首先从静态库开始。
假设我正在用 C++ 创建一个游戏引擎,我只想提供一些 headers 而不是提供整个源代码,而创建新游戏将需要这些 headers例如,提供脚本 class,提供游戏 object class 和组件,数学等..
是的,很明显我想为我的游戏引擎提供 SDK,但该怎么做,如何只提供一些 public headers 并仅隐藏源文件和引擎 headers?如何 link 那些 headers 到源的其余部分?
我在 Linux 平台上使用 Eclipse CDT。
通常,通过提供共享(动态)库并在 headers 中提供纯虚拟接口以及一些外部 C 入口点(对于 cross-compiler 兼容性,因为每个编译器对 C++ 名称的处理方式不同)。
这篇文章可能是一个很好的起点:http://chadaustin.me/cppinterface.html - 它主要针对 Windows,但它也可以应用于 Linux。
实际上我在设计共享库时以此为起点(在 Windows 和 Linux 中工作),但我放弃了自定义运算符 delete 以调用 destroy直接方法(实际上是通过自定义的智能指针)。
在Linux下,还建议使用编译器的"visibility"标志,使所有内容默认隐藏(“-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;
}
}
这就是我实现库接口的原则。建议将分配的 objects 包装在智能指针中,但要进行自定义,以便它调用 Interface::destroy() 而不是 delete.
还要注意 int32_t 的使用 - 一般来说,如果您希望界面尽可能 cross-compiler 兼容,您应该使用固定大小的类型(即不是 size_t,这也适用于布尔和枚举,它们都高度依赖于编译器,但对于 int、short、long 等也是如此)。
进一步注意 try/catch 守卫的使用,如果您预计 API 可能会被不同的编译器使用,通常您不应允许异常通过 API 边界(有时甚至在同一编译器的 debug/non-debug 版本之间,但这更适用于 Windows;但是当库与太多不同版本(例如 GCC 编译器)一起使用时仍然可能存在问题)。 =14=]
这是您想要的视频 -> 在 eclipse 中创建静态库 CDT https://www.youtube.com/watch?v=kw3UD_YCoEk
您也可以创建一个动态库,但首先从静态库开始。