Search Unity

  1. We're looking for feedback on Unity Starter Kits! Let us know what you’d like.
    Dismiss Notice
  2. Unity 2017.2 beta is now available for download.
    Dismiss Notice
  3. Unity 2017.1 is now released.
    Dismiss Notice
  4. Introducing the Unity Essentials Packs! Find out more.
    Dismiss Notice
  5. Check out all the fixes for 5.6 on the patch releases page.
    Dismiss Notice
  6. Help us improve the editor usability and artist workflows. Join our discussion to provide your feedback.
    Dismiss Notice

C# - .jslib 2-way communication

Discussion in 'WebGL' started by DudeGuy, May 4, 2015.

  1. DudeGuy

    DudeGuy

    Joined:
    Jul 17, 2014
    Posts:
    19
    Hello,

    In my WebGL targeted project, I currently have a .jslib file I'm using so that I can access javascript methods from my C# code. This all works great.

    What I'm hoping to be able to do is to call back from the .jslib file into my C# code. I'm ok with either having a direct method to call from javascript or having a delegate/System.Action passed in from the C# side of things. I don't know how to handle either situation.

    Here is a simple example of what I'd like to do from the C# side:

    private System.Action action = null;

    [DllImport("__Internal")]
    private static extern void DoSomething(System.Action callback); // Or delegate if necessary.

    Is this something that is possible and how would I manage it on the javascript side if so?

    Thanks!
     
  2. jonas-echterhoff

    jonas-echterhoff

    Unity Technologies

    Joined:
    Aug 18, 2005
    Posts:
    1,479
    liortal likes this.
  3. DudeGuy

    DudeGuy

    Joined:
    Jul 17, 2014
    Posts:
    19
    Doh, for some reason I got myself under the impression that SendMessage wasn't possible in WebGL. I see, the limitation is that it has to be a MonoBehaviour. I can make it work! I appreciate the reply and sorry for the inconvenience!
     
    Last edited: May 5, 2015
  4. gfoot

    gfoot

    Joined:
    Jan 5, 2011
    Posts:
    548
    SendMessage sucks a bit. If you want your Javascript to call the C# directly without just queuing a message, e.g. if you want your C# to return something, then you can pass callbacks like in your original post, so long as you jump through a few hoops in both the C# and the jslib.

    Here's one I prepared earlier:

    Code (CSharp):
    1. using System;
    2. using System.Runtime.InteropServices;
    3. using AOT;
    4. using UnityEngine;
    5.  
    6. public class JsCallCsTest : MonoBehaviour
    7. {
    8.     [DllImport("__Internal")]
    9.     public static extern void ProvideCallback(Action action);
    10.  
    11.     public void Start()
    12.     {
    13.         ProvideCallback(Callback);
    14.     }
    15.  
    16.     [MonoPInvokeCallback(typeof(Action))]
    17.     public static void Callback()
    18.     {
    19.         Debug.Log("Callback called");
    20.     }
    21. }
    And the jslib:

    Code (JavaScript):
    1. var LibraryJsCallCsTest = {
    2.     $JsCallCsTest: {},
    3.  
    4.     ProvideCallback: function(obj)
    5.     {
    6.         console.log("ProvideCallback");
    7.         console.log(obj);
    8.         JsCallCsTest.callback = obj;
    9.         Runtime.dynCall('v', obj, 0);
    10.     },
    11. };
    12.  
    13. autoAddDeps(LibraryJsCallCsTest, '$JsCallCsTest');
    14. mergeInto(LibraryManager.library, LibraryJsCallCsTest);
    That Runtime.dynCall is the delicate bit. obj is an integer index into a lookup table containing function pointers passed from C++ to Javascript. There's a separate table for each function signature, so in order for it to look in the right table, you need to specify the function signature - that's what the 'v' bit is for, it's one letter for the return type, then one more letter per parameter. 'v' means void (only for the return value). The other commonly-used letters seem to be i (integer), f (float), and d (double).

    You can search your generated code for FUNCTION_TABLE_ to see the various function tables that exist, and you might be able to find your callback adaptor function in the table if you can figure out what to look for, in case you're not sure what to specify for the signature.
     
    Last edited: May 6, 2015
    crushforth, friflo and RostislavS like this.
  5. jonas-echterhoff

    jonas-echterhoff

    Unity Technologies

    Joined:
    Aug 18, 2005
    Posts:
    1,479
    Yes, what gfoot wrote will also work - it requires more boilerplate, but may be the more elegant solution, depending on what you need.
     
  6. DudeGuy

    DudeGuy

    Joined:
    Jul 17, 2014
    Posts:
    19
    This is fantastic! Thanks much! I haven't had the time to mess around with this yet today, but I plan to do so very soon! Is it possible to do anything like this on a class that is not a MonoBehaviour?

    I'm pretty new to javascript in general and so I'm curious about the javascript portion you have there which is:

    $JsCallCsTest: {},

    Does this somehow link itself to the class? And should it be JSCallCsTest.Callback instead of .callback? Either way, I plan to mess around with this very soon.

    Thanks again all!
     
  7. gfoot

    gfoot

    Joined:
    Jan 5, 2011
    Posts:
    548
    Yes it is nothing to do with MonoBehaviour. It only works for static methods, at least according to the docs for MonoPInvokeCallback. I don't know whether that restriction still applies with IL2CPP.

    The $JsCallCsTest variable is a matter of habit, it is not really used there. It is an empty object that can contain data shared between the various functions exported by the jslib.

    The idea was to store the callback for future reference and actually call it from somewhere else, but I guess I just made it call it straight away in the end.
     
  8. DudeGuy

    DudeGuy

    Joined:
    Jul 17, 2014
    Posts:
    19
    Oh ok, very nice, I see what you're saying. You pretty much create an empty object and dynamically add a "callback" field to it. So it works similar to python in that regard. (Sorry, still new to javascript).

    That is great that it doesn't have to work on a MonoBehaviour and I do remember seeing all of the 'v', 'vi', 'iii', etc in code. Wasn't aware that's what it stood for until this thread. This seems doable!

    So thanks again for the help!
     
  9. RostislavS

    RostislavS

    Joined:
    Jun 17, 2015
    Posts:
    4
    Big Thanks for that !

    But how can I pass 'string' to my Callback as a parameter?
     
  10. crushforth

    crushforth

    Joined:
    Jul 22, 2010
    Posts:
    113
    @jonas echterhoff & @gfoot I think I need to use this method of communicating back to the C# from javascript because SendMessage (that I'm currently using) fails when the WebGL is packaged into a chrome web app because of how the security is different (can't use eval or any string to method functionality)

    I'm not fully understanding the above example (and it seems this isn't widely used). How do I actually trigger the callback from normal webpage javascript.

    Code (csharp):
    1.  
    2. var LibraryJsCallCsTest = {
    3.     $JsCallCsTest: {},
    4.    
    5.     ProvideCallback: function(obj) {
    6.         console.log("ProvideCallback");
    7.         console.log(obj);
    8.  
    9.         JsCallCsTest.callback = obj;
    10.     },
    11.  
    12.     TriggerCallback: function() {
    13.         Runtime.dynCall('v', JsCallCsTest.callback, 0);
    14.     },
    15. };
    16. autoAddDeps(LibraryJsCallCsTest, '$JsCallCsTest');
    17. mergeInto(LibraryManager.library, LibraryJsCallCsTest);
    18.  
    To trigger the function would you have to use the library name like LibraryJsCallCsTest.TriggerCallback(); I'll do some experiments (I'm mostly asking as the iteration time on WebGL can be quite lengthy).
     
  11. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    317
    Hello crushforth.

    You can face a security restriction when calling JavaScript from C# using Application.ExternalCall() or Application.ExternalEval().

    EDIT: it appears that SendMessage also involves eval and therefore will also cause security exception

    Of course, there are some situations when you need a special way of interaction, i.e. for performance reasons. Please attach your original interaction code (both C# without Application.ExternalCall() or Application.ExternalEval(), and JavaScript with SendMessage) to make sure that it really is the case for you.

    The solution described above involves direct access to the function tables, which is implementation-specific and therefore may require some support if Emscripten code changes. There is also another interesting solution provided by atti which seems to be more implementation-independent to me: http://forum.unity3d.com/threads/super-fast-javascript-interaction-on-webgl.382734/
     
    Last edited: Feb 2, 2016
  12. crushforth

    crushforth

    Joined:
    Jul 22, 2010
    Posts:
    113
    Hi Alex, I implemented your suggestion yesterday about how to get around using eval using a jslib and not using Application.ExternalCall. This all worked perfectly going in that direction now my problem is once the youtube video finishes playing I need to signal back to the unity code that the iframe/webview has been closed and the app need to continue.

    I just need any method that wont fail in a packaged chrome app of signalling back to unity. Was thinking of maybe using player prefs or something and monitoring a flag.
     
  13. crushforth

    crushforth

    Joined:
    Jul 22, 2010
    Posts:
    113
    I'll re-implement the SendMessage version and show you the error. Maybe I interpreted it incorrectly.
     
  14. crushforth

    crushforth

    Joined:
    Jul 22, 2010
    Posts:
    113
    It does seem to be an issue with either SendMessage or cwrap doing an unsafe-eval.

    Screen Shot 2016-02-02 at 2.23.15 PM.png

    The window.alert is I believe unity trying to she the exception and it being blocked in a chrome app.
     
  15. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    317
    Yes, you are absolutely right. It appears that cwrap involves eval.
    Nevertheless, I believe this can be easily resolved. I can not test on chrome app right now, but here is the idea how to eliminate eval from the SendMessage. Add the following script into your index.html:
    Code (CSharp):
    1. ...
    2. <script src="Release/UnityLoader.js"></script>
    3. <script>
    4.   Module.onRuntimeInitialized = function() {
    5.     SendMessage = function(gameObject, func, param) {
    6.       if (param === undefined)
    7.         Module.ccall("SendMessage", null, ["string", "string"], [gameObject, func]);
    8.       else if (typeof param === "string")
    9.         Module.ccall("SendMessageString", null, ["string", "string", "string"], [gameObject, func, param]);
    10.       else if (typeof param === "number")
    11.         Module.ccall("SendMessageFloat", null, ["string", "string", "number"], [gameObject, func, param]);
    12.       else
    13.         throw "" + param + " is does not have a type which is supported by SendMessage.";
    14.     }
    15.   }
    16. </script>
    17. ...
    This should override the default SendMessage and eliminate the cwrap call. Let me know if this resolved the situation or if some additional steps are required.
     
    crushforth likes this.
  16. crushforth

    crushforth

    Joined:
    Jul 22, 2010
    Posts:
    113
    Thanks Alex, You're quickly becoming one of my favourite Unity employees. Your help and suggestions over the last few days have got me out of some very sticky situations. Very much appreciated.

    That did fix my issue. Would be great to get this implemented in future Unity versions. If you need me to create an issue/bug I'd be glad to.
     
  17. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    317
    Thank you very much for the kind words.
    This will surely be implemented in the future versions of Unity. Feel free to create a bug report (also please post the case number here).

    By the way, if you don't want to deal with index.html or WebGL templates, you can also just call
    Module.ccall("SendMessage", null, ["string", "string"], [gameObject, func]),
    Module.ccall("SendMessageString", null, ["string", "string", "string"], [gameObject, func, param]) or
    Module.ccall("SendMessageFloat", null, ["string", "string", "number"], [gameObject, func, param])

    instead of SendMessage(gameObject, func, param) directly from your jslib plugin. I believe the result will be pretty much the same.
     
    Last edited: Feb 3, 2016
  18. Schubkraft

    Schubkraft

    Unity Technologies

    Joined:
    Dec 3, 2012
    Posts:
    657
    We've got the bug and I've passed it on. Thanks for submitting it.
     
  19. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    42
    Can we use Module.ccall to call static function in C# directly?

    Are there anyway to just call C# function directly without SendMessage?
     
  20. alexsuvorov

    alexsuvorov

    Unity Technologies

    Joined:
    Nov 15, 2015
    Posts:
    317
    Hello Thaina.

    Please check out the following topic http://forum.unity3d.com/threads/super-fast-javascript-interaction-on-webgl.382734/
    There you can find an explanation why C# function can not be called directly, and a working solution proposed by atti, demonstrating how to call a static C# function from JavaScript using C plugin.
     
    Thaina likes this.
  21. Thaina

    Thaina

    Joined:
    Jul 13, 2012
    Posts:
    42
    Now I was trying to play with this feature. It somehow success but I'm so curious why we can only use static method with AOT.MonoPInvokeCallBack. I wish we could just send function pointer from delegate here

    More and more I try to use webgl on unity. More and more I felt that unity never want us to use webgl
     
  22. deniskrop

    deniskrop

    Joined:
    Dec 6, 2014
    Posts:
    25
    Would I be able to use this to send an array of bytes as well as number thats the buffers length, in order to increase the speed of getting websocket data?
     
  23. Nickname219

    Nickname219

    Joined:
    Mar 16, 2014
    Posts:
    1
    Try this:

    Code (CSharp):
    1. using AOT;
    2. using System.Runtime.InteropServices;
    3. using UnityEngine;
    4.  
    5. public class SimpleScript : MonoBehaviour
    6. {
    7.     public delegate void SimpleCallback(System.IntPtr ptr);
    8.  
    9.     [DllImport("__Internal")]
    10.     private static extern void SimpleMethod(SimpleCallback callback);
    11.  
    12.     private void Start()
    13.     {
    14.         SimpleMethod(Callback);
    15.     }
    16.  
    17.     [MonoPInvokeCallback(typeof(SimpleCallback))]
    18.     public static void Callback(System.IntPtr ptr)
    19.     {
    20.         string value = Marshal.PtrToStringAuto(ptr);
    21.         Debug.Log("Managed string: " + value);
    22.     }
    23. }
    and jslib:

    Code (JavaScript):
    1. var SimplePlugin = {
    2.  
    3.     SimpleMethod: function(obj) {
    4.    
    5.         var buffer = getPtrFromString('Hello, world!');
    6.         Runtime.dynCall('vi', obj, [buffer]);
    7.        
    8.         function getPtrFromString(str) {
    9.             var buffer = _malloc(lengthBytesUTF8(str) + 1);
    10.             writeStringToMemory(str, buffer);
    11.             return buffer;
    12.         }
    13.     }
    14. };
    15.  
    16. mergeInto(LibraryManager.library, SimplePlugin);