- C/C++ कोड को Emscripten से WebAssembly में पोर्ट करके ब्राउज़र में चलने वाला वेबऐप बनाने की प्रक्रिया को एक वास्तविक Rubik’s Cube solver उदाहरण के आधार पर विस्तार से समझाया गया है
- Hello World से लेकर multithreading, callback, persistent storage, modularization तक, browser/WebAssembly environment में आने वाली ठोस चुनौतियों और उनके समाधान को चरणबद्ध तरीके से कवर किया गया है
- JavaScript asynchronous initialization, function export, Web Worker और Spectre issues, IDBFS के जरिए IndexedDB persistent storage जैसी चीज़ों पर आधारित व्यावहारिक troubleshooting पर फोकस है
- बार-बार इस बात पर ज़ोर दिया गया है कि Emscripten का abstraction अक्सर वास्तव में 'leaky abstractions' साबित होता है, इसलिए web platform की सीमाओं और उसकी आंतरिक संरचना को समझना ज़रूरी है
- frontend के न्यूनतम JavaScript/HTML ज्ञान के साथ भी जटिल C codebase को web पर ले जाने के वास्तविक अनुभव के आधार पर, यह मौजूदा C/C++ libraries को web में port करना चाहने वाले developers के लिए उपयोगी टिप्स और व्यावहारिक know-how देने वाली अनुभव-आधारित गाइड है
परिचय
- हाल ही में Rubik’s Cube optimal solution algorithm को web app के रूप में लागू करने वाला एक प्रोजेक्ट किया गया
- C में विकसित Rubik’s Cube optimization solver को Emscripten से compile करके WebAssembly के रूप में web browser में चलाने की प्रक्रिया को दर्ज किया गया है
- WebAssembly का मुख्य उपयोग इसलिए है क्योंकि इससे JavaScript की तुलना में लगभग native के करीब performance web पर हासिल की जा सकती है
- यह लेख पारंपरिक web development tutorial नहीं, बल्कि मौजूदा C/C++ code को web पर port करना चाहने वाले developers के लिए एक ‘कष्टदायक यात्रा’ है
- अगर web development का अनुभव कम भी हो, तो HTML, JavaScript की बुनियादी संरचना और browser developer tools का उपयोग जानकर इसे फॉलो किया जा सकता है
environment setup
- सभी example code git repository, github में उपलब्ध हैं
- Emscripten की installation ज़रूरी है (installation method के लिए official site देखें), और web server के लिए darkhttpd या Python
http.server आदि का उपयोग किया जा सकता है
- tutorial code examples को Linux और UNIX family systems पर test किया गया है। Windows users के लिए WSL(Windows Subsystem for Linux) की सिफारिश है
Hello World
- C code के Hello World को
emcc -o index.html hello.c command से compile करने पर index.html(web page), index.wasm(WebAssembly bytecode), index.js(JavaScript glue code) ये तीन files बनती हैं
- यह browser और Node.js दोनों में चल सकता है, और दोनों environments में उपयोग का तरीका अलग होता है
- केवल
.wasm बनाना हो तो -sSTANDALONE_WASM option का उपयोग करें
- Emscripten में केवल
.wasm generate करना संभव है, लेकिन अधिकतर मामलों में JavaScript glue code आवश्यक होता है
Intermezzo I: WebAssembly क्या है?
- WebAssembly(WASM) web browser के भीतर चलने वाली high-performance virtual machine के लिए एक low-level language है
- WASM को 2017 के बाद से सभी प्रमुख browsers support करते हैं
- मूल रूप से Emscripten, C/C++ code को asm.js नामक JavaScript subset में बदलता था, लेकिन WASM आने के बाद यह बदल गया
- इसका text representation भी मौजूद है और यह stack-based structure रखता है। हाल तक यह केवल 32-bit architecture support करता था, इसलिए 4GB से अधिक memory का उपयोग नहीं हो पाता था, लेकिन WASM64 अब धीरे-धीरे browsers में आ रहा है
library build
- C function multiply() को WASM में build करने और फिर JavaScript से call करने का एक बुनियादी उदाहरण दिया गया है
- default build में Emscripten function name के आगे underscore(_) जोड़ देता है (उदाहरण:
_multiply)
- function को बाहर expose करने के लिए -sEXPORTED_FUNCTIONS option देना ज़रूरी है
- library load करते समय initialization asynchronous होती है, इसलिए onRuntimeInitialized या
await जैसी async handling की ज़रूरत पड़ती है
- practice code repository के
01_library folder में है
Intermezzo II: JavaScript और DOM
- JavaScript से HTML के elements तक पहुँचने और उन्हें बदलने के लिए Document Object Model(DOM) का उपयोग करना पड़ता है
- event listener(addEventListener), built-in operators/functions आदि की मदद से dynamic UI बनाया जा सकता है
- उदाहरण के लिए input, button और result display वाले basic HTML/JavaScript integration structure को समझाया गया है
- script को अलग/मर्ज करने के व्यावहारिक तरीके और उनसे जुड़े issues (जैसे
defer का उपयोग, DOM element load order) भी बताए गए हैं
library modularization और loading
- WASM library को कई बार include करने या Node.js और web दोनों में reuse करने के लिए इसे MODULARIZE, EXPORT_NAME options के साथ module form में build किया जा सकता है
- Node.js compatibility के लिए
.mjs (ES6 module) extension recommended है
- web/Node दोनों में import MyLibrary from ... जैसे module usage संभव हैं
multithreading
- WebAssembly में performance बढ़ाने के लिए pthreads-based multithreaded code को port किया जा सकता है
- function के अंदर कई threads बनाकर parallel calculation tasks (जैसे prime numbers की गिनती) चलाई जाती हैं
- build के समय -pthread, -sPTHREAD_POOL_SIZE= options की ज़रूरत होती है
- वास्तविक browser में Cross-Origin-Opener-Policy: same-origin, Cross-Origin-Embedder-Policy: require-corp जैसे HTTP headers भी जोड़ने पड़ते हैं
- सभी examples repository के
03_threads folder में उपलब्ध हैं
Intermezzo III: Web Workers और Spectre
- Emscripten multithreading, Web Workers के जरिए implement होती है (Web Workers अलग process होते हैं और message-based communication structure का उपयोग करते हैं)
- shared memory(SharedArrayBuffer) के उपयोग पर security constraints मौजूद हैं
- 2018 में Spectre vulnerability सामने आने के बाद cross-origin isolated requirement और उससे जुड़े headers अनिवार्य हो गए
main thread blocking से सावधान
- यदि लंबे tasks browser के main UI thread को BLOCK कर दें, तो user experience बहुत खराब हो जाता है
- इससे बचने के लिए web worker(Worker) का उपयोग किया जाता है: UI/input handling और computation को स्पष्ट रूप से अलग किया जाता है
- postMessage, onmessage से main-worker के बीच event-based communication implement किया जाता है
- web worker के भीतर Emscripten-WASM module लोड करके केवल asynchronous computation को संभाला जाता है
callback functions
- C function के parameter के रूप में function pointer(callback) पास करने पर, JavaScript के function object के साथ automatic integration संभव नहीं होती
- Emscripten द्वारा दिए गए addFunction(), UTF8ToString() आदि का उपयोग करना पड़ता है, और build के समय -sEXPORTED_RUNTIME_METHODS, -sALLOW_TABLE_GROWTH options भी जोड़ने होते हैं
- callback को स्थिर रूप से काम करने के लिए केवल main thread पर ही call किया जाना चाहिए (web worker में इस तक पहुँचना संभव नहीं)
persistent storage
- user के browser में data को स्थायी रूप से store करने के लिए Emscripten का IDBFS(IndexedDB-based file system) इस्तेमाल किया जाता है
- build के समय --lidbfs.js flag और **--pre-js ** आदि से initial setup करना पड़ता है
- C code में file I/O functions(
fopen, fread, fwrite) को वैसे ही इस्तेमाल किया जा सकता है, लेकिन data reflect/sync करने की वास्तविक प्रक्रिया के लिए JavaScript में explicit mapping और sync handling ज़रूरी है
- browser की sandbox/security policy के कारण local file system तक सीधी पहुँच केवल Node.js में संभव है; browser में सुरक्षित persistent storage के लिए IDBFS जैसे backend का उपयोग करना पड़ता है
निष्कर्ष
- इस tutorial की पूरी प्रक्रिया से यह विस्तार से सीखा जा सकता है कि जटिल native C/C++ code को न्यूनतम JavaScript और HTML के साथ भी सुरक्षित रूप से और बिना performance loss के browser में कैसे चलाया जाए
- वास्तविक environment में multithreading, callback, async handling, storage integration जैसे सभी मुख्य tracks की चुनौतियों और उनके समाधान का अनुभव मिलता है, साथ ही संबंधित settings और browser constraints जैसे आधुनिक trends भी समझ में आते हैं
- दिए गए Git repository examples को देखकर इन्हें अपने प्रोजेक्ट में लागू और आगे विस्तारित किया जा सकता है
1 टिप्पणियां
Hacker News राय
.jsसे.mjsमें बदला गया है; असलियत यह है कि चाहे जो एक्सटेंशन इस्तेमाल करो, किसी न किसी समस्या से टकराना तय है। dojo से लेकर CommonJS, AMD, ESM, webpack, esbuild, rollup तक तरह-तरह के module systems इस्तेमाल कर चुके व्यक्ति के नज़रिए से, इस बात से 100% सहमति महसूस होती है।versionstab में पिछले एक महीने में सबसे ज़्यादा डाउनलोड हुए version को चुनना पड़ता है, क्योंकि वही आख़िरी commonjs version होने की अच्छी संभावना होती है। यक़ीनन esm को ज़्यादा उन्नत module system कहा जा सकता है, लेकिन tc39 ने इसे commonjs के साथ लगभग जानबूझकर incompatible बनाया है—जैसे top-level await वगैरह—यह बात सच में समझ से बाहर है।importभी इस्तेमाल नहीं कर सकता, यह किसी lifesaver की तरह बहुत काम आ रहा है। JS ecosystem में शायद इसकी ज़रूरत कम हो, लेकिन मेरे लिए यह काफ़ी मददगार है।.esm.jsभी इस्तेमाल नहीं किया जा सकता?varkeyword की जगहletयाconstइस्तेमाल करने की सलाह दी जाएगी।varअभी भी काम करता है, लेकिन आजकल ज़्यादातर JS developers linter सेvarके इस्तेमाल पर रोक लगा देते हैं।varसिर्फ़ function scope देता है, इसलिए दूसरी भाषाओं से आने वाले ज़्यादातर developers कभी न कभी इससे भ्रमित होते ही हैं। native app porting issue के रूप में एक उदाहरण दिया गया कि compile time पर Ctrl-C, Ctrl-V को hardcode करके copy/paste लागू कर दिया जाता है, इसलिए Linux और Windows पर तो चल जाता है, लेकिन Mac पर नहीं। web में इसेcopy,pasteevents detect करके handle करना चाहिए, और Unity जैसे frameworks में भी hardcoded keys की वजह से Mac पर copy-paste न चलने की चीज़ देखी गई है। ज़्यादातर games में इसकी ज़रूरत नहीं होती, लेकिन जब copy-paste चाहिए वाला feature web पर भेजते हैं तो यह ज़रूर समस्या बनता है।APPLE,MSVC,EMSCRIPTENtargets वाला comment दिखा, और आज ही HN पर फिर Emscripten की चर्चा दिख गई। अब लगता है कि सच में समय निकालकर इसे गहराई से समझने का समय आ गया है।httpdऔरrelaydsetup को और बेहतर करने या इसे पूरी तरह अलग domain पर ले जाने पर भी विचार किया जा रहा है।