Posted on 1 Comment

Using those pointers

In my last post I outlined a way to manage native object lifecycle from C# in Unity. But I skipped one of the conveniences of calling native functions. You can call a function without matching the declared type of the pointer parameters. That means you don’t need to use CFTypeRef everywhere.

The pointer you have in C# is untyped, it is void *. That’s the same as CFTypeRef. But you can declare and call native functions which are your type. Let’s take a look at one of the classes we could build from the last post.

// nativeClass.h
@interface NativeClass
- (instancetype) init;
@end
// nativeClass.m
#import "nativeClass.h"
@implementation NativeClass
- (instancetype) init
{
  // omitted for brevity
}
@end

#if __cplusplus
extern "C" {
#endif
CFTypeRef _createNativeClass()
{
  return CFBridgingRetain([[NativeClass alloc] init]);
}

void _destroyNativeClass(CFTypeRef nativeClassInstance)
{
  CFRelease(nativeClassInstance);
}
#if __cplusplus
}
#endif
// NativeClass.cs
using System;
using System.Runtime.InteropServices;
public class NativeClass : IDisposable
{
  [DllImport("__Internal")]
  static extern IntPtr _createNativeClass();

  [DllImport("__Internal")]
  static extern void _destroyNativeClass(IntPtr nativeClassInstance);

  IntPtr m_Instance;

  public NativeClass()
  {
    m_Instance = _createNativeClass();
  }

  public void Dispose()
  {
    _destroyNativeClass(m_Instance);
  }
}

This is a very basic native class which has its lifecycle controlled from C#. But we can take things a step further with our own functions. Let’s add a method to our objective-c interface.

// nativeClass.h
@interface NativeClass
- (instancetype) init;
- (void)doSomeStuff;
@end

Now we can add an implementation, and provide a C function wrapper. Remember, C# can call C functions, but not objective-c. So we need to write a simple C wrapper for any methods we want to call from C#.

// nativeClass.m
#import "nativeClass.h"
@implementation NativeClass
- (instancetype) init
{
  // omitted for brevity
}

- (void)doSomeStuff
{
  // Only the best algorithm
}
@end

#if __cplusplus
extern "C" {
#endif
CFTypeRef _createNativeClass()
{
  return CFBridgingRetain([[NativeClass alloc] init]);
}

void _destroyNativeClass(CFTypeRef nativeClassInstance)
{
  CFRelease(nativeClassInstance);
}

void _doSomeStuffNativeClass(const NativeClass * instance)
{
  [instance doSomeStuff];
}
#if __cplusplus
}
#endif

Notice the declared pointer type in the function parameters. It is NativeClass * instead of CFTypeRef which was used last time. Yes, this actually works. Finally, let’s add the C# code for this method.

// NativeClass.cs
using System;
using System.Runtime.InteropServices;
public class NativeClass : IDisposable
{
  [DllImport("__Internal")]
  static extern IntPtr _createNativeClass();

  [DllImport("__Internal")]
  static extern void _destroyNativeClass(IntPtr nativeClassInstance);

  [DllImport("__Internal")]
  static extern void _doSomeStuffNativeClass(IntPtr instance);

  IntPtr m_Instance;

  public NativeClass()
  {
    m_Instance = _createNativeClass();
  }

  public void Dispose()
  {
    _destroyNativeClass(m_Instance);
  }

  void DoSomeStuff()
  {
    _doSomeStuffNativeClass(m_Instance);
  }
}

In C#, the pointer remains an IntPtr which is essentially a void * or CFTypeRef. But we can call functions with the proper type declared in the function parameters. That means no casting is needed when we marshal our pointer into native objective-c.

Posted on 1 Comment

Unity iOS Native Pointer Pattern

One common situation I find myself in when developing for Unity on iOS is having to integrate with native APIs. Integrating native features is a nice touch for your apps and makes them feel at home running in iOS. It keeps your users familiar with platform conventions instead of having to learn some UI or workflow specific to your app.

When doing this, you inevitably want to use an API which requires you to hold a reference to a pointer. Let’s say you want to use iCloud ubiquity containers in your app. You need to subscribe to changes in the cloud. This requires a persistent object on the native side to receive notifications from the operating system. The information then needs sent up to Unity and processed by your app. You may start with something like this on the native side.

@interface iCloudMonitor : NSObject

@end
@implementation iCloudMonitor {
	NSMetadataQuery * documentStoreMonitor;
}

-(instancetype)init
{
	if (!(self = [super init])) {
		return nil;
	}
	
	[self startSearch];
	return self;
}

-(void)queryDidUpdate:(NSNotification *)sender
{
	// Do what you need to when iCloud updates, this is just an example
	UnitySendMessage("iCloudMonitor", "DocumentsUpdated", "");
}

-(void)startSearch
{
	documentStoreMonitor = [NSMetadataQuery new];
	documentStoreMonitor.searchScopes = [NSArray arrayWithObjects:NSMetadataQueryUbiquitousDataScope,nil];
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queryDidUpdate:) name:NSMetadataQueryDidUpdateNotification object:documentStoreMonitor];
	[documentStoreMonitor startQuery];
}

@end

extern "C" {
	void _createICloudMonitor()
	{
		[[iCloudMonitor alloc] init];
	}
}

This lets you call _createICloudWrapper() from Unity to start monitoring for iCloud changes. But there is a problem. iOS uses a system called Automatic Reference Counting, or ARC, to keep track of when to deallocate objects from memory. I don’t want to go into great depth about ARC here as there are many articles covering it in great detail such as https://www.raywenderlich.com/2992-beginning-arc-in-ios-5-tutorial-part-1. To summarize and simplify, when an object has zero strong references it get deallocated.

The call inside _createICloudMonitor() creates an object, but doesn’t hold a reference to it. As soon as the function returns, ARC will see the object has zero references and deallocate it. You need to keep a reference to the object so it stays around. One option is to make a global variable to keep ARC happy with a reference. This brings all the issues of global variables with it. Another option is to use a singleton, which is slightly better than a global. It keeps ARC happy with a reference, but still has many of the issues of a global variable. A third option is to use the -fno-objc-arc compiler flag to disable ARC on the entire file. But that means we can’t use ARC any where in the file at all. The best option is to transfer the variable out of ARC entirely. This is done through bridging.

Bridging allows us to give the ARC system more information than it can gather on its own. Namely, whether or not the system should deallocate an object. There are two things you can tell the system. One, you can tell it to stop monitoring an object which it previously was. And two, you can tell it to start monitoring an object it wasn’t. Basically you can move ownership of objects between Objective-C land with references and reference counting, and plain old C land with pointers. The function CFBridgingRetain(id) takes a reference and converts it to a CFTypeRef, which is just an alias for void *, and adds one to the retain count. Lets see how to change our code to use this.

	CFTypeRef _createICloudMonitor()
	{
		return CFBridgingRetain([[iCloudMonitor alloc] init]);
	}

Since we have stopped ARC from deallocating this object we need to do it ourselves. Just like in the time before ARC existed, every call to retain required a corresponding call to release. Luckily That function is provided as well. Lets make a function to access it.

	void _destroyICloudMonitor(CFTypeRef monitor)
	{
		CFRelease( monitor );
	}

And that’s everything we need in objective-c land. Now we can make a C# class to use this code. Here is an example of using MonoBehaviour to control the native object. When the MonoBehaviour instance is first loaded it will call into C land to make the native object. And when it is destroyed, it will call to C land again to destroy the native object. This means the native object’s lifecycle follows the MonoBehaviour instance.

using System;
using System.Runtime.InteropServices;
using UnityEngine;

public class iCloudMonitorBehaviour : MonoBehaviour
{
	[DllImport("__Internal")]
	private static extern IntPtr _createICloudMonitor();
	
	[DllImport("__Internal")]
	private static extern void _destroyICloudMonitor();
	
	private IntPtr NativeObject;
	
	void Awake()
	{
		NativeObject = _createICloudMonitor();
	}
	
	void OnDestroy()
	{
		_destroyICloudMonitor(NativeObject);
	}
}

All of this lets us use persistent native objects whose lifecycle is controlled by an object in Unity all without needing extra variables in native land to store the variables. The basics are as follows:

  1. Have a create function to make the object instance we need which bridges that variable out of ARC and returns the pointer to it.
  2. A corresponding destroy function which releases the variable sent to it.
  3. In the C# class have an IntPtr to hold the reference to the object.
  4. Inside Awake call the create function and assign the value to our pointer field.
  5. Inside OnDestroy call the destroy function and send the pointer.

This is applicable anywhere you need to use a persistent native object. Besides that, you can add all the other functions you want.

Alternate C# class

If you want something a bit more flexible than a MonoBehaviour to control the native object, implementing the IDisposable interface works really well too.

using System;
using System.Runtime.InteropServices;

public class iCloudMonitor : IDisposable
{
	[DllImport("__Internal")]
	private static extern IntPtr _createICloudMonitor();
	
	[DllImport("__Internal")]
	private static extern void _destroyICloudMonitor();
	
	private IntPtr NativeObject;
	
	public iCloudMonitor()
	{
		NativeObject = _createICloudMonitor();
	}
	
	public void Dispose()
	{
		_destroyICloudMonitor(NativeObject);
	}
}