Этот пример демонстрирует создание системы с динамически загружаемыми WASM модулями, где основное приложение может подгружать и использовать дополнительные модули по требованию.
wasm-dynamic-loading/
├── src/
│ ├── main.cpp # Основное приложение
│ ├── math_plugin.cpp # Математический плагин
│ └── text_plugin.cpp # Текстовый плагин
├── public/
│ └── index.html # Демо страница
├── build.sh # Скрипт сборки
└── README.md
src/main.cpp
#include <emscripten.h>
#include <emscripten/bind.h>
#include <iostream>
#include <string>
class PluginManager {
public:
// Интерфейс для математических операций
static int callMathPlugin(const std::string& operation, int a, int b) {
if (operation == "multiply") {
return multiply_numbers(a, b);
} else if (operation == "power") {
return power_numbers(a, b);
}
return 0;
}
// Интерфейс для текстовых операций
static std::string callTextPlugin(const std::string& operation,
const std::string& text) {
if (operation == "reverse") {
return reverse_text(text.c_str());
} else if (operation == "uppercase") {
return uppercase_text(text.c_str());
}
return "";
}
private:
// Прототипы функций из динамических модулей
static int multiply_numbers(int a, int b);
static int power_numbers(int base, int exponent);
static const char* reverse_text(const char* text);
static const char* uppercase_text(const char* text);
};
// Экспортируемые функции для JavaScript
extern "C" {
EMSCRIPTEN_KEEPALIVE
int math_operation(const char* op, int a, int b) {
return PluginManager::callMathPlugin(std::string(op), a, b);
}
EMSCRIPTEN_KEEPALIVE
const char* text_operation(const char* op, const char* text) {
std::string result = PluginManager::callTextPlugin(std::string(op),
std::string(text));
// Возвращаем C-строку (память управляется Emscripten)
char* cstr = (char*)malloc(result.length() + 1);
strcpy(cstr, result.c_str());
return cstr;
}
EMSCRIPTEN_KEEPALIVE
void hello_main() {
printf("🚀 Основное приложение запущено!\n");
}
}src/math_plugin.cpp
#include <emscripten.h>
#include <cmath>
extern "C" {
EMSCRIPTEN_KEEPALIVE
int multiply_numbers(int a, int b) {
printf("📊 Math Plugin: %d × %d = %d\n", a, b, a * b);
return a * b;
}
EMSCRIPTEN_KEEPALIVE
int power_numbers(int base, int exponent) {
int result = (int)pow(base, exponent);
printf("📊 Math Plugin: %d^%d = %d\n", base, exponent, result);
return result;
}
EMSCRIPTEN_KEEPALIVE
void math_plugin_info() {
printf("📊 Математический плагин v1.0 загружен\n");
}
}src/text_plugin.cpp
#include <emscripten.h>
#include <string>
#include <algorithm>
#include <cctype>
#include <cstring>
extern "C" {
EMSCRIPTEN_KEEPALIVE
const char* reverse_text(const char* input) {
std::string text(input);
std::reverse(text.begin(), text.end());
// Выделяем память для результата
char* result = (char*)malloc(text.length() + 1);
strcpy(result, text.c_str());
printf("📝 Text Plugin: reversed '%s' -> '%s'\n", input, result);
return result;
}
EMSCRIPTEN_KEEPALIVE
const char* uppercase_text(const char* input) {
std::string text(input);
std::transform(text.begin(), text.end(), text.begin(), ::toupper);
char* result = (char*)malloc(text.length() + 1);
strcpy(result, text.c_str());
printf("📝 Text Plugin: uppercase '%s' -> '%s'\n", input, result);
return result;
}
EMSCRIPTEN_KEEPALIVE
void text_plugin_info() {
printf("📝 Текстовый плагин v1.0 загружен\n");
}
}build.sh
#!/bin/bash
echo "🔨 Начинаем сборку WASM модулей..."
# Создаем директорию для результатов
mkdir -p public/wasm
# Компилируем плагины как side modules
echo "📦 Собираем математический плагин..."
emcc src/math_plugin.cpp -O2 \
-s SIDE_MODULE=1 \
-s EXPORT_ALL=1 \
-o public/wasm/math_plugin.wasm
echo "📦 Собираем текстовый плагин..."
emcc src/text_plugin.cpp -O2 \
-s SIDE_MODULE=1 \
-s EXPORT_ALL=1 \
-o public/wasm/text_plugin.wasm
# Компилируем основное приложение как main module
echo "🏗️ Собираем основное приложение..."
emcc src/main.cpp -O2 \
-s MAIN_MODULE=1 \
-s EXPORT_ALL=1 \
-s MODULARIZE=1 \
-s EXPORT_NAME="WasmApp" \
-s RUNTIME_LINKED_LIBS=["public/wasm/math_plugin.wasm","public/wasm/text_plugin.wasm"] \
-s ALLOW_MEMORY_GROWTH=1 \
--bind \
-o public/wasm/main.js
echo "✅ Сборка завершена успешно!"
echo "🌐 Запустите HTTP сервер в директории public/"public/index.html
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WASM Dynamic Loading Demo</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.section {
margin: 20px 0;
padding: 15px;
border: 2px solid #e0e0e0;
border-radius: 8px;
}
.math-section { border-color: #4CAF50; }
.text-section { border-color: #2196F3; }
button {
background: linear-gradient(45deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
padding: 10px 20px;
margin: 5px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
}
button:hover { opacity: 0.8; }
input, select {
padding: 8px;
margin: 5px;
border: 1px solid #ddd;
border-radius: 4px;
}
#output {
background: #f9f9f9;
border: 1px solid #ddd;
padding: 15px;
border-radius: 5px;
margin-top: 20px;
min-height: 100px;
font-family: 'Courier New', monospace;
font-size: 14px;
}
.loading {
color: #666;
font-style: italic;
}
.error {
color: #d32f2f;
background: #ffebee;
padding: 10px;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="container">
<h1>🚀 Динамическая загрузка WASM модулей</h1>
<p>Демонстрация системы с подгружаемыми плагинами на WebAssembly</p>
<div class="section math-section">
<h3>📊 Математический плагин</h3>
<label>Операция:</label>
<select id="mathOp">
<option value="multiply">Умножение</option>
<option value="power">Возведение в степень</option>
</select>
<input type="number" id="mathA" value="5" placeholder="Число A">
<input type="number" id="mathB" value="3" placeholder="Число B">
<button onclick="testMathPlugin()">Выполнить</button>
</div>
<div class="section text-section">
<h3>📝 Текстовый плагин</h3>
<label>Операция:</label>
<select id="textOp">
<option value="reverse">Обратить текст</option>
<option value="uppercase">В верхний регистр</option>
</select>
<input type="text" id="textInput" value="Hello WASM!" placeholder="Введите текст">
<button onclick="testTextPlugin()">Выполнить</button>
</div>
<button onclick="loadAllPlugins()" style="background: linear-gradient(45deg, #FF6B6B, #4ECDC4);">
🔄 Загрузить все плагины
</button>
<div id="output">
<div class="loading">Ожидание загрузки модулей...</div>
</div>
</div>
<script>
let wasmModule = null;
let pluginsLoaded = false;
function log(message, type = 'info') {
const output = document.getElementById('output');
const timestamp = new Date().toLocaleTimeString();
const className = type === 'error' ? 'error' : '';
if (output.innerHTML.includes('Ожидание загрузки')) {
output.innerHTML = '';
}
output.innerHTML += `<div class="${className}">[${timestamp}] ${message}</div>`;
output.scrollTop = output.scrollHeight;
console.log(message);
}
// Загрузка основного модуля
async function initializeWasm() {
try {
log('🔄 Загружаем основное приложение...');
wasmModule = await WasmApp();
log('✅ Основное приложение загружено');
wasmModule._hello_main();
return true;
} catch (error) {
log(`❌ Ошибка загрузки: ${error.message}`, 'error');
return false;
}
}
// Загрузка и инициализация плагинов
async function loadAllPlugins() {
if (!wasmModule) {
log('⚠️ Основное приложение еще не загружено', 'error');
return;
}
try {
log('🔄 Загружаем плагины...');
// Имитируем загрузку плагинов
await new Promise(resolve => setTimeout(resolve, 500));
// Вызываем информационные функции плагинов
if (wasmModule._math_plugin_info) {
wasmModule._math_plugin_info();
}
if (wasmModule._text_plugin_info) {
wasmModule._text_plugin_info();
}
pluginsLoaded = true;
log('✅ Все плагины успешно загружены и готовы к работе!');
} catch (error) {
log(`❌ Ошибка загрузки плагинов: ${error.message}`, 'error');
}
}
// Тестирование математического плагина
function testMathPlugin() {
if (!wasmModule || !pluginsLoaded) {
log('⚠️ Сначала загрузите плагины', 'error');
return;
}
const operation = document.getElementById('mathOp').value;
const a = parseInt(document.getElementById('mathA').value);
const b = parseInt(document.getElementById('mathB').value);
try {
const result = wasmModule._math_operation(operation, a, b);
log(`📊 Результат: ${operation}(${a}, ${b}) = ${result}`);
} catch (error) {
log(`❌ Ошибка выполнения: ${error.message}`, 'error');
}
}
// Тестирование текстового плагина
function testTextPlugin() {
if (!wasmModule || !pluginsLoaded) {
log('⚠️ Сначала загрузите плагины', 'error');
return;
}
const operation = document.getElementById('textOp').value;
const text = document.getElementById('textInput').value;
try {
const resultPtr = wasmModule._text_operation(operation, text);
const result = wasmModule.UTF8ToString(resultPtr);
log(`📝 Результат: ${operation}("${text}") = "${result}"`);
// Освобождаем память
wasmModule._free(resultPtr);
} catch (error) {
log(`❌ Ошибка выполнения: ${error.message}`, 'error');
}
}
// Инициализация при загрузке страницы
window.addEventListener('load', async () => {
const success = await initializeWasm();
if (success) {
// Автоматически загружаем плагины
setTimeout(loadAllPlugins, 1000);
}
});
</script>
<script src="wasm/main.js"></script>
</body>
</html>-
Установите Emscripten SDK:
git clone https://github.com/emscripten-core/emsdk.git cd emsdk ./emsdk install latest ./emsdk activate latest source ./emsdk_env.sh
-
Соберите проект:
chmod +x build.sh ./build.sh
-
Запустите локальный сервер:
cd public python3 -m http.server 8000 # или npx serve .
-
Откройте браузер:
http://localhost:8000
- Модульная архитектура: Основное приложение + подгружаемые плагины
- Типизированные интерфейсы: Четкое разделение математических и текстовых операций
- Управление памятью: Корректная работа с C-строками в WASM
- Интерактивный UI: Полнофункциональная демонстрация возможностей
- Обработка ошибок: Надежная система логирования и отладки
Этот пример демонстрирует полный цикл создания приложения с динамической загрузкой WASM модулей и может служить основой для более сложных систем плагинов.