Die Idee ist, eine Anwendung (wie sie in einer C++-Bibliothek existieren könnte) zu nehmen und sie nach WebAssembly zu portieren.
2023-11-06T00:00:00+0100
Die Idee ist, eine Anwendung (wie sie in einer C++-Bibliothek existieren könnte) zu nehmen und sie nach WebAssembly zu portieren.
Die Idee dieses Artikels ist, dass eine komplexere Applikation (wie sie in einer C++ Bibliothek bestehen könnte) genommen und in WebAssembly portiert wird. Dabei wird die Applikation zuerst in nativen Maschinencode kompiliert und direkt auf dem Betriebssystem ausführt. Danach in einem zweiten Schritt wird die gleiche Applikation in WebAssembly kompiliert und in einer Webanwendung verwendet.
Die Applikation benutzt die bitmap_image.hpp
von Arash Partow,
welche dazu verwendet wird um mit Bitmaps in C++ zu arbeiten.
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <string>
#include "bitmap_image.hpp"
void cartesian()
{
...
}
void fractal()
{
...
}
extern "C"
void build_bitmap(int choosen)
{
switch(choosen) {
case 1:
();
cartesianbreak;
case 2:
();
fractalbreak;
default:
break;
}
}
int main(int argc, char **argv)
{
int choosen = 0;
if(argc > 1)
{
= std::atoi(argv[1]);
choosen }
(choosen);
build_bitmap
return 0;
}
Die Applikation besteht aus zwei Funktionen cartesian
und fractal
. Welche unterschiedliche Bitmaps erzeugen. Mit
dem Parameterwert 1
oder 2
der
main
Funktion kann die jeweilige Funktion ausgewählt
werden.
Bevor dies nun aber gemacht werden kann muss das Programm kompiliert werden. Dazu wird folgendes Makefile verwendet.
COMPILER = -c++
OPTIONS = -ansi -pedantic-errors -Wall -Wall -Werror -Wextra -o
LINKER_OPT = -L/usr/lib -lstdc++ -lm
all: bitmap
bitmap: bitmap.cpp bitmap_image.hpp
$(COMPILER) $(OPTIONS) bitmap bitmap.cpp $(LINKER_OPT)
valgrind_check:
valgrind --leak-check=full --show-reachable=yes --track-origins=yes -v ./bitmap
clean:
rm -f core *.o *.bak *stackdump *~
Welches über den Befehl make
ausgeführt werden kann. Die
dadurch entstandene ausführbare Datei bitmap
kann nun
aufgerufen werden.
./bitmap 1
./bitmap 2
Was dann die jeweiligen Bitmap-Bilder erzeugt.
Nun kann das Makefile leicht angepasst werden, damit es den Emscripten Compiler verwendet und in WebAssembly kompiliert.
COMPILER = -em++
OPTIONS = -ansi -pedantic-errors -Wall -Wall -Werror -Wextra -o
LINKER_OPT = -L/usr/lib -lstdc++ -lm -s FORCE_FILESYSTEM=1 -s ALLOW_MEMORY_GROWTH=1 -s INVOKE_RUN=0 -s EXPORTED_RUNTIME_METHODS="cwrap, callMain" -s EXPORTED_FUNCTIONS="_main, _build_bitmap"
all: bitmap
bitmap: bitmap.cpp bitmap_image.hpp
$(COMPILER) $(OPTIONS) bitmap.js bitmap.cpp $(LINKER_OPT)
valgrind_check:
valgrind --leak-check=full --show-reachable=yes --track-origins=yes -v ./bitmap
clean:
rm -f core *.o *.bak *stackdump *~
emscripten nicht installiert, lese auch meinen anderen
Artikel oder folge der Anweisung
https://emscripten.org/docs/getting_started/downloads.html Unter Ubuntu
sudo apt intall emscripten |
Welches über make -f MakefileWasm
(Explizites Makefile)
ausgeführt werden kann. Die dadurch entstandenen Dateien
bitmap.js
und bitmap.wasm
können später für
die Webanwendung gebraucht werden.
Zuerst jedoch kurz die Unterschiede zwischen den beiden Makefiles. An
erster Stelle fällt auf, dass ein anderer Compiler verwendet wird,
anstatt c++
wird em++
eingesetzt. Danach folgt
die neue Benennung des Outputs von bitmap
zu
bitmap.js
und zu guter Letzt die einzelnen Optionen für den
Linker (Dokumentation):
-s FORCE_FILESYSTEM=1
: Damit eine Virtualisierung des
Dateisystems in der Webanwendung verwendet werden kann (Dokumentation).-s ALLOW_MEMORY_GROWTH=1
: Damit der Speicher dynamisch
wachsen kann.-s INVOKE_RUN=0
: Damit die main
Funktion
nicht automatisch ausgeführt wird.-s EXPORTED_RUNTIME_METHODS="cwrap, callMain"
: Damit
die cwrap
und callMain
Funktion exportiert
wird (Dokumentation).-s EXPORTED_FUNCTIONS="_main, _build_bitmap"
: Damit die
main
Funktion eigenständig exportiert wird (Dies wäre
eigentlich nicht nötig, da main
standardmässig exportiert
wird). Jedoch kann dank dieser Option auch die build_bitmap
Funktion eigenständig exportiert werden. Damit der Name der Funktion vom
C++ Compiler nicht verändert wird, muss die Funktion in C++ mit
extern "C"
deklariert werden (siehe Sourcecode oben, C++ name
mangling).<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Porting Third Party to WebAssembly</title>
</head>
<body>
<h1>Porting Third Party to WebAssembly</h1>
<button id="create">Create Bitmaps</button>
<canvas id="output"></canvas>
<script src="bitmap.js"></script>
<script src="bitmap_image.js"></script>
<script>
const callMainButton = document.getElementById("create");
.addEventListener("click", function () {
callMainButton// call build_bitmap with cwrap
const build_bitmap = Module.cwrap("build_bitmap", "number", ["number"]);
build_bitmap(1);
// or the exported build_bitmap
._build_bitmap(2);
Module
// or the main function
// Module.callMain(["1"]);
const canvas = document.getElementById("output");
const ctx = canvas.getContext("2d");
const image = FS.readFile("./mandelbrot_set_vga.bmp");
const bmp = getBMP(image.buffer);
const imageData = convertToImageData(bmp);
.width = bmp.infoHeader.biWidth;
canvas.height = bmp.infoHeader.biHeight;
canvas
.putImageData(imageData, 0, 0);
ctx
console.log(image)
;
})</script>
</body>
</html>
Anwendung starten python3 -m http.server
.
Analysieren im Browser http://localhost:8000
.
Das Ausführen des Scripts benötigt einiges an Rechenkapazität und kann zu einer Meldung vom Browser führen, dass die Webanwendung den Browser verlangsamt. Diese kann für dieses Beispiel einfach ignoriert werden.
Ein paar Kurzkommentare zum Code: * bitmap_image.js
ist
eine Hilfsdatei, welche ähnlich der bitmap_image.hpp
dazu
dient die Bitmaps einfacher zu verarbeiten. Sie hilft damit die Bitmap
Datei zu lesen und später in das Canvas zu zeichnen. * Die
build_bitmap
Funktion kann gemäss Linker Optionen auf drei
Wegen aufgerufen werden. Entweder über die cwrap
Funktion,
über die exportierte build_bitmap
Funktion oder über die
main
Funktion. *
FS.readFile("./mandelbrot_set_vga.bmp")
liest die Datei aus
dem virtualisierten Dateisystem. Dies ist möglich, weil die Option
-s FORCE_FILESYSTEM=1
gesetzt ist und somit die File
System API eingebunden wurde. Interessant sind auch weitere Befehle
wie FS.readdir("/")
siehe Screenshot vom Konsolen
Output.
Ich bin gerne bereit den Artikel zu präzisieren, erweitern oder zu korrigieren. Schreibt ein Feedback oder meldet euch direkt bei mir.
Erstellt von Marco Kuoni, November 2023