I was looking at the quake2 source code and opened up gamex86.dll and noticed how all it exports is a function, "GetGameAPI()". As far as I can tell it returns a struct of function pointers.
I had a look at the Crytek (Far Cry's engine) DLL's and they look pretty similar; CryAISystem.dll exports "CreateAISystem()" and another function, "CryModuleGetMemoryInfo()".
I've been working on a little engine the last couple of days and I was thinking about doing this, since right now my core DLL exports almost every bloody class since I'm unsure of a method to... hide stuff.
Guess what I'm asking is: Is the method q2 uses a good idea? It seems a little inflexible, exporting a whole bunch of function pointers. What about Crytek's method?
In case you were wondering, my little project is coming along fine. Slowly, but otherwise fine.
What they are doing is using an interface like this:
This interface is pure virtual and has no constructor or data members. You derive your actual game/renderer class from this interface. You can either create a function as a friend of the class that actually creates an instance of the class and returns a pointer to it. The class design is normally in the form of a singleton where the big three in C++ are declared as private (default constructor, copy constructor, and assignment operator). Take the following example.
virtual void Foo(int x,int y)=0;
virtual void Foo2(void)=0;
In this code D3DXCreateTextureFromFile is what actually instantiates the object and even though COM is not technically C++, it's extremely similar and uses the same types of mechanisms. Initially my pointer is NULL but on return from the function it is now a valid IDirect3DDevice9 interface pointer. This in turn can expose other interfaces.
void CreateTextureTest(IDirect3DDevice9 *pDevice,std::string Filename)
Basically you create a declaration of a pure virtual base class. You create function(s) to instantiate and return a pointer to the object as well as release/destroy it. You stick the pure virtual base interface in the DLL. This is what I would call the lowest level of the engine. The second level or mid-level is where you the programmer begin to craft the game classes, resource managers, etc. This second level then forms the API through which your engine is interacted with. The final level is the highest level which would be classes that encapsulate common functions and members to form a class-based front-end to your engine.
Think MFC. Windows is first and foremost a pure assembly/C product. There are a lot of functions, but none of them are encapsulated. But if you look in the docs, they encapsulate all the functions/messages/structures related to things like edit controls, buttons, windows, file access, timers, low-level disk I/O, etc, etc. MFC then is a bunch of classes that encapsulate this API into efficient units of code to represent objects instead of just a bunch of functions sitting out there in windows.h. The MFC dll's then hold the definitions for the classes declared in the headers.
There are several ways to link with DLLs.
An import library is created when you compile a DLL project in MSVC (regardless of version).
This import library is then linked in at compile time with your code. So your code includes the headers for the import library and you link with the import library in your link options. At run-time as long as the DLL resides in the same folder as the EXE using it, Windows will load from it as it needs to. You declare your classes like this:
In the DLL code: __declspec(dllexport)
In the CPP code using the DLL: __declspec(dllimport)
I use a simple process I found on CodeGuru which some have expressed concern about, but it works.
When _CLASSINDLL is defined the class declaration is export.
#define XTREME_DLL_DECL __declspec(dllexport)
#define XTREME_DLL_DECL __declspec(dllimport)
When _CLASSINDLL is not defined, the class declaration is import.
So when you are creating the DLL you go to your project settings and find where the defines are at. You add _CLASSINDLL. Now when you compile the DLL, the classes are export.
When you are using the DLL classes, you will not define _CLASSINDLL in your project settings and thus they are now declared as import in the headers instead of export.
Once you link with the import library, include the correct headers, and place the DLL you created in the same folder as the EXE using it, you can now use the class code in the DLL just as you would any other C++ class.
The downside to this method is that if you change the version of the DLL, you will have to recompile all of the source. For developers and DirectX/OpenGL this works fine as they just release new SDKs with new DLLs. For other software like games, word processors, applications, etc, this may not be the best option.
However for games most patches now resolve down to a DLL patch or a new DLL. They go to great lengths to avoid breaking the existing code base by changing prototypes, etc, and instead attempt to change the code in the DLL. However, I've seen where a patch is pretty much a new exe and several new DLLs meaning they recompiled everything. Since the textures, sounds, and resources are already installed on your system - the resulting patch is usually anywhere from 1mb to 50mb. The resources are the bulk of the game, not the code.
Gamedev.net shows it this way although their example is quite lacking.
Dynamic linking with DLLs
This method is the hardest and most cumbersome, but also the most useful. It is also the most insecure way of linking. Essentially you load the library using LoadLibrary to load the DLL. Then you provide function pointers and intialize them by using GetProcAddress. This means you must have the exact name of the function you wish to load into your address space. Once you do all of this, you can then call the functions via the pointers.
But again, this method is messy, unsafe, and easy to hack (but then again, what isn't?).
I think most companies started with dynamic linking and have since switched to the import library method because it's much more straightforward, easier to setup, and is a bit safer.
In the end all you are trying to do is return a pointer from the DLL to an instance of the class. Then, with that pointer, you can access the DLL class functions.
Sorry so long, but it is quite involved.
Also you CANNOT use MSVC 6 STL code across DLL or module boundaries. There is another version of the STL available on the net that is both portable across platforms and compatible with DLLs.
MSVC .NET 05 will have no problem with this STL, but MSVC 6 might complain a bit.