[CPP] Guide: C++ on Playdate

How to use C++ on Playdate

This guide was written by someone using Linux. It is slightly less likely to work out of the box if you are using a Mac, and less likely still to work on Windows with MSVC.

C works great on Playdate, but to use C++, a bit of tweaking is required. A few different steps are required, some of which will enable additional features in C as well, such as __atribute__((constructor)).

The Simple Way

➤➤➤ Download and follow the instructions in the readme of this repository. ⮜⮜⮜

More Elaboration

Makefile

TLDR: replace C_API/buildsupport/common.mk:

common.mk.zip (1.9 KB)

Assuming your build script eventually includes C_API/buildsupport/common.mk in the Playdate SDK, you must modify common.mk to support .cpp files. This is very easy. Attached is a modified version of common.mk. If you have already modified common.mk, these are the lines you need to have:

CC   = $(GCC)$(TRGT)gcc -g -Wstrict-prototypes
CPP  = $(GCC)$(TRGT)g++ -g -fno-exceptions -nodefaultlibs -nostdlib
_OBJS	= $(patsubst %.cpp,%.cpp.o,$(patsubst %.c,%.o,$(SRC)))

$(OBJDIR)/%.cpp.o : %.cpp | OBJDIR DEPDIR
    mkdir -p `dirname $@`
    $(CPP) -c $(CPFLAGS) -I . $(INCDIR) $< -o $@

Furthermore, you may wish to remove -Wstrict-prototypes from CPFLAGS as it will cause gcc to emit a warning when compiling C++ source files (as it is meaningless in C++).

CMake

TBA -- contributions welcome!

.init/.fini section support (C_API/buildsupport/setup.c)

TLDR: replace C_API/buildsupport/setup.c:

setup.c.zip (759 Bytes)

C++ relies on the .init/.init_array ELF sections to run constructors on global variables, and to assign values to global variables which cannot be computed at compile time. Some dialects of C also use this feature, for example, to have functions which are run before main (see __attribute__((constructor)) in gcc). Similarly, the .fini section runs destructors after main completes.

Playdate's linker script actually has explicit support for .init/.init_array sections, but they are not actually used at runtime! This is because when the Playdate OS loads your code, it simply calls the eventHandler function (actually the eventHandlerShim function defined in setup.c, which then calls eventHandler). Therefore, we must modify eventHandler or eventHandlerShim to run the .init code (and the .fini code as well). While you can do this in your own main.c, it's simpler and more universal to just modify setup.c instead. Attached above is a modified setup.c which enables this. It simply loops through the list of init functions (and fini functions) and runs them during the start/terminate events.

The linker script also mentions a .preinit_array section. I don't know what this is, but I added support for it anyway.

Exception Handling / Stack Unwinding

TLDR: remove -fno-exceptions Makefile, implement whatever functions the linker reports are missing, and replace link_map.ld:
link_map.ld.zip (698 Bytes)

To implement stack unwinding for exceptions requires the symbols __exidx_start and __exidx_end to be defined. I don't 100% understand how this works, so I just copied this other linker script. Add these lines, then you can remove -fno-exceptions:

.ARM.extab : {
        *(.ARM.extab* *extab.*)
   	} > EXTRAM

   .ARM.exidx : {
        PROVIDE (__exidx_start = .);
        *(.ARM.exidx* *exidx.*)
        PROVIDE (__exidx_end = .);
   	} > EXTRAM

At this point, if you re-enable linking against standard library by removing -nostdlib, you will likely get a linker error that the functions _kill, _exit, and _getpid are missing. In the following step, we will add support for various missing functions, but if you are in a rush, you can implement them yourself. Their signatures are:

void _exit(int code);
int _kill(int pid, int sig);
int _getpid(void);

C++ standard library support

Turns out newlib leaves some important functions as an exercise for the reader. Make sure -fno-exceptions and -nostdlib do not appear in your Makefiles. Then add pdnewlib.c from this repository to your project, and make sure to call eventHandler_pdnewlib() as the first thing in your eventHandler.

8 Likes

Impressive feat! I haven't looked at making C++ because I took the C API as a challenge to go back to C for once, but this might come handy for bigger projects, thanks!

I ended up getting the same issue with _exit missing, as I tried to use it in debug builds when assets are missing, thank you for confirming it wasn't a misconfiguration on my side.

@Eiyeron Are you using linux or mac? I have not tried doing this on windows yet, but I imagine the _exit problem is unique to newlib, which I imagine might not be what msvc uses.

I use Windows, which sports its own C runtime (and should be spec-complete regarding C11, but I haven't confirmed that yet). The difference in runtimes can sometimes provide some fun moments, eh?

Hi! Trying on Intel Mac. Copied the goodies to the playdate SDK buildsupport. Doing a make in the playdate-cpp top level gives me

clang -g -Wa,-ahlms=build/OBJDIR -g -dynamiclib -rdynamic -g -lm -DTARGET_SIMULATOR=1 -DTARGET_EXTENSION=1 -I . -I /Users/markd/Developer/PlaydateSDK/C_API -o build/pdex.dylib pdnewlib.c link.cpp main.cpp /Users/markd/Developer/PlaydateSDK/C_API/buildsupport/setup.c -g -Wa,-ahlms=build/OBJDIR -g
clang: error: unsupported argument '-ahlms=build/OBJDIR' to option 'Wa,'
clang: error: unsupported argument '-ahlms=build/OBJDIR' to option 'Wa,'
(repeats another 6 times)
make: *** [build/pdex.dylib] Error 1

Full make output fwiw:
make-output.txt.zip (1.8 KB)

Thanks!

That is strange. Have you tried removing the -ahlms=build/OBJDIR flag? Or moving it before the Wa, part? Since I don't have a Mac, I am not sure I can debug this.

do you know what that ahlms flag does? - I couldn't find it with casual googling.

Whatever it does, it doesn't seem to be important. I have removed it for the latest version of playdate-cpp. You should be safe to just cut out that argument.