Ceci est une ancienne révision du document !
Table des matières
Téléchargement et installation
git clone --depth 1 https://github.com/emscripten-core/emsdk.git cd emsdk git pull ./emsdk install latest ./emsdk activate latest source ./emsdk_env.sh
Utilisations
Emscripten va permettre de générer un fichier .js
qui va contenir le code passerelle entre le code JavaScript et le code WebAssembly qui sera dans le fichier .wasm
.
C'est donc un outil plutôt adapté pour créer des librairies, même s'il est possible de créer des programmes entiers avec.
An Introduction to WebAssembly for JavaScript Developers Archive du 21/04/2021 le 24/07/2021
Compilation
Liste des settings (-s
).
vcpkg
Après l'installation en utilisant le triplet wasm32-emscripten
, il est possible de le cumuler avec emcmake
.
source ./emsdk/emsdk_env.sh .\vcpkg\bootstrap-vcpkg.bat .\vcpkg\vcpkg install --triplet wasm32-emscripten spdlog emcmake cmake -S . -B [build_directory] -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=.../emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DVCPKG_TARGET_TRIPLET=wasm32-emscripten -DCMAKE_TOOLCHAIN_FILE=.../vcpkg/scripts/buildsystems/vcpkg.cmake cmake --build [build_directory]
Exemples
Librairies
Une simple librairie
- Code source
#include <emscripten.h> EMSCRIPTEN_KEEPALIVE int add(int a, int b) { return a + b; }
- Compilation
emcc add.c -o add.js -s EXPORT_NAME="'ModuleName'"
- Utilisation depuis du code html
Puis on peut utiliser la fonction add depuis le code HTML en passant par la librairie JavaScript générée. Il faudra bien mettre en ligne les deux fichiers .js
et .wasm
.
Par défaut, tous les objets exportés sont regroupés dans un objet global ModuleName
. La fonction est accessible depuis son nom “mangled” (func1@a@@AAEXH@Z
par exemple), avec l'ajout d'un préfix _
. Decorated Names Archive du 05/09/2018 le 04/01/2021
<head> <script type="text/javascript"> var ModuleName = { onRuntimeInitialized: function() { const a = 1.5; const b = 3.8; const ret = ModuleName._add(1.5, 3.8); console.log(`${a} + ${b} = ${ret}`); } }; </script> <script type="text/javascript" src="add.js"></script> </head> <body> </body>
How To Write A WebAssembly App in C/C++ Archive du 12/11/2020 le 04/01/2021
Module
Certaines librairies imposent l'utilisation des modules (jest).
Il faut utiliser l'option -s MODULARIZE=1
.
- Utilisation en HTML
Il faut bien mettre type="test/javascript"
et pas type="module"
.
<script type="text/javascript" src="add.js"></script> <script type="text/javascript"> ModuleName().then(async instance => { const a = 1.5; const b = 3.8; const ret = instance.__Z3addii(1.5, 3.8); console.log(`${a} + ${b} = ${ret}`); }); </script>
- Utilisation avec Jest
const Module = require('./add.js'); test('test name', async () => { return Module().then(async instance => { instance.__Z3addii(...); }); });
Bindings
C'est quand même plus pratique d'utiliser du style orienté objet plutôt que des symboles “mangled”.
Il faut donc déclarer manuellement :
- toutes les classes que l'on souhaite utiliser,
- toutes les fonctions, y compris constructeur, que l'on souhaite utiliser pour chaque classe,
- toutes les classes utilisées dans les arguments et les valeurs retours des fonctions qui seront utilisées,
- les relations d'héritage si le type parent est utilisé pour exécuter les fonctions de l'enfant (polymorphisme).
Dans mes tests, j'ai vu que le lieur d'emscripten semble avoir du mal avec les symboles weak multiples. Il est préférable de tout mettre dans un même fichier source.
EMSCRIPTEN_BINDINGS(jessica) { class_<Jessica::Data::Geotechnical::IFoundationStrip>("IFoundationStrip") .smart_ptr< std::shared_ptr<Jessica::Data::Geotechnical::IFoundationStrip>>( "IFoundationStrip") .function( "setB", select_overload<std::shared_ptr< Jessica::Data::Geotechnical::IFoundationStrip>(double) const>( &Jessica::Data::Geotechnical::IFoundationStrip::B)) .function("getB", select_overload<double() const>( &Jessica::Data::Geotechnical::IFoundationStrip::B)); class_<Jessica::Data::Geotechnical::FoundationStrip< Jessica::Util::Decorator::LogCall< Jessica::Util::Decorator::LogDuration< Jessica::Data::Geotechnical::FoundationStripImpl>>>, base<Jessica::Data::Geotechnical::IFoundationStrip>>( "FoundationStripDeco") .constructor<>() .function( "clone", &Jessica::Data::Geotechnical::FoundationStrip< Jessica::Util::Decorator::LogCall< Jessica::Util::Decorator::LogDuration< Jessica::Data::Geotechnical::FoundationStripImpl>>>:: Clone) .function( "setB", select_overload<std::shared_ptr< Jessica::Data::Geotechnical::IFoundationStrip>(double) const>( &Jessica::Data::Geotechnical::FoundationStrip< Jessica::Util::Decorator::LogCall< Jessica::Util::Decorator::LogDuration< Jessica::Data::Geotechnical::FoundationStripImpl>>>:: B)) .function( "getB", select_overload<double() const>( &Jessica::Data::Geotechnical::FoundationStrip< Jessica::Util::Decorator::LogCall< Jessica::Util::Decorator::LogDuration< Jessica::Data::Geotechnical::FoundationStripImpl>>>:: B)); }
WebAssembly.instantiateStreaming
Il est possible d'utiliser WebAssembly.instantiateStreaming
pour charger le fichier .wasm
sans passer par le fichier .js
qui sert de wrapper.
Exemple en TypeScript. Nécessite npm install --save @assemblyscript/loader
.
import { instantiateStreaming } from "@assemblyscript/loader"; interface MyApi extends Record<string, unknown> { add(a: number, b: number): number; } async function getWasm() { const imports: any = {}; var module = instantiateStreaming<MyApi>(fetch('assets/add.wasm'), imports); return module; } export class MainComponent implements OnInit { private exports?: MyApi; constructor() { getWasm().then((mod) => { this.exports = mod.exports; console.log(this.exports.add(2, 4)); }); } ngOnInit(): void { } }
Mais attention, le deuxième paramètre de instantiateStreaming
peut être nécessaire pour définir un environnement précis et de fonctions de callback entre wasm et javascript. C'est notamment le cas pour l'utilisation des bindings de EMSCRIPTEN_BINDINGS
.
Dans cette nouvelle configuration, le chargement d'un wasm va donner le message d'erreur :
LinkError: import object field '_embind_register_class' is not a Function
Et cette fonction javascript est déclarée dans le fichier .js générée par emscripten. L'utilisation du fichier .js comme wrapper sera probablement nécessaire.
Erreurs
UnboundTypeError: Cannot call FoundationStripRaw.setB due to unbound types: N7Jessica4Data12Geotechnical19FoundationStripImpl4SetBE
Ici, la fonction FoundationStripRaw.setB
a besoin du type N7Jessica4Data12Geotechnical19FoundationStripImpl4SetBE
(classe Jessica::Data::Geotechnical::FoundationStripImpl::SetB
).
Il suffit de rajouter dans EMSCRIPTEN_BINDINGS
le class_<Jessica::Data::Load::VerticalEccentricImpl::SetE>("VerticalEccentricImpl_SetE")
adapté si besoin.