API evolution and keyword arguments in C

by Sergey Lyubka, Cesanta Software, for Dublin C/C++ User Group

API evolution

  • Ever worked on a C/C++ library?
  • A library exports API functions to the external users
  • APIs evolve, new features get added
  • Example: API function int foo(const char *path);
  • 2 Major strategies for API evolution
// My library API

int foo(const char *path);
int bar(void);
int baz(double a, double b);

Strategy 1: backward compatibility


int foo(const char *path);

Version 1


int foo(const char *path);

int foo_2(const char *path, int flags);

Version 2

API function signatures do not change. Library upgrade is seamless, customer's build does not break.

API gets polluted with the foo_2-like functions, e.g. WinAPI  CreateProcessEx, POSIX gethostbyname_r, etc

Strategy 2: breaking backward compatibility


int foo(const char *path);

Version 1


int foo(const char *path, int flags);

Version 2

API function signatures change! Library upgrade requires re-integration.

API stays clean and non-polluted.

Holy grail: combining the two

Is it possible to keep backward compatibility, while not polluting the API by introducing dozen of function "variants" ?

  • C++ has operator overload
  • Some languages like Python have optional and keyword arguments
  • Keyword arguments allow to extend the API without breaking compatibility:
def foo(path, flags=None, otherFutureParam='whatever'):
    print path, flags

Using structures

  • C does not have operator overload
  • C does not have keyword arguments
  • One solution is to use structures as an arguments placeholder:
struct foo_args {
    const char *path;
};



int foo(struct foo_args args);

Version 1

Version 2

struct foo_args {
    const char *path;
    int flags;
};


int foo(struct foo_args args);

Works! For binary compatibilty, include version marker in the structure (WinApi often uses structure size)

Using structures (cont)

  • Many well known API use that approach
  • WinAPI DialogBoxIndirect()
INT_PTR WINAPI DialogBoxIndirect(
  _In_opt_  HINSTANCE hInstance,
  _In_      LPCDLGTEMPLATE lpTemplate,  // <-- a structure
  _In_opt_  HWND hWndParent,
  _In_opt_  DLGPROC lpDialogFunc
);

There is also DialogBox(), DialogBoxParam(), and DialogBoxIndirectParam()

DLGTEMPLATE dialog_template = { ... };
DialogBoxIndirect(NULL, &dialog_template, NULL, DlgProc);

Keyword arguments

But C does not have keyword arguments!

...Or does it ?

  • C99 and later supports designated initialializers for structures
  • C99 and later provides __VA_ARGS__ macro
  • C99 and later supports compound literals which allows to initialize structures in the arguments list

Designated Initializers

C90  initialization


  struct params p1 = { .flags = 1, .path = "my_file.txt" };
  struct params p2 = { .flags = 1 };      // path is NULL
  struct params p3 = { .path = "my_file.txt" };  // flags is 0

  // Structure declaration
  struct params {
    int flags;
    const char *path;
  };

C99  initialization: designated initializers


  struct params p1 = { 1, "my_file.txt" };
  struct params p2 = { 1 };  // path is NULL

Compound Literals

  // Structure declaration
  struct params {
    int flags;
    const char *path;
  };

  struct params p = { .flags = 1, .path = "my_file.txt" };

Declaration and initialization at the same time

Declaration and assigment in different places: using compound literal to make assignment

  // Structure declaration
  struct params {
    int flags;
    const char *path;
  };

  struct params p;

  ....   // Some code

  p = ((struct params) { .path = "my_file.txt" });

Putting it all together

struct foo_args {
  int flags;
  const char *path;
};

#define foo(...) foo_impl((struct foo_args) { __VA_ARGS__ })
void foo_impl(struct foo_args);

lib_foo.h

void foo_impl(struct foo_args args) {
  if (args.path != NULL) {
    ...
  }
}

lib_foo.c

  foo(.path = "my_file.txt");  // flags initialies to 0

  // Expands to:
  // foo_impl((struct foo_args) { .path = "my_file.txt" });

user_code.c

Thank You!

contact me at

sergey.lyubka@cesanta.com

API evolution and keyword arguments in C

By Sergey Lyubka

API evolution and keyword arguments in C

  • 891