0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Windows11 の VSCode+Emscripten で wasm によるハローワールド (6)

Last updated at Posted at 2025-10-10

すべての記事

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

実行結果は 前のもの と変わらない。
長くなったので、サイド・モジュールが利用する関数の調べ方は に記述している。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?