I have a C api to a shared (or static) library using int types and would like to upgrade to int64_t. Doing so, I want to make sure that my previous users can still upgrade the library and do not have to rewrite their whole codes.
I have come up with the solution on this gist and which replicates my code behavior (using callbacks): https://git.io/vMy8G —
// User defines a different type
#define MYLONG int
#include "interface.h"
#include "stdio.h"
// User callback using his own type
int test(const int i, const my_long var, const int j) {
printf("i = %d\n",i);
printf("var = %lld\n",(long long) var);
printf("j = %d\n",j);
printf("%lld\n", 2LL*var-11);
return var - 7;
}
int main() {
// This is what user sees
api_func functionPtr = &test;
define_callback(functionPtr);
// Simulate callback call
call_callback();
return 0;
}
#pragma once
// MYLONG is defined differently internally and externally (before importing this file)
typedef MYLONG my_long;
// Callback definition
typedef int (*api_func)(const int, const my_long, const int); // surround by int to test alignment issues
void define_callback(api_func fptr);
void call_callback();
#define MYLONG long long
#include "interface.h"
// Callback handling
api_func callback_ptr = 0;
void define_callback(api_func fptr) {
callback_ptr = fptr;
}
void call_callback() {
(*callback_ptr)(1000, 100000, 100);
(*callback_ptr)(1000, 10000000000, 100); // will overflow when user uses int
}
The library would always be compiled with #define MYLONG int64_t
while user will either use #define MYLONG int64_t
or #define MYLONG int
(this would be done automatically depending on some other settings). The latest define would ensure backwards compatibility.
Valgrind checks pass with all builds.
My question is the following:
Note that I would like to avoid writing 64 bit versions of all my functions if possible. Also, this has to work on Linux (gcc), Mac (gcc) and Windows (Visual Studio).
No. User in example.c sets callback_ptr
to :
int test(const int i, const int var, const int j)
... and call_callback()
calls that as:
int (*api_func)(const int, const int64_t, const int)
This is UB (not overflow) whenever int
is not the same type as int64_t
.
Yes.
It is undefined behavior (UB). Code is calling a function with one signature, yet the function may have an incompatible one.
Yes.
A. Certainly do not code with mis-matched-able function signatures.
B. In the end, I think OP needs a new approach. I would recommend adding a function define_callback64()
and then have the user call define_callback64()
with int (*f)(const int, int64_t, const int)
or define_callback()
as before. call_callback()
can then use the one that was set.
Code comments
Note that I would like to avoid writing 64 bit versions of all my functions if possible.
OP wants "would like to upgrade to int64_t" yet "avoid writing 64 bit versions of all my functions". This is most curious and apparently contradictory.
Perhaps re-write internal.c
as int64_t
and then call select call back functions.
The 3 const
in typedef int (*api_func)(const int, const my_long, const int)
serve no purpose.
OP seems to assume int
is 32-bit given the title. Better to write code that does not assume this. int
is at least 16-bit. OTOH, I do not see a great problem assuming int
is not wider than int64_t
, but even that is not defined by the C spec.
OP also seem to also assume long long
is int64_t
given code #define MYLONG long long
yet "library would always be compiled with #define MYLONG int64_t
". OP needs to use types correctly and consistently in code and documentation.