Steve Exploring Stuff: Gotchas: Unity Plugins for Multiple Platforms


Gotchas: Unity Plugins for Multiple Platforms

As mentioned in my last post, I was surprised by some of the platform compatibility issues I experienced with the CAINav code. In this post I'll cover some of the issues and how they can be resolved.

A quick side note before getting into the main topic: The first sample pack that includes source code has been released. It includes various demos and feature explorers that will help you get started using CAINav.

The Basics

I'm not going to cover the basics of Unity plugin development. That is covered well by the Unity documentation. There is nothing surprising on the C++ side. Just make sure you include the necessary conditional compile macros for platform specific code (e.g. "__declspec(dllexport)" for Windows) and don't use platform specific syntax (e.g. nullptr).

Special Cases for the iOS Platform

While you may be aware that Unity provides wide, but still limited support for the standard .NET libraries, you may not be aware that there are differences in the support provided for the different platforms. This is especially true for iOS.

Lets start with a simple plugin method that will work for the desktop and Android platforms.

[DllImport("cai-nav-rcn", EntryPoint = "dtnqFree")] 
public static extern void FreeEx(ref IntPtr query);

The DLLImport attribute indicates the name of the plugin (cai-nav-rcn) and remaps its name to a C# friendly version (dtnqFree to FreeEx). In this case the method frees a query object's unmanaged resources.

Issue #1: iOS does not support remapping the name of the extern method. All method names must match the C function name. So the the signature must be simplified to:

public static extern void dtnqFree(ref IntPtr query);

Issue #2: iOS requires the custom plugin name __Internal. This makes the iOS signature incompatible with the other platforms.

One easy way to handle this is using conditional compilation as follows:

internal struct InteropUtil 
    public const string PLATFORM_DLL = "__Internal";
    public const string PLATFORM_DLL = "cai-nav-rcn";

This allows standardized method signatures throughout the rest of the code. Our example is altered as follows:

public static extern void dtnqFree(ref IntPtr query);

Issue #3: With compile time differences between platforms, how do you deploy the C# code?

Due to the incompatible code, just as with the plugins, you an no longer use a single pre-compiled .NET DLL for all platforms. There may be other options, but the easiest is to drop the use of pre-compiled .NET DLL's and distribute the C# code directly to the Unity project. Unity will then decide which type of build to perform.

Since CAINav targets the Windows platform, I chose a different method. The main Unity package contains pre-compiled DLL's, but the source code is organized so the C# code can be easily dropped directly into a Unity project.

Oh No, What About Serialization

If you are using Unity serialization for everything, then there is no problem. But that may not be possible. Often a plugin object contains references to unmanaged memory. Also, Unity doesn't support serialization of custom structures or unsigned primitives such as uint, ushort, etc.

You may be tempted to implement custom serialization based on the ISerializable interface. But that isn't cross-platform compatible since your iOS platform uses a different run-time DLL than the rest of the platforms. So you can't for example, serialize a Navmesh object in a Windows project and share it with an iOS project.

Since CAINav supports standalone use in .NET applications, it supports two serialization methods. The standard ISerializable interface, plus byte array serialization. Byte arrays can be serialized using the standard .NET serialization process and shared between all platforms.

An example of this is the Navmesh class. The Navmesh.GetSerializedMesh() method returns byte[]. This byte array can be serialized using either Unity or .NET serialization. One of the overloads of Navmesh.Build() can be used to re-create the navigation mesh from a byte array. This is how the BakedNavmesh component handles the serialization issue.

I'm sure there are other gotcha's. But this post should help you avoid some of the headaches involved in cross-platform plugin development.

No comments:

Post a Comment