翻译:云荒杯倾
本文是Emscripten-WebAssembly专栏系列文章之一,更多文章请查看专栏。
也可以去作者的博客阅读文章。
这部分是关于如何在 Emscripten编译的代码中使用文件。包括以下部分:
- 文件系统概览:总体介绍Emscripten支持的文件操作。
- 打包文件:怎样使用emcc来打包编译后的代码所需要的文件。
- 同步虚拟XHR后台文件系统使用:介绍如何使用XHR通过http完成二进制数据的懒加载。
下面逐一讲这三部分。
1、文件系统概述
分两部分,一部分介绍Emscripten的文件系统运行环境,一部分介绍Emscripten 文件系统体系架构。
Emscripten的文件系统运行环境
原生代码和JS使用的文件存取模式有很大的不同。原生代码使用libc和libcxx库调用同步文件APIs,而JS中除了web worker都是只允许异步文件获取的。另外,因为JS处在浏览器沙箱环境中,它并不是直接对主机的文件系统进行存取操作的。
Emscripten提供了一个虚拟的文件系统模拟本地文件系统,所以原生代码可以可以在很少或者不需要修改的情况下使用同步文件APIs。
打包文件可以让你指定你需要的文件,通过emcc将他们打包进虚拟文件系统。对于开发者来说,打包文件这一部分的知识是需要了解的。
Emscripten文件系统体系架构
下面列出了Emscripten文件系统的主要元素。大多数原生代码都是调用libc和libcxx库的同步文件API的,接下来他们将依次调用底层的文件系统API,默认使用MEMFS虚拟文件系统。
当运行时初始化时,MEMFS挂载在根目录下。而添加到MEMFS的文件是在编译期间通过emcc打包进来的。当HTML页面加载完成后,JS使用同步XHR异步加载(load)这些文件。只有当异步加载完成,文件在虚拟文件系统可用的时候,才能执行编译代码。
因为MEMFS实际上存在内存中,页面reload完成的时候,所有写入的数据都会丢失掉。如果想持久化这些写入的东西,请在浏览器中挂载IDBFS文件系统,或者Nodejs中挂载NODEFS。NODEFS可以访问本地文件系统。你可以通过自己写JS的方式挂载新的文件系统,然后执行这些文件系统中的适合你的文件操作。
如果你需要从网络取其他文件到文件系统,请使用 Emscripten Asynchronous File System API中的emscripten_wget()和其他方法。这些方法是同步的,应用程序必须等待回调完成完成。
2、打包文件
有预加载(preloading)和嵌入(embedding)两种可交换的打包方式。嵌入式是将具体文件与编译生生的JS文件混到一起,放同一个文件。而预加载可以将文件单独打包到一个文件中。嵌入的方式比预加载低效,使用情况是要打包的文件数量和文件大小都比较小。
emcc使用文件打包器打包文件,然后在创建和加载文件系统的时候生成文件系统调用。虽然emcc是推荐的打包工具,但是你也可以直接手动使用文件打包器进行自行打包。
用emcc打包
打包文件最简单的方式是在编译的时候通过emcc打包。preload和embed代表你选择的打包方式。
下面是一个示例打包命令:
./emcc file.cpp -o file.html --preload-file asset_dir
这个命令会生成file.html,file.js,file.data。这个.data文件包含了asset_dir目录下的所有文件,通过file.js来加载它。
下面这个命令展示了嵌入方式打包。此时,emcc生成file.html,file.js。 asset_dir/ 目录中的内容都直接被打包进file.js了。
./emcc file.cpp -o file.html --embed-file asset_dir
默认下,要打包的文件应该嵌套在或就在编译命令此时cd的目录下。运行时,虚拟文件系统会映射同样的要打包文件的目录结构。虚拟文件系统的根目录对应着编译命令此时cd的目录。
举例,有dir1/dir2/dir3/asset_dir/的一个目录结构,dir2是要编译的项目,我们要打包asset_dir的话,就用相对路径dir3/asset_dir/。emcc的命令窗口在dir2目录打开。
./emcc file.cpp -o file.html --preload-file dir3/asset_dir
打包编译完成后,在虚拟文件系统中也是通过dir3/asset_dir这样的路径来找asset_dir文件夹。相似的,如果我们是在dir2下打包了一个文件的话,项目运行时就应该在虚拟文件系统的根目录下来找dir2目录。
打包时,还有一个@符号的用法,它是指将任何本地文件系统中任何目录下的文件映射到编译后的虚拟文件系统中的某路径。
用文件打包器打包
除了通过emcc命令编译时打包,你也可以手动运行文件打包器file_packager.py来打包。
打包器会成.data文化和.js文件。.js文件包含使用.data文件的代码。
NOTE:
* 使用文件打包器打包使你不用非要在编译的时候(用emccd来编译)打包。
* 使用文件打包器可对多个数据文件逐一打包,然后输出很多.js文件。
改变.data文件的位置
默认下,.data文件和.js文件通过相同URL加载。有时候让你的数据文件和其他类型文件分开,处在不同位置是有用的。
使用 Module.filePackagePrefixURL完成存储前的路径修改。这个属性要在加载它的<script>中指定。
在虚拟文件系统中修改文件位置
默认打包方法是将要打包的文件目录映射到虚拟文件系统的根目录,目录的结构也映射。而用@符号可以显示指定在运行时某个路径下资源应该位于虚拟文件系统的哪里。
note:
之所以搞一个@符号,是因为有时候要打包的文件并不在或者并不嵌套在编译时目录下。因为不能在虚拟文件系统中映射它的位置。
举例,我们通过以下命令可以将../../asset_dir这一一个目录搞成虚拟文件系统中的根目录。
./emcc file.cpp -o file.html --preload-file ../../asset_dir@/
也可以将一个文件映射成新的路径及文件名,下面就是将本地文件系统中的路径../res/gen123.png编译后变成虚拟文件系统中的/main.png。
./emcc file.cpp -o file.html --embed-file ../res/gen123.png@main.png
合法字符集
文件名可以是A-Z, a-z, 0-9,空格,(!#$%&‘()+,-.;=@[]^_`{}~)括号中的字符也可以。
如果你主机系统支持的话,这些字符也可以:"*<>?|,这些在Windows中不行。
如果想用@,有时候需要转义,使用@@,这是为了避免@被理解成路径映射(上面讲的)。
/, 和: 不能用。
监视文件的调用
note:
只打包你程序真正需要的文件,别打包其他,这样可以减少代码体积,提高启动速度。
运行期有一个打印选项可以查看哪些文件被用到。通过定义Module.logReadFiles对象来使用。 每当读到一个文件时,Module.printErr会被调用一次。
另一个方法是在你编译出来的JS中查看FS.readFiles()。这是个对象,它的键都是它读的文件。你会发现它比上面的打印方法更简单,因为它是直接记录了文件,而不是上面那种只是有可能的文件存取。
note:
你也可以修改或删除FS.readFiles()对象,当你想看在两个时间点之间都有哪些文件被读取的时候,这样做是对的。
3、虚拟 同步XHR 后台文件系统的使用
Emscripten支持使用XHR从HTTP服务器中延迟加载二进制数据。此功能可用于创建从编译代码中进行同步文件访问的后端。
后端可以提高启动时间,因为在编译的代码运行之前,整个文件系统不需要预加载。如果web服务器支持字节服务,那么它也是非常有效的—--—在这种情况下,Emscripten可以读取实际需要的文件部分。
警告:
由于浏览器限制,这个机制只能用到web workers中
note:
如果不支持字节服务,那么在读取单个字节时,Emscripten将不得不加载整个文件(无论大小)。
Emscripten代码移植系列文章
Emscripten代码移植主题系列文章是emscripten中文站点的一部分内容。
本文是第四个主题。
第一个主题介绍代码可移植性与限制
第二个主题介绍Emscripten的运行时环境
第三个主题第一篇文章介绍连接C++和JavaScript
第三个主题第二篇文章介绍embind
第四个主题介绍文件和文件系统
第六个主题介绍Emscripten如何调试代码