We just released our latest AppWarp Unity SDK update which includes support for building for iOS and Android from Unity Free. Since Unity restricts the use of .Net sockets on these platforms, native socket APIs (BSD C socket) have to be used if you want to do socket communication. In this post I will share my experience and on how we went about doing this.
There are two requirements when communicating with native unmanaged C code from managed .Net C# code. First you need to be able to call functions with arguments from C# to C and the second is to return data from native code back to C#. The below diagram illustrates how the code is structured.
We will use the example of a library for creating a native socket and returning its socket descriptor. Then we will see how we can read data from the native socket by passing the socket descriptor and a managed buffer which will be filled in by the native library code.
Quick Tip: Get all the ready made widgets for free here.
Building the plugins:
Lets first understand how we build the native plugins. For iOS, this is quite straight forward as you simply have to create a new “iOS Framework and Library” project from XCode. Then add a C file to it and write your native code in it. For example in our case we create a file Socket.c and add the following function in it (skipped the header include statements for brevity).
[code java]int _Create()
{
int sockd = socket(AF_INET, SOCK_STREAM, 0);
return sockd;
}[/code]
Compile and build from XCode and it will generate the library file (eg: libiOSNativeSocket.a in my case).
In case of Android, you’ll need to use Android NDK to build the library. Download and add ndk-build to your path. While its useful to have a basic understanding of how NDK works but not absolutely required for the purpose of this post. Add your C file in a folder and name it “jni”. In this folder, you also need to add a file named Android.mk. It should look like below.
[code java]LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_LDLIBS := -llog
LOCAL_MODULE := socket_android
LOCAL_SRC_FILES := Socket.c
include $(BUILD_SHARED_LIBRARY)[/code]
Now navigate to the parent directory of the jni folder and execute ndk-build command. If successful, it will create a libs folder and put the generated library in it (eg: libsocket_android.so in my case).
Calling the plugin code from Unity
Now that we’ve built the plugins, we are ready to call them from Unity code. You first need to declare the native functions that you will use in managed code. In our example
[code java][DllImport ("__Internal")]
private static extern int _Create ();[/code]
Note that the “__Internal” name is used for iOS plugins. For android plugins you need to use the name of the native library that you have built for example if your android library’s name is “libsocket_android.so”, you will declare a function in it as follows
[code java][DllImport ("socket_android")]
private static extern int _Create ();[/code]
Note that name should not include the prefix (‘lib’) nor the extension (‘.so’) of the filename.
Once declared you can use the functions in your application. Note that its a good practice to check the platform when invoking these methods in Unity. For example
[code java]if (Application.platform == RuntimePlatform.IPhonePlayer){
int _socketFd = _Create ();
}[/code]
Passing data buffers between Unity and native code
Now we’ve covered the basic setup of communication across managed C# Unity and unmnagaed C library, we can look at a slightly more trick use case. The scenario is that we want to create buffer (byte array) in C# and pass it to the native library. The native library will fill it with what it reads from the socket and return the number of bytes read. The following is the simple native C code.
[code java]int _Read(int sockd, char* buff, int buffLen)
{
ssize_t numRead = recv(sockd, buff, buffLen, 0);
return numRead;
}[/code]
The corresponding managed C# code which uses this is non-trivial. The declaration of this in C# code is done as follows. Note the use of IntPtr for byte array starting address.
[code java][DllImport ("__Internal")]
private static extern int _Read (int sockfd, IntPtr msg, int size);[/code]
To using this native method in C# code, we first create a byte array. Then we need to get the native handle of this memory and tell the Mono GC to pin it. This is so that the GC doesn’t free this memory while its being used by the native library. Once the function returns, we call free to inform the GC that the native handle is not being used anymore.
[code java]byte[] buffer = new byte[MAX_BUFFER_SIZE];
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
if (Application.platform == RuntimePlatform.IPhonePlayer) {
retVal = _Read(_SocketFd, handle.AddrOfPinnedObject(), buffer.Length);
handle.Free();
}[/code]
Get started with Unity3D for AppWarp here.
I hope this post was useful to readers who are planning to write native mobile plugins for Unity. Send us a mail on support@shephertz.com with your questions and comments.Acabamos de lanzar nuestra nueva actualización de AppWarp Unity SDK, la cual incluye soporte para la construcción de iOS y Android desde Unity Free. Desde que Unity restringió el uso de conexiones .Net en estas plataformas, conexiones nativas APIs (BSD C socket) tienen que ser usadas si usted quiere hacer una conexión de comunicación. En esta publicación compartiré mi experiencia acerca de cómo nos fue con esto.
Hay dos requerimientos cuando la comunicación con código C nativos sin gestionar de códigos .Net C# gestionados. Primero necesita ser poder llamar funciones con argumentos de C# a C y lo segundo es regresar los datos de códigos nativos a C#. El diagrama de abajo ilustra cómo el código es estructurado.
Usaremos el ejemplo de una biblioteca para crear una conexión nativa y regresar a la conexión del descriptor. Entonces vemos cómo podemos leer datos desde la conexión nativa al pasar la conexión del descriptor a un tampón controlado el cual será llenado con el código nativo de la librería.
Construyendo las conexiones:
Primero entendamos como construir los conectores nativos. Para iOS, es bastante sencillo, tiene que simplemente crear un nuevo proyecto “iOS Framework and Library” desde XCode. Luego agregue un archivo C a esto y escriba su código nativo. Por ejemplo en nuestro caso, creamos un archivo Socket.c y agregamos la siguiente función (para brevedad hemos omitido las instrucciones incluidas de cabecera).
int _Create()
{
int sockd = socket(AF_INET, SOCK_STREAM, 0);
return sockd;
}
Construya y recopile desde XCode, esto deberá generar el archivo de biblioteca (eg: libiOSNativeSocket.a en mi caso).
En el caso de Android, usted deberá usar Android NDK para construir la biblioteca. Descargue y agregue ndk-biuld a su ruta. Es necesario tener un entendimiento básico de como NDK funciona, pero no es un requerimiento para el propósito de esta publicación. Agregue su archivo C en una carpeta y nómbrela “jni”. En esta carpeta, también necesita agregar un archivo llamado Android.mk. Esto debería verse como el que se muestra abajo.
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_LDLIBS := -llog LOCAL_MODULE := socket_android LOCAL_SRC_FILES := Socket.c include $(BUILD_SHARED_LIBRARY)
Diríjase al directorio principal en la carpeta jni y ejecute el comando ndk-build. De tener éxito creará una carpeta libs, ponga la biblioteca generada en esta. (eg: libsocket_android.so en mi caso).
Llamar los códigos es extensión desde Unity
Ahora que hemos construido las conexiones, estamos listos para llamarlos desde Unity code. Primero necesita anunciar las funciones nativas que en los códigos administrativos. En nuestro ejemplo:
[DllImport ("__Internal")]
private static extern int _Create ();
Note que el nombre “_Internal” es usado por extensiones iOS. Para extensiones Android usted necesita usar el nombre de la biblioteca nativa que ha construido, por ejemplo si el nombre de su biblioteca Android “libsocket_android.so”, anunciará una función como a continuación:
[DllImport ("socket_android")]
private static extern int _Create ();
Note que el nombre no debería incluir el prefijo (‘lib’) ni la extensión (‘so’) del nombre del archivo.
Una vez anunciado puede usar las funciones en su aplicación. Note que es un buen ejercicio para revisar las plataformas cuando invoque estos métodos en Unity. Por ejemplo:
if (Application.platform == RuntimePlatform.IPhonePlayer){
int _socketFd = _Create ();
}
Pasar reservas de datos entre Unity y código nativo
Hemos cubierto la configuración básica de comunicación a través de managed C# Unity y la biblioteca unmanagaed C, ahora podemos ver un caso de uso un poco mas complicado. El escenario es ese donde queremos crear una memoria buffer (tabla de bytes) en C# y pasarlo a la biblioteca nativa. La biblioteca nativa se llenara con lo que se lee desde la conexión y retorna el número de bytes leídos. Lo siguiente es el simple código nativo C.
int _Read(int sockd, char* buff, int buffLen)
{
ssize_t numRead = recv(sockd, buff, buffLen, 0);
return numRead;
}
El correspondiente código administrativo C# que usa esto no es trivial. La declaración de este código C# se realiza de la siguiente manera. Note que el uso de IntPtr para la tabla de bytes empieza con la dirección.
[DllImport ("__Internal")]
private static extern int _Read (int sockfd, IntPtr msg, int size);
Para usar método nativo en el código C#, primero creamos una tabla de bytes. Luego necesitamos obtener un gestionador nativo de esta memoria y decirle a Mono GC para fijarlo. Esto es para que el GC no libere esta memoria mientras es usada por la biblioteca nativa. Una vez la función retorna, llamamos con la libertad de informar que a GC que el gestionador nativo no está siendo usado.
byte[] buffer = new byte[MAX_BUFFER_SIZE];
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
if (Application.platform == RuntimePlatform.IPhonePlayer) {
retVal = _Read(_SocketFd, handle.AddrOfPinnedObject(), buffer.Length);
handle.Free();
}
Espero que este post haya sido útil para los lectores quienes estén planeando escribir plugins nativos para móviles con Unity.Send. Escribanos a support@shephertz.com si tiene alguna pregunta o comentario.
Comment
Hello,
This is Tim Chen, a network engineer from China. Glad to meet you.
I read your blog “Writing native iOS and Android Unity Plugins” from
http://blogs.shephertz.com/2014/02/05/writing-native-ios-and-android-unity-plugins/
Would you mind discussing a strange behaviour about BSD socket in Android and iOS with me?
I used socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) to create the socket.
And used recv(soc, &buffer, 0, 0) to receive data from server.
It worked well on iOS for several years.
But when I tried to port it to Android, I found the socket behaviours are different between Android and iOS.
In iOS, after I turned off the wifi, the TCP socket keeps on without any error returned.
(i.e, the TCP socket of iOS does not hear the interface shutdown. It should be the pretty expected BSD action)
But, in Android, the TCP socket returned a read error as soon as I turned off the wifi interface.
It looks like the Android’s TCP socket keeps on monitoring the interface perpetually.
As a newbie of Android, it confused me very much.
Also, this issue made the application to be weak for any wireless network-jitter on Android.
Have you met such a strange issue on your programming?
Would you please provide some advices for the issue ?
Thank you.
Tim