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: http://docs.unity3d.com/Manual/webgl-interactingwithbrowserscripting.html
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: Code (CSharp): 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: Code (JavaScript): 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.
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!
@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): 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: Code (CSharp): ... <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.
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.
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.
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
Try this: Code (CSharp): using AOT; using System.Runtime.InteropServices; using UnityEngine; public class SimpleScript : MonoBehaviour { public delegate void SimpleCallback(System.IntPtr ptr); [DllImport("__Internal")] private static extern void SimpleMethod(SimpleCallback callback); private void Start() { SimpleMethod(Callback); } [MonoPInvokeCallback(typeof(SimpleCallback))] public static void Callback(System.IntPtr ptr) { string value = Marshal.PtrToStringAuto(ptr); Debug.Log("Managed string: " + value); } } and jslib: Code (JavaScript): var SimplePlugin = { SimpleMethod: function(obj) { var buffer = getPtrFromString('Hello, world!'); Runtime.dynCall('vi', obj, [buffer]); function getPtrFromString(str) { var buffer = _malloc(lengthBytesUTF8(str) + 1); writeStringToMemory(str, buffer); return buffer; } } }; mergeInto(LibraryManager.library, SimplePlugin);
So here's an issue that I don't think .jslib has solved. I have two independent apps that need to talk to each other -- one javascript app that uses jquery, pixi, and a number of other libraries, and one Unity WebGL app. They do separate functions but occasionally need to pass data back and forth in the same browser window. How am I supposed to call a javascript function from unity that, say, has jquery lines in it, using this method? It seems to me that I would have to stuff jquery, pixi, and my entire javascript app into .jslib files, which is absurd. ExternalCall did this perfectly well, and since this is not going to be a public project (it's for a scientific study with maybe 50-100 users), I see no reason not to stick with that. Unless I'm misunderstanding something, which is totally possible!
If they stay in the same html page, then you can use JS<->JS interaction. You can place all of your public classes / functions in window scope (pixi, jquery, controllers for change html etc) and move this js-files to Template folder (for stay in your directory of game when you rebuild, otherwise you can make this scripts outside, but make sure to include this js files to html page and your redist of game) Then you can call JS-native (from Template, or not.. simple included in html page) from JS-plugin (in jspre and jslib extensions) and vise-versa Also you can call JS-plugin<->C# etc
What a about passing back 2 strings? Is this possible with passing 2 buffers or should it be concat in one big string? Trying: Code (CSharp): Runtime.dynCall('vii', obj, [buffer1, buffer2]) Fails miserably. Thanks
Try Runtime.dynCall('vii', obj, buffer1, buffer2); But it seems that there were some changes in emscripten, and you might want to try Module['dynCall_vii'](obj, buffer1, buffer2);
Both of these didn't seem to work for me to be honest. I resorted to concatining the two strings and splitting them again in c#..
I was able to pass two strings with this code. (Unity 2019.4.1f1) Code (CSharp): [DllImport("__Internal")] private static extern void cbTest(Action<string, string> cb); private void Start() { cbTest(cbStrStr); } [MonoPInvokeCallback(typeof(Action<string, string>))] private static void cbStrStr(string str1, string str2) { Debug.Log($"str1:{str1}, str2:{str2}"); } Code (JavaScript): mergeInto(LibraryManager.library, { cbTest: function(cb) { var str1 = 'foo'; var len1 = lengthBytesUTF8(str1) + 1; var strPtr1 = _malloc(len1); stringToUTF8(str1, strPtr1, len1); var str2 = 'bar'; var len2 = lengthBytesUTF8(str2) + 1; var strPtr2 = _malloc(len2); stringToUTF8(str2, strPtr2, len2); Module.dynCall_vii(cb, strPtr1, strPtr2); } }); I have a question. In the documentation ʻIf the string is a return value, then the il2cpp runtime will take care of freeing the memory for you` Is it necessary to do _free() in this case? How do I run _free() if I need to?
Thanks for posting a great example! No need to call _free() here. When a return value is a string, it *must* be allocated via _malloc(), and IL2CPP will take responsibility of calling _free() on the string memory.
No, integers, floats and doubles do not need to be freed. Only pointers returned by a call to _malloc() need to be freed, with that only exception of returning a string from a function to C#. The exception is there because otherwise there would be no way to free up the memory afterwards - passing of ownership must occur in that scenario.
@jukka_j Thax! There is another question I had after posting the question. What if I return an Array (Typed Array) type?
Because WebAssembly operates on a single typed array heap only and cannot address other typed arrays, it is not possible to return a typed array, but one will need to first _malloc() an array, then write the elements out to that array. Check out Emscripten documentation site on how to access HEAP8/HEAP16/HEAP32/HEAPF32/HEAPF64 to write to arrays from Javascript.
how can one call a function in another .js file from the .jslib? I can call functions in .jslib from c# but can't get .jslib to call my .js
You should be able to. From a JS function in a .jslib file, the whole global JS context is there, so whatever scripts one defines in the main .html file are visible to the .jslib content. If that is not working, then something is wrong with either the JS code on the .jslib side, or on the main .html side. Perhaps you can make a small example that is not working for you and ask in the forums, maybe someone will catch what is going wrong.
That is a good suggestion. Currently the compiler version we are using runs some JS optimizer passes that only manage ES5 code. Added a note to test out how well ES6 will work once we finish updating our compiler version.
Quick question this raises: I know you need to do the _malloc thing when returning strings, but do you also need to do it when calling Module.SendMessage or Module.dynCall_vii ? I'm confused because @gtk2k does below but this guy wrote a whole library where he uses SendMessage callbacks passing strings and never uses _malloc at all https://github.com/rotolonico/FirebaseWebGL/tree/master/Assets/FirebaseWebGL/Plugins
@travlake If you want to pass a string with dynCall_xxxxx(), you need to do _malloc(), but when use SendMessage() does not need.
It seem `Runtime.dynCall` is the old version of emscripten. `Module['dynCall_vii']` is just newer version move the api around. You should try both and log the result to see which one is exist in your build
I'm using 2020.3.0f1 and Runtime.dynCall seems to work although I get weird errors like this: https://forum.unity.com/threads/typeerror-npa-is-not-a-function.1078505/ I'll try Module['dynCall_vii'] and see if it changes something.