dlopen,dlsym による動的リンク
前の記事 で静的ライブラリとして wasm にリンクしていたものを共有ライブラリとして作成する。
共有ライブラリは JavaScript で fetch() したものを仮想ファイルシステムに保存し、これを C++ のソースコードで dlopen,dlsym を使って関数を解決する必要がある。
以下にプロジェクトのファイルと、静的ライブラリから変更した点を掲載する。
C:\wasm\project\05-dll-dlopen\.vscode\launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "localhost:8080",
"url": "http://localhost:8080/",
}
]
}
C:\wasm\project\05-dll-dlopen\CMakePresets.json
{
"version": 5,
"cmakeMinimumRequired": {
"major": 3,
"minor": 20,
"patch": 0
},
"configurePresets": [
{
"name": "wasm-config-debug",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_CXX_STANDARD": "17",
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
"CMAKE_CXX_EXTENSIONS": "OFF",
"CMAKE_TOOLCHAIN_FILE": "$env{EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
"CMAKE_BUILD_TYPE": "Debug"
}
}
],
"buildPresets": [
{
"name": "wasm-build-debug",
"configurePreset": "wasm-config-debug",
"verbose": true
}
]
}
C:\wasm\project\05-dll-dlopen\CMakeLists.txt
- TARGET_SUPPORTS_SHARED_LIBS を書かないと共有ライブラリが作成できない (Windowsの場合)
- POSITION_INDEPENDENT_CODE により位置独立コードとなるよう指定している (PIC とか PIE の事だと思う)
- MAIN_MODULE=2 により C のライブラリ(libc,libm,...) をリンクする wasm を指定
- サイド・モジュールである util.c の中で vfprintf を使っているため、EXPORTED_FUNCTIONS でこれを指定する必要がある
- デフォルトでは共有ライブラリのファイル名が libXXX.so となってしまうので、set_target_properties(OUTPUT_NAME...) により変更している
cmake_minimum_required(VERSION 3.20)
project(05_dll_dlopen LANGUAGES CXX)
set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE)
add_executable(05_dll_dlopen_main func.cpp)
set_target_properties(05_dll_dlopen_main PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_link_options(05_dll_dlopen_main PRIVATE
"-gsource-map"
"-sASSERTIONS=1"
"-sSAFE_HEAP=1"
"-sMAIN_MODULE=2"
"-sEXPORTED_FUNCTIONS=_malloc,_free,_vprintf"
"-sEXPORTED_RUNTIME_METHODS=wasmExports,ccall,stringToUTF8OnStack,UTF8ToString,HEAP32"
)
add_library(05_dll_dlopen_side SHARED util.cpp)
set_target_properties(05_dll_dlopen_side PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_link_options(05_dll_dlopen_side PRIVATE
"-gsource-map"
"-sASSERTIONS=1"
"-sSAFE_HEAP=1"
"-sSIDE_MODULE=2"
)
set_target_properties(05_dll_dlopen_side PROPERTIES
OUTPUT_NAME "05_dll_dlopen_side"
PREFIX ""
SUFFIX ".wasm"
)
C:\wasm\project\05-dll-dlopen\index.html
Module.onRuntimeInitialized() ではサイド・モジュールをダウンロードして仮想ファイルシステムに保存した後に、初期化関数(initSideModule) を呼び出す。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8"/>
<script src="build/05_dll_dlopen_main.js"></script>
<script>
const cpp_func_example = () =>
{
let v = 0;
let s = '';
// 数値を扱う関数: ccall による呼び出し
v = Module.ccall('increment', 'number', ['number'], [v]);
console.log(`return=${v}`);
// 数値を扱う関数: wasmExports を利用した呼び出し
v = Module.wasmExports.increment(v);
console.log(`return=${v}`);
// 数値を扱う関数: 直接呼出し
v = Module._increment(v);
console.log(`return=${v}`);
// 文字列を扱う関数: ccall による呼び出し
v = Module.ccall('str_repeat', 'number', ['string', 'number'], ['HELLO1 ', 3]);
s = Module.UTF8ToString(v);
Module._free(v);
console.log(`return=${s}`);
// 文字列を扱う関数: wasmExports を利用した呼び出し
v = Module.stringToUTF8OnStack('HELLO2 ');
v = Module.wasmExports.str_repeat(v, 3);
s = Module.UTF8ToString(v);
Module._free(v);
console.log(`return=${s}`);
// 数値配列を扱う関数
const js_nums = Array.from({ length: 10 }, (_, i) => i + 1);
const intptr = Module._malloc(js_nums.length * Module.HEAP32.BYTES_PER_ELEMENT);
Module.HEAP32.set(js_nums, intptr / Module.HEAP32.BYTES_PER_ELEMENT);
v = Module.wasmExports.sum(intptr, js_nums.length);
console.log(`return=${v}`);
Module._free(intptr);
};
Module.onRuntimeInitialized = () =>
{
fetch('/build/05_dll_dlopen_side.wasm')
.then(response => response.arrayBuffer())
.then(buffer => {
const byteArray = new Uint8Array(buffer);
FS.writeFile('/side.wasm', byteArray);
Module._initSideModule(Module.stringToUTF8OnStack('/side.wasm'));
cpp_func_example();
});
};
</script>
</head>
<body>
</body>
</html>
C:\wasm\project\05-dll-dlopen\util.cpp
EMSCRIPTEN_KEEPALIVE によるエクスポートを追加
// UTF-8N CRLF
#include <emscripten.h>
#include <cstdio>
#include <cstdarg>
extern "C"
EMSCRIPTEN_KEEPALIVE
void log_impl(const char* format...)
{
va_list args1;
va_start(args1, format);
vprintf(format, args1);
va_end(args1);
}
// EOF
C:\wasm\project\05-dll-dlopen\func.cpp
初期化関数(initSideModule) が最初に呼び出されるので、dlopen,dlsym により関数を解決しグローバル変数の log_impl に保存している。
log() マクロは log_impl が真の場合に、その関数を呼び出す。
// UTF-8N CRLF
#include <emscripten.h>
#include <cstdio>
#include <cstring>
#include <numeric>
#include <dlfcn.h>
using fptr_t = void (*)(const char*...);
fptr_t log_impl;
#define log(...) if (log_impl) log_impl(__VA_ARGS__); else puts("dll load error")
extern "C"
EMSCRIPTEN_KEEPALIVE
void initSideModule(const char* path)
{
if (void* handle = dlopen(path, RTLD_NOW))
{
if (fptr_t fptr = (fptr_t)dlsym(handle, "log_impl"))
{
log_impl = fptr;
}
//dlclose(handle);
}
}
extern "C"
EMSCRIPTEN_KEEPALIVE
int increment(int v)
{
log("-- increment(%d)\n", v);
return v + 1;
}
extern "C"
EMSCRIPTEN_KEEPALIVE
char* str_repeat(const char* str, int num)
{
log("-- str_repeat(%s, %d)\n", str, num);
char* ret = (char*)malloc(strlen(str) * num + 1);
ret[0] = '\0';
for (int i=0; i<num; ++i) {
strcat(ret, str);
}
return ret;
}
extern "C"
EMSCRIPTEN_KEEPALIVE
int sum(const int* nums, int len)
{
log("-- sum(%d..%d)\n", nums[0], nums[len - 1]);
return std::accumulate(nums, nums + len, 0);
}
// EOF