跳到主要内容

在Javascript调用C#

💡 PuerTS 3.0 同时支持 LuaPython 调用 C#,语法各有不同,可点击链接查看对应教程。

在上一篇中,我们简单试了一下Hello world

// Hello World(3.0 推荐写法)
void Start() {
var env = new Puerts.ScriptEnv(new Puerts.BackendV8());
env.Eval(@"
console.log('hello world');
");
env.Dispose();
}

// Hello World(兼容写法,JsEnv 在 3.0 中已标记为 [Obsolete])
void Start() {
Puerts.JsEnv env = new Puerts.JsEnv();
env.Eval(@"
console.log('hello world');
");
env.Dispose();
}

事实上,此处console.log和浏览器的console.log并不太一致。这个console.log被PuerTS所内置劫持了,实际会将字符串内容调用UnityEngine.Debug.Log打印。

在Puerts的帮助下,Javascript和C#的打通还可以更精彩,请往下看:


对象创建

//2. 创建C#对象
void Start() {
var env = new Puerts.ScriptEnv(new Puerts.BackendV8());
env.Eval(@"
console.log(new CS.UnityEngine.Vector3(1, 2, 3));
// (1.0, 2.0, 3.0)
");
env.Dispose();
}

在本例中,我们直接在 Javascript 中创建了一个 C# 的Vector!

在 PuerTS 所创建的 Javascript 环境里,你可以通过CS这个对象,输入任意类的 FullName (包含完整命名空间的路径),访问任意的 C# 类,包括直接创建一个 Vector3 对象。

当然,写出完整的命名空间还是比较麻烦的,不过你也可以通过声明一个变量别名来简化

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

属性访问

对象创建出来了,调用其方法、访问其属性也是非常容易的。

//3. 调用C#函数或对象方法
void Start() {
var env = new Puerts.ScriptEnv(new Puerts.BackendV8());
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
");
env.Dispose();
}

可以看出,不管是函数调用还是属性访问/赋值,用法上都和 C# 一模一样。


特殊调用

不过,C# 还是会有一些在 JS 里不常见的用法,比如ref,out泛型。就需要借助 PuerTS 提供的 API 来实现

//4. out/ref/泛型
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() {
var env = new Puerts.ScriptEnv(new Puerts.BackendV8());
env.Eval(@"
// 通过puer.$ref创建一个可以用于使用out/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

// 通过puer.$generic来创建一个List<int>类型
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);
");
env.Dispose();
}

也并没有非常复杂,就可以完成了!

需要注意的是,可能你会想“Typescript明明支持泛型,为什么不用上呢?“。遗憾的是,Typescript泛型只是一个编译时的概念,在实际运行的时候还是运行的是Javascript,因此还是需要puer.$generic来处理。


数组与索引器访问

C# 中的 [] 操作符(包括数组索引、List 索引、Dictionary 索引以及任何自定义索引器)在 JS 中不能直接使用 [] 语法访问,必须使用 get_Item() / set_Item() 方法:

void Start() {
var env = new Puerts.ScriptEnv(new Puerts.BackendV8());
env.Eval(@"
// 创建 C# 数组
let arr = CS.System.Array.CreateInstance(puer.$typeof(CS.System.Int32), 3);
arr.set_Item(0, 42); // 等价于 C# 的 arr[0] = 42
let val = arr.get_Item(0); // 等价于 C# 的 val = arr[0]
console.log(val); // 42

// 同样适用于 List<T>
let List = puer.$generic(CS.System.Collections.Generic.List$1, CS.System.Int32);
let lst = new List();
lst.Add(10);
let first = lst.get_Item(0); // 等价于 C# 的 lst[0]
lst.set_Item(0, 20); // 等价于 C# 的 lst[0] = 20

// 同样适用于 Dictionary<TKey, TValue>
let Dict = puer.$generic(CS.System.Collections.Generic.Dictionary$2, CS.System.String, CS.System.Int32);
let dict = new Dict();
dict.set_Item('key', 100); // 等价于 C# 的 dict['key'] = 100
let v = dict.get_Item('key'); // 等价于 C# 的 v = dict['key']
");
env.Dispose();
}

⚠️ 重要:这是 JS 和 C# 之间的一个关键差异。JS 的 [] 操作符只能用于 JS 原生对象,对于 C# 对象的索引访问必须通过 get_Item() / set_Item() 方法。


typeof与运算符重载

除了这上述特殊的用法之外,还要介绍两种情况:typeof函数和运算符重载:

//5. typeof/运算符重载
void Start() {
var env = new Puerts.ScriptEnv(new Puerts.BackendV8());
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)
");
env.Dispose();
}

因为 C# 的typeof无法通过 C# 命名空间的方式访问,有点类似关键字的角色,因此PuerTS 提供内置方法$typeof访问

另外由于 JS 尚未全面支持运算符重载(TC39还在草案阶段),这里需要用 op_xxxx 代替运算符


异步

让我们来看 Javascript 调用 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() {
var env = new Puerts.ScriptEnv(new Puerts.BackendV8());
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)
})
");
env.Dispose();
}

对于 C# 的async函数,JS 侧通过puer.$promise包装一下 C# 返回的 task,即可 await 调用了


这一部分是有关 JS 调用 C# 的。下一部分我们反过来,介绍 C# 调用 JS

📖 其他语言调用 C# 的教程:Lua 调用 C# | Python 调用 C# | 三语言对比速查表