- edited description
Using IOSystem or an extension of it breaks on Unity IL2CPP
In Unity and IL2CPP, configuring an IOSystem
breaks at runtime because of the calls to Marshal.GetFunctionPointerForDelegate
in the constructor, because IL2CPP does not know how to marshal instance methods:
NotSupportedException: IL2CPP does not support marshaling delegates that point to instance methods to native code.
Looking up some information around this, static class methods should work. Since Assimp supports taking UserData
, I tried passing an instance pointer, fetching it in a static callback, and then forwarding the call to the instance method.
IL2CPP then complains that this static method must be marked with MonoPInvokeCallback
. This seems impossible, however, since AssimpNet is not Unity-specific whilst this annotation is.
I managed to work around this problem by making both existing instance callbacks protected, extracting an Initialize
method from the constructor, and adding a boolean that allows skipping the initialization. I can then initialize the IOSystem
myself in my extension class, and set the pointers to my static class methods in Unity - which can have these attributes. This isn’t ideal, however, since I had to expose more API surface in IOStream
- this could all have been transparently handled in IOStream
proper, if it weren’t for the required Unity annotations and IL2CPP shenanigans.
EDIT: The same appears to apply to IOStream
constructor.
Comments (5)
-
reporter -
reporter - edited description
-
repo owner Yeah, IL2CPP has been a problem for this library in the past. Do you have your solution available somewhere? Maybe we can work out a cleaner way to do this.
-
reporter It’s a proprietary project, but I can share some snippets that show what I’ve done now to get this to work, using
IOSystem
as an example.In AssimpNet, I modified
IOSystem
’s constructor as follows:public IOSystem(bool initialize = true) { if (initialize) { Initialize(OnAiFileOpenProc, OnAiFileCloseProc, IntPtr.Zero); } m_openedFiles = new Dictionary<IntPtr, IOStream>(); } protected void Initialize(AiFileOpenProc fileOpenProc, AiFileCloseProc fileCloseProc, IntPtr userData) { AiFileIO fileIO; fileIO.OpenProc = Marshal.GetFunctionPointerForDelegate(fileOpenProc); fileIO.CloseProc = Marshal.GetFunctionPointerForDelegate(fileCloseProc); fileIO.UserData = userData; m_fileIOPtr = MemoryHelper.AllocateMemory(MemoryHelper.SizeOf<AiFileIO>()); Marshal.StructureToPtr(fileIO, m_fileIOPtr, false); }
By setting the new
initialize
tofalse
in my code, I avoidIOSystem
immediately callingGetFunctionPointerForDelegate
with an instance method, which is what fails on IL2CPP because it does not support this.In my code, then, I do something like:
public sealed class IOSystem : Assimp.IOSystem { private GCHandle? instanceHandle; public IOSystem(): base(false) { instanceHandle = GCHandle.Alloc(this); Initialize(OnAiFileOpenProcDispatcher, OnAiFileCloseProcDispatcher, (IntPtr)instanceHandle); } protected override void Dispose(bool disposing) { base.Dispose(disposing); if (disposing) { instanceHandle?.Free(); instanceHandle = null; } } [MonoPInvokeCallback(typeof(AiFileOpenProc))] private static IntPtr OnAiFileOpenProcDispatcher(IntPtr fileIO, string pathToFile, string mode) { AiFileIO fileIOInstance = MemoryHelper.MarshalStructure<AiFileIO>(fileIO); IOSystem ioSystemInstance = ((GCHandle)fileIOInstance.UserData).Target as IOSystem; return ioSystemInstance.OnAiFileOpenProc(fileIO, pathToFile, mode); } [MonoPInvokeCallback(typeof(AiFileCloseProc))] private static void OnAiFileCloseProcDispatcher(IntPtr fileIO, IntPtr file) { AiFileIO fileIOInstance = MemoryHelper.MarshalStructure<AiFileIO>(fileIO); IOSystem ioSystemInstance = ((GCHandle)fileIOInstance.UserData).Target as IOSystem; ioSystemInstance.OnAiFileCloseProc(fileIO, file); } }
As you can see, what I’m doing is using static methods instead of instance methods to pass to Assimp, which is fine for IL2CPP. Because I still want to have access to the instance, I use Assimp’s
UserData
to marshall the pointer back and forth.I originally wanted to place this code inside AssimpNet itself, which would make this behaviour transparent and not require application changes, but I can’t because of the (unfortunately required)
MonoPInvokeCallback
attribute, which seems to be Unity-specific and not available in the .NET context of AssimpNet.I’ve used the same approach for
IOStream
. -
reporter I’ve created PR #5 with the AssimpNet-specific bits of this that allow overriding the callbacks passed to Assimp from Unity. The Unity bits are mostly out of scope for AssimpNet itself, though they could be added to the wiki, README, or Unity sample code, as they are necessary to work with custom
IOSystem
s in Unity when using IL2CPP. - Log in to comment