Creating a native iOS plugin for Unity3d. Undocumented Features

Unity3d has the ability to connect native plugins to the application. The official website of Unity3d has documentation on interacting with native code on iOS. This documentation is limited to describing how to call a function from the plugin that you built yourself and how to make a callback. The documentation describes what rules the described function should comply with and a little about how to pass parameters to it. At the end of the article with the documentation there is a link where you can download an example called Bonjour.

Unfortunately, the documentation does not describe what to do if your plugin needs to intercept an event that the user has subscribed to Push Notification or the application has switched to the background (AppDidEnterBackgound), while access to this event may be necessary for some plugins that you may want to write.

To begin with, the documentation describes that you can pass parameters from C # or js code to native and vice versa. However, these rules are not explicitly described, and it may make sense to describe them in more detail. The rules for passing parameters formulated below were obtained when studying existing plugins: Fb Unity SDK and GoogleAnalytics .

Types of parameters that can be passed between native code and C # (and vice versa):

string = const char* //Из Unity в натив 
char* = string  //Из натива в Unity 
bool //(конвертация не нужна) 
int //(конвертация не нужна) 
float //(конвертация не нужна) 

When working in native code, const char * can simply be converted to a more convenient iOS NSString in this way:

NSString* CreateNSString (const char* string)
{
     if (string)
          return [NSString stringWithUTF8String: string]; 
     else
          return [NSString stringWithUTF8String: ""];
}

String values ​​returned from the native method must be UTF – 8 encoded, encoded, and allocated on the heap. Mono sort calls are free for strings. Allocation on the heap is necessary because the Mono compiler for the returned C lines creates a .NET string and calls free for the return value.

In order to avoid problems, you can call the method that creates a copy of the string on the heap (from Bonjour example):

char* MakeStringCopy (const char* string)
{
      if (string == NULL)
           return NULL;
      char* res = (char*)malloc(strlen(string) + 1);
      strcpy(res, string);
      return res;
}

The functionality associated with changing the state of the application or its view is not documented in Unity, a project was built to receive information, in which the official Fb SDK for Unity3d was integrated. The project used Unity3d version 5.1.2f1 and Fb Unity SKD version 6.2 ( can be found here ), iOS SDK 8.4. The project was built under iOS, IL2CPP was chosen as the Scripting Backend in Player Settings.

Why might this information be needed? To develop a native iOS plugin that uses native functions and listens for messages about changes in the state of the application ( The App Life Cycle - Apple Developer), state changes of the main View application and renderer. For example, this functionality may be required to integrate native plugins of social networks or plugins of various analysts that have supported native iOS plugins, but there are no versions of plugins for Unity3d.

In this case, there are 2 outputs:
  • Generate an Xcode project. Find the source code of the application delegate, modify it and integrate the plugin. The disadvantage of this approach is that if the version of your application is far from the final and changes will be made in the future, then the Xcode project will have to be regenerated and the plug-in integrated from scratch. I used this approach only once. The publisher insisted on integrating its own install and purchase tracker, and the project used the Unibill plug-in, respectively, the payment tracking was spoiled by this plug-in and I called the code that caused the tracking of the payment from the publisher from the native part of the Unibill sources. This solution was used only because the publisher did not give time to write a Unity plug-in based on the native one, and the integration itself took half an hour.
  • Use the native Unity3d functionality implemented in the iOS project. The downside is that this functionality is not documented. However, it is used by Google Analytics and Fb SKD plugins for Unity.

Since the FB SDK was used in the current project, and there was enough time, it was decided to figure out what is happening under the hood of the Xcode project that Unity generates.

The FB plug-in for Unity includes two files that are of most interest to FbUnityInterface.mm and .h, which contain native code for iOS.

The following section of code is striking in the .mm file:

-(void)didBecomeActive: (NSNotification *)notification {
  [FBAppCall handleDidBecomeActiveWithSession:self.session];
}
-(void)willTerminate:(NSNotification *)notification {
  [self.session close];
} 
-(void)didFinishLaunching:(NSNotification *)notification {

It looks like FB intercepts calls to standard application delegate methods. In the header file we see the following sinks:

#if UNITY_VERSION >= 430
#import "AppDelegateListener.h"
@interface FbUnityInterface : NSObject 

AppDelegateListener.h is located in the Classes / PluginBase / Xcode directory of the project. In order to intercept delegate events, Unity describes 4 protocols. For those who are not familiar with the concept of the Objective-C protocol - the English version and the Russian version of the documentation .

The main protocol is LifeCycleListener. It is he who is expanded by two of the three remaining protocols. It allows you to listen to events:

- (void)didFinishLaunching:(NSNotification*)notification;
- (void)didBecomeActive:(NSNotification*)notification;
- (void)willResignActive:(NSNotification*)notification;
- (void)didEnterBackground:(NSNotification*)notification;
- (void)willEnterForeground:(NSNotification*)notification;
- (void)willTerminate:(NSNotification*)notification;

And also declares 2 methods:

void UnityRegisterLifeCycleListener(id obj);
void UnityUnregisterLifeCycleListener(id obj);

The first method signs the object to receive the listed events, the second unsubscribes. The following is a look at the AppDelegateListener protocol:

@protocol AppDelegateListener

It gives access to messages about Push Notifications, opening ULR applications, and some others. All messages that are accessible to a class that implements AppDelegateListener can be viewed in AppDelegateListener.mm.

The RenderPluginDelegate protocol provides access to Unity render events and also extends LifeCycleListener. The source contains good comments and information on all methods can be obtained from there.

@protocol RenderPluginDelegate

Last in line is the UnityViewControllerLIstener protocol. It allows you to access the main events of the main view of the application. This protocol is not dependent on LifeCycleListener. Event List:

- (void)viewDidDisappear:
- (void)viewWillDisappear:
- (void)viewDidAppear:
- (void)viewWillAppear:
 - (void)interfaceWillChangeOrientation:
- (void)interfaceDidChangeOrientation:

From the source code of the Fb SDK it can be seen that these features appeared in Unity3d only since version 4.3.

#if HAS_UNITY_VERSION_DEF
  #include "UnityTrampolineConfigure.h"
#endif
#if UNITY_VERSION >= 430
#import "AppDelegateListener.h"
@interface FbUnityInterface : NSObject 
#else

The constant HAS_UNITY_VERSION_DEF, storing information that the project has information about the version of Unity3d used to build the project, is declared in the header file RegisterMonoModules.h:

void RegisterMonoModules();
#define HAS_UNITY_VERSION_DEF 1

This is all the contents of RigisterMonoModules.h. If HAS_UNITY_VERSION_DEF is declared in the project, then you can refer to the UNITY_VERSION constant for the Unity3d version value. Thus, if you use Unity3d version lower than 4.3, then you should not rely on the presence of the four protocols described above.

Some native plugins may require the presence of a key in the .plist file of the native application. In fact, plist can be assumed equivalent to xml. The Fb SDK includes the sources FbPlistParser and PlistDic, which you can use to add the key that your plugin will need. For example, you can add a script with PostProcessBuildAttribute to the project in order to find a plist file in the Xcode project and add the necessary keys with values ​​to it in the Xcode project.

I hope that this information has been useful and helped someone in the fight against native plugins.

Also popular now: