To call a managed function from unmanaged code, pass a function pointer to the unmanaged code. Use a delegate to define the signature of the callback function. The callback function must be static and have an AOT.MonoPInvokeCallbackAttribute that indicates the matching delegate type.
The following C# example defines a delegate type and a static callback function, and passes the callback to the unmanaged RegisterMyCallback function:
using System;
using System.Runtime.InteropServices;
using UnityEngine;
public class PluginImport : MonoBehaviour
{
delegate void MyFuncType();
[AOT.MonoPInvokeCallback(typeof(MyFuncType))]
static void MyFunction() {
Debug.Log("Got a call back from native code.");
}
[DllImport("__Internal")]
static extern void RegisterMyCallback(MyFuncType func);
// Keep a reference to the delegate so the garbage collector doesn't collect it
// while native code still holds the function pointer.
static MyFuncType s_callback;
void Start()
{
s_callback = MyFunction;
RegisterMyCallback(s_callback);
}
}
Important: When you pass a method to RegisterMyCallback, the runtime creates a delegate instance. The marshaller keeps that delegate alive only for the duration of the call, so a callback that native code invokes immediately is safe. If native code stores the function pointer to call later, as in this example, keep a managed reference to the delegate, such as in a static field, for as long as native code might call it. Otherwise, the garbage collector can collect the delegate, and a later callback uses an invalid function pointer, which crashes or corrupts your application.
Note: If you use a precompiled, dynamically linked library, change the DllImport value to the library name. Use the special string __Internal for source code plug-ins and statically linked libraries. For more information, refer to DllImport attribute.
The unmanaged code must define a function pointer type that corresponds to the C# delegate. It must also export a function that receives the pointer to your delegate:
// On Windows, a C# delegate passed to native code defaults to the __stdcall calling convention,
// but C and C++ function pointers default to __cdecl.
// Define a macro so the function pointer type and the exported function use a matching convention on every platform.
// A mismatch crashes the 32-bit Windows Player.
// Check the target platform (_WIN32), not the compiler, because every Windows compiler
// (MSVC, MinGW, and Clang) supports __stdcall.
#if defined(_WIN32)
#define STDCALL __stdcall
#else
#define STDCALL
#endif
typedef void (STDCALL *MyFuncType)();
MyFuncType registeredCallback;
extern "C"
{
void STDCALL RegisterMyCallback(MyFuncType func) {
registeredCallback = func;
}
}
Your unmanaged code can invoke the callback immediately, or store the function pointer and invoke it later:
if (registeredCallback)
{
registeredCallback();
}