How To Best Wrap An API?

Ok i've separated my project well enough so that it has dependant parts and independant parts. My question is when do you know that it is ok to link them. If I wrap an API (system functions) into my api do is it ok to do a one to one mapping of everything?

For example if I call CreateWindowEx() and get a handle to a window.
But I wrap this function into my function accepting different parameters, return this handle as an opaque pointer, would this be acceptable behavior for an API. Or, would it be better to develop my own system of storing the outputs of the systemAPI functions into classes, caches, or on the hdd during runtime???

You know, maybe create my own structs that store the output of the OS system callss. I know the drawback to that would be slower speed. But It would also allow for more engine control. Is it standard practice to associate a define or id (index) with the actual stored element, or would it be better to have all resources be associated with a string? I have no idea how to implement the latter in terms of going from a string to an index, aside from using std::map, abut I still don't completely understand the concept of hashing.
Last edited on
closed account (S6k9GNh0)
There are various ways to hide implementation. I personally like opaque pointers but most would claim this is the C way of doing things, not the C++ way. C++ often uses the PIMPL idiom or abstract interfaces (like what irrlicht does).

Design is hard in this case... Take the following as advice and not sure-fire ways of doing things.

1. You can determine whether something is viable to be stored on HDD or caches if, and only if, that something is useful in multiple processes. In this case, your window is only useful in that one process, never in another instance of it. So it's not viable and you're basically just slowing things down for no reason if you do this.

2. The only time when something should be identified from string in a map is whenever you need a possibly infinite number of entries and fetching time does not matter. In this case, it might not hurt but it's certainly a waste.

3. There's nothing wrong with an opaque pointer being returned. A C++ junkie would probably suggest interfaces over an opaque pointer however. In this case, you can determine which type of interface implementation to use, create an instance of it, and pass it back. The user would just expect the interface, not the implementation, so its completely abstract.

However, resource management with this becomes complicated. You return something that requires allocation in the library and the user must learn to do something to deallocate or to let the library deallocate it (which in itself is complicated as the object would then either have to be reference counted, garbage collected, or live through its entire life).

SFML does a different approach. It just uses a global resource base that anything can access (using TLS for thread safety) that's initialized before you even start your own program. Whenever you call sfml::Window, it uses that base (or maybe not, you don't actually know) for anything that should have been initialized before hand. It then uses RAII and expects the class to clean up after itself whenever it moves out of scope.
Last edited on
If I wrap an API (system functions) into my api do is it ok to do a one to one mapping of everything?
Generally speaking, no. A one-to-one mapping is a function with the same signature that passes its parameters unmodified to the wrapped function and returns the return value unmodified. There are situations where this makes sense, though (e.g. you want to log certain calls but don't want to losse any functionality).

You'll more often want a more abstract wrapper that gives your application exactly as much functionality as you want. Say, you want a wrapper around CreateFile() that opens a temporary file. You don't really need to pass any parameters to that function, since the file name will be randomly generated and the access will be R/W.

Or, would it be better to develop my own system of storing the outputs of the systemAPI functions into classes, caches, or on the hdd during runtime???
What do you mean by that last part?

associate a define or id (index) with the actual stored element, or would it be better to have all resources be associated with a string?
I'm not sure what you mean. Can you give a couple examples?
helios wrote:
associate a define or id (index) with the actual stored element, or would it be better to have all
resources be associated with a string?

I'm not sure what you mean. Can you give a couple examples?

I think he means one of
a. ID: std::map<id_type, object_type> objects;,
b. Index: std::vector<object_type> objects;, or
c. String: std::map<std::string, object_type> objects;.

Personally, I would go with (a) in almost all cases. (c) is out because any string large enough to give sufficient uniqueness is likely to take too long to parse for most cases, and (b) is out because passing around indexes into your arrays is unsafe because any index becomes invalid if any previous element is removed, or if any new element gets inserted before it. It's worse if you have multiple threads, because then not only can the indexes become invalid in between calls to the array-owning class' interface, but they can become invalid during calls too. This could lead to very subtle bugs even in only one thread: imagine one function stores something in the array and gets the value 5. In between calls to that function, another function inserts a new element. Now, 5 refers to a different element, and the function that stored it is none the wiser. You can somewhat solve this problem by only allowing operations on the end of the stack, but that costs you a lot of flexibility. Option (a) is the clear winner.

The main downside of (a) is that you can run out of IDs, but if you have a known limit on the number of IDs (e.g. in a game, you would have a limited number of assets that can be loaded which is unlikely to exceed the word size of the platform even on 16-bit platforms (16 bits gives you 65,535 IDs)) then you'll never have that problem, and if you use a big enough data type, you can effectively ignore the possibility of running out of IDs: even 64 bits is enough that, if you generated 1 billion IDs per second, it would take you almost 300 years to run out. It has the advantages of using unique strings of equal length without the massive performance hit of generating and parsing them because you can do something as simple as just incrementing a counter and returning the result every time you need to generate a new ID. The more uniqueness you need, the larger integers you can use, although it gets complicated if you want to use more than 64 bits because the standard only guarantees up to 64-bits, any more than that is implementation defined (e.g. std::uint_least64_t could be 128-bits wide on platform x, but as there's no std::uint128_t, you couldn't rely on the extra bits unless you were writing unportable code). There is a performance hit involved with using increasingly large integer types, but it's not as large as if you were using strings, and it's exactly the same performance hit you'd have if you used indexes without any of the inflexibility. Plus, if the word size of platforms increases, the performance hit will decrease significantly (on a 64-bit platform, using 64-bit integers is as fast as using 32-bit ones on a 32-bit platform). The only remaining caveat is that if you want to use or allow multiple threads, you have to make sure your ID generator can't generate the same ID twice. I suppose you could use a mutex but that may introduce other problems (race conditions) and it wouldn't make the function re-entrant.

Here's an example of option (a): http://www.cplusplus.com/forum/lounge/113988/3/#msg626972
Note: That example is not thread-safe because two threads calling it concurrently could get the same ID for different resources.
Last edited on
Topic archived. No new replies allowed.