11RIA 闪客社区 - 最赞 Animate Flash 论坛

搜索
楼主: general_clarke
上一主题 下一主题

[交流 & 讨论] AS3转码其他语言(本帖以 C#+Unity 举例),工具开发记录

[复制链接] TA的其它主题
 楼主| 发表于 2018-12-22 23:49:23 | 显示全部楼层
因暂时抽调到其他项目,暂停更新,明年一月份继续。
回复

使用道具 举报

 楼主| 发表于 2019-1-3 22:37:17 | 显示全部楼层
本帖最后由 general_clarke 于 2019-1-3 23:17 编辑

新一年开始,辞旧迎新
更新继续。

我们使用了300个AS类测试转换,
从有记录开始,第一批AS3转出编译器错误是6900个。
可喜可贺地,去年末今年初,这个数字缩减到0。
AS3转出的C#代码成功通过编译
已进入调整运行时状态。

在AS3转换C#的过程中,出现最多的问题是装箱拆箱的强制类型转换问题。

装箱拆箱是一个贴切的称谓。
将强类型装进弱类型称为“装箱”,即
AS3::  var a:* = 1;
C#::   object a = 1;
将弱类型转回强类型称为“拆箱”,即
AS3::   var b:int = a;
C#::    int b = (int)a;
AS3是JavaScript的一个超集,AS3可以使用通配类型"*"来装载任何类型的对象,
AS3的数组、字典等容器也是能容纳任何类型的优秀容器。
C#的最大容器是object,这个容器必须进行强制类型转换才能正常运转。


所以一句简单的AS3代码可能对应复杂的类型转换。

  1. AS3::
  2. public function func(obj:Object, arr:Array, a:*, x:*):void{
  3.         obj[a] = arr[x] * 3;
  4. }
  5. C#::
  6. public virtual void func(AS3Dictionary<string> obj, Array arr, object a, object x)
  7. {
  8.         obj[a.ToString()] = (double)arr[(int)x] * 3d;
  9. }
复制代码

语法树是

  1. [AS3FunctionBody] func
  2.         [AS3Value]
  3.                 [AS3Value] obj
  4.                         [object AS3Atom<symbol> @157] obj
  5.                         [object AS3Call]
  6.                         [object AS3Atom<symbol> @159] a
  7.                 [object AS3Atom<oper> @162] =
  8.                 [AS3Value]
  9.                         [AS3Value] arr
  10.                                 [object AS3Atom<symbol> @164] arr
  11.                                 [object AS3Call]
  12.                                 [object AS3Atom<symbol> @166] x
  13.                         [object AS3Atom<oper> @169] *
  14.                         [object AS3Atom<number> @171] 3
复制代码

转码过程中,是否作出强转是根据上下文语法需要的类型,和变量当前类型是否匹配判断的。
递归执行,从叶节点到根节点开始返回字符串。
在语法树的"[AS3Value] arr"节点中,AS3::Array的索引一定是整形,而b是通配类型,因此有arr[(int)x]
在语法树的"[AS3Value] obj"节点中,AS3::Object作为字典使用时,键一定是字符串,而a是通配类型,所以等式左面写为obj[a.ToString()];
    其中a.ToString()不能写为(string)a,否则在a的值是数字等无法强转字符串的类型时出现错误。
表示等式右侧的节点中,运算符乘号决定了其左右两端是数字类型,在不损失精度情况下使用最大精度的double。
    乘号左侧的arr[x]返回值类型通配,需要强转double,成为(double)arr[(int)x]。
    等式右面数字3返回值类型int,需要强转double,成为3d
因此最后得到上面转换形式。






回复

使用道具 举报

 楼主| 发表于 2019-1-3 23:15:39 | 显示全部楼层
本帖最后由 general_clarke 于 2019-1-8 02:10 编辑

去繁就简,谈一谈装箱拆箱的要领。


以下情况下才需对语法元素使用强制类型转换。
1,语法元素在等式右侧,等式左侧是强类型,语法元素不是等式左侧的类型或其派生类型时;
2,语法元素处于四则、二进、逻辑等运算中,语法元素的类型与运算符要求的类型不匹配时;
3,语法元素被写在中括号中,作为数组、对象索引时类型与数组、对象需要的索引类型不对应;
4,语法元素是作为实参传递给函数的参数,且不是形参要求类型或其派生类型时。

设var a:*, b:*, arr:Array, obj:Object下,
对上面4种情况分别进行举例


  1. 1,语法元素在等式右侧,等式左侧是强类型,语法元素不是等式左侧的类型或其派生类型时;
  2. var x:int = 10;
  3. arr.length = x;
  4. 因arr.length是uint类型,x是int类型,不是uint的派生类,所以转成C#为
  5. int x = 10;
  6. arr.Length = (uint)x;
复制代码


  1. 2,语法元素处于四则、二进、逻辑等运算中,语法元素的类型与运算符要求的类型不匹配时;
  2. trace(b<<a)
  3. 位运算要求左侧是int或者uint,但是我们通常不会作出对有符号数进行位运算的作死行为,
  4. 兹设定位运算左侧使用uint类型,
  5. 在C#中强制要求位移运算右侧需要有符号int型号,所以右侧强制转换成int类型
  6. FlashCore.trace((uint)b << (int)a);

  7. 对每种运算符需要单独编写其左右需要的类型用于判断
  8. 对加号这种有歧义的运算符单独处理(加号左右是通配类型时无法判断是字符串加法还是数字加法)
复制代码


  1. 3,语法元素被写在中括号中,作为数组、对象索引时类型与数组、对象需要的索引类型不对应;
  2. trace(arr[a])
  3. 因为数字的索引是整型,所以转换成
  4. FlashCore.trace(arr[(int)a]);
复制代码


  1. 4,语法元素是作为实参传递给函数的参数,且不是形参要求类型或其派生类型时。
  2. public function func(a:*):void{
  3.         func2(a);
  4. }
  5. public function func2(p:int):void{
  6. }
  7. 因为func2明确地要求了传入的类型是int类型,所以进行转换得到
  8. public virtual void func(object a)
  9. {
  10.         func2((int)a);
  11. }
  12. public virtual void func2(int p)
  13. {
  14. }
复制代码


回复

使用道具 举报

 楼主| 发表于 2019-1-3 23:16:40 | 显示全部楼层
本帖最后由 general_clarke 于 2019-1-9 05:47 编辑

AS3的通配类型强大的兼容性也制造了不少二义性。
例如

  1. public function func(a:*, b:*):void{
  2.     var c:String = a + b;
  3. }
复制代码

在传递a = 3,b = 4时,c = "7"
在传递a = "3",b = "4"时,c = "34"
因此根据a,b实际值类型不同,上例有两种转换形式,但只有一种是正确的。两种转换形式分别为

  1. public virtual void func(object a, object b)
  2. {
  3.     string c = ((double)a + (double)b).ToString()
  4. }
  5. public virtual void func(object a, object b)
  6. {
  7.     string c = a.ToString() + b.ToString();
  8. }
复制代码

因此不得不对此类必须已知值类型才能正确运行的情况补充处理办法,
自行编写一个WideCast类,静态方法public static object add(object a, object b)的逻辑,判断a,b类型再处理或使用多态。

在加法等式左右有未知类型时写作
string c = (WideCast.add(a, b)).ToString();

效率低下,还好这样的情况并不多见,属于可以忍耐的范畴。

另一种情况是
public function func(a:Object)
因AS3中传递Object有两种情况,
一是利用Object是所有类型基类表示“传递任何类型对象”
二是利用Object的动态类特性表示“传递一个键为字符串,值为任何类型的字典”
为解决此问题,我们修改了原始as3代码,将所有为实现第二种情况的"Object"改为"*"
回复

使用道具 举报

 楼主| 发表于 2019-1-9 05:44:50 | 显示全部楼层
本帖最后由 general_clarke 于 2019-2-16 16:11 编辑

【六】

此楼开始连续几个楼层,是AS3转C#过程中发现语法不同之处的处理办法。

因为总量太多,完全阅读请慎重。从有用信息的密度来讲,作者推荐直接跳过这两个楼层

一边归纳总结一边感叹写AS3如此便利灵活,劳资才不想转C#

发布顺序是问题出现顺序

0,
AS3::分号加不加无所谓
C#::语句后面必须有分号

1,
AS3::import不存在的类不报错
C#::using引用不存在包会报错
解决:提前过滤import引用的类是否为空

2,部分顶级常用类因为重名必须更改名字或包,
如Date、Math
解决:创建一个as3包,改为as3.Date,as3.Math,调用时用完全限定名

3,部分常用方法大小写改变
AS3::toString
C#::ToString

4,一些相同功能静态常量写法改变了,如
as3::int.MAX_VALUE
C#::int.MaxValue

5,
AS3::Class类型变量可以直接new,var a:Class = Sprite;var b:* = new a;
C#::Type类型变量不能直接new出来
解决:改为使用System.Activator.CreateInstance创建

6,
AS3::通配类型*可以和所有类型比较
C#::object只能和其他引用类型比较,不经强转无法和int等值类型比较
解决:发现object与值类型对比时object强转

7,
AS3::支持全局函数。即.as文件中不写public class直接写public function。getTimer等就是这么实现的
C#::不支持,必须创建一个类使用静态方法。
解决:创建了C#::FlashCore类容纳上述函数。

8,
AS3::setter中可以使用任意标识作为形参
C#::setter的形参固定为"value"
解决:批量查找如果转码位置有其他变量也叫value必须改名

9,
AS3::标识符可以使用英文或下划线美元符开头,此后是英数下划线美元符
C#::美元符不支持
解决:批量修改所有"$"为"_z_"

10,
AS3::case后可以是任何类型
C#::case后只能是数字、字符串或者枚举
解决:实际上AS3的case后跟复杂类型会被编译成if,将有问题的switch..case改为if..elseif

11,
AS3::所有函数可以被变量引用var a:Function = this.someFunc
C#::函数被变量引用时需要封装成委托
解决:用反射形式编写一个BindingMethodClosure类,调用时也使用反射办法调用
上一句的转换方式变为MethodClosure a = new BindingMethodClosure(this, "someFunc", 2)最后是2是参数个数

12,
AS3::try..catch中catch到的变量名可以任意定义
C#::这个变量名不能和外层函数里的局部变量重名
解决:统统换成生僻名字

13,以下在C#中是关键字,flash中不是
abstract base bool byte char class decimal delegate enum event checked
explicit extern FALSE TRUE fixed float foreach implicit lock long operator
out params readonly ref sbyte sealed short sizeof stackalloc string struct
ulong unchecked unsafe ushot using virtual volatile
解决:在as中批量搜索这些标识符,全部在后面加一个"_"
因为flash.event包包含了"event",这个出现的最多

14,对键迭代循环
AS3::for(var s:* in dic)
C#::foreach(object s in dic.Keys)。

15,
AS3::var i:*;for(i in obj)是正常语句
C#:: object i;foreach(i in obj.Keys)是病句,需要改为foreach(object i in obj.Keys)
此外:C#中同函数内不同foreach也无法使用同一个变量作为迭代器,需要分别申请

16,
AS3::查字典中不存在元素返回undefined
C#::报错,必须先检查dic.ContainsKey

17,AS3使用XML/XMLList的语法过于灵活,转C#基本全挂。
例如xml.ns::versionNumber,xml.@a[0],关于XML类处理后面单独整理。

18,
AS3::返回值*的函数可以不加return
C#::返回值不是void就必须return

19,
AS3::switch语句最后可以省略break
C#::break必须写。

20,
AS3::闭包写法和JS差不多
C#::推荐使用lambda表达式

21,
AS3::生成Array和Object可以用类似JSON写法,如var a:Object = {"a":100, "b":200}
C#::只有最基础的数组支持这样的写法,构建单独的函数来创建Array和Object

22,
AS3::public static const
C#::改为public const,const一定是静态的,另外不支持成员const

23,
C#需要被继承重写的函数的前缀必须加virtual

24,
AS3::静态方法和实例方法可以重名
C#::不行





回复

使用道具 举报

 楼主| 发表于 2019-1-20 11:15:22 | 显示全部楼层
25,
AS3::存在undefined表示变量未定义
C#::需要单独声明一个常量undefined并使其转布尔值设置为假。

26,
AS3::支持对动态类型使用点号访问子项,如var obj:Dictionary = {}; trace(obj.a)
C#::必须专门制作访问方法,或者重写中括号运算符用obj["a"]访问

27,
第三方类部分浮点数属性使用float而不是double类型,
为这些变量赋值必须后面加上"f"

mySpine.x = 10.5;
需要写为
mySpine.x = 10.5f;

28,
AS3::支持声明常量时将其初始值设为新对象,如public static const a:Array = new Array(XXX, YYY)
C#::不支持该语法,常量声明的初始值一定是值类型或者其它常量引用

29,
AS3::作为形参数的object默认值可以设置成任意类型,如public function func(a:Object = 100, b:Object = false):void{xxx}
C#::不支持,object形参默认值只支持null
解决:利用C#多态,将上述函数头转换为三个,分别是
void func(){object a = 100;object b = false;xxx}
void func(object a){object b = false;xxx}
void func(object a, object b){xxx}

30,
AS3::支持正则表达式简写,如var reg:RegExp = /\d+/g
C#::必须使用构造函数构建。

31,
AS3::正则表达式支持"g" GLOBAL标识
C#::不支持,字符串的遍历搜索和个例搜索写成了不同函数
解决:使用C#构建一个RegexHandler类,包装原有正则,并支持GLOBAL标签。

32,
AS3::var a:int = 1.2;自动将浮点数1.2转换为1
C#::类型不匹配报错

33,
AS3::0, false, undefined, "", NaN均为假,用"=="判断它们是相等的
C#::不同类型不能比较,需要专门处理对空字符串、NaN等的检测
解决:转码时预判类型作出强转,如obj == false改为FlashCore.toBoolean(obj) == false
如等式两端都是Object类型,制作FlashCore.compare(obj1, obj2)静态函数,在函数内加以类型判断

34,
AS3::arr[1]+=10
C#::因arr[1]返回值是Object,需要拆箱,此句变为(int)arr[1] += 10,语法错误导致此句不成立
需要改写为arr[1] = (int)arr[1] + 10;

35,
AS3::parseInt参数可以是任意类型
C#::int.parse参数只能是string
解决:提前判断类型,如果传入的是其它类型比如浮点数,转成其它代码

36,
AS3::var a:int = 10;var str:String = String(a),可以将str赋值"10"
C#::int a = 10;string str = (string)a;报错
解决:改为a.ToString()

37,
AS3::支持使用短路法则赋值,如a = a || 100;
C#::不支持,上句改写为a = FlashCore.toBoolean(a) ? a : 100

38,
AS3::"get"既可以作为声明getter的关键字,又可以作为方法名
C#::保留"get",不允许其作为变量名或者方法名
解决:个例改名

39,
AS3::构造方法调用在super之前可以先写一部分其它代码
C#::进入构造方法一定先调用base,无法在base之前添加代码
解决:因使用并不频繁,修改AS3源码

40,
AS3::子类对父类getter/setter重写可以只重写其中一个
C#::如子类对父类只重写getter,会导致变量变成只读,父类setter不可访问
解决:为每个这样情况补充只调用base.同名方法的setter

41,
C#的int和uint不能相减,需要先转换成同一类型
回复

使用道具 举报

发表于 2019-1-23 21:32:08 | 显示全部楼层
收藏了
回复

使用道具 举报

 楼主| 发表于 2019-2-16 16:37:02 | 显示全部楼层
【七】

转换过程中出现错误总量有数百条,
继续枚举鱼龙混杂,诸如“Number要转double”这样的记录没有什么营养,

后面将针对转换过程遇到的大量出现、需要算法的案例进行个别分析
此层要谈的是“函数引用”


命题是,希望将下面语句翻译成C#

  1. var quote:Function = this.func;
  2. quote(10);
复制代码



分析:
AS3使用Function/Object/*类型直接应用静态函数或成员方法。
AS3一个var quote:Function可以在引用了一个返回值为int的函数后重新赋值引用返回值为String的函数
C#的函数引用是通过委托实现的,声明委托时,要同时声明这个委托的参数类型。
已经声明好类型的委托无法中途更换。

解决:
参考了AS3和JS函数引用底层的实现机制,使用C#新建一个类型
C#::BindingMethodClosure
上述代码转成
var quote:BindingMethodClosure = new BindingMethodClosure(this, "func", 1);
quote.call(10);
其中构造函数传递三个参数分别表示对函数this关键字引用
需要通过反射调用的函数名称
以及此函数需要传递的参数个数,在此例调用逻辑中无实际作用,但as3用户偶尔会用Function.length判断函数可接受参数个数

BindingMethodClosure内部执行逻辑为
public MethodInfo info;
public object saveThis;
public int length;
public BindingMethodClosure(object saveThis, string funcName, int length){
        this.saveThis = saveThis;
        this.length = length;
        this.info = saveThis.GetType().GetMethod(funcName);
}
public object call(params object[] args){
        return info.Invoke(saveThis, args);
}

籍此通过反射解决可变函数引用问题
回复

使用道具 举报

 楼主| 发表于 2019-2-17 20:39:03 | 显示全部楼层
本帖最后由 general_clarke 于 2019-2-17 20:48 编辑

关于反射
as3因动态类和弱类型的原因,存在反射的位置很多

例如
var a:* = new Sprite
var b:* = new Sprite
a.x = b.x-10;
此代码因为无法判断a的类型,实际需要通过反射取到a的属性x

为解决此问题,参考flash.utils.Proxy类用法,使用C#创建工具类,命名为FRef
FRef类拥有三个函数

  1. void setProperty(object from, string key, object value)
  2. object getProperty(object from, string key)
  3. object callProperty(object from, string key, params object[] p)
复制代码


将AS3
a.x = b.x+10;
转为C#
FRef.setProperty(a, "x", (int)FRef.getProperty(b, "x") - 10)

上述语法有一些注意事项,
首先是getProperty后一般需要类型强转。
使用上述语法获得的Function类型需要转换为参考楼上的BindingMethodClosure对象。

具体解析过程
对一个
a.b.c.d
逐个判断每一层变量类型

如a对象不是动态类型(*, Object,MovieClip等)那么使用正常的点号表达式表示a.b
如果a.b是动态类型,那么从此位置开始,后面的每个点号均转换成FRef.getProperty/setProperty形式
得到FRef.getProperty(FRef.getProperty(a.b, "c"), "d")
回复

使用道具 举报

 楼主| 发表于 2019-2-22 18:26:49 | 显示全部楼层
本帖最后由 general_clarke 于 2019-2-22 18:39 编辑

前文例举的第20项,关于闭包的详细处理办法

AS3原文

  1. stage.addEventListener(Event.ENTER_FRAME, function func(e:Event):void{trace(2)});
复制代码

利用C#的特性拉姆达表达式,能比使用委托更容易地处理这种情况
一个很大的优点是可读性良好。

转换为

  1. stage.addEventListener(Event.ENTER_FRAME, new BindingMethodClosure(null, (e)=>{
  2.         FlashCore.trace(2);
  3. }));
  4. //关于BindingMethodClosure参考38楼
复制代码


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

站长推荐 上一条 /1 下一条

感谢所有支持论坛的朋友:下面展示最新的5位赞助和充值的朋友……更多赞助和充值朋友的信息,请查看:永远的感谢名单

SGlW(66139)、 anghuo(841)、 whdsyes(255)、 longxia(60904)、 囫囵吞澡(58054)

下面展示总排行榜的前3名(T1-T3)和今年排行榜的前3名的朋友(C1-C3)……更多信息,请查看:总排行榜今年排行榜

T1. fhqu1462(969)、 T2. lwlpluto(14232)、 T3. 1367926921(962)  |  C1. anghuo(147)、 C2. fdisker(27945)、 C3. 囫囵吞澡(58054)



快速回复 返回顶部 返回列表