Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Hourglass Interfaces for C++ APIs - CppCon 2014

18,651 views

Published on

The full video from CppCon is available here: https://www.youtube.com/watch?v=PVYdHDm0q6Y

C++ provides a much richer set of abstractions than C. Classes, templates, overloading, and other core C++ features can be leveraged for more readable syntax, better compile time typechecking, more open ended genericity and improved modularity. On the flip side, C89 still boasts some advantages over C++, especially when viewed through a pragmatic lens. C ABIs on many platforms have been stable for decades, practically every language supports binding to C code through foreign function interfaces, and including nearly any C89 header has a negligible effect on compile time on modern computers.

The hourglass pattern provides the best of both worlds. By placing a headers-only C++ interface on top of a minimal C89 interface, it makes ABI issues a non-concern, and enables just a single binary to be shipped for a given platform. It makes providing bindings from other languages easier, and prevents ABI issues like incompatibilities between debug and release variants of runtimes.

This talk provides an overview of the pattern and teaches practical techniques for its implementation using C++14. Real shipping projects inspired the best practices described in the slides.

Code corresponding to this presentation can be found here: https://github.com/CppCon/CppCon2014/tree/master/Presentations/Hourglass%20Interfaces%20for%20C%2B%2B%20APIs%20-%20Stefanus%20Du%20Toit%20-%20CppCon%202014/code

Published in: Software
  • Thanks for a really nice talk. Helped me quite a bit. Although I have a question regarding this pattern and vectors which I have posted on stack overflow at: https://stackoverflow.com/questions/33429270/how-to-pass-vectort-between-library-module-boundary-using-hourglass-pattern
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • The video for this talk is here: https://www.youtube.com/watch?v=PVYdHDm0q6Y
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Eric, videos from CppCon (including this talk) will be made available over the next few weeks.
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Is there a video of this talk?
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

Hourglass Interfaces for C++ APIs - CppCon 2014

  1. 1. Hourglass Interfaces for C++ APIs Stefanus Du Toit Thalmic Labs @stefanusdutoit Image © Erik Fitzpatrick, CC BY 2.0
  2. 2. Motivation and Introduction
  3. 3. Shared Libraries Library Code Data
  4. 4. Shared Libraries Interface Code Data Library
  5. 5. Shared Libraries Interface Code Data Client! Program Library Code Data
  6. 6. Shared Libraries Interface Code Data Client! Program Library Code Data ABI! ! Common libraries
  7. 7. Different ABIs? Subtle problems… Class A Class B
  8. 8. Different ABIs? Subtle problems… Class A Class B Debug Build Release Build
  9. 9. My Goal for Libraries Get out of the client’s way.
  10. 10. My Goal for Libraries Don’t make my clients: • build my code if they don’t want to • change how they build their code • rebuild their code because I shipped a fix in mine • learn a new language if they don’t want to
  11. 11. Why does this help? • Avoids ABI-related binary compatibility issues • Enforces no use of non-trivial C++ types in the binary interface (e.g. std::string) • Keeps internal layout of data types a secret • Makes binding to other languages much easier ! • Still get all the convenience of C++ at both ends
  12. 12. Example: libhairpoll • A library for creating hair-related polls, taking votes, and tallying the results
  13. 13. hairpoll.h typedef struct hairpoll* hairpoll_t; ! hairpoll_t hairpoll_construct(const char* person); void hairpoll_destruct(hairpoll_t poll); ! int32_t hairpoll_add_option(hairpoll_t hairpoll, const char* name, const char* image_url); ! void hairpoll_vote(hairpoll_t hairpoll, int32_t option); ! typedef void (*hairpoll_result_handler_t)(void* client_data, const char* name, int32_t votes, const char* html); ! void hairpoll_tally(const hairpoll_t hairpoll, hairpoll_result_handler_t handler, void* client_data);
  14. 14. HairPoll class - client-facing class HairPoll { public: HairPoll(std::string person); ~HairPoll(); ! int addOption(std::string name, std::string imageUrl); void vote(int option); ! struct Result { std::string name; int votes; std::string html; }; std::vector<Result> results() const; ! private: hairpoll_t _opaque; };
  15. 15. Best Practices
  16. 16. Parts of C to use in the thin API • C89 + const and // comments • Functions, but no variadic functions • C primitive types (char, void, etc.) • Pointers • forward-declared structs • enumerations • Function pointers are OK, too
  17. 17. Opaque Types typedef struct hairpoll* hairpoll_t; ! void hairpoll_vote( hairpoll_t hairpoll, int32_t option );
  18. 18. Opaque Types extern "C" struct hairpoll { Poll actual; };
  19. 19. Opaque Types extern "C" struct hairpoll { template<typename... Args> hairpoll(Args&&... args) : actual(std::forward<Args>(args)...) { } ! Poll actual; };
  20. 20. Opaque Types class Poll { public: Poll(std::string person); ! struct option { option(std::string name, std::string url); ! std::string name; std::string url; int votes; }; ! std::string person; std::vector<option> options; };
  21. 21. Opaque Types hairpoll_t hairpoll_construct(const char* person) { return new hairpoll(person); } ! void hairpoll_destruct(hairpoll_t poll) { delete poll; }
  22. 22. Integral types Prefer stdint.h types (int32_t, int64_t, etc.) over plain C integer types (short, int, long): ! void hairpoll_vote( hairpoll_t hairpoll, int32_t option );
  23. 23. Integral types Using char is OK, though: ! int32_t hairpoll_add_option( hairpoll_t hairpoll, const char* name, const char* image_url );
  24. 24. Enumerations typedef enum motu_alignment { heroic_warrior = 0, evil_warrior = 1, }; ! int32_t motu_count( int32_t alignment );
  25. 25. Error Handling void hairpoll_vote( hairpoll_t hairpoll, int32_t option, error_t* out_error );
  26. 26. Error Handling typedef struct error* error_t; ! const char* error_message( error_t error ); ! void error_destruct(error_t error); ! ! void hairpoll_vote(hairpoll_t hairpoll, int32_t option, error_t* out_error);
  27. 27. Errors → Exceptions struct Error { Error() : opaque(nullptr) {} ~Error() { if (opaque) { error_destruct(opaque); } } error_t opaque; }; ! class ThrowOnError { public: ~ThrowOnError() noexcept(false) { if (_error.opaque) { throw std::runtime_error(error_message(_error.opaque)); } } ! operator error_t*() { return &_error.opaque; } ! private: Error _error; };
  28. 28. Errors → Exceptions void hairpoll_vote(hairpoll_t hairpoll, int32_t option, error_t* out_error); ! void vote(int option) { return hairpoll_vote( _opaque, option, ThrowOnError{} ); }
  29. 29. Exceptions → Errors template<typename Fn> bool translateExceptions(error_t* out_error, Fn&& fn) { try { fn(); } catch (const std::exception& e) { *out_error = new error{e.what()}; return false; } catch (...) { *out_error = new error{"Unknown internal error"}; return false; } return true; }
  30. 30. Exceptions → Errors void hairpoll_vote(const hairpoll_t poll, int32_t option, error_t* out_error) { translateExceptions(out_error, [&]{ if (option < 0 || option >= poll->actual.options.size()) { throw std::runtime_error("Bad optn index"); } ! poll->actual.options[option].votes++; }); }
  31. 31. Callbacks typedef void (*hairpoll_result_handler_t)( void* client_data, const char* name, int32_t votes, const char* html ); ! void hairpoll_tally(const hairpoll_t hairpoll, hairpoll_result_handler_t handler, void* client_data, error_t* out_error);
  32. 32. Using lambdas as callbacks std::vector<Result> results() const { std::vector<Result> ret; ! auto addResult = [&ret](const char* name, int32_t votes, const char* html){ ret.push_back(Result{name, votes, html}); }; ! auto callback = [](void* client_data, const char* name, int32_t votes, const char* html){ auto fn = static_cast<decltype(&addResult)>(client_data); (*fn)(name, votes, html); }; ! hairpoll_tally(_opaque, callback, &addResult, ThrowOnError{}); ! return ret; }
  33. 33. Symbol visibility #if defined(_WIN32) || defined(__CYGWIN__) #ifdef hairpoll_EXPORTS #ifdef __GNUC__ #define HAIRPOLL_EXPORT __attribute__ ((dllexport)) #else #define HAIRPOLL_EXPORT __declspec(dllexport) #endif #else #ifdef __GNUC__ #define HAIRPOLL_EXPORT __attribute__ ((dllimport)) #else #define HAIRPOLL_EXPORT __declspec(dllimport) #endif #endif #else #if __GNUC__ >= 4 #define HAIRPOLL_EXPORT __attribute__ ((visibility ("default"))) #else #define HAIRPOLL_EXPORT #endif #endif
  34. 34. Symbol visibility #include "visibility.h" ! … ! HAIRPOLL_EXPORT hairpoll_t hairpoll_construct(const char* person); ! HAIRPOLL_EXPORT void hairpoll_destruct(hairpoll_t poll); ! …
  35. 35. Symbol visibility On Clang and GCC, add to your compile: ! -fvisibility=hidden ! (applies to library only)
  36. 36. Remember to extern “C”! #ifdef __cplusplus extern "C" { #endif ! // C API goes here ! #ifdef __cplusplus } // extern "C" #endif
  37. 37. Lifetime and Ownership • Keep it simple: construct (if needed) and destruct • Always use RAII on the client side! • Can use refcounting (e.g. shared_ptr) both internally to the library and in the client • For callbacks, you can use the stack
  38. 38. Interfacing with those “other” languages
  39. 39. Foreign Function Interfaces • “something that allows calling code written in one language from another language” • C = de-facto standard • Languages with support for calling C functions in shared libraries include: • Common Lisp, Java, JavaScript, Matlab, .NET (C#, F#, etc.), Python, Ruby, OCaml, basically everything…
  40. 40. Hair Polls in Python hairpoll = lib.hairpoll_construct("Stefanus Du Toit", None) ! skeletor = lib.hairpoll_add_option(hairpoll, "Skeletor", "<long URL omitted>", None) beast = lib.hairpoll_add_option(hairpoll, "Beast Man", "<long URL omitted>", None) ! lib.hairpoll_vote(hairpoll, skeletor, None) lib.hairpoll_vote(hairpoll, beast, None) lib.hairpoll_vote(hairpoll, beast, None) ! def print_result(client_data, name, votes, html): print name, votes ! lib.hairpoll_tally(hairpoll, hairpoll_result_handler(print_result), None, None) ! lib.hairpoll_destruct(hairpoll)
  41. 41. Hair Polls In Python from ctypes import * ! lib = CDLL("libhairpoll.dylib") lib.hairpoll_construct.restype = c_void_p lib.hairpoll_construct.argtypes = [c_char_p, c_void_p] ! lib.hairpoll_destruct.restype = None lib.hairpoll_destruct.argtypes = [c_void_p] ! lib.hairpoll_add_option.restype = c_int lib.hairpoll_add_option.argtypes = [c_void_p, c_char_p, c_char_p, c_void_p] ! lib.hairpoll_vote.restype = None lib.hairpoll_vote.argtypes = [c_void_p, c_int, c_void_p] ! hairpoll_result_handler = CFUNCTYPE(None, c_void_p, c_char_p, c_int, c_char_p) lib.hairpoll_tally.restype = None lib.hairpoll_tally.argtypes = [c_void_p, hairpoll_result_handler, c_void_p, c_void_p]
  42. 42. Summary • Hourglass = C++ on top of C89 on top of C++ • Avoids ABI issues, sneaky dependencies, etc. • Hides object layout and other implementation details • Makes FFI bindings easy
  43. 43. Q&A http://blog.sduto.it/ @stefanusdutoit

×