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 necessary. A few different steps are required, some of which will enable additional features in C as well such as __atribute__((constructor))
.
Edit: Many thanks to @MrBZapp, who has come in and redone everything in order to add compatibility with Playdate SDK v2.0. Unfortunately, exceptions are currently broken on this version -- you'll have to modify the linker script to enable them yourself :S
The Simple Way
➤➤➤ Download and follow the instructions in the readme of this repository. ⮜⮜⮜
More Elaboration
(This was written for v1.0, some of it may be out of date.)
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 find a way to do this once and reuse.Contained within the library built by this project is a modified version of setup.c
which does this. It simply loops through the list of init
functions (and fini
functions) and runs them during the start/terminate events.
All your project has to do is link against this library (pdcpp_core
) and add a particular setup function eventHandler_pdnewlib
in the eventHandler
.
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
Currently, as of Playdate SDK 2.0, exceptions aren't working in the library linked above. Here are the steps to make exceptions work on SDK 1.0 in the meantime (perhaps they still work):
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
.