Atry

 
 
 
 
 
 

在 mxmlc 编译的 swf 中使用 swfc 编译的 swf

2009-11-25 22:33:53 阅读(0) 评论(0)

SWFTOOLS 是一套 swf 文件相关工具,其中的 swfc 能把 .sc 格式的源文件编译成 .swf 文件。我以前以为他是没有办法向 ActionScript 导出资源的,不过这两天我最终还是找到办法能够在 mxmlc 编译的 .swf 中使用 swfc 导出的资源,最关键的是,能够静态链接到这些资源。
首先,编写 .sc 文件的时候,要再多写一个 .as 文件,在里面编写要生成的 swf 的主类,主类要从 flash.display.Sprite 派生出来(当然 flash.display.MovieClip 也可)。如果资源中用到了 .png 之类指令定义的形状,主类中也得声明。
ResourceMain.as
package { import flash.display.* public class ResourceMain extends Sprite { public var png1:Shape } }
其次,.sc 文件要用 .action 指令引用定义主类的 .as 文件。
ResourceMain.sc
.flash bbox=200x200 version=6 fps=50 name="ResourceMain.swf" compress .action filename="ResourceMain.as" .png png1 # ... .end 
这样一来,就可以用 swfc 编译一个有主类的 swf 了。你可以用 Loader 加载它,然后调用 Loader 的 contentLoaderInfo.applicationDomain.getDefinition("ResourceMain") 方法来获取这个类。
如果要在编译时就找到这个类的符号,想要直接 import 这个类,还要做以下两个步骤:
  1. 用 compc 编译那个主类的 .as 文件。这样编译出来的 swc 只包含类定义,而没有资源素材。
  2. 用 mxmlc 编译主程序的时候,用 -compile.extern-library-path 而不是 -compile.library-path 引用上一步编译出来的 swc
用这种办法编译出来的主程序认识资源的符号,但是并不包含资源,所以必须在运行时用 Loader 把资源 swf 加载到ApplicationDomain.currentDomain 之后再用那些用到了该资源的函数或者类,不然就会有运行时异常。
var loader:Loader = new Loader loader.load(new URLRequest("resource.swf"), new LoaderContext(false, ApplicationDomain.currentDomain))
如果不想在运行时加载,而想静态链接,用 swfcombine 把资源 swf 捆进主程序即可。

顺便说一句,swfc 不支持中文变量名或类名,我写了一个补丁让它支持,在这里下载:

阅读(0) | 评论(0) | 阅读全文>>

用 lua 代替 GNU Make

2009-11-15 22:28:49 阅读(170) 评论(6)

我们团队一直在用 GNU Make ,我们老大云风尤其把 make 用得十分高级。但使用过程中,碰到一些问题确实比较讨厌:
  1. 斜杠的处理
  2. 参见 http://blog.codingnow.com/2009/03/gnu_make_backslash.html
  3. 递归目录
  4. 参见 http://blog.codingnow.com/2009/06/make_recursion_directory.html,这个用到了神技(eval)
  5. 自动创建目录
  6. 参见 http://blog.codingnow.com/2009/07/gnu_make_mkdir.html,这个也用到了神技。
  7. 包含空格的文件名的处理
  8. 据高人说,可以在调用 foreach 等内置函数以前先把空格替换成别的字符,再在作为文件名使用时把空格替换为 。但这个做法等于是自己定义一种转义规则了。
  9. shell的依赖性
  10. make 的命令行都是调用操作系统的 shell ,这样会带来移植性问题。就连基本的文件操作都需要定义很多环境变量来处理 cp / copy cat / type 之类的差异。不同操作系统的命令行转义规则也各不相同,如果命令行参数中出现特殊字符,要想在不同的 shell 下都正确转义难度极大。
  11. 算术运算
  12. 比如我想要从 1 循环到 10,声明 frame_1.png 依赖于 frame_1.tga,frame_2.png 依赖于 frame_2.tga,frame_3.png 依赖于 frame_3.tga …… frame_10.png 依赖于 frame_10.tga。这样的事情在 C 里面就是 for (i = 0; i < 10; ++i) 即可。在 make 中, for 倒是可以用递归来模拟,但 make 里面除非求助于 shell 就没有办法能做 ++i 这个事情。
  13. 子模块依赖关系
  14. 这个是 make 的死穴。如果一个项目中有多个模块,模块 a 依赖于模块 b 的输出文件 libb.a,那应该怎么写依赖关系呢?一般有两种做法
    • 不同的模块使用独立的 Makefile ,a 模块依赖于 libb.a,如果 libb.a 不存在,就调用 b 模块的 make 来创建 libb.a,大概代码如下:
      ../b/libb.a:
      cd ../b && $(MAKE)

      a.exe: ../b/libb.a xxx.c yyy.c xxx.h
      gcc ……
    • 这样做的问题在于如果模块 b 中的某个源文件修改了,在 a 模块下 make 时, libb.a 不能自动重新编译,因而 a 模块也不会重新编译
    • 用 include 之类的办法把所有模块的 Makefile 统一起来,这是目前我们做的方式。缺点在于:
      • 编写子模块的 Makefile 时需要知道别的模块的信息,破坏了 Makefile 的局部性
      • 为了解决这个问题,云风想的办法是自己定义了一套语法,在子模块 make 时,通通调用根一级的 Makefile ,只是把子模块的信息通过环境变量传进去。但这样带来了另一个缺点,即自定义的语法有学习成本,和直接的 make 语法相比,维护起来要稍微困难一些。
      • 即使只构建一个子模块,也需要生成整个项目的依赖图
      • 尤其当文件很多的时候,生成整个依赖图会比较慢。这个问题在 bjam 中也存在。因为我们现在的做法就相当于用 make 实现了一套 bjam ,所以我们也会面临 bjam 的一些问题。

    当一个项目构建过程比较复杂的时候,可以考虑用通用语言来管理构建过程。对于通用语言来说,处理字符串转义、算术运算、循环遍历等事情易如反掌,无需使用 make 那些神奇而又晦涩的神技。而跨平台的通用语言的文件系统库往往也比 shell 有更好的移植性。至于子模块依赖问题,对于支持 closure 、 coroutine 的语言来说,很容易就能实现依赖关系的惰性计算,只需要生成整个依赖图中于构建目标相关的那部分,速度就快多了。

    我在做的一个项目的构建过程就十分复杂,需要把一些配置文件从一种格式转成另一种格式,然后再嵌入主程序中;用到的美术素材也需要转格式。这些过程需要用到好几种转换工具,有一些转换工具就直接要用 lua 编写。我原先是用 GNU Make 来做这件事情的,把各种神奇的特性都用上了,最后碰到空格文件名的时候,还是放弃了,决心自己用 lua 实现一套构建系统,我把它叫做 lua-make

    因为编译器、转换工具都是命令行工具,所以这个构建系统碰到的第一个问题是要能启动进程。lua 标准库中启动进程有两个函数,io.popen 和 os.execute 。其中 io.popen 不支持 Windows。而 os.execute 要依赖于操作系统的 shell ,移植性不佳比如,在 bash 中,我们可以写这样的代码:

    cat 111.txt | a/b/foo 'xxxxx' | bar 'yyyyy' 'aaa' 2> out/err > out/log

    如果要用 lua 来启动,就是

    os.execute[[cat 111.txt | a/b/foo 'xxxxx' | bar 'yyyyy' 'aaa' 2> out/err > out/log]]

    可是上述代码在 Windows 中就跑不通了。这是因为 Windows 的 shell 是 cmd ,语法不同,字符串转义规则不同,目录分隔符也不同。

    归根到底, os.execute 是不可移植的。所以我用了 Lua-Ex 。Lua-Ex 提供了创建管道的函数(ex.pipe),启动进程并重定向标准输入输出的函数(ex.spawn),刚好适合做构建系统。不过,pipe 和 spawn 是比较底层的函数,要想简单的完成一串管道的命令,还需要一些语法糖。

    我做了一个 path 模块和一个 shell 模块来做实现这个语法糖。先前的 bash 表达式,用我提供的语法糖来写的话就是这样:

    "111.txt" / path["a/b/foo"]("xxxxx") / path["bar"]('yyyyy', 'aaa') % path["out/err"] / path["out/log"]

    上述表达式中,运算符/用来重定向标准输出,相当于 bash 中的|<>;运算符%用来重定向标准错误,相当于 bash 中的2>

    path["out/log"] 是一个路径对象。我重载了路径对象metatable中的 __div ,所以也可写成 path["out"] / path["log"] 或者 path.out / path.log 又或者 path.out / "log" 。如果用 setfenv 把当前执行环境设成 path ,甚至可以直接写成 out/log 。

    路径的比较也十分简单,我能保证同一个路径在内存中只有一份,所以 path["x/b"] 、 path["x"] / b 、 path["x/a/../b"] 、 path["x\\b"] 都是同一个对象。

    我也重载了路径对象metatable中的 __call ,当它被调用的时候就会被当成可执行文件来执行。这就是为什么上述代码可以直接写path["bar"]('yyyyy', 'aaa') 的缘故。

    对于一个构建系统来说,核心部分是目标的依赖关系管理。这方面,我主要是造搬 GNU Make 的模型,只是语法用 lua 的表来描述。

    这里我碰到一个问题,就是多任务的并行执行。make 有一个 -j 选项,可以并行执行多个任务。用 lua 怎么实现类似的功能呢?用 ex.spawn 可以创建进程,用 ex.wait 可以等待一个进程退出,但是如果同时启动了多个进程,怎样才能等待其中任意一个退出呢?Windows 中有一个 WaitForMultiObjects 可以做这件事情,但是一方面这不可移植,另一方面我也不想为了这一个功能而多写一个 lua C库。我用的办法是在主进程调用 ex.pipe 创建一个 pipe,为需要监视的进程创建一个辅助进程来监视,辅助进程等待被监视的进程退出,退出时向 pipe 发一个消息。所以主进程之需要读这个 pipe 就可以知道任意一个进程退出了。我把这个监视模块叫做 shell.watchdog 。

    最终,要使用这个构建系统,大概是这样来写Makefile:

    require "shell"
    require "path"
    require "shell.watchdog"
    require "make"
    require "make.filetarget"

    local watchdog = shell.watchdog()

    make(10, make.filetarget {
    path = "xxxx.exe",
    dependencies = {
    {
    run = function ()
    print("这是一个伪目标")
    end
    },
    make.filetarget {
    path = "xxxx.c"
    },
    make.filetarget {
    path = "xxxx.h"
    }
    }
    run = watchdog + path.gcc("-o", "xxxx.exe", "xxxx.c")
    })

    watchdog:wait()

    make 函数的第一个参数指最多允许多少个并行任务的意思,这里传 10,就相当于GNU Make 的 -j 10

    make.filetarget 是说这是一个文件目标,而不是一个伪目标(即 GNU Make 的 .PHONY)。文件目标和伪目标相比,要增加判断文件修改时间、自动创建父级目录等功能。在我实现的构建系统中,一个目标默认是没有这些功能的,如果需要这些功能就得写 make.filetarget {......}

    这里的watchdog +是一个语法糖,意思是要把接下来的这一段 shell 表达式加入到 watchdog 这个监视对象中去监视。

    大体上我就是这样实现 Lua 构建系统的,欢迎回帖讨论

    阅读(170) | 评论(6) | 阅读全文>>

    trackAsMenu的作用

    2009-10-16 9:14:44 阅读(12) 评论(0)

    用ActionScript的时间长了,实在是不能容忍里面还有我不认识的API。昨天终于搞清楚trackAsMenu的用处了,于是AS里面我不认识的API又少了一个。

    在Flash创作工具中文版的界面上,这个trackAsMenu被翻译为“音轨作为菜单”,看到这几个字,我眼睛湿润了。trackAsMenu的意思应该是“以菜单的方式追踪鼠标”。这个选项在ActionScript3里面是SimpleButton和MovieClip上的属性,如果打开这个选项,有两个效果:

    1. 如果在别的对象上按下鼠标,然后不松开鼠标,移到一个trackAsMenu为true的对象上,再松开鼠标的话。这个trackAsMenu为true的对象就会收到MouseEvent.CLICK事件。而如果把trackAsMenu设为false的话,这个click事件是收不到的。
    2. 如果在一个trackAsMenu为true的对象上按下鼠标,然后不松开鼠标,移到另一个trackAsMenu为true的按钮上,进入的这个按钮会显示overState的图像。但如果是移到另一个trackAsMenu为flase的按钮上,则那个按钮还是显示upState的图像。

    所以,这个功能用来制作菜单时会有用。只要把各个菜单项都设上trackAsMenu属性,原先按下鼠标时的那个对象以外的对象也可以触发click事件了。

    阅读(12) | 评论(0) | 阅读全文>>

    Windows命令行参数的转义

    2009-10-15 21:37:21 阅读(9) 评论(0)

    Windows命令行参数的转义分为两个步骤,第一个步骤是cmd解析你输入的字符串决定要启动哪些进程,进程之间是否要标准输入输出重定向等等;第二个步骤是CRT把传入进程的命令行字符串切成多个参数,填好argc和argv传入main。

    如果一个程序不是被cmd启动的,而是通过快捷方式或者bash等别的shell启动的,或者干脆直接调用API CreateProcess,那么第一个步骤可以无视;如果一个程序并不使用argc、argv,而自行调用GetCommandLine等Windows API,则第二个步骤可以无视。

    第一个步骤中,| ^ " &等字符是特殊字符。如果特殊字符出现在双引号外,需要被转义。但如果出现在双引号中,就不需要也不能被转义。转义时,^^代表^^|代表|^"代表"等等

    第二个步骤的具体实现取决于CRT,在Windows XP中用得最广泛的CRT是msvcrt.dll。Windows XP附带的系统程序大都使用了它,用VC6或mingw的gcc编译的程序也使用这个CRT。我看了一下VC6附带的源码,切参数这件事情的规则相当复杂。

    首先是空格和tab如果不在双引号,就会被当作参数的分隔符,而双引号引住的空格和tab代表空格tab字符本身。

    其次是连写双引号的特殊处理。如果想要把双引号作为参数内的字符,那么至少要连写两个双引号:""。当他遇见连续的两个双引号时,他会把其中一个双引号作为双引号字符,另一个双引号作为引用范围的开始或结束。也就是说,如果你已经在引号中了,连写2个双引号的话,就会产生一个"字符,并且关闭引用。要想产生一个"字符但不改变引用状态,就需要连写三个双引号"""

    最变态的是用反斜杠(即\)转义的规则。对于n个反斜杠外加一个双引号会被转义。如果n为奇数,那么会被转义成(n-1)/2个\字符外加一个"字符。如果n为偶数,那么会被转义成n/2个\字符,然后再进入或结束引用范围。而如果反斜杠后面并不跟随双引号,反斜杠就不转义。

    值得注意的是,因为cmd和CRT对双引号字符的转义规则并不一致,所以cmd和CRT对于某个字符是否被引用的判断也是不一致的。比如说,如果你想进行一次Subversion提交,注释中写上

    hello " | " | " | " world
    ,那你得写:
    svn commit -m "hello """ ^| """ | """ ^| """ world"

    注意,其中某些|必须要转义,而另一些则必须不转义。

    上面说的还仅仅是msvcrt.dll切参数的规则,这个规则和cygwin的CRT是不一致的,也就是说,如果你在cmd中要调用cygwin编译的程序,那么转义规则又不一样。幸好这种情况并不是很多,用cygwin的话,就用bash好了,至少全世界的bash解析参数的方式都一样。

    阅读(9) | 评论(0) | 阅读全文>>

    放弃在现有项目中使用haXe

    2009-9-2 12:56:01 阅读(18) 评论(0)

    第一个原因是如果新写的代码改用haXe,新写的代码依赖于部分AS代码,而另一部分AS代码又会依赖于haXe,那么就会导致循环依赖。虽然有办法可以解决,但是每种解决方案都有代价,要么是把现有AS代码拆成两部分来编译,要么是用ApplicationDomain.getDefinition之类的字符串解耦合办法。这些办法都不是我想要的。

    第二个原因在于haXe用的全局变量flash.Lib.current. 把一段haXe代码编译成SWC或者SWF,分别作为静态库和动态库使用时,会有微妙的差异(参见http://haxe.org/doc/advanced/swc)。而我如果想要在开发期间缩短编译时间,必须要把haXe部分编译成动态链接的SWF。这个问题体现了“全局变量的邪恶性”。全局变量之所以邪恶在于其“全局”概念的不可控,在模块角度看是全局的东西,到了装载器的角度来看就有时候是而有时候不是了,就会导致行为不一致。


    阅读(18) | 评论(0) | 阅读全文>>

    非主流的一些flash开发工具

    2009-8-30 21:44:27 阅读(37) 评论(0)

    主流的flash开发工具当然是Flex Builder/Flex SDK/Flash创作工具。一般用Flash创作工具生成素材,用Flex SDK来编译代码,喜欢IDE环境的就用用Flex Builder。但除此之外,还有一些非主流的开发工具选择。

    现在能生成flash的编译器已经有很多了,真的不必拘泥于龟一般的mxmlc. 前一篇文章,我介绍了haXe。除此之外,另一个选择是swftools中的as3compile,这个编译器号称是要兼容mxmlc,不过就我目前测试的情况来看,它有一个缺陷,即不支持闭包中访问upvalue。

    当然我不认为mxmlc慢是因为它用了Java,javac也是Java写的,快得很呢。

    由于AVM开源了,还可以用AVM开源后提供的编译器,AVM开源后的项目名叫做Tamarin,源码在这里下载。这是一个C++编写的编译器,现在想尝尝鲜的话,可以找到Tamarin里面的utils/avmc,里面就是这个编译器。不过avmc编译出来的目标文件是.abc,还需要包装上swf的文件头才能用,这件事情可以用前面提到的as3compile来做。比较头疼的问题在于如何引用flashplayer的API,这些API在flex_sdk_3/frameworks/libs/player/playerglobal.swc里面是有,要把这个playerglobal.swc导出成.abc我没试过,但应该不难。

    以上是一些编译器,至于素材的制作,可以考虑swftoolsswfc。和官方的Flash创作工具相比,它的优势在于源文件.sc格式是一个文本格式,工具又是命令行工具,因此易于用脚本生成代码。缺点有两个,首先是缺乏“所见即所得”编辑器,其次是一些功能缺失,比如向ActionScript导出元件。用swfc来打包素材已经足用了,我就用swfc来把一些公司自定格式的动画素材批量转换成swf格式。

    阅读(37) | 评论(0) | 阅读全文>>

    haXe 学习笔记

    2009-8-30 21:13:40 阅读(54) 评论(0)

    其实我早就听说过haXe。那是大概两年前,mtasc停止开发,转而开发haXe. 当时我没怎么注意,直到现在对mxmlc的编译速度忍无可忍的时候,才偶然发现改用haXe也许是个解决办法。

    haXe主页上强调这是一种“multiplatform language”的语言,可以编译成JavaScript、Flash、NeckoVM(这个没听说过)、PHP、C++。不过这些我不关心,我只关心它能编译成Flash.

    从haXe的语言特性来看,这是一种大路货的面向对象语言,跟Java、C#、ActionScript3都差不多。对我而言,haXe比ActionScript3好的地方有:

    1. haXe编译速度奇快,比mxmlc快得多,这应该是选用它的最大动机。
    2. 更完备的静态类型系统。比如支持泛型,此外,其函数类型也是强类型。这解决了ActionScript3语言陷阱和缺陷一文提到的第三个缺陷
    3. 支持类型推断,不用每个变量都写类型。
    4. 局部变量的范围是某个区块而不是整个函数。这解决了ActionScript3语言陷阱和缺陷一文提到的第四个缺陷
    5. 支持枚举

    haXe比ActionScript3差的地方有:

    1. 不支持自由函数,因而函数必须放在类中。flash.utils.setTimeout等自由函数也就不能直接调用了。
    2. 没有const关键字。

    从ActionScript3迁移到haXe时还需要考虑的事情包括:

    1. 调试。根据我测试,在haXe 2.x中只需要加上 -debug -D fdb 参数就可以使用Flex SDK中的fdb来调试了。
    2. 动态加载。这个说来话长,得另写一篇文章来说这个问题了。简单的说一下要点吧。要使用 haxe --gen-hx-classes 来生成头文件以便在编译时找到符号,然后在运行时加载动态库。运行时加载要指定传一个ApplicationDomain.currentDomain进去。
    3. 如果想要和现有的swf编译到一起,需要用-swf-lib把已有的swf捆进来。此外,调用其中已有的AS代码则还需要--gen-hx-classes生成的头文件。AS要调用haXe代码的话,则需要haxe -swf9 xxx.swc来生成一个swc文件让AS能通过这个文件找到符号
    4. 要编译一般在网上用的swf,需要加入-D network-sandbox参数。
    5. haXe的学习成本。对我而言,学习成本很低,花费一天时间足够搞懂。再次强调,这是一种大路货语言。语法和ActionScript区别不大,就算有些犄角旮旯之处不同,遇到时也很容易解决。比较讨厌的一个语法在于每行代码都必须加上分号。

    阅读(54) | 评论(0) | 阅读全文>>

    ActionScript3语言陷阱和缺陷

    2009-8-14 11:31:53 阅读(49) 评论(0)

    1. 闭包会产生多余的引用

    2. ActionScirpt3的闭包会引用外层函数的所有局部变量,而不仅仅只是用到的变量。这些多余的引用是不必要的。它们会使垃圾收集器认为这些数据仍然被引用着而不去释放,哪怕这些数据本来已经不可能再被用到。例如,以下代码会导致内存泄露:
      package {
      import flash.display.*
      public final class C extends Sprite {
      private static function ref(f:Function):Function {
      return function():void {
      }
      }
      private var f:Function = function():void{}
      public function C() {
      for(var i:uint = 0; i < 1000000; i++) {
      f = ref(f)
      }
      }
      }
      }
    3. 与JavaScript的异同

    4. ActionScript2和JavaScript一致的地方更多些,而ActionScript3则少一些,这样会迷惑很多用惯JavaScript的人,例如:
      http://www.ac.net.blog.163.com/blog/static/1364905620060274329791/
      http://www.ac.net.blog.163.com/blog/static/1364905620060297425813/
      还有一些区别在ASDoc中也提到了,比如=== 和 == 用在Number、uint、String上时,AS2和AS3行为是不同的。
    5. 编译器不检查函数返回值类型

    6. 例如,以下代码可以通过编译。
      function xxx():uint {
      return 0
      }
      var a:Array = xxx()
    7. 局部变量的范围是整个函数而不是某个区块

    8. 例如以下代码会出错:
      function f(b:Boolean):void {
      if(b) {
      var a:String = "a"
      } else {
      var a:Array = []
      }
      }
      这一点对于闭包的使用来说,很麻烦,例如
      var a:Array = []
      for(var i:uint = 0; i < 10; i++) {
      a[i] = function():uint {
      return i
      }
      }
      trace(a[3]()) // 输出10而不是3
      上述代码会trace出 10 而不是 3
    9. const并不真的不可改变

    10. 更有甚者,即使你使用 const 关键字声明一个常量,实际上它还是会被改变:
      var a:Array = []
      for(var i:uint = 0; i < 10; i++) {
        const c:uint = i
      a[i] = function():uint {
      return c
      }
      }
      trace(a[3]()) // 还是输出10而不是3
    11. setTimeout不自动释放对闭包的引用

    12. ASDoc中的的文档有下面这句话:
      If you intend to use the clearTimeout() method to cancel the setTimeout() call, be sure to assign the setTimeout() call to a variable (which the clearTimeout() function will later reference). If you do not call the clearTimeout() function to cancel the setTimeout() call, the object containing the set timeout closure function will not be garbage collected.
      意思是说,如果是说可以用clearTimeout来取消setTimeout设置的定时器。让人觉得言下之意是,如果不想取消定时器,不调用clearTimeout也可以。实际上,如果不调用clearTimeout,就会导致内存泄露。setTimeout并不自动释放对闭包的引用。

    阅读(49) | 评论(0) | 阅读全文>>

    D语言2.0的const和immutable

    2009-8-12 9:45:50 阅读(11) 评论(0)

    翻译后记 2007年的时候,我关注过D语言,甚至还用它来写过一个东西。当时D社区里面讨论得比较激烈的特性就是final/const/invariant这三种数据类型。那时候我就觉得D 2.0的这三个东西会很有用。
    过了两年,突然发现这些东西已经改得面目全非,invariant已经改名叫做immutable了,而final数据类型竟然被砍掉了。我在D的论坛上泡了半天也没搞清楚前因后果。所以我翻译了这篇文章,可能会对被D语言这些常量所迷惑的同学有帮助。原文地址:http://www.digitalmars.com/d/2.0/const3.html

    对数据结构或接口来说,如果能轻易地分辨出哪些数据不会改变,哪些数据可能会改变,以及谁可能改变这些数据,就会非常有用。以上可借助语言的类型系统做到。数据可以被标记为const或immutable,而其默认是可变的(即mutable ) 。

    不 可改变的数据可以用immutable表示。immutable数据构建之后,在程序整个运行期间都会保持原值。immutable数据可以放在 ROM(只读存储器)中或者由硬件设为只读的内存中。由于immutable数据不会改变,所以可以得到许多程序优化的机会,还可便于采用函数式编程。

    const适用于不能通过引用本身改变数据的引用。不过,这块数据还时可能被其他引用所改变。若向接口传入数据时需要保证该接口中不修改该数据,则应采用const。

    immutable和const都是transtive(具有可传递性),这意味着通过immutable引用取得的数据同样也是immutable的,const亦是如此。

    immutable存储类

    immutable最简单的用法是用作存储类。可以用它来声明明确的常量。

    immutable int x = 3; // 将x设为3
    x = 4; // 错误,x不可改变
    char[x] s; // s 是一个包含三个char的数组

    数据类型也可以由初始化表达式推断:

    immutable y = 4; // y的类型是int
    y = 5; // 错误,y不可改变

    如果不写初始化表达式,相应的构造函数也可以初始化immutable数据:

    immutable int z;
    void test()
    {
    z = 3; // 错误,z不可改变
    }
    static this()
    {
    z = 3; // 正确,没有静态初始化表达式的immutable数据可以赋值
    }

    除了函数类的immutable数据外,其他immutable声明的初始化表达式都必须能在编译时求值:

    int foo(int f) { return f * 3; }
    int i = 5;
    immutable x = 3 * 4; // 正确,12
    immutable y = i + 1; // 错误,无法在编译时求值
    immutable z = foo(2) + 1; // 正确,foo(2)能在编译时求得值——7

    非静态的局部immutable数据的初始化表达式是在运行时计算的:

    int foo(int f)
    {
    immutable x = f + 1; // 运行时求值
    x = 3; // 错误,x不可改变
    }

    由于immutable数据具有可传递性,由immutable数据引用的数据也是不变的:

    immutable char[] s = "foo";
    s[0] = 'a'; // 错误,s指向一块不可改变的数据
    s = "bar"; // 错误,s不可改变

    immutable声明可以作为左值出现,比如它们的地址都是可以获取到的,再如它们会占据存储空间。

    const存储类

    除了以下区别之外,以const声明的数据和immutable的完全相同:

    • 不能通过const声明来修改它所引用的数据,但这块数据还是可能被指向该数据的其他引用所修改。
    • const声明自身也是const的。

    immutable类型

    一定不会被改变的数据可以标记为immutable。immutable关键字可以被用作type constructor:

    immutable(char)[] s = "hello";

    括号中的类型被标记为immutable。因此,一方面,s能被设为新的值,另一方面,s[]的内容却不能被改变:

    s[0] = 'b'; // 错误,s[]不可改变
    s = null; // 正确,s自身并不是immutable的

    immutable具有可传递性,这意味着被immutable类型引用的数据也是immutable的:

    immutable(char*)** p = ...;
    p = ...; // 正确,p可以改变
    *p = ...; // 正确,*p可以改变
    **p = ...; // 错误,**p不可改变
    ***p = ...; // 错误,***p不可改变

    把immutable用作存储类相当于把immutable用作整个声明类型的type constructor:

    immutable int x = 3; // x的类型是immutable(int)
    immutable(int) y = 3; // y不可改变

    创建不变数据

    第一种方式:使用本来就不变的字面量(literal),比如字符串字面量。字符串字面量始终都是immutable的。

    auto s = "hello"; // s的类型是immutable(char)[5]
    char[] p = "world"; // 错误,immutable类型不能隐式转换为非immutable类型。

    第二种方式是把数据转换为immutable。这样做时,需要由程序员确保不存在其他的对该块数据的可变引用。

    char[] s = ...;
    immutable(char)[] p = cast(immutable)s; // 未定义行为
    immutable(char)[] p = cast(immutable)s.dup; // 正确,只存在一个引用

    使用.idup属性,可以便利的创建某个数组的不变副本。

    auto p = s.idup;
    p[0] = ...; // 错误,p[]不可改变

    通过转换移除immutable

    immutable类型可通过强制转换而被移除。

    immutable int* p = ...;
    int* q = cast(int*)p;

    不过,这并不意味着可以修改这块数据。

    *q = 3; // 编译通过,但结果会导致未定义行为

    在有些情况下,强制抹除不变性是必要的。比如涉及某个不能更改的库,而库中的静态类型并不正确。有史以来,强制转换一直很直接很有效。使用强制转换抹除不变性时,必须要承担保证数据不变的责任,因为它已经不再由编译器静态保证了。

    immutable成员函数

    immutable成员函数表示this以及被this引用的任何数据都是不变的。可以这样声明:

    struct S
    { int x;

    immutable void foo()
    {
    x = 4; // 错误,x不可改变
    this.x = 4; // 错误,x不可改变
    }

    函数的constimmutable属性也可以写在参数列表的右括号之后:

    struct S
    {
    void bar() immutable
    {
    }
    }

    const类型

    const类型和immutable类型类似,唯一的区别在于const建立的是数据的只读视图。数据本身随时可能被其他引用了该数据的变量所修改。

    const成员函数

    在const成员函数中,不允许通过该函数的this指针改变对象任何部分的数据。

    隐式转换

    immutable和非immutable类型都可以隐式转换为const类型。非immutable类型不能隐式转换为immutable类型,反之亦然。

    对比D和C++的immutable/const

    Const, Immutable Comparison
    特性 D C++98
    const关键字
    immutable关键字
    const表示方式 函数式:
    //指向指向const int的const指针的指针
    const(int*)* p;
    后缀:
    //指向指向const int的const指针的指针
    const int *const *p;
    有传递性的const 有:
    //指向指向const int的const指针的const指针
    const int** p;
    **p = 3; // 错误
    无:
    //指向指向int的指针的const指针
    int** const p;
    **p = 3; // 通过
    强制抹除const 有:
    // 指向const int的指针
    const(int)* p;
    int* q = cast(int*)p; // ok
    有:
    // 指向const int的指针

    const int* p;
    int* q = const_cast<int*>p; //ok
    强制抹除const后进行修改 无:
    // 指向const int的指针
    const(int)* p;
    int* q = cast(int*)p;
    *q = 3; // 未定义行为
    有:
    // 指向const int的指针
    const int* p;
    int* q = const_cast<int*>p;
    *q = 3; // 通过
    以顶级const区分重载的函数 有:
    void foo(int x);
    void foo(const int x); //正确
    无:
    void foo(int x);
    void foo(const int x); //错误
    变量的const别名 有:
    void foo(const int* x, int* y)
    {
    bar(*x); // bar(3)
    *y = 4;
    bar(*x); // bar(4)
    }
    ...
    int i = 3;
    foo(&i, &i);
    有:
    void foo(const int* x, int* y)
    {
    bar(*x); // bar(3)
    *y = 4;
    bar(*x); // bar(4)
    }
    ...
    int i = 3;
    foo(&i, &i);
    变量的immutable别名 无:
    void foo(immutable int* x, int* y)
    {
    bar(*x); // bar(3)
    *y = 4; // 未定义行为
    bar(*x); // bar(??)
    }
    ...
    int i = 3;
    foo(cast(immutable)&i, &i);
    没有immutable
    字符串字面量的类型 immutable(char)[] const char*
    将字符串字面量转换为非常量 不允许 允许但不推荐

    阅读(11) | 评论(0) | 阅读全文>>

    用gimp脚本批量转换TGA到PNG

    2009-8-5 16:06:23 阅读(46) 评论(0)

    本来在Windows里面用ACDSee批量转格式的,结果发现用ACDSee把TGA转到PNG之后,透明通道都丢失了。
    没办法,只好到Linux里面用gimp脚本来转。gimp的脚本得用Scheme写,gimp里面就能找到gimp的API。不过我没用过Scheme,不知道Scheme有些啥库函数能遍历文件系统的,所以绕了个圈子,用Shell脚本遍历文件,再把文件名硬编码到Scheme脚本中:
    #!/bin/bash
    (
    find . -iname '*.TGA' -exec echo '
    (let ((image (car (file-tga-load 1 "{}" "{}"))))
    (file-png-save-defaults 1 image (car (gimp-image-get-active-drawable image)) "{}.png" "{}.png")
    (gimp-image-delete image)
    )
    ' \;
    echo '(gimp-quit 1)'
    ) | gimp-console -d -i -b -

    阅读(46) | 评论(0) | 阅读全文>>

    查看所有日志>>

     
     
     
     
     

    自定义模块

     
     
    模块内容加载中...
     
     
     
     
     
     
     
     

      Atry

    浙江省 杭州市 双鱼座

     发消息  写留言

     
    博客等级加载中...
    今日访问加载中...
    总访问量加载中...
    最后登录加载中...
     
     
     
     
     
     
     
    日志评论
     
     
     
     
     

    自定义模块

     
     
    模块内容加载中...
     
     
     
     
     
     
     
    模块内容加载中...
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     

    页脚

    网易公司版权所有 ©1997-2009