Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
How to write better game libraries (handmade.network)
146 points by LucaSas on Dec 8, 2019 | hide | past | favorite | 123 comments


It always baffles me when a library comes with its own arcane build code. Instructions like "Requires Python" invariably turn out to actually mean "Requires hours of debugging".


IMO the ideal way to get your library used is to generate a single .h and .c file.


Some libraries don't even ship a .c file, all the code is in the header


The downside of that is that all code is in the header. As in you will compile all the library code whenever you include the header. As third-party library code is something you probably seldom change yourself, you don't want your build to spend time building it all the time.

Sure some of those libs may have some define you need to set in one of your .c files so the implementation will go there but then whenever you change that file it will result in a recompile of the third-party lib. So then you go and create a separate .c file just for the lib and we're back to why the lib author didn't do that in the first place?

No, I think what sqlite does is pretty neat. If you're doing development in the sqlite code-base you have lots of files (so you can manage it) but if you just want to use sqlite, you have the amalgamation which is one .c and one .h.


The problem is that C is a "lingua franca" and yet has no inherent facility for compile-time codegen (i.e. "real", grammar-level macros, as opposed to C's lever-level macros), so you need something else (whether that be the venerable m4 or yacc, or random Python scripts) to serve as your substitute for "real" macros.

In languages that do have "real" macros, you don't see the same problem. (On the other hand, in most of them, you see a need to call out to a C compiler to generate FFI code, which is sometimes just as bad when you're using a PL with a managed runtime and your build-env didn't otherwise have any need for a C toolchain.)


This is bad advice on the c++ part. If you don't use RAII, then you could've just not done the c++ lib (and have just a c lib). Exceptions, raii and smart pointers are what makes modern c++ so pleasant, next to templates.


Hi, I am the author of the article. Regarding RAII, what I mention in the article is that it is ok to provide RAII wrappers as long as you also provide C++ wrappers for the non-RAII plain structs and functions from C. I think this is the best way to satisfy everyone.

When it comes to exceptions and rtti the sad reality is that about 50% of the C++ community doesn't use (for one reason or another), so if you do use them in a library it can have an impact.


FWIW I’ve never been at a job that allows exceptions...but smart pointers and RAII are great.


No exceptions. No exceptions.


Exceptions are being fixed. See https://www.youtube.com/watch?v=os7cqJ5qlzo


On the other side of the coin, I don't think I've ever had a job in which anyone would have ever considered banning exceptions (although I have worked at some where people did know that some other companies did ban them). The world of programming is a very broad church indeed.


Examples of companies or projects that ban exceptions

Google, and therefore Chromium

https://google.github.io/styleguide/cppguide.html

Mozilla, and therefore Firefox

https://firefox-source-docs.mozilla.org/tools/lint/coding-st...

I'm pretty sure WebKit also doesn't use exceptions. It's not listed in their style guide but searching the repo I don't find any instances and the repo history shows turning them off.


Often bare-metal embedded toolchains don't support stack unwinding. I've even worked on an embedded linux where the toolchain didn't support stack unwinding. It made porting certain code to the platform interesting, to say the least.


What kinds of work were you doing that allowed exceptions?


Off the top of my head, just listing a handful of various projects; a commercial static analyser, various GUIs, a broadcast industry projectile tracking and information presenting system, a multiple networked radar (and similar) network data fusion project, broadcast automation software, various data processing and display software sets, software simulating mechanical movement for artificial test data generation, image processing and morphing. I could go on, but hopefully that gives a flavour. A whole big bag of things.

From my perspective, banning exceptions is the exception (a ha ha); not the rule. I've worked on embedded software that (I expect) didn't support exceptions in the provided compiler, but I don't consider that an explicit ban on exceptions - they simply weren't possible.


> modern C++

> pleasant

Nice joke. Hour-long compilations, type-unsafe templates with unreadable errors, and circular references are considered pleasant now?


- reasonable organized code doesn't take hours. even template heavy code is quite fast these days

- templates are type safe. it is just the error messages are not helpful a lot of times. C++20's concepts should help

- circular references. that's a thing in C as well. as soon as you need interact with your libraries user to allocate/deallocate resources.

C++ has a large surface to interact with. Yes you can shot yourself in your foot. However, the more you know you can use the features to your advantage instead of shotting yourself in the foot. At the point you gain more than you lose.


Templates are duck typing at C++ compile time (which is their runtime). Yes, concepts should help.

'Modules via copy-and-paste' (= #include) is what I would hurl at C++ these days. Though they are working on that as well, I heard. Though https://vector-of-bool.github.io/2019/01/27/modules-doa.html doesn't sound hopeful.


> Templates are duck typing at C++ compile time (which is their runtime).

That description only applies of you venture into the dark realm of template metaprogramming, and anyone who ventures into those lands is wise enough to understand that the tool is not the one to blame if one decides to abuse a feature to apply it in a way it was not (initially) designed to be used.


You must be trolling... Templates in C++ ARE for metarogramming. The fact that they work as a glorified text substitution (and they are only coming around to fix them in 2020) leaves no apology for the tool.


> You must be trolling... Templates in C++ ARE for metarogramming.

That's an ignorant statement both wrt generic programming (you know, the whole purpose of C++ templates) as well as the history of C++, in particularly how C++ template metaprogramming was discovered by accident after C++ templates were already implemented, widely used (see the STL), and standardized.


Template metaprogramming makes things harder, but even in the most boring C++ hello world program, you have someone misusing a bit-shift operator for IO.

Concepts would presumably enforce some kind of thematic relationship between different overloads for the same group of functions and operators. (At least that's how we are doing it in Haskell with typeclasses. But we have a more forgiving syntax that allows us to make new operators.)


Just a few days ago I've read accounts of C++ projects compiling for hours because C++ has a buggy compilation process, e.g. it copies and pastes every header everywhere instead of only one copy. Don't blame developers for obviously flawed language standards.

Templates are of course type unsafe, I'm not sure why you're lying while also admitting that they will only become type-safe in C++20.

Circular references deserves more comment: there is a difference between unmanaged and managed memory. C is unapologetically unmanaged, which cements its position on the embedded scene. But C++ has tried to be the in-between language: you have your raw pointers, and you have your refcount GC (“smart pointers"). And the thing that strikes me is the Cppistas have overwhelmingly chosen GC, but their language's GC is the worst kind, with circular references, performance costs (yes, smart pointers have a lot) and ultimately without memory safety (as raw pointers are still there). They've truly chosen tge worst of both worlds yet don't have the integrity to admit that the future is with managed, traced GC. C++ is like driving a car from the fifties and bragging about how comfortable and modern it is with that new internal combustion engine!


> Hour-long compilations,

If your project takes hours to compile them you only have yourself to blame.

> type-unsafe templates

There is no such thing.

> with unreadable errors,

That's an implementation issue, not a language issue.

> and circular references

If you write buggy code then you only have yourself to blame.


> Exceptions, raii and smart pointers are what makes modern c++ so pleasant

Point in case for lack of portability, UE4 doesn't support exceptions unless you do a custom build.


I don't see it mentioned, but it's important to note that you don't have to write your library in the same language that you expose to your users. You can write everything in C++/Rust/Python and expose something compatible with the C ABI and people will be able to use it (provided they have your toolchain…)


>provided they have your toolchain…

That's important here. Some of my projects have something around 10 cross-compilation toolchains for various platforms. If a library is written in standard C99, adding it to the project is a breeze. Otherwise...


Indeed. And a C style api is still valid C++ too. Nothing in C++ mandates one to design a boost-esque inheritance and template monstrosity.

Functions and opaque handles are often way simpler for the user.


Fully agree with you.

Anyone providing a library written in pure C, better be serious about security and prove that they have taken all the required steps to handle memory corruption and UB exploits.

We really need more liability on software development.


Do note where this article resides. Casey Muratori (of Handmade Hero, a game dev tutorial project) is a self-proclaimed anything-not-C hater. A community has formed around his project, and has, of course, adopted his hard-line stance.


Interesting, thanks for the hint.


The earlier comment was misinformed. Handmade Network is inspired by the series, yes, but stands on its own and embraces new languages and very different projects. See the Handmade conference[0] for examples.

[0] https://handmade-seattle.com


The problem for game libraries in general is that game dev lives in c++ world and c++ is awful for libraries. Most c++ devs I know would rather start writing a program by defining string than by learning to handle proper library management tooling. I'm not an expert so I'm not sure if it's just a culture thing or if there are inherent features in the language that make library usage hard, but yeah. That's that.


It’s pretty inherent to the tooling around the language. Basically because theres a lack of “standard” tooling, so you either go lowest-common-denominator (header only), or you have to fuss with very gross build systems that have weird quirks on different platforms.

C is easier in this regard just because the language evolves so slowly that even though the build and library systems are bad, theyre a known bad that people can work around. No such luck with C++, where its bad and ever changing.


I wish the C++ world would adopt bazel eventually as their standard tooling base.


> I wish the C++ world would adopt bazel

Why do you believe picking a specific build system is relevant wrt libraries, particularly a build system whose main selling point is build speed.


As a warning: While C99 is a really nice language - especially over C89 - please note that the Visual Studio C compiler still does not support the full C99 specification, and it is not possible at all to compile C99 as C++ code (which sometimes is useful).

I usually recommend to write library-level code in the "subset of C99 which compiles both in C and C++ mode on GCC, clang and MSVC". This is basically a version of C that's somewhere C89 and C99 (basically a "C95").

Another quite valid option is to define a pure C-API first, but implement the "inner library code" in a simple C++ (just be careful with "modern C++", since this usually results in increased compilation time and binary size).


The one feature from C99 that I rely on (from the top of my head) is designated initializers which is still not in C++...

    enum {
        FPGAPARAM_FOO,
        FPGAPARAM_BAR,
        FPGAPARAM_BAZ,
        NUM_FPGAPARAM_KINDS
    };

    struct FpgaparamInfo {
        const char *name;
        int address;
        int args;
    };

    static struct FpgaparamInfo fpgaParamInfo[NUM_FPGAPARAM_KINDS] = {
        [FPGAPARAM_FOO] = { "FOO", 0x1337, -1 },
        [FPGAPARAM_BAR] = { "BAR", 0x666, -1 },
        [FPGAPARAM_BAZ] = { "BAZ", 0x42, -1 },
    };
The FpgaParamInfo is basically a mapping from a FPGAPARAM_??? value to additional information. We can make as many of these mappings as we want, and can define them where we want, which means it's all nicely modular. That's not really possible when modelling in a OOP fashion.

I really like this style of programming since it's data first and it minimizes the amount of actual code. Designated initializers are important because the order in which the items in "fpgaParamInfo" are given doesn't matter. Without designated initializers, programming in this style would probably lead to many hard to find bugs when the enum is changed and not all associated data items are updated.


Thanks for the comment, I might steal your "subset of C99 which compiles both in C and C++ mode on GCC, clang and MSVC" quote and put it in the article if that's ok ;)


Is there a benefit to building C code with a C++ compiler?


C++ has some stricter type checking by default, but most of this can be achieved by raising the warning level when compiled as C.

Nevertheless I received enough requests to make my C libraries 'C++ compliant' that I gave in :)


Only if your project is already mostly C++ and you would like to use your C++ compiler for everything, to keep the build process simpler.


Nice article :)

Might be worth touching on error callbacks/logging as an error handling strategy.

Sometimes an error is not recoverable in the sense that the calling code can't really do anything about it, but the library should attempt to make progress anyway instead of halting the entire program.

By allowing users to specify an error callback, this means they can log errors, capture stack traces, assert, or whatever.

This isn't that helpful for smaller libraries with smaller-scoped processes, but if it's something like a renderer or interactive audio lib, those often just need to be given a bunch of frame time to do work with the complex input you've prepped and fed to it, and trying to propagate error codes up out of that simulation step would both contort the inner code and not be as helpful as an error callback.

With an error callback you can assert, set breakpoints, or do whatever. But more importantly, by default you can have it just log so when you inevitably in a bug it doesn't prevent everyone else from getting work done while it keeps asserting until you fix your shit.

This will be a less common need than the other standard error reporting mechanisms, but is important to get right if your library has these complex internal preconditions that you want to make visible to the client when violated.


Honestly, for me all I would like is the ability to include/import one or more files as needed, helpfully separated by basic functionality, without needing to include the entire library, have a bare minimum of dependencies, hopefully, module independent, the ability to build painlessly for different platforms with a minimum of code changes and a painless build process for the library itself, ideally one I can include in my own build process without a lot of effort.

I've used some libraries that do some of these things, but I can't think of any that does all of them.


> Not everyone wants to use C++ (some prefer C).

This could be very well flipped over. If I have the source available, I'd much rather deal with C++ bindings.


Hi, I mention in the article that "It is easier in general for a C++ user to use a C library than it is for a C user to use a C++ library." which is where the advice comes from.


I agree. But "wants" is a term indicating personal preference. I understand that C is best suited for maximum portability and performance. But as a C++ developer I prefer libraries that come with C++ interfaces (and you do mention that it is good to provide a C++ wrapper as well).


Thanks a lot for your feedback. I decided to remove that since I think you are right about indicating personal preference. I do not want this article to be a list of preferences but rather an analysis of the considerations that popular C libraries make (eg: stb, sokol, etc) and that new authors should also reason about.


Because the binary interfaces generated by C libraries are simple and therefore universally supported. It's just normal symbols and calling conventions. Conforming to this ABI benefits users of every other language, not just C++.

C++ ABIs are rather complex and much harder to interface with. The existence of concepts like virtual functions and exceptions significantly complicates the implementation of foreign language interfaces.

https://wiki.osdev.org/System_V_ABI https://itanium-cxx-abi.github.io/cxx-abi/abi.html


Why not a C++ library with an optional C API?


It's possible to use the "hourglass" pattern where your implementation is written in C++, you export a C API (and ABI), and then you can provide an additional, light C++ wrapper based on the C API.


Because C++ is one of the most complex languages in the world, hence a pain to interop with. C FFIs, on the other hand, are ubiquitous.


The idea (and one that worked well in several projects I've worked on) is that you use C++ as main language of the library and make outside bindings C.

It does require some discipline, but C++ has many portable constructs that make life significantly easier when writing business logic.


Some of these recommendations are awesome, others aren’t good, IMO.

> Always prefix your names to avoid name collisions

Solved by C++ namespaces.

> Use header guards instead of #pragma once.

#pragma once is supported by all mayor compilers, header guards can introduce bugs, also #pragma once builds measurably faster: https://github.com/electronicarts/EASTL/blob/3.15.00/include...

> Expose constants to the user using constexpr variables.

Modern C++ has strongly-typed scoped enums for such constants.


Hi, thanks for your feedback. I am the original author.

Regarding prefixes, I advice that you start by writing the library in C and then wrap it in C++ for a variety of reasons that you might want to consider. In C++ you should indeed always use namespace.

Header guards have the advantage over pragma once that they are standard and you can also use them to check if a library is included. I might remove that since maybe it's not that important and people might different views.

Regarding constants, I was referring to things such as numeric constants for which you would constexpr in C++. Maybe I can be more explicit there. Thanks for the feedback.


I advise you start by writing the library in C++ and then wrap it in C if you need C ABI of the library.

It’s very hard to write correct C code which does IO and supports multithreading. Take a look at Microsoft’s implementation of fprintf, copy-pasted from Windows 10 SDK: https://gist.github.com/Const-me/f1bb320969adde6c79694265ea6... They use RAII to set & revert the locale, and to lock RAM buffer to avoid corruption by another threads. They use C++ lambda for exception handling. They even use C++ template to avoid code duplication between printf and wprintf.

But these C++ shenanigans are not exposed to user, user calls their `printf` (possibly in a code built by C compiler) and it just works.


> It’s very hard to write correct C code which does IO and supports multithreading

This is just a random unsubstantiated statement. There's nothing particularly "hard" about writing IO libraries that is language-specific. Multithreaded or not.


When you do IO and multithreading, functions often need to acquire and release stuff: mutexes or other locks, resources, locales. Even more so in videogames, e.g. OpenGL code often calls glMapBuffer / glUnmapBuffer many thousand times each frame.

The hard part is making sure you release stuff every time you acquire stuff, exactly once. C++ RAII makes it almost trivially simple, but standard C has nothing comparable. When you only targeting gcc and clang can use __attribute__(cleanup) in C, it helps but still it’s more limited and more error prone compared to destructors.


> The * hard part * is making sure you release stuff every time, exactly once.

Oy vey... you can't be serious. That's rudimentary basics of using any API.


It’s easy enough to correctly use a small API in a small program. It doesn’t matter at all for short-living apps which clean up their resources by exiting the process.

There’re also other programs in wide use, which need to reliably work for hours, sometimes weeks. Some of them have huge amount of code they built from, written by many people over many years. Combine that with large enough APIs (some peripheral devices have hundreds of writeable registers of state; or D3D11 exposes huge amount of very complicated state, only limited by VRAM amount which is measured in gigabytes) and it’s very easy to make bugs in such programs.

Leaks of memory, handles, sockets, and many other resource types e.g. GPU ones. Deadlocks caused by locked mutexes, or threads which exit but forgot to release something they needed to release. Unwanted changes to global or thread state, both internal to the process and external (locales, formatting options, console colors, process and thread priorities, current directory, environment variables, CPU registers like FPU flags and interrupt masks, GPU render states) caused by some code changing stuff but not reverting the changes back. Unwanted state changes of custom peripheral devices, due to the same reason.

C++ RAII is not a silver bullet, but it does help a lot for all these things.


It's easy enough to implement correct API usage semantics in a program of any size.

If you need to rely on RAII in order not to screw things up, then it's an issue with the coding style or the application design. That's what needs fixing. Not the language choice. You got it backwards.


It’s a hard problem, and language + runtime support helps. That’s why C++ has RAII, C# has IDisposable / using, Java has try-with, python has enter / exit / with, golang has defer, and so on. The only reason C has nothing comparable, it’s almost 50 years old now.

The problem is only tangentially related to API semantics. The problem is mutating state. The state is not necessarily managed by an API, for instance CPU registers aren’t, you modify them directly. Same with other global state like I/O formatting options and locales, these things are just global variables.


C++ features don’t help when you’re programming in straight C, as the article advises.


Other than UNIX like kernels and tiny PIC micro-controllers no one else should still be programming in straight C in 21st century.


Reading through your comments the only take-away appears to be that you just really don't like C for whatever reason.

C is a perfectly fine language for tasks of all kinds. The fact that it expects programmers to take greater care when using it is not a reason enough to dismiss it as a general-purpose language. Not everyone needs (or wants) to ride a tricycle wearing knee pads and a helmet to get from A to B.


Ah the old straight jacket argument against computer security, never gets old.

The reason is very simple, the billions of wasted money fixing security exploits caused by industry's adoption of C.

Morris worm is more than 30 years old, and the old ways can still be used to attack modern systems that people insist in writing using C.

https://msrc-blog.microsoft.com/2019/06/14/prevent-the-impac...

https://support.apple.com/en-us/HT210348

https://msrc-blog.microsoft.com/2019/07/16/a-proactive-appro...

https://kernsec.org/wiki/index.php/Kernel_Self_Protection_Pr...

https://security.googleblog.com/2019/08/adopting-arm-memory-...


I agree that "straight C" is not the best choice anymore... but C++ isn't great either. It inherits most of C's weaknesses and for libraries, it makes it very difficult to use the code from languages other than C++. By making a library in C++ you are basically limiting it to being used only in C++ projects. (Yes, wrapper generators for C++-to-whatever exist, but my experience with using e.g. Qt from other languages has always been pretty terrible).

Even in 2019, the C ABI is the one universally agreed-upon cross-language ABI for languages compiled to native code.


In what concerns security, both should be nuked.

However given that at least C++ does support ways to tame C, after all the whole purpose was for Bjarne never to repeat his Simula into BCPL rewrite experience ever again, it is up for security conscious to decide what legacy they want to leave, when the option is between both those languages.

On the other hand maybe C should be used to write Skynet, so that we stand a chance.


That may be so, but the central thesis of the article is that C is the still the appropriate language in which to write libraries for interoperation. If you believe otherwise, I suspect that a rebuttal of the author’s arguments in favor of C would be well-received as a top-level comment here— there seem to be a lot of people that agree with you, but nobody has yet stated why they hold that opinion.


Sure, first of all there is no such thing as C ABI, only OS ABI.

C ABI happens to be the mixed up with OS ABI, on OS written in C like UNIX clones, on mainframes, and other competing OSes that isn't the case, because they use other systems languages on their stack, or even some kind of bytecode based interoperability format.

However since the context here is game libraries, C ABI == OS ABI pretty much applies everywhere (except WebAssembly or Android JNI) and lets leave at there.

Since C++98, writing a library in C++, even if exposing it as extern "C", provides the following benefits for the quality of code implementation:

- less implicit conversions

- use of reference types instead of pointers for memory accesses we can be sure are never allowed to be null

- use of namespaces instead of Assembly style programming of having to come up with prefixes for code organization

- bounds checking for strings, vectors and other related data structures provided one uses the library types. They can even be left turned on for release mode, if the profiler shows there is no visible impact on hot paths

- use of RAII to manage library internal state and reduce leak occurrences

- ISO C++ working group is actually striving for reducing the amount of UB from its 200+ use cases, unlike ISO C group

- strong typed enums introduced in C++11 don't have implicit conversions and must map to their underlying types

- type safe compile time code execution, specially helpful in games, used for stuff like generating trigonometry tables

- templates as replacement for pre-processor magic that eventually goes subtly wrong when the #include order gets misplaced or too few parenthesis are used

If this still sounds absurd, well all major C compilers are now implemented in C++, Microsoft rewrote their C standard library in C++ with extern "C" entry points, Android NDK is actually implemented in a mix of C++ and Java (via JNI) also using extern "C" calls.


> but nobody has yet stated why they hold hat opinion.

I did, in my first comment.

C++ namespaces lead to much more readable code, compared to these prefixed names.

C++/11 scoped enums eliminate a class of bugs: when you have many different constants on the API surface, multiple functions accepting them, and erroneously use the constant of a wrong function. Example:

    enum Lod { Low, High };
    enum SomethingElse { Other };
    void setLoD( enum Lod v );
    void bug()
    {
        setLoD( Other );
    }


Especially with this expanded commentary, you do show some advantages of C++ over C, but you have only addressed the advice for how to proceed once you’ve chosen C and not the author’s reasons for preferring C to C++ (or any other language):

— Every language out there has a way to call into C

— If your code is slower than C, someone will rewrite it in C.

— If your library is written in C it means it can be used on any OS, console or mobile device and even on the web.

— Not everyone wants to use C++ (some prefer C).

— It is easier in general for a C++ user to use a C library than it is for a C user to use a C++ library.

— C++ is not as easy to write wrappers for in other languages.

— Unless you limit which C++ features you use (to the point where you are pretty much left with C) a lot of people won’t be able to use your library.

PS. If your original comment had been phrased the way you put it here I might have made the same comment, but I would not have downvoted it. Here, you’re at least providing some supporting evidence for your assertions which makes it a much more valuable contribution to the conversation.


> If your code is slower than C, someone will rewrite it in C.

When used correctly, C++ is not slower than C. Sometimes faster, a classic example is C qsort versus std::sort.

> If your library is written in C it means it can be used on any OS, console or mobile device and even on the web.

C++ is good in that regard. I know only 1 mainstream platform where C++ adds significant friction compared to C, that’s iOS, because their objective C is a superset of C. The rest of them (Windows, Linux including embedded, game consoles, android) support C++ just fine.

> Not everyone wants to use C++ (some prefer C).

Most people are OK with C++, especially in the context of game development.

> to the point where you are pretty much left with C

No, not with C. Namespaces and scoped enums are awesome.

Another thing, inside the implementation of the library, you can use whatever C++ language features you please, even the features that would be inappropriate when exposed at the API surface of the library. For example, MS implemented parts of their C runtime library with C++ classes, RAII, lambdas and templates, eliminating duplicated code for char/wchar_t routines. Obviously, you don’t need C++ to consume that library, just C is enough, but it’s implemented in modern C++. On my system, that source is in "C:\Program Files (x86)\Windows Kits\10\Source\10.0.18362.0\ucrt\stdio\output.cpp".


> I know only 1 mainstream platform where C++ adds significant friction compared to C, that’s iOS, because their objective C is a superset of C.

Apple’s compiler supports overlaying Objective-C features on top of C++ instead of C; it’s called Objective-C++.


I once forked a C library, porting some parts to C++ in the process: https://github.com/Const-me/nanovg/ I’ve retained original C API, only used C++ in the implementation.

Then I’ve got an e-mail from a developer who asked a few things how to back port my changes to C. I answered their questions, but I was curious why. They replied it’s because Objective C and iOS. Personally, I haven’t been developing for iOS for several years, but I don’t think people would do such things for lulz.


That developer must be misinformed. I have been developing a native component that needs to interface with C++ APIs from the app and Objective C APIs from iOS. The only thing you need to remember is to not mix exceptions from C++ and Objective C. The object models are also incompatible, but the compiler prevents you from mixing them.


Also, if C++ is only used in the library implementation... you wouldn't even need Objective-C++ unless you needed to add Objective-C API calls to the library's own source files. Otherwise, you could just compile the library's source files as regular C++, and link them together with your Objective-C code. This works exactly the same way as combining C and C++, and Xcode (the IDE) has no issue with it. So it seems very likely that the developer was misinformed.


Except that only old timers, have the Objective-C++ documentation available, because Apple has removed it from their web site.


It actually extremely easy to write C++ wrappers for other languages. Examples I‘m familiar with are pybind11 (similar to boost python) and the node C++ API. That other languages don’t have similarly convenient libraries is entirely their own fault.


If people choose to and it gives them joy, and they are productive and even learn valuable things, why not let them be?


Because if someone else happens to use that library, it might blow up on their face, e.g. WhatsApp dependency on android-gif-drawable.

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-1193...


Are you looking for a safe way to use your computer? Turn it off.

Other than that, I'm not surprised the file in question is C. Even ignoring the fact that it was you to get the link. And even ignoring that it's about a security problem...

Other than that, from a user perspective, software written in C is among the most reliable software I'm using. I'm looking at Linux, vim, xterm and so much of the infrastructure that I don't even know by name.

Some people value "security" more than they value the beauty of a nice, a maintainable, or a practical program. That's ok as far as I'm concerned...


The guys attending the Linux Kernel Security Summit seem to think otherwise, plenty of stuff there to entertain yourself.


Sure there is. But never be blinded, Linux is very performant and rock solid from an end user's viewpoint. Sometimes it helps putting things into perspective.



If you like random links, here is another one... https://news.ycombinator.com/item?id=21741504


The difference being that my links have to do with damage C keeps bringing into IT and the respective monetary losses.

They are not random, only to those that don't care about improving the quality of our eco-system.

Software liability couldn't come soon enough.


What do you think are the monetary losses by unmaintainable software, resulting in bad and extremely unsafe practices (not related to memory, but...) like in my link? What do you think are the monetary losses from developers going mentally ill because they don't understand and don't like the shit they're doing?


> Solved by C++ namespaces.

I've never understood what's the practical difference between NS::foo() and NS_foo() with regards to preventing name collisions. Can someone enlighten me?

I know some disadvantages of the namespacing variant, though. There are now multiple names for the items defined in the namespace: the qualified one and the unqualified one. The latter is often not unique in practice, since the programmer relies on the qualified name for uniqueness. In effect, making simple text searches for identifiers is very unreliable. Note I do use IDEs, but I also code in vim, and I need to do simple text searches even when working in Visual Studio.

Additionally, there's no guarantee that there are no spaces around the scoper. I believe "NS :: foo" is just as valid, which makes me uneasy with regards to text search, as well.

Another issue I have:

    $ cat test.cpp 
    namespace NS { int foo() { return 0; } };
    $ g++ -c -o test.o test.cpp
    $ nm test.o
    0000000000000000 T _ZN2NS3fooEv  # I hate my life
> Modern C++ has strongly-typed scoped enums for such constants.

I haven't found those working for me. Apart from the namespacing issue described above, I have issues with explicit enum types. One issue is that I often need to put sentinel / "missing" values (typically the value is -1) where an enum value is expected. Even more often, I want to iterate over the values of an enum. C++'s enum "type safety" makes working like this really unergonomic.

The way I go about this is I don't even use names for my enum types, and I fully qualify the enumeration values.

    enum {
        FPGAPARAM_BLA,
        FPGAPARAM_BLUB,
        FPGAPARAM_FOO,
        NUM_FPGAPARAM_KINDS
    };

    struct ASDF {
        int fpgaparamKind;   // obvious what kind of values are expected here...
    };

    for (int i = 0; i < NUM_FPGAPARAM_KINDS; i++) {
        struct ASDF asdf;
        asdf.fpgaParamKind = i;
        do_asdf(&asdf);
    }
In programming, the slightest mistakes, like putting a "-" instead of a "+", result in program bugs. These mistakes are much more likely to be made (and much harder to spot) than mistakes involving enum values from the wrong set. I won't let programming ergonomics be ruined in the name of "type safety".


> what's the practical difference between NS::foo() and NS_foo() with regards to preventing name collisions.

They both do the job. There’re 2 practical differences.

1. You can write `using namespace` inside functions or the whole .cpp files. This often makes the consuming code more readable.

2. Sometimes you want to replace implementations. With prefixes it gonna be massive changes likely to introduce new bugs. With namespaces, replace `using std::vector` with `using eastl::vector` and you’re done.

> Even more often, I want to iterate over the values of an enum.

I only need to do that rarely. When I do, I cast types like this:

    enum struct eParamKind : uint8_t
    {
        Bla, Blub, Foo, valuesCount
    };
    for( uint8_t i = 0; i < (uint8_t)eParamKind::valuesCount; i++ )
    {
        const eParamKind pk = (eParamKind)i;
        // Whatever
    }
> I won't let programming ergonomics be ruined in the name of "type safety".

I disagree on ergonomics. VS makes much easier to consume API with strongly typed enums: ePar<Ctrl+Space>::f<Enter>, to type eParamKind::Foo It’s similar with namespaces versus prefixes BTW, IDE will first auto-complete the namespace, then only list members of that namespace once you type the `::`

Update: another C++ feature relevant for game development is overloaded operators. Games often do non-trivial amount of math on small vectors, matrices and quaternions. Overloaded operators make sense for them.


Code completion works very well with plain identifiers. No need for namespaces.

> replace `using std::vector` with `using eastl::vector` and you’re done.

The pipe dream of reusability.. If I ever happen to be in a situation where that will work, I'll happily use a text replace to change my identifiers. Or just link a different library if it has the same names.


> Code completion works very well with plain identifiers.

It doesn’t on my PC.

I’ve copy-pasted C enum from your example, when I type FPG<Ctrl+Space> there’s no way to auto-complete just the FPGAPARAM_ part, to be able to then press F to get FPGAPARAM_FOO. Using VC2017 here, with latest Visual Assist.

Which C++ IDE are you using?

> The pipe dream of reusability.

Did it more than once.

Here’s one open source project where I’ve replaced most parts of the C++ standard library with EASTL: https://github.com/Const-me/vis_avs_dx

Here’s my header-only C++ library which allows users to switch between 16-bytes/32-bytes wide SIMD by using different C++ namespace, either Intrinsics::Sse or Intrinsics::Avx: https://github.com/Const-me/IntelIntrinsics/


I use VS at work, and I'm very happy with its code completions (except that the auto-popup behaviour is so unreliable or at least unintuitive. I'm sure there is an option to turn that off). I'm not aware of a way to complete to the largest prefix that is common to all completions, although something like that would be rather easy implement, too.


And then there is things like windows.h defining max which breaks std::max. Sure that one has another define which prevents it but not all libraries have.

C++ defines don’t respect namespaces so you are pretty much screwed in anycase.


The OP’s advice to avoid macros in the library’s API is a good one. Please read my previous comments, it begins with “Some of these recommendations are awesome”.

BTW, you can disable these windows.h macros by defining NOMINMAX. I usually do, because I prefer min/max from <algorithm>; in some edge cases std::min / std::max can be twice as fast because compiler guarantees to compute arguments exactly once.


C++ is not being used by all indie game developers so I imagine the article was written with that in mind, that the library needs to work with C code as well.


Make is a much the lingua franca of build systems as C is for programming, so there's no reason not to provide a (simple) makefile. The alternative they suggest is worse anyway.


Windows is the reason make is not a good choice. Visual Studio is a de-facto standard for Windows development and it's not exactly makefile-oriented.

The lowest common denominator for build instructions that works everywhere is literally "set your include paths like so and compile these sources".


How strange. I have tinkered with a couple of FOSS games of Windows and none of them required VS (not to mention another couple of other non-game projects). Maybe that's because they often are multi-platform projects so Make is the best choice at the global level for them?

> The lowest common denominator for build instructions that works everywhere is literally "set your include paths like so and compile these sources".

Yes, but that's quite inconvenient and error-prone ; a typo in a -D flag is very likely to not compile what you wanted.

Make is not a multi-megabyte program with a frack tonne of dependencies. Having your users to install it in order to build your project is far from being completely outrageous (unless for coughmicrosoftcough cults that consider the command-line is evil).


Funny thing is that AtmelStudio (for embedded development of Atmel/Microchip chips) is basically a VisualStudio shell with a makefile generator (built in c#). This is then pushed through make and invokes gcc...


build tools are a mistake in the majority of cases


And then the author provides example where there’s Dear IMGUI which is c++.


Dear ImGui has a very C-style API though, it's just flat a function API wrapped in a namespace, and about the only other C++ feature that's used in the API is default function arguments.

For usage from C code, there's cimgui, which is an automatically generated C-API wrapper:

https://github.com/cimgui/cimgui


We just need more single-file headers like nothings' stb libraries


Since this article lays so much emphasis on C, I have an honest question to everyone. What is a good way for a beginner to learn C in the current time? The minefield of undefined behavior is really overwhelming to a beginner. Are there any good resources that teach C the right way with good advice and best practices to navigate the UB minefield?


I can’t advise on modern resources as I’ve known C for a while now, but I can speak a bit about the mindset required. Programming in C isn’t much different than programming in any other language, just without the safety railings. If you’re at the stage where you are typically fixing bugs by understanding what is happening and then making a single targeted change to correct the issue, you should have no significant trouble working in a C codebase.

If, on the other hand, you’re in the habit of shotgun debugging, where you make repeated changes to the problem code until it appears to work, you’re quite likely to leave behind various problems that will be hard to figure out.

Often, experience is the best teacher. If you’re not exposing your program to malicious users (aka. the public at large), the most serious issue you’re likely to run into is either a program crash or data corruption— nothing that will really harm your computer, but may cause you grief as you try to figure it out. In that process, though, you’ll learn an awful lot about how everything works. So, go write some programs for your own use and see how they crash and burn so that the next thing you make is more stable. Eventually you’ll start to intuitively spot trouble before it actually happens.


Being careful with malloc/free and using a modern toolchain that will warn on 99% of stuff is pretty much all you need. People way overemphasize UB


Yes, you need to dive into books like

Writing Secure Code

https://www.amazon.com/Writing-Secure-Second-Developer-Pract...

Secure Programming Cookbook for C and C++

http://shop.oreilly.com/product/9780596003944.do

SEI CERT C Coding Standard

https://wiki.sei.cmu.edu/confluence/display/c


As a good resource for learning C I would recommend Casey Muratori's tutorials: https://www.youtube.com/watch?v=F3ntGDm6hOs&list=PLEMXAbCVnm...

If you are interested in learning more about writing complex software in C consider checking out HandmadeHero.

Sadly there need to be more good resources on learning how to write good C and low level software. I am hoping my article can be a starting point for people who wish to learn about library design for example.


If you already know it a bit, I think https://modernc.gforge.inria.fr/ is pretty good.


> the UB minefield

While UB quirks exist, they are WAY off the beaten path and it takes an effort to run into them. Doubly so if you are just starting with the language.

Just treat C as a thin convenient layer over the hardware that expects you to think and act responsibly in exchange for this nearly raw access.


In C (and C++) something as simple as forgetting to initialize a variable lands you in UB-land already, nearly every person I knew when I was learning myself (and still now) ran into these things _very quickly_.

Not to mention compilers make fun-times out of this by sometimes zeroing memory in debug and then not doing so for release builds (Hi MSVC!)..

The "thin layer over hardware" idea is a thing of the past as soon as optimizations come into the picture, and even then.


Please add (C) to the title of the article. In this form, it is misleading because most of the advice only applies to C.


The target audience isn’t C programmers, but library authors that want their library to be usable with multiple programming languages. Most of the advice applies to C libraries because the article starts by advising you to write your library in C, and gives several reasons.


The (games) in the title pretty much limits the article to C / C++


>> C is the lingua franca of programming

I don't have the exact numbers, but this seems debatable.


It's the lingua franca in that it's the standard for interoperating. Every high level language I've used has had at least one C FFI readily available.


We do have a mutual understanding on whether C foreign function interfaces are available for other programming languages, which does enable languages to send system requests (for systems which are written in C) or to create optimized C code to parse documents or calculate formulas. I am aware that the concept of making languages talk with C interfaces exist. And I made the above statement knowing this.


I think the term lingua franca is most often used to indicate a commonly understood language, not one that everybody actively uses.

Six of the eight most popular programming languages (from https://www.tiobe.com/tiobe-index/) have C-like syntax. I think most people would agree.

Edit: I think that in this context, Reelin's comment is more appropriate: This is not about understanding, but about interoperability.


> Write the library in Standard C99

Why not write it in Rust, and provide C99 bindings? Using stock C for the actual library doesn't sound like a good idea to me, when today there are better alternatives.

And for sure avoid using Metal lock-in directly. Use Vulkan, which Apple should have supported from the beginning. For lock-in targets, there are translation options from Vulkan.


Hi, the reason for using C99 is that it is fully compatible with other languages. If you write a library in Rust you might be tempted to use Rust only feature which will then make it hard to wrap the library for other languages. And if you don't use those features from Rust in your library people will comment that your library isn't Rust enough. Same thing applies to C++. To that extent my advice is to write C in C rather than C in Rust or C++.

Also if people don't have a Rust toolchain setup, compiling the library and using it from source would be hard. In some cases integrating Rust in their toolchain could be hard.

Regarding metal and vulkan I will edit the article to mention Vulkan there too alongside metal. Thanks for your feedback.


I suppose wrapping it for other languages can be harder, but should be doable I think. The benefit of Rust though is using a much better language with rich standard library.

As for toolchain, Rust can be set up basically anywhere llvm can, which is quite a lot. There are rare cases where llvm wasn't ported yet to, but I don't think they are enough to make C a compelling option in general, and I don't think any of them are gaming related.

If you are in such case - then sure, but otherwise, I'd still prefer Rust.


I think the Rust standard library is actually a liability in this case, not an advantage. The rust stdlib will essentially be an extra dependency and also the rust standard library doesn't have allocators, so unless you are very careful you might violate the principle of not doing allocations for the user. Also, even if it did have allocators, passing allocators from to Rust via a C interface would probably be awkward.

So if you do choose to make the implementation in another language, there are extra considerations that you have to take into account.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: