⬆️ ⬇️

Creating a native iOS plugin for Unity3d. Undocumented features

In Unity3d, it is possible to connect native plugins to an application. On the official Unity3d website there is documentation on interaction with native code on iOS. This documentation is limited to the description of how to call a particular function from a plugin that you built yourself and how to make a callback. The documentation describes what the rules should correspond to the described function 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 the event that the user subscribed to the Push Notification or the application switched to the background (AppDidEnterBackgound), while access to this event is necessary for some plugins that you may want to write.



To begin with, the documentation describes that you can transfer parameters from C # or js code to native and back. However, these rules are not explicitly described, and it may be worthwhile to describe them in more detail. The rules on the transfer of parameters formulated below were obtained when studying existing plug-ins: Fb Unity SDK and Google Analytics .



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 be simply converted to a more convenient for 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 in UTF – 8 encoding, encoded and allocated on the heap. Mono sort calls are free for strings. Heap allocation is necessary because the Mono compiler for returned C strings creates a .NET string and calls free for the return value.



In order to avoid problems, you can call a method that creates a copy of the string on the heap (from the 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 in which the official Fb SDK for Unity3d was integrated was collected for information. 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 compiled under iOS, in Player Settings as the Scripting Backend was chosen IL2CPP.



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



In this case, there are 2 outputs:



Since the current project used the FB SDK, and there was enough time, it was decided to understand what was happening under the hood of the XCode project that Unity generates.



The FB plugin for Unity includes two files that are most interested in FbUnityInterface.mm and .h, which contain the native code for iOS.



In the .mm file catches the eye this code:



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


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



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


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



The main protocol is LifeCycleListener. Two of the three remaining protocols expand it. 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<LifeCycleListener> obj); void UnityUnregisterLifeCycleListener(id<LifeCycleListener> obj); 


The first method signs the object to receive the listed events, the second one writes it down. Next, consider the AppDelegateListener protocol:



 @protocol AppDelegateListener<LifeCycleListener> 


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



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



 @protocol RenderPluginDelegate<LifeCycleListener, NSObject> 


The last in line is the UnityViewControllerLIstener protocol. It allows you to access the main events of the main view of the application. This protocol has no dependency on LifeCycleListener. List of events:



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


From the source code of the Fb SDK, you can see that these features appeared in Unity3d only starting from version 4.3.



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


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



 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 for the value of the Unity3d version you can refer to the UNITY_VERSION constant. Thus, if you are using Unity3d version lower than 4.3, then you should not count on the presence of the four protocols described above.



For some native plugins, it may be necessary to have one or another key in the native application .plist file. In fact, plist can be made equivalent to xml. The Fb SDK includes the FbPlistParser and PlistDic sources, which can be used to add the key that your plugin needs. For example, you can add a script with PostProcessBuildAttribute to a project so that when the project is finished building, find the plist file in the XCode project and add the necessary keys with values ​​to it.



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

Source: https://habr.com/ru/post/266893/



All Articles