Handmade Hero»Forums»Code
19 posts
Designing a C API
Hi everybody, sorry to post this here, but the handmade.network forums don't seem to have a code forum, but Casey has made an API video in the past and maybe has some ideas about my following questions.

I am currently developing an API to be used by external developers and I need the API to be pure C since I want it to be easily accessible from other languages. Just wanted to know if my following list is missing anything that might make it hard/impossible to interface from other languages, e.g. .Net, Python, COM, or really anything else.
One thing I noticed with COM is that it doesn't seem to support "unsigned" types, only int and long (not sure if that's correct though). Some of my parameters don't make sense to be signed values so I'd prefer to use unsigned int in those cases.

- Can't use references, need to use pointers
- struct as function parameter / return value need to use #pragma pack(push,1) so that there are not compiler dependant alignment issues
- enum evaluates to int, so I will be using int for enum values instead
- if a 64 bit enum is required I probably need to fudge it by using #define 0x0123456789ABCDEFLL

Anything else?
Thanks.
511 posts
Designing a C API
pragma pack isn't necessary, every language understands C's struct packing (which is standardized across the platform).

And designing a struct without padding isn't that hard.

You don't really need 64 bit enums, if you need more than 4 billion values then you don't want to statically allocate them.
Mārtiņš Možeiko
2559 posts / 2 projects
Designing a C API
Afaik COM supports signed types. At least MS themselves are using unsigned types everywhere, for example DWORD here: https://msdn.microsoft.com/en-us/...ary/windows/desktop/dd370875.aspx

And I don't think it's the COM you need to be worried about for not supporting unsigned types. It's the Java that doesn't support signed type, if you want to support Java

If you don't need to support more that 2billion or 2^63 numbers, then I would not worry about this and just use unsigned types for all reasonable languages. Then Java people simply can use signed type for this and hope or assume it won't overflow.

I would suggest not to pass and return struct's by value. Sometimes this makes interop a bit harder. Pass them by address.
19 posts
Designing a C API
Thanks for your feedback!
I wouldn't have thought that passing structs by reference would be better/easier, but now thinking about it that actually makes sense.
So with the structs I will just have to make sure that all members are 64 bit aligned then and add padding at the end to make the whole struct a multiple of 64 bit? And then just omit the pragma(pack, 1) ?

The reason that I'd like a 64 bit enum to exist is not for actually using that many values; there is one type where I use 64 bit values and some of the bits are actually flags. An enum just looks a bit nicer than having a big number of #defines "floating" around. Not a big issue anyway.
The values are just constants and they are not used for doing maths with them anyway, so casting the values to int won't change them anyway.

On that note, some of my colleagues want me to use const, e.g. for returning a version string which is an unmutable string (const char*); I don't really like const and agree with the points Casey made about it. I would like to know, however, that if I decided to use const if that would cause any issues with trying to access my C API functions from other languages?

Thanks.
511 posts
Designing a C API
If you have more than 32 flags then it's probably better to separate them into smaller logical groups (makes it easier if you ever need to add a few).

Const tends to infect the code like a virus, which is fine if you don't mind enforcing const correctness. You can const_cast around the interface but it's kinda ugly.
Mārtiņš Možeiko
2559 posts / 2 projects
Designing a C API
Edited by Mārtiņš Možeiko on
You can still have 64-bit enums and avoid using define's. Just use uint64_t argument (or similar) as type.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
enum Foo
{
   Foo_Flag0 = 1ULL<<0,
   Foo_Flag1 = 1ULL<<1,
   Foo_Flag2 = 1ULL<<2,
   ...
   Foo_Flag63 = 1ULL<<63,
};

void API_fun(uint64_t flags);


As for const-corectness - in my opinion for return types you can do whatever you want. If you want const'ness there, then use it. If not, then don't. For users it won't matter much. I myself prefer seeing const in API. It serves me as nice auto-documentation feature that tells me that I should not try to modify returned memory.

But you really should use that for argument types in functions or members in structures. Because if user of your API writes const correct code, then he won't be able to use your API cleanly. He will need to put bunch of ugly casts to cast away const from his types.

Anyway this const stuff matters only for C/C++. Typically when people write wrappers/marshallers in other languages const almost doesn't matter or its not possible to do that at all (to specify const, but its still possible to use your const API of course). For example - ctypes in Python, or P/Invoke in C# - nobody cares about specifying const there.
19 posts
Designing a C API
I have just been revisiting the 64-bit enum situation and it turns out that the C-standard defines enum values to be of size int (enum values must fit into an int); so this basically means that pure C does not support 64-bit enums.
C++ on the other hand is more flexible and will allow 64-bit enums.
29 posts
I enabled the dark theme.
Designing a C API
Edited by drjeats on Reason: better wording
A little more on signedness of integers and their width:

JavaScript and Lua pre-5.3 don't have a strong integer type, they use doubles, which have a safe range for integers, plus or minus 2^53-1:

https://developer.mozilla.org/en-...l_Objects/Number/MAX_SAFE_INTEGER

Lua 5.3 added support for honest-to-goodness signed integers. The standard width is 64 bits. It's possible to have Lua use 32 bit integers, but it's directed at embedded folks and requires defining a symbol when compiling:

https://www.lua.org/manual/5.3/manual.html#2.1

Also PostgreSQL of all things doesn't support unsigned integers xD. It's not a real problem, folks just use one size up or reinterpret to signed when storing, and reinterpret back to unsigned when processing query results. Just a small thing to make note of in case any folks using your lib might want to store handles or enum flags or something in a database.