[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 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.

11 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.

1 Like

@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.

Has anyone gotten c++ working for the latest 2.0.1 SDK ? I'm kinda wanting to use c++ for my next project as converting it to c would be a real pain in this case. Or is it no longer possible todo so ? I tried the files in the repo but could not get it to compile at all so i'm guessing it is (currently) not compatible the 2.X.X version of the sdk

2 Likes

Hi all,

I just recently had a conversation with NaOH, I think they're going to update the post at the top of this thread at some point, but in the interim, I have an update, which just so happens to also answer the question about supporting SDK 2.0.1 and up.

I've made some updates to the package which do support the most recent version of the SDK, and the changes were pretty siesmic. Please take a look at the README on the repo (it's been updated), and be prepared to follow some new processes to get it to work for you.

The biggest changes to the workflow are as follows:

  1. CMake, no Make.
  2. Things are now built as a static library to link against in your project. This allows the project to be significantly less invasive to the SDK, which will hopefully improve our ability to keep the project functional longer term.

Stay tuned too for a library of extensions which will provide some nice C++ handles to the C API in an effort to help people write as much C++ as possible.

--Z

5 Likes

I'm getting a ".data has ordered sections with incompatible alignments" linker error from ld.exe when building the example project for the Playdate device from Windows.

For anyone else running into this problem, Phillip updated the ARM toolchain to v.12.3.rel1 and it resolved the issue. I've updated the README to reflect the recommendation.

@MrBZapp, is it possible to add another example to the repo which shows how to do c++ I/O with lua?

I have a cpp library that i’d like to use with my project and I’m very unfamiliar with both c & cpp.

So far I’ve managed to make a c library containing a simple struct containing some methods to get/set/increment an int counter in c and all this works with lua calls. I used the “Particles” C_API demo as my basis, but when I attempted to make it c++ I am getting syntax errors due to how the struct is marshaled inside c++ methods when received.

Sure, no problem, just added it.

Keep in mind that creating an extension's Lua API is always going to be a little closer to C than C++. extension registration requires a list of structs with names and function pointers which must be stand-alone functions, not class methods, and both object and argument passing needs to go through the Playdate C API, so you're always going to need a handful of "shim" functions that form the actual handles you'll have in Lua.

However, you are allowed to write C++ within those functions now, and maybe that's enough! If not, you might at least find you can make the shims quite thin indeed with some clever use of the playdate SDK's Lua functions.

The example should at least demonstrate that it is possible to clearly delineate where and how that hand off occurs.

1 Like

Thank you so much! I will go through the example this week and report back on my integration efforts.

I'm attempting to get my build enviornment setup. I'm on a M1 Macbook Pro.

When the readme.md says you need "ARM toolchain of version 12.3.rel1". Which download am I supposed to do?

I downloaded from here: https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads

The one I went with was: arm-gnu-toolchain-12.3.rel1-darwin-arm64-arm-none-eabi.tar.xz under the heading "macOS (Apple silicon) hosted cross toolchains, AArch32 bare-metal target (arm-none-eabi)"

I extracted the download to /usr/local/playdate/ and then I renamed the existing toolchain (installed by the PlayDate SDK I believe) and symlinked the new toolchain in its place so now i have this:

drwxr-xr-x  10 dean  staff   320B Jul 14 05:54 arm-gnu-toolchain-12.3.rel1-darwin-x86_64-arm-none-eabi
lrwxr-xr-x   1 dean  admin    55B Sep 18 13:42 gcc-arm-none-eabi-9-2019-q4-major -> arm-gnu-toolchain-12.3.rel1-darwin-x86_64-arm-none-eabi
drwxr-xr-x   6 dean  admin   192B Oct 31  2019 gcc-arm-none-eabi-9-2019-q4-major_orig

I went into my project, ran: cmake (with -DCMAKE_TOOLCHAIN_FILE), I received the following:

dean@COMPUTER build % cmake -DCMAKE_TOOLCHAIN_FILE=~/Developer/PlaydateSDK/C_API/buildsupport/arm.cmake ..
CMake Warning (dev) at CMakeLists.txt:1 (project):
  cmake_minimum_required() should be called prior to this top-level project()
  call.  Please see the cmake-commands(7) manual for usage documentation of
  both commands.
This warning is for project developers.  Use -Wno-dev to suppress it.

-- arm.cmake loaded
-- arm.cmake loaded
-- The C compiler identification is GNU 12.3.1
-- The CXX compiler identification is GNU 12.3.1
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/local/bin/arm-none-eabi-gcc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/local/bin/arm-none-eabi-g++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Playdate SDK Path: /Users/dean/Developer/PlaydateSDK
-- Building for Playdate hardware
CMake Warning (dev) at CMakeLists.txt:69 (project):
  cmake_minimum_required() should be called prior to this top-level project()
  call.  Please see the cmake-commands(7) manual for usage documentation of
  both commands.
This warning is for project developers.  Use -Wno-dev to suppress it.

-- Adding playdate application retro_player
CMake Warning (dev) in CMakeLists.txt:
  No cmake_minimum_required command is present.  A line of code such as

    cmake_minimum_required(VERSION 3.27)

  should be added at the top of the file.  The version specified may be lower
  if you wish to support older CMake versions for this project.  For more
  information run "cmake --help-policy CMP0000".
This warning is for project developers.  Use -Wno-dev to suppress it.

-- Configuring done (7.0s)
-- Generating done (0.0s)
-- Build files have been written to: /Users/dean/Documents/Development/my_git/retro_player/build

Then I ran make and got this:

[ 14%] Building C object CMakeFiles/playdate_sdk.dir/playdate-cpp/src/setup.obj
[ 28%] **Linking C static library libplaydate_sdk.a**
[ 28%] Built target playdate_sdk
[ 42%] Building C object CMakeFiles/pdcpp_core.dir/playdate-cpp/src/pdnewlib.obj
[ 57%] Building CXX object CMakeFiles/pdcpp_core.dir/playdate-cpp/src/pdnewdelete.obj
[ 71%] **Linking CXX static library libpdcpp_core.a**
[ 71%] Built target pdcpp_core
[ 85%] Building CXX object CMakeFiles/retro_player.dir/main.obj
[100%] **Linking CXX executable retro_player.elf**
/usr/local/playdate/arm-gnu-toolchain-12.3.rel1-darwin-x86_64-arm-none-eabi/bin/../lib/gcc/arm-none-eabi/12.3.1/../../../../arm-none-eabi/bin/ld: warning: retro_player.elf has a LOAD segment with RWX permissions

So I think it compiled? does this look right to you?

I'm not sure where to go from here (how to get my retro_player.elf into a .pdx that I can run on device.

thanks for any assistance you can provide

1 Like

an update, I was able to get the sample to build fully. If i use the CMAKE_TOOL_CHAIN_FILE argument my resulting pdx will run on device but not in the simulator. If i remove that argument, it seems to run in both places.

@MrBZapp Thank you so much for helping me with this sample code, I think it will get me on my way.

1 Like

This is really great and much easier to setup than previously.

I do have one request is it possible to add the following ?

I needed to add this manually if i wanted to compile for the simulator on msys2 using mingw what i did was create msys makefiles by running cmake .. -G "MSYS Makefiles" under a msys2 prompt but it kept saying platform not supported because the WIN32 section did not exist so i had to add it manually.

I also managed to change the code to make the arduboy collection (ports) recompile made by eric lewis and i've gotten confirmation from a person that had a REV B playdate with the new cpu that it was working on their playdate

I'm so glad it's working out for you, that's fantastic!

As for adding that block for MSYS2, help me out here a little because I'm less familiar with MSYS2/MINGW than most of the other tools we're explicitly supporting.

Is WIN32 the narrowest possible scope for supporting your MSYS2/MINGW, or are there flags we can use that can target this change to your tools even more specifically? I am wary of casting a large net if a smaller one will do, but ultimately I do want to support this.

it's not just for MSYS2 / MINGW. there basically is no platform set for windows one except visual studio c compiler, which did not work it only worked if i compiled using GCC (but this could be related to the code i was trying to compile), One can download for example gcc compiler for windows (using mingw version or so) and compile from a CMD prompt as well instead of from the msys2 one and then you need that as well. Basically that entry is to support at least some default for the windows platform when using gcc and windows uses .dll's so its the same as the linux one except for the extensions. I'm not sure if there is a way in cmake to explictly detect msys2 i basically wanted a default for windows (in case of not using visual studio) which currently does not have one. If you keep this one always as last one and you have more specific compilers you can add them before this entry. But if specific targets in cmake do exists it might help more to be less broaden but i think a generic windows one when using gcc (nomatter where) should at least exist as you have a darwin (mac) and linux one. Also be aware i only needed todo this for compiling for the simulator because visual studio c compiler was complaining about (probably) certain specific code where gcc did not error out on. Compiling for the playdate itself worked fine straight out of the box but that was using the arm gcc compiler and not visual studio either, so i just replicated the same thing using an x64 mingw-gcc compiler running on windows and that worked fine for running it in the simulator. If anything at all one should detect perhaps mingw compiler instead of msys2 but not sure thats possible at all and if it's possible should probably give visual studio one preference