假设我正在用C ++创建一个游戏引擎,我想只提供一些标题而不是提供完整的源代码,并且需要这些标题来创建新的游戏实例,提供Script类,提供游戏对象类和组件,数学等。
是的,显然我想为我的游戏引擎提供SDK但是怎么做,如何只提供一些公共标题并隐藏源文件和仅引擎头?如何将这些标题链接到源的其余部分?
我在Linux平台上使用Eclipse CDT。
答案 0 :(得分:2)
一般情况下,通过提供共享(动态)库并在头文件中提供纯虚拟接口,您可以获得最佳(轻松)可实现的二进制兼容性,并具有一些外部C入口点(用于交叉编译器兼容性,如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;
}
}
这就是我如何实现库的接口的原则。建议将已分配的对象包装在智能ptr中,但要进行自定义,以便调用Interface :: destroy()而不是delete。
另请注意int32_t的使用 - 通常,如果希望接口尽可能与交叉编译器兼容,则应使用固定大小类型(即不是例如size_t,这也适用于bool和enums,这些都是高度依赖编译器的,但对于int,short,long等也是如此。)。
进一步注意使用try / catch防护,一般情况下,如果您希望API可能来自不同的编译器(或者有时甚至在调试/非调试版本之间),则不应允许异常传递API边界。相同的编译器,但更适用于Windows;但是当库与太多不同的版本(例如GCC编译器)一起使用时,仍然可能会出现问题。
答案 1 :(得分:0)
这是你想要的视频 - &gt;在eclipse CDT中创建一个静态库 https://www.youtube.com/watch?v=kw3UD_YCoEk
你也可以创建一个动态库,但先从静态开始。