I am writing a C++ program to compile to web assembly with Emscripten, to run in the browser.
What's the simplest way to cause the user's browser to open a file selector dialogue for file upload, and to access the contents of that file from C++?
I don't want to have to add a visible element to the page containing the wasm canvas if possible, ideally the process should be triggered from the C++ program itself.
UPDATE: I have now released a pure-C++ header-only library to offer file "uploads" and "downloads" in Emscripten:
There are a number of ways to do this, but by far the cleanest (requiring least code, and no requirement for complex use of the Emscripten filesystem API) is as follows:
In your build system, add -sEXPORTED_RUNTIME_METHODS=[ccall]
to export ccall, allowing you to call C functions from javascript.
In your html script section, add the following function - this creates a FileReader which reads a file from a file input, copies it to the heap, and passes the address and size to a C function you define:
var open_file = function(e) {
const file_reader = new FileReader();
file_reader.onload = (event) => {
const uint8Arr = new Uint8Array(event.target.result);
const num_bytes = uint8Arr.length * uint8Arr.BYTES_PER_ELEMENT;
const data_ptr = Module._malloc(num_bytes);
const data_on_heap = new Uint8Array(Module.HEAPU8.buffer, data_ptr, num_bytes);
data_on_heap.set(uint8Arr);
const res = Module.ccall('load_file', 'number', ['number', 'number'], [data_on_heap.byteOffset, uint8Arr.length]);
Module._free(data_ptr);
};
file_reader.readAsArrayBuffer(e.target.files[0]);
};
Somewhere in your C++ code, define a C function to process the file contents:
extern "C" {
EMSCRIPTEN_KEEPALIVE int load_file(uint8_t *buffer, size_t size) {
/// Load a file - this function is called from javascript when the file upload is activated
std::cout << "load_file triggered, buffer " << &buffer << " size " << size << std::endl;
// do whatever you need with the file contents
return 1;
}
}
EMSCRIPTEN_KEEPALIVE
and extern "C"
are both needed here; EMSCRIPTEN_KEEPALIVE also adds the function to exports, so you do not need to export it manually.
Finally, when you want to pop open the file selector dialogue, to prompt the user to choose a file, insert the following in your C++ code:
#include <emscripten.h>
...
EM_ASM(
var file_selector = document.createElement('input');
file_selector.setAttribute('type', 'file');
file_selector.setAttribute('onchange','open_file(event)');
file_selector.setAttribute('accept','.png,.jpeg'); // optional - limit accepted file types
file_selector.click();
);
This creates a file selector input element with the properties you specify, and immediately simulates a click to it - this method lets you use buttons in your own interface, or other C++ logic, without relying on the browser rendering of an input element.