Integrating Unity as a Subview into a native iOS-App went from being a pain in the ass to being kinda smooth while Unity evolved from 3 to 4. But when you needed to pass data(and i mean more than serialized stuff via sendMessage) into the view the whole Mono-stuff was still… lets say inconvenient. But fear not, Unity 5 is here for the rescue!
Stepping from 4 to 5, Unity decided to drop Mono and use il2cpp instead. Since we went through a lot with mono and it was always in a state of “i hope this isnt blowing off” we surely took a look into the new and fancy stuff.
Hooray pure cpp!
First thing we noticed is the huuuge amount of cpp code Unity is generating. Oh and don’t ever try to load the generated header files into your xcode project, except if you like your xcode to stop working and having to manually remove them from your project file before being able to open the project again…
But beside that there is one big benefit, and that is the pure cpp code. No more monoObjects! Every Type within Unity is mapped to a cpp-class. Yes, creating an object is not only calling the cpp-constructor, but you also have to call the ctor method generated by unity. But hey, i still love it. Working with the classes is just fun. No writing nasty code for retrieving field-definitions and then using them to actually getting or setting the value of the field. Fields are really fields of the object and you can access them directly. just so!
And: there are real exceptions! If there is a problem within the unitycode which isnt handled within unity, just surround the call with a try catch and everything is peachy… well kind of, at least.
The dark side of generated code
But of course there is a downside too… When generating and mapping all the types, Unity uses an unique number which looks like its just counting up during the code generation process. So your C# class MyClass is transformed to MyClass_123 where 123 is probably changing everytime you do a little change in the unityproject. Which is kind of everytime you run an export.
And it doesnt stop with classes. Each method is getting its own number too. So in the end you have cpp classes and methods. Codecompletion and everything you dreamed of, but the names change with every export… No problem if you use a handful of them, but with more than 10 methods to change every day it gets… lets say annoying.
But again: Fear not, there is a solution!
When generating the code, you can tell Unity to generate Headers with all needed declarations. Luckily those headers (at least the names) dont have those nasty numbers, so you can rely on them not to change. And within those headers, every declaration is preceded by a comment including the pure C# Methodname. In the end we ended up writing a post-export script (within unity) which scans throu all those headers, generating an additional header including tons of preprocessor defines mapping the C# names to the generated cpp names. So in the Code we just use those defines and everything is peachy again…
Still tons of generated code, but after compiling (which takes quite some time now) its faster, better and of course easier to read and maintain! And its 64 bit! Hooray 64 bit.
I know you are all waiting for some hard facts. So here are some snippets to compare the change from mono to il2cpp, enjoy 😉
IL2CPP: Hands On
We created some macros (yes, macros came in really handy for the unity 5 integration) to help. With il2cpp you have the class itself and the ClassName_il2cpp_TypeInfo containing all information about the fields, default values, methods, static fields etc. Because the object itself is not a class but struct.
#define TYPE_INFO(x) x ## _il2cpp_TypeInfo #define IL2CPP_NEW_OBJECT_FROM_TYPE(typename) (typename *) il2cpp_codegen_object_new( InitializedTypeInfo(&TYPE_INFO(typename)))
Now this is what it looks like to create an object from a custom C# Class named CustomUnityObjectType. It also shows how to call a method on an Object: just pass the object as the first parameter and done.
CustomUnityObjectType_t709* unityObject= IL2CPP_NEW_OBJECT_FROM_TYPE(CustomUnityObjectType_t709); CustomUnityObjectType__ctor_m3326(unityObject,NULL);
Now we want to access a member. Lets assume CustomUnityObjectType has a member count which is an integer:
int value= unityObject->___count;
The 3 “_” makes a funny look, but thats all. And even Strings are not really complicated:
or the other way round:
char* c_string= il2cpp_codegen_marshal_string(unityString);
And thats all to it! If you have any questions or found a better way to handle problem with the generation-time-method-ids please leave a comment.