C# - .jslib 2-way communication

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!

As written here, you can call functions in your Unity code from JS using SendMessage:

1 Like

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!

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:

using System;
using System.Runtime.InteropServices;
using AOT;
using UnityEngine;

public class JsCallCsTest : MonoBehaviour
{
    [DllImport("__Internal")]
    public static extern void ProvideCallback(Action action);

    public void Start()
    {
        ProvideCallback(Callback);
    }

    [MonoPInvokeCallback(typeof(Action))]
    public static void Callback()
    {
        Debug.Log("Callback called");
    }
}

And the jslib:

var LibraryJsCallCsTest = {
    $JsCallCsTest: {},

    ProvideCallback: function(obj)
    {
        console.log("ProvideCallback");
        console.log(obj);
        JsCallCsTest.callback = obj;
        Runtime.dynCall('v', obj, 0);
    },
};

autoAddDeps(LibraryJsCallCsTest, '$JsCallCsTest');
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.

16 Likes

Yes, what gfoot wrote will also work - it requires more boilerplate, but may be the more elegant solution, depending on what you need.

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!

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.

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!

Big Thanks for that !

But how can I pass ‘string’ to my Callback as a parameter?

@jonas-echterhoff_1 & @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.

var LibraryJsCallCsTest = {
    $JsCallCsTest: {},
    
    ProvideCallback: function(obj) {
        console.log("ProvideCallback");
        console.log(obj);

        JsCallCsTest.callback = obj;
    },

    TriggerCallback: function() {
        Runtime.dynCall('v', JsCallCsTest.callback, 0);
    },
};
autoAddDeps(LibraryJsCallCsTest, '$JsCallCsTest');
mergeInto(LibraryManager.library, LibraryJsCallCsTest);

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).

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/

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.

I’ll re-implement the SendMessage version and show you the error. Maybe I interpreted it incorrectly.

It does seem to be an issue with either SendMessage or cwrap doing an unsafe-eval.

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

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:

...
<script src="Release/UnityLoader.js"></script>
<script>
  Module.onRuntimeInitialized = function() {
    SendMessage = function(gameObject, func, param) {
      if (param === undefined)
        Module.ccall("SendMessage", null, ["string", "string"], [gameObject, func]);
      else if (typeof param === "string")
        Module.ccall("SendMessageString", null, ["string", "string", "string"], [gameObject, func, param]);
      else if (typeof param === "number")
        Module.ccall("SendMessageFloat", null, ["string", "string", "number"], [gameObject, func, param]);
      else
        throw "" + param + " is does not have a type which is supported by SendMessage.";
    }
  }
</script>
...

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.

1 Like

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.

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.

We’ve got the bug and I’ve passed it on. Thanks for submitting it.

Can we use Module.ccall to call static function in C# directly?

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

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.

1 Like