Search Unity

Native Plugin <-> C# interop: Crash in callback?

Discussion in 'Scripting' started by PhobicGunner, Jun 29, 2015.

  1. PhobicGunner

    PhobicGunner

    Joined:
    Jun 28, 2011
    Posts:
    1,813
    Running into a strange situation which I'm afraid is way over my head.

    The gist is this: I have a native plugin which accepts a callback. Here's the function type in C#:

    Code (csharp):
    1.  
    2. public delegate void HBlankHandler( int scanline );
    3.  
    And here's the extern function:

    Code (csharp):
    1.  
    2. #if UNITY_IPHONE || UNITY_XBOX360
    3.     [DllImport ("__Internal")]
    4. #else
    5.     [DllImport( "RetroCanvas" )]
    6. #endif
    7.     private static extern void GFX_SetHBlankCallback( int handle, HBlankHandler callback );
    8.  
    And here's the function which calls that:
    Code (csharp):
    1.  
    2. public void SetHBlankCallback( HBlankHandler callback )
    3. {
    4.         GFX_SetHBlankCallback( this.gEngineHandle, callback );
    5. }
    6.  
    And finally, here's the place where that is called:
    Code (csharp):
    1.  
    2. canvas = new NativeCanvas( 320, 240 );
    3. canvas.SetHBlankCallback( this.hblank );
    4.  
    5. // ...
    6. float time = 0.0f;
    7. void hblank( int scanline )
    8. {
    9. }
    10.  
    This works, so far. This, however, does NOT work:

    Code (csharp):
    1.  
    2. void hblank( int scanline )
    3. {
    4.     float test = this.time; // <-- accessing this.time causes a freeze
    5. }
    6.  
    When I try and access this.time from the callback function, it works for a few seconds before suddenly locking up Unity. Am I not allowed to access fields in function callbacks which are passed to native code?? If anybody has some insight they can share, that would be fantastic.
     
    olivierg4f likes this.
  2. PhobicGunner

    PhobicGunner

    Joined:
    Jun 28, 2011
    Posts:
    1,813
    Actually, I think I may have solved it. It looks like this:

    Code (csharp):
    1.  
    2. canvas.SetHBlankCallback( this.hblank );
    3.  
    Implicitly constructed a new instance of HBlankHandler. However, nothing keeps a reference to this new instance (the .NET runtime doesn't know or care about the native side having a pointer to the function, so that doesn't count), and therefore it is garbage collected. With an empty function, this apparently isn't an issue, but the moment it tries to access an instance member it throws a fit upon garbage collection.

    So the solution appears to be to explicitly store a reference to the passed HBlankHandler on the C# side to ensure that the GC does not collect it.
     
    eisenpony likes this.
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,738
    Thanks for this... good info to know. I see your 320x240 resolution call and cannot help but think you're tinkering with some good old VGA-era game code written in C/C++ and presenting its output in Unity... Am I even close? :)
     
  4. PhobicGunner

    PhobicGunner

    Joined:
    Jun 28, 2011
    Posts:
    1,813
    Very close. I'm writing a plugin (currently named RetroCanvas) which is intended to sort of emulate the way the graphics hardware worked in the SNES and Genesis, primarily for myself but also in case there are others who want the ease of C# and cross platform of Unity, but also want really very authentic retro-style graphics.

    I'm doing it in C++ because that's apparently the only performant way to directly render to a texture, and I wanted to do the rendering on the CPU side so that I can render scanline-by-scanline. In the above case, I allow the C# side to hook up a callback for when each scanline is finished rendering (IIRC the Genesis handled this with hardware interrupts). You can modify the state of the graphics engine within this callback, so for instance if you wanted an old-school wavy effect you could change the tile map scrolling on a per scanline basis, like so (uploaded this video right after getting callbacks figured out):



    Could also use this to palette swap mid-frame. For instance, Sonic The Hedgehog would do a palette swap mid-frame for an underwater effect.
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,738
    I approve! I have ported all my old PalmOS software-rendered retro arcade games (ones I wrote circa 2000-2002 or so) to run as a native plugin under Unity, and I'm on the verge of releasing them.

    I chose to keep the interface one-directional however, Managed calling Native only: Unity sends opcodes containing input state , and the native apps return a string that C# breaks apart to decide what to do in response. Finally there is a single call from C# that pins a Texture2D's pixel buffer down, passes it into native, and then the C code blasts the software-rendered buffer into it for Unity to stick up on a quad.

    You may find it easier to do your scan line trickery in native code, such that Unity doesn't even need to know about it. I'd be leery of those native-to-managed code calls. They had a lot of strange limitations and gotchas... if I recall correctly, they happened a full frame AFTER the frame you were on, etc. Be careful!
     
  6. PhobicGunner

    PhobicGunner

    Joined:
    Jun 28, 2011
    Posts:
    1,813
    This doesn't appear to be the case, in my tests. As far as I can see, managed -> native calls, and vice versa, are instant (which makes sense, given that native code can return values to the managed code which would be a blocking operation). However, the rendering happens on another thread entirely, so on the C# side, HBlank and VBlank callbacks cannot call into the Unity API. Also, modifying graphics state in, say, Update can result in screen tearing for this reason.

    So it looks like I mostly just need to keep thread safety in mind for rendering.
     
  7. larku

    larku

    Joined:
    Mar 14, 2013
    Posts:
    1,422
    Have you tried using callbacks to static methods? I had a similar issue ( a long time ago ) and solved this by wrapping my callbacks with static methods.
     
  8. PhobicGunner

    PhobicGunner

    Joined:
    Jun 28, 2011
    Posts:
    1,813
    Thanks, but I already fixed it. Turns out, the way I was passing the delegate ended up *implicitly* creating a new instance of HBlankHandler, but since nothing actually stored a reference to the implicitly created delegate, it ended up being garbage collected (which, when the plugin tried to call it, caused Unity to have the falling fits)

    The solution was to explicitly store a reference to the delegate so that it would not be garbage collected.
     
  9. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    974
    Thanks for sharing!
    For my own interest, and for posterity, could you show the code for creating and holding this reference?
     
  10. PhobicGunner

    PhobicGunner

    Joined:
    Jun 28, 2011
    Posts:
    1,813
    OK, so assuming your delegate looks like this:

    Code (csharp):
    1.  
    2. public delegate void MyCallbackDelegate();
    3.  
    And the extern function looks like this:

    Code (csharp):
    1.  
    2. #if UNITY_IPHONE || UNITY_XBOX360
    3.    [DllImport ("__Internal")]
    4. #else
    5.    [DllImport( "DllNameHere" )]
    6. #endif
    7.    private static extern void SetNativeCallback( MyCallbackDelegate callback );
    8.  
    You then pass delegates to it like this:

    Code (csharp):
    1.  
    2. private MyCallbackDelegate delegateInstance;
    3.  
    4. // ...
    5.  
    6. public void SetCallback( MyCallbackDelegate callback )
    7. {
    8.     this.delegateInstance = callback; // <- IMPORTANT! otherwise it will be garbage collected
    9.     SetNativeCallback( callback );
    10. }
    11.  
    And can call it like this:

    Code (csharp):
    1.  
    2. void someCallback()
    3. {
    4.     // do stuff
    5. }
    6.  
    7. // ...
    8.  
    9. myPluginWrapperClass.SetCallback( this.someCallback ); // this implicitly creates a new instance of MyCallbackDelegate, which myPluginWrapperClass will explicitly keep a reference to so that it is not garbage collected.
    10.  
     
    eisenpony likes this.
  11. PhobicGunner

    PhobicGunner

    Joined:
    Jun 28, 2011
    Posts:
    1,813
    Ugh. And now for the brick wall - I can register a callback between C# and C++, but apparently calling C# code from C++ on another thread (such as the render thread) is unsupported and doing so freezes the editor upon closing. Which would be presumably OK if I could just disable multithreaded rendering, but apparently that is impossible (or at the very least I cannot find any setting for it anywhere), because why wouldn't it be? -_-
     
  12. Giometric

    Giometric

    Joined:
    Dec 20, 2011
    Posts:
    170
    If you go to Player Settings, and switch the Inspector to Debug mode, a checkbox for "MT Rendering" will be there that you can turn off. Not sure personally whether that checkbox has any effect at all, but it is there.
     
  13. PhobicGunner

    PhobicGunner

    Joined:
    Jun 28, 2011
    Posts:
    1,813
    I found a script to set that value on or off from a custom menu, but unfortunately Unity seems to completely ignore it (I did some checks and the rendering is still happening on another thread)
     
  14. larku

    larku

    Joined:
    Mar 14, 2013
    Posts:
    1,422
    Edit, just reread your issue - my post would not address your problem. - deleted.
     
  15. ZakTheFallen

    ZakTheFallen

    Joined:
    Jun 25, 2019
    Posts:
    1
    I know this is an old thread, but I suddenly ran into this exact problem when creating button events through code. My game generates menues and buttons automatically (the game content is moddable, so it's a must), but when I used a delegate to pass strings into a function it calls, the editor would lock up and crash no matter what version I was using.

    I never thought to store those strings in variables first, but that completely solved the issue.
     
    Kurt-Dekker likes this.