Unity supports Native Plugins, which are libraries of native code (e.g. C or Objective-C) that can be called from your C# code. This is very useful for integrating with the native SDKs on each platform, such as Apple's "Authorization Services" framework.
The native code you write must expose a "C-compatible" API, but is otherwise normal native code, that can call all the libraries you want. The resources below (Mono's docs especially) speak to the details of how data is converted back and forth between native C and C#.
Some important callouts to list here though:
- Callbacks passed into native code (known there as "function pointers")
need to be static,
so we use
MonoPInvokeCallbackfor a static function and a map (_completionHandlers) ofIntPtrto whatever the "actual" callback is as a way to map the callback back to a particular instance of the class instead. - All functions exposed by the native code are brought into your C# class as
static methods, using
[DllImport(...)]and theexternkeyword.
Finally, Managed Code is a term that shows up a lot (in contrast to "Unmanaged" code) in this topic. The gist of it is:
- Managed Code is C# level code, managed by the .NET runtime, including Garbage Collection.
- Unmanaged Code, on the other hand, is not handled by .NET, meaning the native code is in charge of managing its memory1.
Objective-C does not have garbage collection per se, but it does operate under Automatic Reference Counting, which means when an object is no longer referenced by anyone else (i.e. there are no more pointers to it), then it will be deallocated from memory.
The important thing to note, is that references to a native class from C#
code, via a void* type, do not count towards ARC, which means if that
is all you have, the native object under your C# wrapper may be removed from
memory before you intend.
The important thing is that when you move between your Objective-C class types
and void* pointer types, you must do so consciously, with one of the
following:
(__bridge T*)ptrtakes a C#ptrand gives you an Objective-C reference, without changing anything about ARC (so if it wasn't counted before, it will not be counted now).(__bridge void*)objecttakes an Objective-C reference and gives you a C# pointer, also without affecting ARC. You can think of both as just "casting" between types, but know that casting an Objective-CT*to a C#void*with__bridgemeans once the Objective-CT*is dropped, the reference count will decrease, as the C#void*does not count. If you are familiar with the terminology, you can broadly think of__bridgeas casting into a weak pointer on either side.(__bridge_retained void*)objecttakes an Objective-C referenceobjectand gives you a C# pointervoid*, but in this case, says to ARC that this pointer does count, and so until it is explicitly released usingCFRelease, the object will not be deallocated.(__bridge_transfer T*)ptrtakes a C#ptrand gives you an Objective-C reference, again transferring ownership of the memory from C# to Objective-C.
The resources below will do a great job diving deep on each of these! But the gist of it is:
- Use
(__birdge_retained void*)objectwhen you have initialized a new Objective-C class that you want to manage via a C# wrapper that isIDispoable. - When using the object throughout its life, use the basic
__bridgeto go back and forth between C# and Objective-C. - Once the C#
Disposemethod is called, make sure to callCFReleasein the native layer to clear up the memory.
- Mono Docs: Interop with Native Libraries.
- Unity Docs: Native plug-ins.
- Apple Docs: Toll-Free Bridged Types.
- Clang Docs: Objective-C Automatic Reference Counting.