Handmade Hero » Forums » Code » Designing a C API
19 posts
#11203 Designing a C API
2 weeks, 5 days ago

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?
233 posts
#11204 Designing a C API
2 weeks, 5 days ago

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
1247 posts
1 project
#11206 Designing a C API
2 weeks, 5 days ago

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
#11207 Designing a C API
2 weeks, 5 days ago

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?

233 posts
#11208 Designing a C API
2 weeks, 5 days ago

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
1247 posts
1 project
#11209 Designing a C API
2 weeks, 5 days ago Edited by Mārtiņš Možeiko on March 8, 2017, 5:10 p.m.

You can still have 64-bit enums and avoid using define's. Just use uint64_t argument (or similar) as type.
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.