From 98b2c66ba5163b7b9b6ec1af725beebf5a211279 Mon Sep 17 00:00:00 2001 From: uklaad Date: Mon, 16 Feb 2026 22:44:33 +0100 Subject: [PATCH] add class-based call_js_from_cpp example and member-function bind overload --- CMakeLists.txt | 3 + examples/C++/README.md | 1 + .../C++/call_js_from_cpp_class/GNUmakefile | 138 +++++++++++++++++ examples/C++/call_js_from_cpp_class/Makefile | 63 ++++++++ examples/C++/call_js_from_cpp_class/main.cpp | 143 ++++++++++++++++++ include/webui.hpp | 51 +++++-- 6 files changed, 390 insertions(+), 9 deletions(-) create mode 100644 examples/C++/call_js_from_cpp_class/GNUmakefile create mode 100644 examples/C++/call_js_from_cpp_class/Makefile create mode 100644 examples/C++/call_js_from_cpp_class/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a4d3bf424..38b8c2774 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,15 +122,18 @@ if (WEBUI_BUILD_EXAMPLES) add_executable(minimal ${CMAKE_CURRENT_SOURCE_DIR}/examples/C++/minimal/main.cpp) add_executable(call_js_from_cpp ${CMAKE_CURRENT_SOURCE_DIR}/examples/C++/call_js_from_cpp/main.cpp) + add_executable(call_js_from_cpp_class ${CMAKE_CURRENT_SOURCE_DIR}/examples/C++/call_js_from_cpp_class/main.cpp) add_executable(call_js_from_c ${CMAKE_CURRENT_SOURCE_DIR}/examples/C/call_js_from_c/main.c) target_link_libraries(minimal webui) target_link_libraries(call_js_from_cpp webui) + target_link_libraries(call_js_from_cpp_class webui) target_link_libraries(call_js_from_c webui) if (MSVC) set_target_properties(minimal PROPERTIES LINK_FLAGS "/SubSystem:\"Windows\"" VS_DPI_AWARE "ON") set_target_properties(call_js_from_cpp PROPERTIES LINK_FLAGS "/SubSystem:\"Windows\"" VS_DPI_AWARE "ON") + set_target_properties(call_js_from_cpp_class PROPERTIES LINK_FLAGS "/SubSystem:\"Windows\"" VS_DPI_AWARE "ON") set_target_properties(call_js_from_c PROPERTIES LINK_FLAGS "/SubSystem:\"Windows\"" VS_DPI_AWARE "ON") endif() diff --git a/examples/C++/README.md b/examples/C++/README.md index 80099bf56..998370c63 100644 --- a/examples/C++/README.md +++ b/examples/C++/README.md @@ -7,6 +7,7 @@ The only requirement to build the examples is a a C++11 compiler. - `minimal`: Creates a minimal WebUI application. - `call_c_from_js`: Calls C++ from JavaScript. - `call_js_from_c`: Calls JavaScript from C++. +- `call_js_from_cpp_class`: Calls JavaScript from C++ using class methods and member-function bind. - `serve_a_folder`: Uses WebUI to serve a folder that contains multiple files. To build an example, cd into its directory and run the make command. diff --git a/examples/C++/call_js_from_cpp_class/GNUmakefile b/examples/C++/call_js_from_cpp_class/GNUmakefile new file mode 100644 index 000000000..2b085f59a --- /dev/null +++ b/examples/C++/call_js_from_cpp_class/GNUmakefile @@ -0,0 +1,138 @@ +# WebUI C++ Example + +# == 1. VARIABLES ============================================================= + +MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) +PROJECT_DIR := $(dir $(MAKEFILE_PATH))/../../../ +TARGET := $(firstword $(MAKECMDGOALS)) +LIB_DIR := $(PROJECT_DIR)/dist +ifeq ($(TARGET), debug) +LIB_DIR := $(LIB_DIR)/debug +endif +INCLUDE_DIR := $(PROJECT_DIR)/include +WEBUI_LIB_NAME = webui-2 +ifeq ($(WEBUI_USE_TLS), 1) +WEBUI_LIB_NAME = webui-2-secure +endif + +# ARGS +# Set a compiler when running on Linux via `make CC=g++` / `make CC=clang` +CC = g++ +# Build the WebUI library if running via `make BUILD_LIB=true` +BUILD_LIB ?= + +# BUILD FLAGS +STATIC_BUILD_FLAGS = main.cpp -I"$(INCLUDE_DIR)" -L"$(LIB_DIR)" +DYN_BUILD_FLAGS = main.cpp -I"$(INCLUDE_DIR)" -L"$(LIB_DIR)" + +# Platform conditions +ifeq ($(OS),Windows_NT) + # Windows + PLATFORM := windows + SHELL := CMD + STATIC_BUILD_FLAGS += -l$(WEBUI_LIB_NAME)-static -lws2_32 -Wall -luser32 -lstdc++ -luuid -static + COPY_LIB_CMD := @copy "$(LIB_DIR)\$(WEBUI_LIB_NAME).dll" "$(WEBUI_LIB_NAME).dll" + DYN_BUILD_FLAGS += "$(WEBUI_LIB_NAME).dll" -lws2_32 -Wall -luser32 -lstdc++ -luuid + STATIC_OUT := main.exe + DYN_OUT := main-dyn.exe + LWS2_OPT := -lws2_32 -lole32 + STRIP_OPT := --strip-all + CONSOLE_APP := -Wl,-subsystem=console + GUI_APP := -Wl,-subsystem=windows +else + STATIC_BUILD_FLAGS += -l$(WEBUI_LIB_NAME)-static -lpthread -lm -ldl + DYN_BUILD_FLAGS += -l$(WEBUI_LIB_NAME) -lpthread -lm -ldl + STATIC_OUT := main + DYN_OUT := main-dyn + ifeq ($(shell uname),Darwin) + # MacOS + PLATFORM := macos + CC = clang + COPY_LIB_CMD := @cp "$(LIB_DIR)/lib$(WEBUI_LIB_NAME).dylib" "lib$(WEBUI_LIB_NAME).dylib" + WKWEBKIT_LINK_FLAGS := -framework Cocoa -framework WebKit + else + # Linux + PLATFORM := linux + COPY_LIB_CMD := @cp "$(LIB_DIR)/lib$(WEBUI_LIB_NAME).so" "lib$(WEBUI_LIB_NAME).so" + STRIP_OPT := --strip-all + ifeq ($(CC),clang) + LLVM_OPT := llvm- + endif + endif +endif + +# == 2.TARGETS ================================================================ + +all: release + +debug: --validate-args +ifeq ($(BUILD_LIB),true) + @cd "$(PROJECT_DIR)" && $(MAKE) debug +endif +# Static with Debug info +ifneq ($(WEBUI_USE_TLS), 1) + @echo "Build C++ Example ($(CC) debug static)..." + @$(CC) -g $(CONSOLE_APP) $(STATIC_BUILD_FLAGS) $(LWS2_OPT) $(WKWEBKIT_LINK_FLAGS) -o $(STATIC_OUT) +endif +# Dynamic with Debug info + @echo "Build C++ Example ($(CC) debug dynamic)..." + $(COPY_LIB_CMD) + @$(CC) -g $(CONSOLE_APP) $(DYN_BUILD_FLAGS) $(LWS2_OPT) $(WKWEBKIT_LINK_FLAGS) -o $(DYN_OUT) +# Clean +ifeq ($(PLATFORM),windows) + @- del *.o >nul 2>&1 +else + @- rm -f *.o + @- rm -rf *.dSYM # macOS +endif + @echo "Done." + +release: --validate-args +ifeq ($(BUILD_LIB),true) + @cd "$(PROJECT_DIR)" && $(MAKE) +endif +# Static Release +ifneq ($(WEBUI_USE_TLS), 1) + @echo "Build C++ Example ($(CC) release static)..." + @$(CC) -Os $(GUI_APP) $(STATIC_BUILD_FLAGS) $(LWS2_OPT) $(WKWEBKIT_LINK_FLAGS) -o $(STATIC_OUT) + @$(LLVM_OPT)strip $(STRIP_OPT) $(STATIC_OUT) +endif +# Dynamic Release + @echo "Build C++ Example ($(CC) release dynamic)..." + $(COPY_LIB_CMD) + @$(CC) $(GUI_APP) $(DYN_BUILD_FLAGS) $(LWS2_OPT) $(WKWEBKIT_LINK_FLAGS) -o $(DYN_OUT) + @$(LLVM_OPT)strip $(STRIP_OPT) $(DYN_OUT) +# Clean +ifeq ($(PLATFORM),windows) + @- del *.o >nul 2>&1 +else + @- rm -f *.o + @- rm -rf *.dSYM # macOS +endif + @echo "Done." + +clean: --clean-$(PLATFORM) + +# INTERNAL TARGETS + +--validate-args: +ifneq ($(filter $(CC),g++ clang aarch64-linux-gnu-g++ arm-linux-gnueabihf-g++ musl-g++),$(CC)) +$(error Invalid compiler specified: `$(CC)`) +endif + +--clean-linux: --clean-unix + +--clean-macos: --clean-unix + +--clean-unix: + - rm -f *.o + - rm -f *.a + - rm -f *.so + - rm -f *.dylib + - rm -rf *.dSYM + +--clean-windows: + - del *.o >nul 2>&1 + - del *.dll >nul 2>&1 + - del *.a >nul 2>&1 + - del *.exe >nul 2>&1 diff --git a/examples/C++/call_js_from_cpp_class/Makefile b/examples/C++/call_js_from_cpp_class/Makefile new file mode 100644 index 000000000..0867f354e --- /dev/null +++ b/examples/C++/call_js_from_cpp_class/Makefile @@ -0,0 +1,63 @@ +# WebUI C++ Example +# Windows - Microsoft Visual C++ + +SHELL = CMD +LIB_DIR = ../../../dist +INCLUDE_DIR = ../../../include +WEBUI_LIB_NAME = webui-2 +!IF "$(WEBUI_USE_TLS)" == "1" +WEBUI_LIB_NAME = webui-2-secure +!ENDIF + +# Build the WebUI library if running `nmake BUILD_LIB=true` +BUILD_LIB = + +all: release + +debug: +!IF "$(BUILD_LIB)" == "true" + @cd "$(LIB_DIR)" && cd .. && $(MAKE) debug +!ENDIF +# Static with Debug info +!IF "$(WEBUI_USE_TLS)" != "1" + @echo Build C++ Example (Debug Static)... + @cl /Zi /EHsc /std:c++17 main.cpp /I"$(INCLUDE_DIR)" /link /LIBPATH:"$(LIB_DIR)/debug" /SUBSYSTEM:CONSOLE $(WEBUI_LIB_NAME)-static.lib user32.lib Advapi32.lib Shell32.lib Ole32.lib /OUT:main.exe 1>NUL 2>&1 +!ENDIF +# Dynamic with Debug info + @echo Build C++ Example (Debug Dynamic)... + @cl /Zi /EHsc /std:c++17 main.cpp /I"$(INCLUDE_DIR)" /link /LIBPATH:"$(LIB_DIR)/debug" /SUBSYSTEM:CONSOLE $(WEBUI_LIB_NAME)-static.lib user32.lib Advapi32.lib Shell32.lib Ole32.lib /OUT:main-dyn.exe 1>NUL 2>&1 +# Clean + @- del *.exp >nul 2>&1 + @- del *.ilk >nul 2>&1 + @- del *.lib >nul 2>&1 + @- del *.obj >nul 2>&1 + @- del *.pdb >nul 2>&1 + @echo Done. + +release: +!IF "$(BUILD_LIB)" == "true" + @cd "$(LIB_DIR)" && cd .. && $(MAKE) +!ENDIF +# Static Release +!IF "$(WEBUI_USE_TLS)" != "1" + @echo Build C++ Example (Release Static)... + @cl /EHsc /std:c++17 main.cpp /I"$(INCLUDE_DIR)" /link /LIBPATH:"$(LIB_DIR)" /SUBSYSTEM:WINDOWS $(WEBUI_LIB_NAME)-static.lib user32.lib Advapi32.lib Shell32.lib Ole32.lib /OUT:main.exe 1>NUL 2>&1 +!ENDIF +# Dynamic Release + @echo Build C++ Example (Release Dynamic)... + @cl /EHsc /std:c++17 main.cpp /I"$(INCLUDE_DIR)" /link /LIBPATH:"$(LIB_DIR)" /SUBSYSTEM:WINDOWS $(WEBUI_LIB_NAME).lib user32.lib Advapi32.lib Shell32.lib Ole32.lib /OUT:main-dyn.exe 1>NUL 2>&1 +# Clean + @- del *.exp >nul 2>&1 + @- del *.ilk >nul 2>&1 + @- del *.lib >nul 2>&1 + @- del *.obj >nul 2>&1 + @- del *.pdb >nul 2>&1 + @echo Done. + +clean: + - del *.obj >nul 2>&1 + - del *.ilk >nul 2>&1 + - del *.pdb >nul 2>&1 + - del *.exp >nul 2>&1 + - del *.exe >nul 2>&1 + - del *.lib >nul 2>&1 diff --git a/examples/C++/call_js_from_cpp_class/main.cpp b/examples/C++/call_js_from_cpp_class/main.cpp new file mode 100644 index 000000000..2ca69fbd0 --- /dev/null +++ b/examples/C++/call_js_from_cpp_class/main.cpp @@ -0,0 +1,143 @@ +// Call JavaScript from C++ Class Example + +// Include the WebUI header +#include "webui.hpp" + +// Include C++ STD +#include +#include +#include +#include + +class CounterApp +{ +public: + CounterApp() + { + // Bind HTML elements with class methods + my_window.bind("my_function_count", this, &CounterApp::my_function_count); + my_window.bind("Exit", this, &CounterApp::my_function_exit); + } + + void run() + { + // HTML + const std::string my_html = R"V0G0N( + + + + + Call JavaScript from C++ Class Example + + + +

WebUI - Call JavaScript from C++ (Class)

+
+

0

+
+ +
+ +
+ + + + + )V0G0N"; + + // Set WebUI configuration to process UI events one at a time + webui::set_config(ui_event_blocking, true); + + // Show the window + my_window.show(my_html); // my_window.show_browser(my_html, Chrome); + + // Wait until all windows get closed + webui::wait(); + } + +private: + webui::window my_window; + + void my_function_exit(webui::window::event * /*e*/) + { + // Close all opened windows + webui::exit(); + } + + void my_function_count(webui::window::event *e) + { + // Create a buffer to hold the response + char response[64]; + + // This is another way to create a buffer: + // std::string buffer; + // buffer.reserve(64); + // my_window.script(..., ..., &buffer[0], 64); + + // Run JavaScript + if (!e->get_window().script("return GetCount();", 0, response, 64)) + { + if (!e->get_window().is_shown()) + std::cout << "Window closed." << std::endl; + else + std::cout << "JavaScript Error: " << response << std::endl; + return; + } + + // Get the count + int count = std::stoi(response); + + // Increment + count++; + + // Generate JavaScript + std::stringstream js; + js << "SetCount(" << count << ");"; + + // Run JavaScript (Quick Way) + e->get_window().run(js.str()); + } +}; + +int main() +{ + CounterApp app; + app.run(); + return 0; +} + +#if defined(_MSC_VER) +int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, PSTR cmdline, int cmdshow) { main(); } +#endif diff --git a/include/webui.hpp b/include/webui.hpp index 62b0e6e1c..1890893db 100644 --- a/include/webui.hpp +++ b/include/webui.hpp @@ -12,9 +12,11 @@ #define _WEBUI_HPP // C++ STD -#include +#include +#include #include #include +#include // WebUI C Header extern "C" { @@ -49,13 +51,14 @@ namespace webui { class handler { public: - using callback_t = void (*)(event*); + using callback_t = std::function; private: - static inline std::array callback_list{}; + static inline std::unordered_map callback_list{}; // List of window objects: webui::window - static inline std::array window_list{}; + static inline std::unordered_map window_list{}; + static inline std::mutex callback_mutex{}; public: handler() = delete; @@ -66,6 +69,10 @@ namespace webui { ~handler() = delete; static void add(size_t id, webui::window* win, callback_t func) { + if ((id == 0) || (win == nullptr) || !func) + return; + + std::lock_guard lock(callback_mutex); // Save window object window_list[id] = win; // Save callback @@ -73,18 +80,36 @@ namespace webui { } static void handle(webui_event_t* c_e) { + if (c_e == nullptr) + return; + // Get the binded unique ID const size_t id = c_e->bind_id; - if (id > 0) { + if (id == 0) + return; + + webui::window* win = nullptr; + callback_t callback; + + { + std::lock_guard lock(callback_mutex); + const auto win_it = window_list.find(id); + const auto cb_it = callback_list.find(id); + if ((win_it != window_list.end()) && (cb_it != callback_list.end())) { + win = win_it->second; + callback = cb_it->second; + } + } + + if ((win != nullptr) && callback) { // Create a new event struct - event e(*window_list[id], *c_e); + event e(*win, *c_e); // Call the user callback - if (callback_list[id] != nullptr) - callback_list[id](&e); + callback(&e); } } - static webui::window& get_window(const size_t index) { return *window_list[index]; } + static webui::window& get_window(const size_t index) { return *window_list.at(index); } }; // ------ Event methods `e->xxx()` ------ @@ -212,6 +237,14 @@ namespace webui { event::handler::add(id, this, func); } + // Bind a class member function. + template + void bind(const std::string_view element, T* instance, void (T::*method)(event*)) { + bind(element, [instance, method](event* e) { + (instance->*method)(e); + }); + } + // Show a window using a embedded HTML, or a file. If the window is already opened // then it will be refreshed. bool show(const std::string_view content) const {