在C项目中引入外部依赖,通常有两种方法:引入库源代码、使用静/动态库。因为Emscripten做的工作主要是将C代码编译成asm.js或者WebAssembly,所以对于Emscripten编译的C项目而言,也是可以使用以上两种方法添加依赖。接下来将以在Ubuntu16.04 x64系统下使用Emscripten编译Faac项目为例进行讲解。
Faac是基于C编写的开源库,是目前较成熟的AAC音频编码器。假设以下程序test.cc需要使用Faac的依赖:
#include
#include "faac.h"
int main()
{
unsigned long inputSample;
unsigned long maxOutputBytes;
faacEncHandle encoder;
encoder = faacEncOpen(8000, 1, &inputSample, &maxOutputBytes);
std::cout << (int)inputSample << std::endl;
}
程序中开启了Faac编码器,获取将采样率为8000Hz、单声道的PCM音频数据编码为一帧AAC数据时所需要输入的字节数inputSample。
之前我们也有提到在项目中引入外部依赖库的方法,接下来我们分别使用两种方法引入依赖库,再使用Emscripten编译成webAssembly模块。
使用这种方法,需要将Faac中include文件夹下的faac.h、faaccfg.h和libfaac文件下的c文件和头文件拷贝至项目中。然后执行以下指令进行编译,即在test.cc后添加Faac库引入的所有C文件:
emcc test.cc bitstream.c fft.c frame.c blockswitch.c util.c channels.c filtbank.c tns.c quantize.c huff2.c huffdata.c stereo.c -s ALLOW_MEMORY_GROWTH=1 -o test.html
在默认设置下,Emscripten堆一经初始化,内存容量就固定了,无法再扩容。而某些程序在运行时需要的内存容量在不同情况下可能有很大的波动。为了满足某些极端需求而将TOTAL_MEMORY设置得非常高无疑是非常浪费的,为此,Emscripten提供了可在运行时扩大内存容量的模式,欲开启该模式,需要在编译时增加-s ALLOW_MEMORY_GROWTH=1参数。Faac编码需要的较大内存,故此处编译增加了上述选项。
在可变内存模式下,使用malloc等函数分配内存时,若可用空间不足,将引发Emscripten堆扩容。扩容时,内存中原有的数据会被拷入扩容后的内存空间中,因此扩容并不会导致数据丢失或地址变更。
可变内存虽然提供了很多便利,但当编译目标为asm.js时,可变内存模式会影响性能。然而可扩容的内存是WebAssembly的自有特性,当编译目标为wasm时,使用可变内存模式非常高效,不会影响运行性能,因此在编译为WebAssembly时,可变内存是推荐用法。
注意:即使采用了可变内存模式,内存容量仍然受32位地址空间限制。
正常情况下,gcc编译含有Makefile的项目通常使用以下命令:
./configure
make
若需要将编译后的动态库或静态库注册入系统中,还需执行以下命令:
make install
使用Emscripten编译项目的步骤也是类似的,但是需要使用emcc替换gcc编译器。Emscripten中提供了emconfigure指令为C++编译器和C编译器设置合理的环境变量。使用Emscripten指令替换以上编译指令,有:
emcongiure ./configure
emmake make
sudo make install
以编译Faac项目为例,在官网下载源码,打开后可以看到项目中已有configure.in以及Makefile.am,所以不必自行编写以上两个文件,在Faac根目录下执行命令编译即可:
aclocal
autoheader
autoconf
automake
emcongiure ./configure
emmake make
sudo make install
执行成功之后,在usr/local/lib目录下可以看到生成了
libfaac.a libfaac.la libfaac.so libfaac.so.0 libfaac.so.0.0.0
这里些就是Emscripten编译生成的包含LLVM bitcode的文件。将编译生成的libfaac.a文件添加到test项目中,目录结构如下:
执行以下命令,即可将含有Faac库依赖的项目编译成WebAssembly:
emcc test.cc libfaac.a -s ALLOW_MEMORY_GROWTH=1 -o test.html
注意:虽然使用./configre与make也能生成libfaac.a等文件,但是这些文件是不含LLVM bitcode的,执行emcc时会报错:
emcc:WARNING: xxx is not a vaild input file
shared:ERROR: no input files
note that input files without a known suffix are ignored, make sure your input files end with one of: ('.c', '.C', '.i', '.cpp', '.cxx', '.cc', '.c++', '.CPP', '.CXX', '.CC', '.C++', '.ii', '.m', '.mi', '.mm', '.mii', '/dev/null', '.bc', '.o', '.obj', '.lo', '.dylib', '.so', '.a', '.ll', '.h', '.hxx', '.hpp', '.hh', '.H', '.HXX', '.HPP', '.HH')
至于使用哪一种方法,因人而异。对于比较大型的项目,个人还是比较建议使用Makefile的方法进行编译,有兴趣的小伙伴可以了解以下如何生成Makefile文件。