This may not be a good solution to your specific needs but I'm gonna tell you how I handled a similar problem.
I developed a c++ object oriented game and wanted to encapsulate DirectX and COM in general inside my own classes, because I didn't wanted to use COM as base for the whole game.
The COM parts of my game (at the moment direct3d, direct2d, directWrite, WIC and directInput) are all encapsulated in two main classes:
GraphicsEngine: everything that has to do with 3d, 2d (direct3d) and font rendering (direct2d and directWrite) as well as image and texture loading (WIC)InputEngine: Handles user input (directInput)
Each object which wants to render or use any of the COM functionality just stores a pointer to the object of these classes (They are singleton, so only one object exists). Both classes ensure the correct usage of the components and provide easy access:
Constructors and Destructors:
They handle initialization as well as releasing COM objects
Member functions:
They delegate access to the COM functionality. However, there is generally no direct access to COM objects like ID3D11Device or IDirectInput8.
example:
Instead of using for instance
deviceContext -> VSSetShaderResources(slot, count, shaderResourceViews); one would use
graphicsEngine -> SetShaderResources(ShaderType::VertexShader, slot, count, shaderResourceViews); ShaderType beeing a enumeration of all different shader types. This reduces the amount of member functions since XXSetShaderResources exists for multiple shader types.
Error checking
Since the COM functionality is encapsulated in these classes, it is possible to automaticly check for errors (for instance HRESULT return type of COM functions). This reduces the code classes using COM have to write.
example:
hr = this -> device -> CreateSamplerState(samplerDescriptions[i], &samplerState); if(!SUCCEEDED(hr)) { // error handler here } This code is contained inside a method called GraphicsEngine::AddSamplerStates.
asynchronous object loading
DirectX gives you the possibility to create objects e.g. ID3D11Buffer on a different thread as the main thread. This allows you to load objects while the game is rendering other stuff (splash screen or loading animation). However, initializing a thread and later synchronize with the thread when loading is done (even in c++11) requires quite a bit of code. My classes allow me to encapsulate this loading functionality nicely. The class holds an array of loading threads currently loading objects. Once an object is complete, a flag is set and the main thread makes the newly created object available in the public interface of the class. This ensures the correct loading of objects and prevents the use of not fully loaded objects. (See example in the next section)
object management
This is the most important job the wrapper classes do. All objects created using one of the public wrapper methods around COM functions are stored in the class. The class uses a sort of Key - Value mechanism to give access to objects.
Example
HashKey vertexBufferLoaderId = this -> graphicsEngine -> AddBuffers(bufferKeys, bufferDescriptions, bufferData, 5); This piece of code starts an object loading job for multiple ID3D11Buffer objects. HashKey is the type used by the classes as keys (code possessing a key can use the COM object stored under that key). vertexBufferLoaderId will (after the method returns) contain a key to a ObjectLoader, a class which keeps track of the loading progress. The code which launched the loading process can get the loaded object once ObjectLoader affirms the completion of the loading progress. bufferKeys is an array of HashKey pointers (HashKey**), that's where the the keys for the loaded objects are stored after the objects are done loading. bufferDescriptions and bufferData are parameters needed by direct3d to create the buffers. The last parameter holds the amount of buffers to load.
The caller can fetch the loading progress at any time using the key to ObjectLoader. When the process is done, the caller can access the created objects using the keys (bufferKeys) he provided.
(almost) all encapsulated functions of COM which need a COM object as parameter, are implemented as follow:
Example
void SetVertexBuffer(HashKey vertexBuffer, Slot slot, UInt32 stride, UInt32 offset); instead of
void SetVertexBuffer(ID3D11Buffer* vertexBuffer, Slot slot, UInt32 stride, UInt32 offset); By using keys instead of objects, the class can check whether the passed objects is valid and make sure it has been created by the wrapper itself:
if(!this -> buffers.Exists(vertexBuffer)) { // error handling } conclusion:
These wrapper classes are far from finished. Whenever I need a specific functionality of COM, I add specific methods and members for it, so the classes are "evolving" with my game.
pros of my approach:
- (almost, there are exceptions) no COM objects visible
- automatic management of COM objects
- easy access through keys (proprietary use of objects)
- multiple function calls forming a specific task are regrouped
- easy and safe multithreaded object creation
cons:
- a lot of code
- additional function call for each api call (a little bit slower but not crucial)
- each time a new functionality is needed it needs to be implemented
It surely took me some time (two weeks of my free time) to code and debug but now it works fine and I never have to worry about using resources such as ID3D11Texture or ID3D11SamplerState correctly in my game.
I hope I could give you an idea of how such an implementation could be done. This is the first time I programmed a thing like that so I'm curious to know whether it helped you and what do you think of it.