wasm动态链接

概述

从官方文档Dynamic-Linking可以了解到,动态链接主要是将wasm文件包拆分为一个主模块(Main module)和若干个子模块(Side module)。这个看起来和c的动态库机制差不多,但还是有一个差异值得注意

  1. Main modules, which have system libraries linked in.

  2. Side modules, which do not have system libraries linked in.

wasm的子模块是不包含系统库调用的,依赖于主模块所依赖的系统库,c平台的动态库是可以自动调用所需系统库的,所以在使用wasm的动态链接方案时,需要注意主模块系统依赖是否完善。

和c平台一样,wasm的动态库链接支持编译时自动链接(加载主模块时自动加载子模块)和运行时加载(在主模块中进行动态控制加载)

编译时链接

先准备子模块

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
//side.h
int side(void);


//side.c
#include "side.h"
#define SIZE 5000
char dummy[SIZE] = {};

int side(void)
{
    return SIZE;
}

主模块

1
2
3
4
5
6
7
8
//main.c
#include <stdio.h>
#include "side.h"

int main() 
{
    printf("side module size: %d byte\n", side());
}
  • 将side模块代码用 -sSIDE_MODULE链接参数进行链接, 这里假设输出了side.wasm
1
emcc side.c -s SIDE_MODULE=1 -o side.wasm
  • 将main模块代码用-sMAIN_MODULE编译参数进行链接,同时指定side模块的文件 , 如下代码
1
emcc -s MAIN_MODULE main.c side.wasm -o main.hmtl

编译完成后,运行

1
emrun main.html

result

运行时链接

而运行时链接,需要在主模块里面增加加载动态库的逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <dlfcn.h>

int main() 
{
    void *handle;
    typedef int (*func_t)(void);

    //与linux一样,dlopen加载动态库
    handle = dlopen("side.wasm", RTLD_NOW);

    if (!handle) 
    {
        printf("failed to open the library\n");
        return 0;
    }
    
    //dlsym查找动态库函数入口指针
    func_t func = (func_t)dlsym(handle, "side");

    if (!func) 
    {
        printf("failed to find the method\n");
        dlclose(handle);
        return 0;
    }
    printf("side module size: %d byte\n", func());
}

编译字库的命令还是没有区别

1
emcc side.c -s SIDE_MODULE=1 -o side.wasm    

编译主模块命令有所变化,需要将side.wasm文件通过preload file参数预先存放在wasm的虚拟文件系统

1
emcc main.c -o main.html -s MAIN_MODULE --preload-file side.wasm

编译完成后,文件目录下有以下文件

1
2
3
4
5
6
.
├── main.data
├── main.html
├── main.js
├── main.wasm
└── side.wasm

编译完成后,运行

1
emrun main.html

result

结果是一致的。

注意事项

  1. wasm的子模块是不包含系统库调用的,依赖于主模块所依赖的系统库,c平台的动态库是可以自动调用所需系统库的,所以在使用wasm的动态链接方案时,需要注意主模块系统依赖是否完善。
  2. 使用纯c接口导出,避免cpp奇怪的符号问题
1
2
3
4
5
6
7
8
9
#ifdef __cplusplus    //__cplusplus是cpp中自定义的一个宏
extern "C" {          //告诉编译器,这部分代码按C语言的格式进行编译,而不是C++的
#endif

    /**** some declaration or so *****/  

#ifdef __cplusplus
}
#endif
  1. 多个子模块时,注意使用编译器参数fvisibility控制符号的导出,避免同名函数符号的冲突

visibility用于设置动态链接库中函数的可见性,将变量或函数设置为hidden,则该符号仅在本so中可见,在其他库中则不可见。g++在编译时,可用参数-fvisibility指定所有符号的可见性(不加此参数时默认外部可见,参考man g++中-fvisibility部分);若需要对特定函数的可见性进行设置,需在代码中使用__attribute__设置visibility属性。编写大型程序时,可用-fvisibility=hidden设置符号默认隐藏,针对特定变量和函数,在代码中使用__attribute__ ((visibility(“default”)))另该符号外部可见,这种方法可用有效避免so之间的符号冲突。

参考:

  1. https://github.com/WebAssembly/design/blob/main/DynamicLinking.md
  2. https://stackoverflow.com/questions/63150966/how-to-dlopen-a-side-module-larger-than-4kb

微信公众号