当前位置: 首页 > 工具软件 > Emscripten > 使用案例 >

Emscripten中的文件系统

鲜于凯康
2023-12-01

跨平台的C/C++程序常使用fopen()、fread()、fwrite()等Libc/LibCXX提供的同步文件访问函数。通常在文件系统方面,JavaScript程序与C/C++本地程序有巨大的差异,主要体现在:

  1. 运行在浏览器中的JavaScript程序无法访问本地文件系统;
  2. 在JavaScript中,无论ajax()还是fetch(),都是异步操作。

Emscripten提供了一套虚拟文件系统,以兼容Libc/LibCXX提供的同步文件访问函数。

在最底层,Emscripten提供了3种文件系统,分别为MEMFS(内存文件系统)、NODEFS、IDBFS。它们各自的特点:

  • MEMFS系统的数据完全存储于内存中,程序运行时写入的数据在页面刷新或程序重载后将丢失;
  • NODEFS是Node.js文件系统,可以访问本地文件系统,可以持久化存储数据,但只能用于Node.js环境。
  • IDBFS是IndexedDB文件系统,是基于浏览器的IndexedDB对象,可以持久化存储数据,但只能用于浏览器环境

一、基于MEMFS的打包文件系统

文件导入MEMFS系统之前,需要先将其打包。文件打包有两种方式:

  1. emcc命令:embed和preload。
  2. 单独的文件打包工具file_packager.py。

1、embed模式

文件数据被转为JavaScript代码;

(对于embed模式,其需要将数据文件化编码,所产生的文件包体积大于preload模式下产生的文件包体积,因此除非需要打包的文件总数据量非常小,否则尽可能使用preload模式)

2、preload模式

除了生成.js文件外,还会额外生成同名的.data文件。其中,.data文件包含所有文件的二进制数据,.js文件包含.data文件包下载、装载操作的胶水代码。

int main()
{
    FILE* fp = fopen("hello.txt","rt");
    if(fp)
    {
        while(!feof(fp))
        {
            char c = fgetc(fp);
            if(c!=EOF)
            {
                putchar(c);
            }
        }
        fclose(fp);
    }
    return 0;
}

emcc pack.c -o pack.js --preload-file hello.txt

--preload-file参数不仅可以打包单个文件,还可以打包整个目录。

emcc pack.c -o pack.js --preload-file dat_dir

生成的打包文件pack.data包括dat_dir内的所有内存

3、file_packager.py打包 

步骤一:

python /home/hyde/emsdk/upstream/emscripten/tools/file_packager.py fp.data --preload file --js-output=fp.js

将file目录内的文件打包成fp.data和fp.js文件。

步骤二:

使用外挂文件包时,主程序编译必须增加 -s FORCE_FILESYSTEM=1参数以强制启用文件系统。

emcc test.c -o test.js -s FORCE_FILESYSTEM=1

步骤三:

在网页中,必须先引入外挂文件包.js,再引入主程序.js

<script src="fp.js"></script>
<script src="test.js"></script>

虽然下载文件包是异步的,但是Emscripten可以确保运行时准备就绪时,文件系统初始化完成,因此在Module.onRuntimeInitialized()回调函数中使用文件系统是安全的。

二、NODEFS文件系统

void setup_nodefs(){
	EM_ASM(
	    FS.mkdir('/data');
		FS.mount(NODEFS,{root:'.'},'/data');
	);
}
int main()
{
	setup_nodefs();
	FILE* fp = fopen("/data/nodefs_data.txt","r+t");
	if(fp == NULL)
	{
		fp = fopen("/data/nodefs_data.txt","w+t");
		
	}
	int count =0 ;
	if(fp)
	{
		fscanf(fp,"%d",&count);
		count++;
		fseek(fp,0,SEEK_SET);
	    ...
	}
	else{
		printf("fopen failed\n");
	}
	return 0;
}
  • FS.mkdir('/data');//在虚拟文件系统中创建了‘/data’目录
  • FS.mount(NODEFS,{root:'.'},'/data');//把当前的本地目录挂接到了/data目录。

三、IDBFS文件系统

EMSCRIPTEN_KEEPALIVE	
void test()
{
	FILE* fp = fopen("/data/nodefs.txt","r+t");
	...
}

int main(){
	EM_ASM(
		FS.mkdir('/data');
		FS.mount(IDBFS,{},'/DATA');
		FS.syncfs(true,function(err){
			assert(!err);
			ccall('test','v');
		});
	);
	return 0;
}
  • IDBFS的挂接是通过FS.mount()方法完成的。
  • 事实上在运行时,IDBFS仍然是使用内存来存储虚拟文件系统,只不过IDBFS可以通过FS.syncfs()方法进行内存数据与IndexedDB的双向同步,达到数据持久化存储的目的。
  • FS.syncfs()是异步操作,因此,例子中的读写文件的test()函数必须在FS.syncfs()的回调函数中调用。

 类似资料: