Calling browser-side JavaScript functions from C# functions in Unity(WebGL)

Posted by jaz529 on Tue, 11 Jan 2022 09:34:01 +0100

How to link with JavaScript on the browser side

If you know WebGL:Communicating with a browser script , there seem to be two main ways.
  1. Application.External Call()/Send Message() -------- however, the first one was discarded after a certain version of unity2018,
  2. Use the plug-in (. jslib) to write code to execute on the browser side.
In this article, we use the second plug-in format to describe methods that call each other.

Plug ins (. jslib) cannot be written using es2015 (or later)

This is a bit painful. If you write it in es2015 (or later), there will be syntax errors and you will not be able to build it
Is it the emscripten specification?

establish. jslib file

Create a folder with the path Assets/Plugins/WebGl and place the JavaScript code to be executed by the browser. The plug-in extension is jslib, save the code file in this path. When you publish, this code (specifically) will be extended in build JS
 1 ----------.jslib------- 
 2 var HogePlugin = { 
 3     Fuga: function(int x, int y) { 
 4         return x + y; 
 5     }, 
 6     Piyo: function(arg) {
 7      var str = Pointer_stringify(arg); 
 8     } 
 9 } 
10 mergeInto(LibraryManager.library, HogePlugin);    

 

The plug-in function is extended in build JS, and a "" is added at the beginning of the function name

When you start publishing and packaging, the functionality of the plug-in will be extended in the build JS, and a "" will be added at the beginning of the function name
Therefore, when calling these functions from the JS side, please add "" before the functions
 1 ----------.jslib------- 
 2 var HogePlugin = { 
 3     Fuga: function(int x, int y) {
 4          return x + y; 
 5     }, 
 6     Piyo: function(arg) {
 7          var str = Pointer_stringify(arg); 
 8     } 
 9 } 
10 mergeInto(LibraryManager.library, HogePlugin);
1 ----------js------- 
2 // ... 
3 function _Fuga(int x, int y) { 
4     return x + y; 
5 }, 
6 function _Piyo(arg) {
7      var str = Pointer_stringify(arg); 
8 } 
9 // ...

 

Starting with Unity 5.6, it will expand in blocks

Although it is a deployment target, it is easy to access from external JS code because it has been extended to the global scope up to 5.5. However, since 5.6, it has been expanded in blocks, so it cannot be accessed from external JS code
Therefore, in order to make it accessible from external JS code, you can write code to set the properties of the global object in the plug-in
 

Calling a JS function from C#

You can read WebGL: Communicating with a browser script To learn, but temporarily you can get the return value by calling a function with a return value
Call JS function and use DllImport attribute to define and call the function to be used, which is similar to DLL function call
 1 using System.Runtime.InteropServices; 
 2 class Hoge { 
 3   [DllImport("__Internal")] 
 4   public static extern int Fuga(int n); 
 5 
 6   public void CallFuga(n) { 
 7     var ret = Fuga(n); 
 8     Debug.Log(ret); 
 9   } 
10   // .... 
11 }
1 ----------.jslib-------
2 var HogePlugin = {
3   Fuga: function(t) { 
4   return t + 10; 
5   }
6 } 
7 mergeInto(LibraryManager.library, HogePlugin);

 

Functions that are not DllImport will not be extended

If you only write DllImport for Fuga() and build it as shown in the following example, she will check correctly and Piyo() will not be extended to build JS
1 ----------.jslib------- 
2 var HogePlugin = { 
3   Fuga: function(int x, int y) {
4     return x + y; 
    },
5   Piyo: function(arg) { 6     var str = Pointer_stringify(arg); 7   } 8 } 9 mergeInto(LibraryManager.library, HogePlugin);
1 using System.Runtime.InteropServices; 
2 class Hoge { 
3   [DllImport("__Internal")] 
4   public static extern string Fuga(string configuration); 
5 }
1 ----------JS In script------- 
2 // ... 
3 function _Fuga(int x, int y) { 
4   return x + y; 
5 },
6 // ...
 

If you want to define a function, array or object on the JS side, add $and execute autoAddDeps()

If you have a function on the C# side that is not (directly) used (do not DllImport) but wants to be extended to JS, or you want to define arrays or objects separately, you can use the following code in the '$' plug-in to expand JS with '$' function name or variable name on the JS side by describing it with member name and executing autoAddDeps(). Therefore, as a warning, when these are used on the JS side, such as "" Use a name with "$".
However, it seems impossible to use primitive things such as Booleans, numbers, and strings. Specifically, in the case of Boolean value or numeric value, it is not expanded. In the case of string, for some reason, the string becomes the variable name and is output (null, arguments) to this variable as an applicable function.
 1 ----------.jslib------- 
 2 var HogePlugin = {
 3   $hpFunc: function () {
 4   return null; 
 5   },
6   $hpBool: true, 7   $hpNum: 100, 8   $hpStr: "test", 9   $hpArr: [1, 2, 3], 10   $hpObj: { 11     hoge: 'fuga', 12     piyo: 2 13   }, 14   TestFunc: function () { 15     return null; 16   }, 17   TestFuga: function () { 18     return null; 19   } 20 }; 21 autoAddDeps(HogePlugin, '$hpFunc'); 22 autoAddDeps(HogePlugin, '$hpBool'); 23 autoAddDeps(HogePlugin, '$hpNum'); 24 autoAddDeps(HogePlugin, '$hpStr'); 25 autoAddDeps(HogePlugin, '$hpArr'); 26 autoAddDeps(HogePlugin, '$hpObj'); 27 mergeInto(LibraryManager.library, HogePlugin);
 1 ----------JS In script------- 
 2 // ... 
 3 var hpObj = { 
 4   hoge: "fuga", 
 5   piyo: 2 
 6 }; 
 7 var hpArr = [ 1, 2, 3 ]; 
8 function hpStr() { 9   return _\u30c6\u30b9\u30c8.apply(null, arguments); 10 } 11 // ...
 

When a parameter is passed to a JS function, a numeric type is passed as is, but if a non numeric string type or array is passed, a pointer is passed

bool types are passed as 0 and 1
String use Pointer_stringify() gets the string from the pointer
When passing an array of numeric types (int,float, etc.), in addition to the array itself, you also need to pass the number of elements of the array
Getting a numeric array from a pointer is executed from HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32, HEAPF32, and HEAPF64
 1 ----------.jslib------- 
 2 var HogePlugin = {
 3     // Prepare a function that returns arrays (subarrays) of various numeric types from a pointer 
 4     $arrFromPtr: function(ptr, size, heap) {
 5     // Returns an array that is not generated HEAP Subarray (value copy) 
 6     var startIndex = ptr / heap.BYTES_PER_ELEMENT; 
 7     // The starting index is calculated by dividing by the number of bytes in the type 
 8     return heap.subarray(startIndex, startIndex + size); 
 9   }, 
10   Fuga: function(n, f, pStr, pShortArr, shortArrLen, pFloatArr, floatArrLen) {
11     console.log(n, f);  // The value type can be obtained as is 
12     str = Pointer_stringify(pStr);  // For string types, use Pointer_stringify() Gets the string from the pointer. 
13     // from HEAPnn Array of values obtained 
14     var shortArr = arrFromPtr(pShortArr, shortArrLen, HEAP16);
15     var floatArr = arrFromPtr(pFloatArr, floatArrLen, HEAPF32); 
16   } 
17 } 
18 autoAddDeps(HogePlugin, '$arrFromPtr');
19 mergeInto(LibraryManager.library, HogePlugin);

 

When a string type is used as a parameter, passing null will result in '' (empty string)

If you set the parameter type of JS function to string and call it (specifically, the return value of Pointer_stringify() will be "")
1 using System.Runtime.InteropServices; 
2 class Hoge : MonoBehaviour { 3 [DllImport("__Internal")] 4 public static extern void Fuga(string str);
5 void Start() { 6 Fuga(null); 7 } 8 }
 1 ----------sample3.jslib------- 
 2 
 3 var HogePlugin = {
 4     Fuga: function(arg) {
 5        var str = Pinter_stringify(arg); 
 6     console.log('isNull:' + (str === null), 'isEmpty:' + (str === '')); // isNull:false isEmpty:true 
 7     } 
 8 } 
 9 
10 mergeInto(LibraryManager.library, HogePlugin);    

 

String array, object array and object type cannot be passed (JSON and other serialization)

It's not hard to imagine that you can't pass arrays other than object or numeric types. Therefore, if you want to pass such a value, you should pass methods such as serialization to JSON and deserialization on the JS side.
 

Overload not available

C # can overload functions, but JS does not overload functions
So, as in the following example, I wrote C# the code with overloading and wrote a function with JS code, and tried
The build worked well, but did not produce the expected results.
Specifically, when I run the following code, JS console The result of log() is
 1 >1,undefined 2 >2,undefined 
It seems that only the first DllImport. Is called
 1 using System.Runtime.InteropServices;
 2 
 3 class Hoge : MonoBehaviour { 
 4     [DllImport("__Internal")] 
 5     public static extern void Fuga(int arg1); 
 6     [DllImport("__Internal")] 
 7     public static extern void Fuga(int arg1, int arg2); 
 8 
 9     void Start() {
10         Fuga(1);
11         Fuga(2, 3); 
12     } 
13 }    
1 ----------sample3.jslib------- 
2 var HogePlugin = {
3     Fuga: function(arg1, arg2) {
4         console.log(arg1, arg2); // 1, undefined 
5                                             // 2, undefined 
6     } 
7 }
8 mergeInto(LibraryManager.library, HogePlugin);    

 

Object or Nullable types cannot be specified as return types

The build can pass, but there will be errors at run time.
Due to the Emscripten specification, I think this is inevitable.

When Object is specified as the return value

 1 using System.Runtime.InteropServices; 
 2 
 3 class Hoge : MonoBehaviour { 
 4     [DllImport("__Internal")] 
 5     public static extern object Fuga(int n); 
 6 
 7     void Start() {
 8         var ret = Fuga(n); 
 9         Debug.Log(ret); 
10     } 
11 }    
1 ----------.jslib------- 
2 var HogePlugin = {
3     Fuga: function(t) {
4         return t + 10; 
5     } 
6 } 
7 mergeInto(LibraryManager.library, HogePlugin);

1 ----------- JS console print information ----------- 2 marshaldirectiveexception: cannot Marshal type 'system Object' 

 

When int? Is specified as the return value

 1 using System.Runtime.InteropServices; 
 2 
 3 class Hoge : MonoBehaviour {
 4     [DllImport("__Internal")] 
 5     public static extern int? Fuga(int n); 
 6 
 7     void Start() {
 8         var ret = Fuga(n);
 9         Debug.Log(ret); 
10     } 
11 }
1 ----------.jslib------- 
2 var HogePlugin = {
3     Fuga: function(t) {
4         return t + 10; 
5     } 
6 } 
7 
8 mergeInto(LibraryManager.library, HogePlugin);

1 ----------- JS console print information ----------- 2 marshaldirectiveexception: cannot Marshal type 'system Nullable`1<System. Int32>' 

 
Unfinished To be continued

Topics: C# Unity webgl