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