A native plug-in provides a C interface that you can access from managed (C#) scripts.
Unity native plug-in support uses the standard .NET Platform invoke feature. This feature lets C# code call unmanaged functions exported by native code libraries. It uses a process called marshalling to transfer data between managed and unmanaged code.
For an example of a native plug-in, check out the Simplest Plug-in example on Github.
You can import native plug-ins into a Unity project in two basic forms:
To work in Unity, a native plug-in must export its external functions using C linkage. This type of linkage avoids C++ name mangling and other Application Binary Interface (ABI) issues.
To access an unmanaged function from managed C# code, you declare a C# static extern function with the same name and compatible return and parameter types, then call it like any other method. Refer to Call functions for the full declaration rules and examples.
Unity supports precompiled static libraries on Apple mobile platforms (iOS, tvOS and visionOS) and on Android. Plug-ins that you import into your Unity project as source code files are also statically linked. Unity supports source code plug-ins in projects that use the IL2CPP scripting backend. Source code plug-ins aren’t supported for Editor scripts or when code runs in Play mode.
After you import plug-in files into your Unity project, you must set properties that determine when the library should be loaded. To do this, select the imported files in the Unity Project panel and set the relevant properties in the Inspector. Refer to Change plug-in settings for more information.
Note: When you load a native plug-in during a Unity Editor session – either through an Editor script or in Play mode, the Editor cannot unload it. To update a native plug-in after it has been loaded by the Editor process – either as a precompiled binary or as a source code file, you must restart the Editor application. Otherwise, the Editor continues to use the loaded version of the plug-in instead of the updated version.
The C# code that you write in Unity is managed by the scripting runtime. The scripting runtime manages where data is stored in memory and routinely moves data around to avoid memory fragmentation. Unmanaged code in a native plug-in, on the other hand, handles its own memory directly and can use pointers to address specific memory locations. When you write managed code that interoperates with unmanaged code, you have to be careful about where the data is stored, when the data is accessed, and which side is responsible for freeing memory that’s no longer needed.
Some data types can be represented in different ways in managed code as compared to native code. .NET provides an interop marshaller that provides a default representation and conversion for most types. For situations where the default behavior is incorrect or suboptimal, the .NET Interop Services API provides the following attributes that you can use to explicitly define the desired representation and conversion for specific data types:
Refer to Pass data between managed and unmanaged code for more information.
A simple, C-language native library with a single function might have code that looks like this:
float ExamplePluginFunction ()
{
return 5.0F;
}
Note: This minimal example omits the export annotation for brevity. A precompiled dynamic library (such as the one imported below with [DllImport("PluginName")]) must export its functions. Refer to Call unmanaged functions from managed code for the EXPORT_API annotation that exports a function on each platform.
If you compiled this code separately and imported the compiled library into your Unity project, you could use the following C# script to call the ExamplePluginFunction():
using UnityEngine;
using System.Runtime.InteropServices;
class ExampleScript : MonoBehaviour
{
// Use the library file name for dynamically linked libraries.
[DllImport ("PluginName")]
private static extern float ExamplePluginFunction ();
void Awake ()
{
// Calls the ExamplePluginFunction inside the plugin
// And prints 5 to the console
Debug.Log (ExamplePluginFunction ());
}
}
If you import the native code as source code, then you must use __Internal in the DllImport attribute rather than the library name:
using UnityEngine;
using System.Runtime.InteropServices;
class ExampleScript : MonoBehaviour
{
// Use __Internal instead of the library name for source-code plug-ins.
[DllImport ("__Internal")]
private static extern float ExamplePluginFunction ();
void Awake ()
{
// Calls the ExamplePluginFunction inside the plugin
// And prints 5 to the console
Debug.Log (ExamplePluginFunction ());
}
}
Refer to DllImport attribute for more information on how to identify the native plug-in library to load.
Interoperation between managed and unmanaged code is a complex topic. This documentation is only intended to provide a brief orientation to the subject. Refer to the following .NET documentation to gain a more thorough understanding:
Note: This documentation discusses the Platform Invoke interop model. However, you can also use the COM Interop model discussed in Microsoft’s .NET documentation.