Reputation: 55
Let's say that I'm creating a game engine in C++ and I want to provide only some headers instead of providing whole source code, and those headers will be needed to create new game instance, provide Script class, provide game object class and components, math, etc..
Yeah obviously I want to provide SDK for my game engine but how to do it, how to provide only some public headers and hide source files and engine only headers? How to link those headers to the rest of the source?
I'm using Eclipse CDT on Linux platform.
Upvotes: 0
Views: 327
Reputation: 6066
In general, youd'd get the best (easily) achievable binary compatibility by providing a shared (dynamic) library and provide pure virtual interface in the headers, with some extern C entry points (for cross-compiler compatibility, as C++ names are mangled by each compiler differently).
A good starting point could be this article: http://chadaustin.me/cppinterface.html - it is mainly targeted to Windows, however it can be applied to Linux as well.
I've actually used that as a starting point when designing a shared library (working in both Windows and Linux), but I dropped the custom operator delete in favor of calling the destroy method directly (actually by a customized smart pointer).
Under Linux, it is also advisable to use the "visibility" flag of the compiler, make everything hidden by default ("-fvisibility=hidden") and only flag as __attribute__ ((visibility ("default")))
the functions which need to be exported (note that you only need to export the extern "C" entry point and the pure virtual interfaces do not need to be exported).
For even better binary compatibility you would need to avoid even virtual methods and implement your own virtual tables (compatible with every compiler the user might use), but pure virtual interfaces are actually compatible reasonably enough.
With static libraries you might have issues, because you might then need to provide a static library for every compiler (and sometimes even different versions of the same compiler) the user might use.
As an example, the interface might look like:
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();
The implementation can then look like:
// 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;
}
}
This is in the principle how I implemented the interface of the library. It is advisable to wrap the allocated objects inside a smart ptr, but customized so that it calls Interface::destroy() instead of delete.
Also note the use of int32_t - in general, if you want the interface to be as cross-compiler compatible as possible, you should use fixed size types (i.e. not for example size_t, and that also applies to bool and enums, which all are highly compiler dependent, but even so for int, short, long etc.).
Further note the use of try/catch guards, in general you should not allow exceptions to pass the API boundary if you expect the API might be used from a different compiler (or sometimes even between debug/non-debug versions of the same compiler, but that applies more to Windows; however there still could be issues when the library will be used with too different version of e.g. the GCC compiler).
Upvotes: 2
Reputation:
This is a video of what you want -> creating a static library in eclipse CDT https://www.youtube.com/watch?v=kw3UD_YCoEk
You could also create a dynamic library but start with static first.
Upvotes: 0