Dependency-injection in C++ is not convenient, especially when you bring “Free” C functions into the mix. There are several approaches you can use to isolate these functions, but the one I’ve had the most success with is to create C++ classes whose sole purpose is to wrap calls to C functions. This approach works quite well, both in minimizing the impact to the production code, and simplifying unit testing, particularly when paired with the googlemock C++ Mocking framework.

I’ve been doing this for some time and have started to feel some pain in the maintenance area. Wrapper functions are starting to be duplicated between wrapper modules, and classes depending on wrappers are importing functions that they’ll never call. But most of all, building out and maintaining these wrapper classes takes time and is extremely tedious. So being the lazy developer that I am, I wrote C Function Wrapper Generator (CFWG) to do the heavy lifting for me.

Basically it’s a collection of Python scripts that generate C++ classes whose sole responsibility are to provide interfaces to C Functions.  They provide a transparent API to the C functions so you don’t have to change how you’re calling them (much), but the main advantage they provide is during unit testing: you now have a seam to control side-effects and return values of C functions that you would otherwise have had to deal with by hand.

The main interface is a YAML configuration file that looks like this:

  1. Functions:
  2.     – name         CreateFileA
  3.       real_header  winbase.h
  4.       include_headerwindows.h
  5.     – name         CloseHandle
  6.       real_header  winbase.h
  7.       include_headerwindows.h
  8.     – name         WriteFile
  9.       real_header  winbase.h
  10.       include_headerwindows.h
  11.     – name         CreateEnvironmentBlock
  12.       real_header  userenv.h
  13.       include_headeruserenv.h
  14. Aggregators:
  15.     – nameFileExists
  16.       functions:
  17.          – CreateFileA
  18.          – CloseHandle

This basically tells CFWG what functions you want to wrap, where it can find them, and what header should be included in real code that calls it.  You can also specify custom aggregators if you want.  This allows you to pull in just the functions you need.  In the case of shared libraries this will prevent callers from getting linkage they don’t actually use.

CFWG generates three files:

  1. ICWrappers.h – Contains an interface for each C function in the form ICFunction (e.g. ICreateFileA).  This also contains an IMasterCWrapper (which multiply inherits from the individual ICFunctions) which comes in handy for unit testing (and my examples) and an interface for each aggregator specified in the configuration.  You want your production code to depend on these interfaces.
  2. Component/CWrappers.h – Contains the implementation for each interface listed in ICWrappers.h.  These actually call the real C function, simply passing the arguments down.  You pass an instance of one of these to any production code that needs the function (or functions in the case of an aggregator).
  3. Mock/CWrappers.h – Contains GMock implementations for IMasterCWrapper in ICWrappers.h and one for each Aggregate interface specified in the configuration file.
  1. /* Unit class (has dependencies) */
  2. class Unit::Foo
  3. {
  4. public:
  5.     Foo(ICreateFileA &createFileA, IWriteFile &writeFile, ICloseHandle &closeHandle)
  6.         : m_createFileA(createFileA), m_writeFile(writeFile), m_closeHandle(closeHandle)
  7.     {}
  8.     void bar();
  9. private:
  10.     ICreateFileA &m_createFileA;
  11.     IWriteFile &m_writeFile;
  12.     ICloseHandle &m_closeHandle;
  13. };
  14. void
  15. Unit::Foo::Bar()
  16. {
  17.     HANDLE handle = m_createFileA.myCreateFileA(
  18.         “testFile.txt”,
  19.         GENERIC_WRITE,
  20.         0,
  21.         0,
  22.         CREATE_NEW,
  23.         FILE_ATTRIBUTE_NORMAL,
  24.         0);
  25.     if (INVALID_HANDLE_VALUE == handle)
  26.     {
  27.         throw std::exception(“CreateFileA failed!”);
  28.     }
  29.     const char *toWrite = “yay!!”;
  30.     DWORD numBytesWritten = 0;
  31.     if (!m_writeFile.myWriteFile(
  32.         handle,
  33.         toWrite,
  34.         strlen(toWrite),
  35.         &numBytesWritten,
  36.         0) || numBytesWritten != strlen(toWrite))
  37.     {
  38.         m_closeHandle.myCloseHandle(handle);
  39.         throw std::exception(“WriteFile failed!”);
  40.     }
  41.     m_closeHandle.myCloseHandle(handle);
  42. }
  43. int
  44. main()
  45. {
  46.     Component::MasterCWrapper wrapper;
  47.     Unit::Foo foo(wrapper, wrapper, wrapper);
  48.     foo.Bar();
  49. }

Now you can write unit tests for Foo and easily test all these conditions.  Well, you could before too, now you just don’t have to maintain all those boilerplate wrappers anymore.

Check it out on Github and let me know what you think!

P.S. This depends heavily on the cppclean C++ AST generator, so props to those guys for providing a great tool.