Skip to content
Open
26 changes: 26 additions & 0 deletions examples/cross_build/wasm/bindigs/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
cmake_minimum_required(VERSION 3.15)
project(wasm_example CXX)

find_package(Eigen3 REQUIRED)
add_executable(wasm_example main.cpp)

target_link_libraries(${PROJECT_NAME} PRIVATE Eigen3::Eigen)

install(TARGETS wasm_example DESTINATION "."
RUNTIME DESTINATION bin
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
)

# Required for Emscripten + embind
set(CMAKE_EXECUTABLE_SUFFIX ".html")

set_target_properties(wasm_example PROPERTIES
LINK_FLAGS
"-sEXPORTED_FUNCTIONS=['_malloc','_free'] \
-sEXPORTED_RUNTIME_METHODS=['ccall','cwrap','getValue','setValue'] \
-sENVIRONMENT=web \
-sALLOW_MEMORY_GROWTH=1 \
-sNO_EXIT_RUNTIME=1 \
--shell-file ${CMAKE_SOURCE_DIR}/shell.html"
)
26 changes: 26 additions & 0 deletions examples/cross_build/wasm/bindigs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# WASM project with bindings and conan dependency


## Build and run

To compile the project:

```sh
$ conan build . -pr:h ../profiles/wasm32 --build=missing
```

To open a WASM webpage locally, most of the browsers will complain due to security reasons as WASM must be loaded asynchronous

The easiest way of opening the generated webpage (should be in `build/release-wasm/wasm_example.html`) is by running a local server.
This can be done via `emrun` command (needs to be downloaded):

```sh
$ emrun --browser <browser_name> build/release-wasm/wasm_example.html
```

Or using python `http.server` module:

```sh
$ python -m http.server 8080
```
Then, navigating to your build folder and open `wasm_example.html`
45 changes: 45 additions & 0 deletions examples/cross_build/wasm/bindigs/conanfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import os

from conan import ConanFile
from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout
from conan.tools.files import copy


class WasmExampleRecipe(ConanFile):
name = "wasm-example"
version = "1.0"
package_type = "application"

# Optional metadata
license = "<Put the package license here>"
author = "<Put your name here> <And your email here>"
url = "<Package recipe repository url here, for issues about the package>"
description = "<Description of exe package here>"
topics = ("<Put some tag here>", "<here>", "<and here>")

# Binary configuration
settings = "os", "compiler", "build_type", "arch"

# Sources are located in the same place as this recipe, copy them to the recipe
exports_sources = "CMakeLists.txt", "src/*"

def layout(self):
cmake_layout(self)

def requirements(self):
self.requires("eigen/3.4.0")

def generate(self):
deps = CMakeDeps(self)
deps.generate()
tc = CMakeToolchain(self)
tc.generate()

def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()

def package(self):
cmake = CMake(self)
cmake.install()
53 changes: 53 additions & 0 deletions examples/cross_build/wasm/bindigs/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include <Eigen/Core>
#include <cstdint>
#include <emscripten/emscripten.h>
#include <iostream>
#include <string>

#ifdef __cplusplus
#define EXTERN extern "C"
#else
#define EXTERN
#endif

EXTERN EMSCRIPTEN_KEEPALIVE uint32_t fib(uint32_t n) {
std::cout << "Calculating Fibonacci for n = " << n << std::endl;
if (n <= 1)
return n;
uint32_t a = 0, b = 1, c;
for (uint32_t i = 2; i <= n; ++i) {
c = a + b;
a = b;
b = c;
}
return c;
}

EXTERN EMSCRIPTEN_KEEPALIVE void printMessage(const char *message) {
std::cout << "Message from C: " << message << std::endl;
std::string script =
"alert('Message from C++: " + std::string(message) + "')";
std::cout << "Executing script: " << script << std::endl;
emscripten_run_script(script.c_str());
}

EXTERN EMSCRIPTEN_KEEPALIVE void addOne(int32_t *input, int32_t *output) {
*output = *input + 1;
}

EXTERN EMSCRIPTEN_KEEPALIVE float sumArray(const float *data, int32_t size) {
// print data input
std::cout << "Data input: ";
for (int i = 0; i < size; ++i) {
std::cout << data[i] << " ";
}
std::cout << std::endl;
Eigen::Map<const Eigen::ArrayXf> vec(data, size);
return vec.sum();
}

int main() {
std::cout << "Hello World!" << std::endl;
auto data = new float[5]{1.0f, 2.0f, 3.0f, 4.0f, 5.0f};
std::cout << sumArray(data, 5) << std::endl;
}
160 changes: 160 additions & 0 deletions examples/cross_build/wasm/bindigs/shell.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
<!doctype html>
<html lang="en-us">
<body>
<h1>Conan C++ WebAssembly Example</h1>
<br />
<div id="status">Downloading...</div>
<div>
<progress value="0" max="100" id="progress" hidden="1"></progress>
</div>
<textarea id="output" rows="8" style="width: 100%"></textarea>
<hr />

<!-- Example of calling JS from C -->
<button onclick="printMessage()">Print Message</button>
<hr />

<!-- Example of a simple invocation to fibonachi -->
<input type="number" id="fibInput" placeholder="e.g., 10" />
<button onclick="fibExample()">Compute Fibonacci</button>
<p id="fibResult"></p>
<hr />

<!-- Example of a function call using two buffers -->
<button onclick="pressBtn()">Click me to increase counter!</button>
<p id="counterResult"></p>
<hr />

<!-- Example of a function call using a Float32Array and Eigen -->
<input
type="text"
id="numbersInput"
placeholder="e.g., 42.2, 2.1, 8"
size="50"
/>
<button onclick="sumExample()">Compute Float32 Sum with Eigen</button>
<p id="sumResult"></p>

<script type="text/javascript">
var statusElement = document.getElementById("status");
var progressElement = document.getElementById("progress");

var Module = {
print: (function () {
var element = document.getElementById("output");
if (element) element.value = ""; // clear browser cache
return (...args) => {
var text = args.join(" ");
console.log(text);
if (element) {
element.value += text + "\n";
element.scrollTop = element.scrollHeight; // focus on bottom
}
};
})(),
setStatus: (text) => {
Module.setStatus.last ??= { time: Date.now(), text: "" };
if (text === Module.setStatus.last.text) return;
var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
var now = Date.now();
if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
Module.setStatus.last.time = now;
Module.setStatus.last.text = text;
if (m) {
text = m[1];
progressElement.value = parseInt(m[2]) * 100;
progressElement.max = parseInt(m[4]) * 100;
progressElement.hidden = false;
} else {
progressElement.value = null;
progressElement.max = null;
progressElement.hidden = true;
}
statusElement.innerHTML = text;
},
totalDependencies: 0,
monitorRunDependencies: (left) => {
this.totalDependencies = Math.max(this.totalDependencies, left);
Module.setStatus(
left
? "Preparing... (" +
(this.totalDependencies - left) +
"/" +
this.totalDependencies +
")"
: "All downloads complete.",
);
},
};
Module.setStatus("Downloading...");
window.onerror = () => {
Module.setStatus("Exception thrown, see JavaScript console");
Module.setStatus = (text) => {
if (text) console.error("[post-exception status] " + text);
};
};

// Example of auto string handle by WASM on simple string parameter passing
const printMessage = () => {
Module.ccall(
"printMessage",
null,
["string"],
["Hello from C++ WebAssembly!"],
);
};

// Example of a simple invocation to fibonachi
const fibExample = () => {
const fib = Module.cwrap("fib", "number", ["number"]); // returns a number
const input = parseInt(document.getElementById("fibInput").value);
if (isNaN(input)) {
alert("Please enter a valid integer.");
return;
}
const result = fib(input);
document.getElementById("fibResult").textContent =
"Fibonacci of " + input + " is: " + result;
};

var value = 0; // (static) value to increment by one
const pressBtn = () => {
const addOne = Module.cwrap("addOne", null, ["number", "number"]); // void function
// alloc 4 bytes of memory for the input and 4 for the output (32-bit integers)
const inputPtr = Module._malloc(4);
const outputPtr = Module._malloc(4);

Module.setValue(inputPtr, value, "i32");
addOne(inputPtr, outputPtr);
const result = Module.getValue(outputPtr, "i32");
value = result;
document.getElementById("counterResult").textContent = "Sum: " + result;

// dealloc memory to avoid memory leaks
Module._free(inputPtr);
Module._free(outputPtr);
};

const sumExample = () => {
const sumArray = Module.cwrap("sumArray", "number", [
"number",
"number",
]);
// Get the input string and split by commas
const inputStr = document.getElementById("numbersInput").value;
const numberStrings = inputStr.split(",").map((s) => s.trim());

// Convert to Float32Array
const inputArray = new Float32Array(numberStrings.map(Number));
const len = inputArray.length;
const bytesPerElement = inputArray.BYTES_PER_ELEMENT;
const inputPtr = Module._malloc(len * bytesPerElement);
Module.HEAPF32.set(inputArray, inputPtr / bytesPerElement);
const result = sumArray(inputPtr, len);
Module._free(inputPtr);
document.getElementById("sumResult").textContent = "Sum: " + result;
};
</script>
{{{ SCRIPT }}}
</body>
</html>
26 changes: 26 additions & 0 deletions examples/cross_build/wasm/profiles/wasm32
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[settings]
arch=wasm
build_type=Release
compiler=apple-clang
compiler.cppstd=gnu17
compiler.cstd=gnu11
compiler.libcxx=libc++
compiler.version=17
os=Emscripten

[tool_requires]
emsdk/[*]

[conf]
tools.build:exelinkflags=['-sALLOW_MEMORY_GROWTH=1', '-sMAXIMUM_MEMORY=4GB', '-sINITIAL_MEMORY=64MB' ]
tools.build:sharedlinkflags=[ '-sALLOW_MEMORY_GROWTH=1', '-sMAXIMUM_MEMORY=4GB', '-sINITIAL_MEMORY=64MB' ]

# Uncomment the following line to use emcc and em++ system compilers instead of conan packaged ones
# tools.build:compiler_executables={'c':'emcc', 'cpp':'em++'}

# Verbosity
tools.build:verbosity=verbose
tools.compilation:verbosity=verbose

# Distinguish between wasm32 and wasm64 architecture
tools.cmake.cmake_layout:build_folder_vars=['settings.build_type', 'settings.arch']
31 changes: 31 additions & 0 deletions examples/cross_build/wasm/profiles/wasm64
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[settings]
arch=wasm64
build_type=Release
compiler=apple-clang
compiler.cppstd=gnu17
compiler.cstd=gnu11
compiler.libcxx=libc++
compiler.version=17
os=Emscripten

[tool_requires]
emsdk/[*]

[conf]
tools.build:cflags=['-sMEMORY64=1']
tools.build:cxxflags=['-sMEMORY64=1']

# In this early stage of wasm64, ALLOW_MEMORY_GROWTH is not having effect. Also it may not be the most efficient solution.
# wasm64 for now needs to declare INITIAL_MEMORY as the maximum memory
tools.build:exelinkflags=[ '-sMEMORY64=1', '-sALLOW_MEMORY_GROWTH=1', '-sMAXIMUM_MEMORY=16GB', '-sINITIAL_MEMORY=16GB' ]
tools.build:sharedlinkflags=[ '-sMEMORY64=1', '-sALLOW_MEMORY_GROWTH=1', '-sMAXIMUM_MEMORY=16GB', '-sINITIAL_MEMORY=16GB' ]

# Uncomment the following line to use emcc and em++ system compilers instead of conan packaged ones
# tools.build:compiler_executables={'c':'emcc', 'cpp':'em++'}

# General config
tools.build:verbosity=verbose
tools.compilation:verbosity=verbose

# Distinguish between wasm32 and wasm64 architecture
tools.cmake.cmake_layout:build_folder_vars=['settings.build_type', 'settings.arch']
4 changes: 4 additions & 0 deletions examples/cross_build/wasm/wasm64/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.15)
project(wasm-alloc CXX)

add_executable(wasm-alloc main.cpp)
Loading
Loading