hourglass interfaces for c++ apis - cppcon 2014
DESCRIPTION
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/codeTRANSCRIPT
![Page 1: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/1.jpg)
Hourglass Interfaces for C++ APIs
Stefanus Du Toit Thalmic Labs
@stefanusdutoitImage © Erik Fitzpatrick, CC BY 2.0
![Page 2: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/2.jpg)
Motivation and Introduction
![Page 3: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/3.jpg)
Shared Libraries
Code DataLibrary
![Page 4: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/4.jpg)
Shared Libraries
Code Data
Interface
Library
![Page 5: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/5.jpg)
Shared Libraries
Code Data
Interface
Client!Program
Library
Code Data
![Page 6: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/6.jpg)
Shared Libraries
Code Data
Interface
Client!Program
Library
Code DataABI!
!
Common libraries
![Page 7: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/7.jpg)
Different ABIs? Subtle problems…
Class A
Class B
![Page 8: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/8.jpg)
Different ABIs? Subtle problems…
Class A
Class B
Debug Build
Release Build
![Page 9: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/9.jpg)
My Goal for Libraries
Get out of the client’s way.
![Page 10: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/10.jpg)
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
![Page 11: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/11.jpg)
![Page 12: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/12.jpg)
![Page 13: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/13.jpg)
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
![Page 14: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/14.jpg)
Example: libhairpoll• A library for creating hair-related polls, taking
votes, and tallying the results
![Page 15: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/15.jpg)
hairpoll.htypedef 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);
![Page 16: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/16.jpg)
HairPoll class - client-facingclass 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; };
![Page 17: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/17.jpg)
Best Practices
![Page 18: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/18.jpg)
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
![Page 19: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/19.jpg)
Opaque Types
typedef struct hairpoll* hairpoll_t; !
void hairpoll_vote( hairpoll_t hairpoll, int32_t option );
![Page 20: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/20.jpg)
Opaque Types
extern "C" struct hairpoll { Poll actual; };
![Page 21: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/21.jpg)
Opaque Typesextern "C" struct hairpoll { template<typename... Args> hairpoll(Args&&... args) : actual(std::forward<Args>(args)...) { } !
Poll actual; };
![Page 22: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/22.jpg)
Opaque Typesclass 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; };
![Page 23: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/23.jpg)
Opaque Typeshairpoll_t hairpoll_construct(const char* person) { return new hairpoll(person); } !
void hairpoll_destruct(hairpoll_t poll) { delete poll; }
![Page 24: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/24.jpg)
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 );
![Page 25: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/25.jpg)
Integral types
Using char is OK, though: !
int32_t hairpoll_add_option( hairpoll_t hairpoll, const char* name, const char* image_url );
![Page 26: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/26.jpg)
Enumerations
typedef enum motu_alignment { heroic_warrior = 0, evil_warrior = 1, }; !
int32_t motu_count( int32_t alignment );
![Page 27: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/27.jpg)
Error Handling
void hairpoll_vote( hairpoll_t hairpoll, int32_t option, error_t* out_error );
![Page 28: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/28.jpg)
Error Handlingtypedef 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);
![Page 29: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/29.jpg)
Errors → Exceptionsstruct 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; };
![Page 30: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/30.jpg)
Errors → Exceptionsvoid hairpoll_vote(hairpoll_t hairpoll, int32_t option, error_t* out_error);
!
void vote(int option) { return hairpoll_vote( _opaque, option, ThrowOnError{} ); }
![Page 31: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/31.jpg)
Exceptions → Errorstemplate<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; }
![Page 32: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/32.jpg)
Exceptions → Errorsvoid 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++; }); }
![Page 33: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/33.jpg)
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);
![Page 34: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/34.jpg)
Using lambdas as callbacksstd::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; }
![Page 35: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/35.jpg)
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
![Page 36: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/36.jpg)
Symbol visibility#include "visibility.h" !
… !
HAIRPOLL_EXPORT hairpoll_t hairpoll_construct(const char* person); !
HAIRPOLL_EXPORT void hairpoll_destruct(hairpoll_t poll); !
…
![Page 37: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/37.jpg)
Symbol visibility
On Clang and GCC, add to your compile: !
-fvisibility=hidden !
(applies to library only)
![Page 38: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/38.jpg)
Remember to extern “C”!#ifdef __cplusplus extern "C" { #endif !
// C API goes here !
#ifdef __cplusplus } // extern "C" #endif
![Page 39: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/39.jpg)
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
![Page 40: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/40.jpg)
Interfacing with those “other” languages
![Page 41: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/41.jpg)
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…
![Page 42: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/42.jpg)
Hair Polls in Pythonhairpoll = 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)
![Page 43: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/43.jpg)
Hair Polls In Pythonfrom 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]
![Page 44: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/44.jpg)
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
![Page 45: Hourglass Interfaces for C++ APIs - CppCon 2014](https://reader036.vdocuments.mx/reader036/viewer/2022081413/547e842db37959652b8b549c/html5/thumbnails/45.jpg)