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.

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

CFTypeRef _createNativeClass()
{
  return CFBridgingRetain([[NativeClass alloc] init]);
}

void _destroyNativeClass(CFTypeRef nativeClassInstance)
{
  CFRelease(nativeClassInstance);
}
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 it’s 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.

- (void)doSomeStuff;

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

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

void _doSomeStuffNativeClass(const NativeClass * instance)
{
  [instance doSomeStuff];
}

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.

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

  IntPtr m_Instance;

  void DoSomeStuff()
  {
    _doSomeStuffNativeClass();
  }

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.

Unity Android Plugin Environment

I’ve recently begun work on android platform integrations into Crowman & Wolfboy. We’ve had a playable build of the game on Android for a while now, but some parts need more integration than others. I ended up having to write a java library for inclusion in the project.

This posed a new problem for us. All our iOS native plugins were included as objective-c files. Unity then copied those source files into the compiled project for XCode to build. This made it easy to include the source files in version control while keeping the build process simple. Android plugins aren’t so simple.

Unity requires android plugins be precompiled JAR files. I think it has something to do with Unity’s build process for android apps, but that doesn’t really matter. Those JAR files must be in the ‘Special Folder’ at the path Assets/Plugins/Android/. I wanted a way to easily keep the java source files in the same repository as the main project, but also keep the build process for the plugin simple.

The first thing I tried was putting the whole Eclipse workspace under versio control. That didn’t work because a lot of paths are saved as part of the workspace. The JDK path is different for different developers. The Android SDK path is different too.

I ended up making an Eclipse workspace adjacent to the main project folder. This workspace contained java projects as usual. Those java projects then used linked source folders. The source files were in a sub folder of the special android plugin folder. Each eclipse project then linked to its specific source folder. Then I can export the JAR to an external location (that being the special android plugin folder).

This setup will requires each developer configure their own eclipse workspace. Other than that the build process for a change to java code is easy as an export in Eclipse followed by a build in the Unity Editor. Not quite the ideal one-click-build, but java code shouldn’t be changed very often anyway.