Have you ever seen a codebase crawling with
HANDLE and similar types? I bet it was a Windows application which never has been (and probably never will be) ported to another operating System, because it had fallen victim to a leaky or nonexistent encapsulation of WinAPI.
This problem is not restricted to WinAPI or other platform specific APIs, it can occur whenever you choose to call thirdparty code form your program.
Bound to the API
When calling thirdparty APIs we not only call functions of that API, we often have to use types the API designers chose. Those types can be function parameter types or return types, but they also can be classes we have to instantiate to be able to use the API.
If we are careless, we have API calls all over our code base, so we have to include the headers of that API in many of our source files. We are tightly coupled to it, and that is a bad situation to be in.
Imagine you want to port that Windows application to Linux. Or you want to get rid of that crappy compiler and IDE that is causing endless debug sessions, but you have to get rid of its proprietary framework of GUI and other classes first.
I have been in both situations, and such endeavors really are a pain in the neck if your complete code base heavily depends on an API, because you can’t easily replace it with something else.
Encapsulation is the key
The first step to get rid of API dependencies is to encapsulate those API calls in a few dedicated classes. One option is to write wrapper classes and only use the API functions in their implementation. That way you can hide the implementation details, i.e. which API you are calling and how you are calling it, inside the source files and they won’t propagate through your whole code base.
If you want to be able to exchange APIs quickly, e.g. because you want to compile your code for different target platforms, you can do so by providing a basic interface for the functionality you need and derive classes from it that each use another API to implement that functionality.
Usually this approach also leads to a better design, because it complies the SoC and SRP principles: The “normal” classes in your code concentrate on why and when to call the functionality, while the wrapper class decides how to call the API.
As an additional benefit, having the API wrapped away makes it much easier to plug in a mock implementation that can come in very handy if you want to properly unit test the code that uses it.
Encapsulating the API calls is not enough
Now that we have encapsulated the API calls in dedicated classes, we still might not be completely decoupled from it, because we have to provide parameters and use their return types. This is where I have seen several projects do a suboptimal job, because they still pass the types of the APIs through all of their code.
Sometimes you can use just ordinary types, e.g. a
LPDWORD is just a
unsigned long*, so you can pass an
unsigned long* in and out of your WinAPI wrapper and it just works.
There are other types that are not as easily manageable, e.g. if you really want to separate your code from the API you don’t want to use the API’s enums and instead provide your own and have some translation going on each time you pass an enumerator in and out of your wrappers.
If you use several APIs in parallel and they implement the same concept differently, those easy solution often won’t work any more. In such cases type erasure and other methods to provide handles to unknown types can come in handy.
Encapsulating third party APIs can be tedious business and often involves a lots of boring boilerplate. However, if you want to stay flexible and independent of a specific API, it’s better to invest some time early than being stuck with undesirable dependencies for ever – because “later” you probably won’t have the time to correct those things.