Skip to main content

Calling C# from JavaScript

In the previous example, we briefly tried Hello World:

//1. Hello World
void Start() {
Puerts.JsEnv env = new Puerts.JsEnv();
env.Eval(@"
console.log('hello world');
");
}

In fact, the console.log here is not quite the same as the one in the browser. This console.log is intercepted by PuerTS and will actually call UnityEngine.Debug.Log to print the string content.

With the help of Puerts, the integration between JavaScript and C# can be even more exciting. See below for more:


Object creating

//2. Creating a C# object
void Start() {
Puerts.JsEnv env = new Puerts.JsEnv();
env.Eval(@"
console.log(new CS.UnityEngine.Vector3(1, 2, 3));
// (1.0, 2.0, 3.0)
");
}

In this example, we directly created a C# Vector in JavaScript!

In the Javascript environment created by PuerTS, you can access any C# class by entering the FullName (the complete namespace path) of any class, including creating a Vector3 object directly, using the CS object.

Of course, it is still cumbersome to write out the full namespace, but you can also simplify it by declaring a variable alias:

    const Vector2 = CS.UnityEngine.Vector2;
console.log(Vector2.one)

properties accessing

Once the object is created, calling its methods or accessing its properties is also very easy.

//3. Calling C# functions or object methods
void Start() {
Puerts.JsEnv env = new Puerts.JsEnv();
env.Eval(@"
CS.UnityEngine.Debug.Log('Hello World');
const rect = new CS.UnityEngine.Rect(0, 0, 2, 2);
CS.UnityEngine.Debug.Log(rect.Contains(CS.UnityEngine.Vector2.one)); // True
rect.width = 0.1
CS.UnityEngine.Debug.Log(rect.Contains(CS.UnityEngine.Vector2.one)); // False
");
}

As you can see, whether it's function calls or property access/assignment, the usage is exactly the same as in C#.

special calls

However, there are still some usage in C# that are not commonly seen in JS, such as ref, out, and generics. We need to use the API provided by PuerTS to implement them.

//4. out/ref/generics
class Example4 {
public static double InOutArgFunc(int a, out int b, ref int c)
{
Debug.Log("a=" + a + ",c=" + c);
b = 100;
c = c * 2;
return a + b;
}
}
void Start() {
Puerts.JsEnv env = new Puerts.JsEnv();
env.Eval(@"
// create a variable that can be used for out/ref parameters through puer.$ref
let p1 = puer.$ref();
let p2 = puer.$ref(10);
let ret = CS.Example4.InOutArgFunc(100, p1, p2);
console.log('ret=' + ret + ', out=' + puer.$unref(p1) + ', ref=' + puer.$unref(p2));
// ret=200, out=100, ref=20

// create a List<int> type through puer.$generic
let List = puer.$generic(CS.System.Collections.Generic.List$1, CS.System.Int32);
let lst = new List();
lst.Add(1);
lst.Add(0);
lst.Add(2);
lst.Add(4);
");
}

done easily!

It should be noted that you may think, "Typescript supports generics, why not use them?" Unfortunately, TypeScript generics are only a compile-time concept. At runtime, JavaScript is still running, so puer.$generic is still needed to handle them.


typeof and operator overload

In addition to these special usages, there are two more situations to introduce: typeof function and operator overload:

//5. typeof/operator overload
void Start() {
Puerts.JsEnv env = new Puerts.JsEnv();
env.Eval(@"
let go = new CS.UnityEngine.GameObject('testObject');
go.AddComponent(puer.$typeof(CS.UnityEngine.ParticleSystem));

const Vector3 = CS.UnityEngine.Vector3;
let ret = Vector3.op_Multiply(Vector3.up, 1600)

console.log(ret) // (0.0, 1600.0, 0.0)
");
}

Because C#'s typeof cannot be accessed through C# namespaces and plays a role similar to keywords, PuerTS provides the built-in method $typeof for access.

Furthermore, because JS has not fully supported operator overload yet (TC39 is still in the proposal stage), op_xxxx needs to be used instead of the operator.


async

Let's look at the last case of JS calling C#: async.

// async
class Example6 {
public async Task<int> GetFileLength(string path)
{
Debug.Log("start read " + path);
using (StreamReader reader = new StreamReader(path))
{
string s = await reader.ReadToEndAsync();
Debug.Log("read " + path + " completed");
return s.Length;
}
}
}

void Start() {
Puerts.JsEnv env = new Puerts.JsEnv();
env.Eval(@"
(async function() {
let task = obj.GetFileLength('xxxx');
let result = await puer.$promise(task);
console.log('file length is ' + result);
})()
.catch(err=> {
console.error(err)
})
");
}

For C#'s async function, on the JS side, by wrapping the task returned by C# with puer.$promise, we can use await to call it.

This section is about calling C# from JS. In the next section, we will reverse the process and introduce calling JS from C#.