Overview

6 Creating a WebAssembly module that talks to JavaScript using function pointers

This chapter shows how to make a WebAssembly module call back into JavaScript using function pointers, evolving from direct host calls to flexible, callback-driven designs. It explains why validation workflows benefit from asynchronous coordination, and contrasts two approaches: leveraging Emscripten’s runtime helpers for convenience, and building a lean setup that interacts with the WebAssembly Table directly. Throughout, the focus is on adapting C/C++ interfaces, mapping callbacks across the boundary, and structuring JavaScript to compose results cleanly.

With Emscripten’s plumbing, the C++ code is refactored to accept typedef’d callbacks for success and error, converting validation functions to return void and invoke provided function pointers instead. Compilation reserves space for function pointers and exports runtime helpers so JavaScript can register callbacks via addFunction and later remove them with removeFunction. On the JavaScript side, validation is modeled with Promises and coordinated using Promise.all; helper utilities create and free function pointers, translate strings from module memory, and pass indices into ccall with a null return type to reflect the new void signatures. This approach keeps the ergonomics high and hides most low-level details.

Without Emscripten’s glue code, the chapter exposes how function pointers are realized as entries in the module’s Table and invoked via indirect calls. The callbacks (success and error for name and category) are imported, their table indices discovered at runtime by an exported function that reports them back to JavaScript, and then passed explicitly into validation calls. JavaScript constructs the Memory and Table in the import object, wires callback handlers that resolve or reject stored Promise callbacks, and manages linear memory for strings and arrays. This path provides a clear view of the WebAssembly Table, import wiring, and index passing, trading convenience for control while preserving the same callback-driven validation flow.

The module calling a function in the JavaScript code
The module calling a JavaScript function pointer
Steps showing modified existing C++ logic to accept function pointers and then turned into WebAssembly for use by a website and the server-side code. The server aspect, Node.js, is discussed in a future chapter.
The first step of the process is to adjust the code so that it now accepts function pointers.
The second step of the process is to ask Emscripten to generate both the WebAssembly and JavaScript files.
The third step of the process is to copy the generated files to where your HTML and JavaScript files are located. Then you’ll update the JavaScript code to pass JavaScript functions to the module.
A JavaScript function being passed to Emscripten’s backing array to be called later by the module
The Product Entry page’s validation error when the name is too long
Steps where the C++ logic is turned into WebAssembly for use by a website and the server-side code but without any generated Emscripten JavaScript code. The server aspect, Node.js, is discussed in a later chapter.
A module that has imported the onSuccess and onError JavaScript functions at instantiation. When the ValidateName module function calls either function, it’s calling into the JavaScript code.
You will modify the C++ code from section 6.1 to work with function pointers when you have no Emscripten plumbing code.
The second step of the process is to ask Emscripten to generate only the WebAssembly file. Emscripten will not generate the JavaScript plumbing file in this case.
The third step of the process is to copy the generated Wasm file to where the HTML file is and adjust how the JavaScript code interacts with the module.
The Product Entry page’s Category validation error

Summary

In this chapter, you learned the following:

  • You can define a function pointer’s signature directly in a function parameter in C or C++.
  • It’s possible to define the signature using the typedef keyword and then use the defined signature name in the function parameters.
  • Under the hood, function pointers are not really called directly by WebAssembly code. Instead, function references are held in the Table section of the module and the code asks the WebAssembly framework to call the desired function at the index specified.
  • It’s possible to pass a function pointer to a JavaScript function in order to tell the JavaScript code what the function’s index is in the Table section of the module.

FAQ

What problem do function pointers solve in this chapter?They let JavaScript choose, at runtime, which callbacks the WebAssembly module should invoke for validation success and error. Instead of returning values, the C++ functions call the provided success/error function pointers so JS can react asynchronously.
How do I change the C++ code to accept and use function pointers?Define typedefs, for example typedef void(*OnSuccess)(void); and typedef void(*OnError)(const char*);. Update exported functions like ValidateName and ValidateCategory to return void and accept OnSuccess and OnError parameters, then call the appropriate pointer instead of returning an int.
If imports are fixed at instantiation, how can JS “pass” a function later?Emscripten instantiates the module with imported stubs that match your function-pointer signatures and maintains a backing array of JS functions. JS uses addFunction to put a callback in that array and passes its index to Wasm. When the module calls the pointer, Emscripten dispatches to the JS function currently at that index.
Which Emscripten compile flags are needed for runtime function pointers?Reserve slots and expose helpers, for example: emcc validate.cpp -s RESERVED_FUNCTION_POINTERS=4 -s EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','UTF8ToString','addFunction','removeFunction'] -o validate.js. Adjust the reserved count to the maximum number of concurrent pointers you’ll use.
How do I pass and free JS callbacks when using Emscripten’s plumbing?- Add with Module.addFunction(fn, sig) (e.g., success: 'v', error: 'vi') and pass the returned indices to Module.ccall as arguments.
- When done, call Module.removeFunction(index) to avoid leaks. Helper functions like createPointers and freePointers keep this tidy.
Why do the JS validation functions return Promises, and how are they orchestrated?The module calls the callbacks asynchronously, so each validation function wraps its logic in a Promise and resolves/rejects from the callbacks. The UI handler uses Promise.all([validateName(...), validateCategory(...)]) to run both checks and handle success or the first error.
What signature strings do I use with addFunction for the callbacks?Use 'v' for the success callback (void return, no params) and 'vi' for the error callback (void return, one 32‑bit int param for the Wasm pointer to the UTF‑8 error string). Convert the string with Module.UTF8ToString(ptr).
How do I implement function pointers without Emscripten’s JS plumbing?Instantiate the module yourself with an env.table (WebAssembly.Table) and related imports. Import the callback entry points (_OnSuccessName, _OnErrorName, etc.), discover their table indexes via a helper call (e.g., _WhatAreTheIndexes → _HereAreTheIndexes), store those indexes, and pass them to the module’s functions. Track resolve/reject in JS objects and call them from the imported functions.
What is the Table section and how are function pointers invoked in Wasm?The Table holds references to functions (funcref). Compiled C/C++ function-pointer calls become call_indirect instructions that target a table index. After instantiation you can only reference functions already in the table (module-defined or imported), not arbitrary JS functions.
What common pitfalls should I watch for?- Not reserving enough slots: set RESERVED_FUNCTION_POINTERS to cover simultaneous callbacks.
- Forgetting removeFunction or freeing allocated memory.
- Mismatched addFunction signatures (e.g., using 'v' vs 'vi').
- Not updating ccall types/return (null for void, add 'number' types for pointers).
- Incorrect table size or missing imports (env.table, __table_base) when not using plumbing.
- Passing strings without proper UTF‑8 conversion.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • WebAssembly in Action ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • WebAssembly in Action ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • WebAssembly in Action ebook for free