C++ Scripting: Part 8 – Platform-Dependent Compilation
The series continues today to fill a small, but important gap in the C++ plugin’s build script. Unity helpfully provides symbols like UNITY_IOS
for us to check with #if
. This lets us add and remove blocks of code that should only be present in a certain build of the game. We’d like the same functionality in C++ that Unity provides to C#, so today we’ll upgrade the build script to allow that.
Table of Contents
- Part 1: C#/C++ Communication
- Part 2: Update C++ Without Restarting the Editor
- Part 3: Object-Oriented Bindings
- Part 4: Performance Validation
- Part 5: Bindings Code Generator
- Part 6: Building the C++ Plugin
- Part 7: MonoBehaviour Messages
- Part 8: Platform-Dependent Compilation
- Part 9: Out and Ref Parameters
- Part 10: Full Generics Support
- Part 11: Collaborators, Structs, and Enums
- Part 12: Exceptions
- Part 13: Operator Overloading, Indexers, and Type Conversion
- Part 14: Arrays
- Part 15: Delegates
- Part 16: Events
- Part 17: Boxing and Unboxing
- Part 18: Array Index Operator
- Part 19: Implement C# Interfaces with C++ Classes
- Part 20: Performance Improvements
- Part 21: Implement C# Properties and Indexers in C++
- Part 22: Full Base Type Support
- Part 23: Base Type APIs
- Part 24: Default Parameters
- Part 25: Full Type Hierarchy
- Part 26: Hot Reloading
- Part 27: Foreach Loops
- Part 28: Value Types Overhaul
- Part 29: Factory Functions and New MonoBehaviours
- Part 30: Overloaded Types and Decimal
Unity automatically specifies the correct symbols for the current build target. This lets us tell the compiler what code to compile on which platform:
// C# void HandleInput() { #if UNITY_EDITOR // Handle keyboard and mouse input #elif UNITY_IOS || UNITY_ANDROID // Handle touch screen input #endif }
These symbols are only defined by Unity when it builds C# code that’s directly placed in the Assets
directory. If we’re building a .NET DLL on our own, we’ll have to specify the symbols for ourselves. The same goes for building plugins like we’re doing in this series so that we can script in C++.
C++ has a similar system to C#’s #if
preprocessor directives. It’s actually much more powerful as we can use it to define macros, get line numbers, and much more. For now, we’ll just use it to set boolean flags like in C#. The code is very similar in C++:
// C++ void HandleInput() { #if defined(UNITY_EDITOR) // Handle keyboard and mouse input #elif defined(UNITY_IOS) || defined(UNITY_ANDROID) // Handle touch screen input #endif }
Sometimes using #ifdef X
is more convenient than #if defined(X)
. See here for the full list of what we can do.
Remember from part six of the series that we’re using CMake to build our C++ plugin. This actually makes it quite easy to detect which platform we’re building for and define the appropriate symbols.
For now, we’ll support five platforms:
- Windows (editor and standalone)
- macOS (editor and standalone)
- Linux (editor and standalone)
- iOS
- Android
Unity supports a lot more platforms and we’ll see how easy they would be to add support for as we go. We’ll start with these five for now.
CMake has the familiar concepts of if (X)
, else()
, elseif (X)
, and endif()
. With these we can check variables that help us understand what environment we’re building project files for. For example, CMake defines a WIN32
boolean variable that we can use to detect Windows. It also defines a CMAKE_SYSTEM_NAME
string variable that we can use to check for macOS (“Darwin”) or Linux.
That takes care of three platforms, so we just need to cover Android and iOS. Recall that building for Android requires us to call CMake with the NDK’s directory as a command-line parameter: -DANDROID_NDK=/path/to/android/ndk
. That means we can use if (ANDROID_NDK)
to check if we’re building for Android.
Previously, we hadn’t built a CMake project for iOS. This is because we can simply drop C++ files into our project’s Assets
directory and Unity will automatically include them in the Xcode project it builds for iOS. That meant we’d use CMake to generate project files for another platform and use that instead. Now that we want to specify UNITY_IOS
among other symbols, we need to allow CMake to build for iOS.
Just like with Android, we’ll need a CMake “toolchain” file for iOS. I’ve added one to the Assets
directory so we just need to pass -DCMAKE_TOOLCHAIN_FILE=/path/to/Unity/CppSource/iOS.cmake
on the command line to CMake. The toolchain file will set the IOS
variable, so we just need to check for if (IOS)
to determine we’re on that platform.
This change means that the location of the C++ files needs to change. They used to be in the Unity project’s Assets
directory, but now that iOS plugins are built via CMake we don’t want Unity to include the C++ source in the Xcode project anymore because we have already built a bundle which Unity will automatically include in the Xcode project. The new location for C++ files is in the Unity project’s CppSource
directory.
Finally, there’s the difference between building for the Unity editor and for a standalone build when on Windows, macOS, and Linux. We’ll also add that as an option to the CMake command line so we can simply pass -DEDITOR=TRUE
if we want an editor build.
Now we’ve got everything we need to detect which platform we’re building for. The last step is to define the symbols for C++ to use with #if
. CMake makes that really easy via a built-in add_definitions
function. We simply call add_definitions(-DUNITY_IOS)
when appropriate and the symbol will be defined in the generated project files.
With that, let’s go ahead and write a simple if-else
block in our CMakeLists.txt
script to detect the platform and define the appropriate symbols:
if (EDITOR) add_definitions(-DUNITY_EDITOR) if (WIN32) add_definitions(-DUNITY_EDITOR_WIN) elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") add_definitions(-DUNITY_EDITOR_OSX) elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux") add_definitions(-DUNITY_EDITOR_LINUX) endif() else() add_definitions(-DUNITY_STANDALONE) if (WIN32) add_definitions(-DUNITY_STANDALONE_WIN) elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") add_definitions(-DUNITY_STANDALONE_OSX) elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux") add_definitions(-DUNITY_STANDALONE_LINUX) endif() endif() if (IOS) add_definitions(-DUNITY_IOS) endif() if (ANDROID_NDK) add_definitions(-DUNITY_ANDROID) endif()
Finally for today, let’s discuss how to switch between build targets. Switching platform as usual will still work and switch to using the new platform’s C++ plugin. Unlike C# scripting, the C++ IDE won’t automatically have its build target changed because only the C# project files that Unity generates and we largely ignore are updated. So we’ll need to take a couple of extra steps. Here’s how we change platforms:
- In the Unity editor, go to
File > Build Settings
- Select a platform from the
Platforms
list - Click the
Switch Platform
button - Open a command prompt and go to the CMake build directory
- Delete all build files:
rm -fr *
(macOS, Linux) ordel *.*
(Windows) - Re-run CMake (e.g.
cmake -G Xcode /path/to/Unity/CppSource
)
That’s all there is to it. Switching platforms should now be almost as easy in C++ as it is in C#!