3

I have a C++ project that I have converted into javascript using emscripten. I need help with implementing file input into the program via node. As I understand it the default file system in emscripten uses preloaded data that can only be done on a web page or web worker. I need mine to work with node.js on the command line.

Looking at the documentation I see that there's a way to use NODEFS instead of the default MEMFS which should allow me to do this. However, I'm unsure how I'm supposed to go about this. I don't really understand the test code that's provided.

Here's how the file handling is being done in the original C++ project:

void InputFile(std::string &fileName)
  {
    std::ifstream in(fileName);

    if (in.fail())
    {
      std::cerr << "ERROR, Could not open " << fileName << std::endl;
      exit(1);
    }
  }

But when I attempt to run the converted program with a file, node project.js -f test.file I get the error message: ERROR, Could not open test.file meaning that opening the file failed. The original C++ project was able to open the file without any issues, so I know there's not problem with the file itself.

I'm not sure what I have to do to make the converted project work with file inputs, any help would very much appreciated.

quepas
  • 956
  • 1
  • 13
  • 30
Dianne
  • 166
  • 1
  • 13

1 Answers1

6

Explanation

WebAssembly module, built using emscripten, has no information about files in your physical file system. Instead, it uses a virtual file system. All you have to do is to create a link between files on your physical system to the files on the module's virtual system. NODEFS gives you this opportunity.

Quick solution

We will start at modifying your C++ code by adding the aforementioned link between physical and virtual file systems using embedded JS code (with EM_ASM). First (1), we create a directory '/temp' on the virtual file system where all referenced files will be located in. Then (2), we link this new virtual directory with a real physical location (the current working directory '.') where all the referenced files are already.

#include <emscripten.h>
#include <emscripten/bind.h>
#include <iostream>
#include <fstream>

void InputFile(const std::string &fileName)
{
   EM_ASM(
       FS.mkdir('/temp');                         // (1)
       FS.mount(NODEFS, {root : '.'}, '/temp');); // (2)
   std::ifstream in(std::string("/temp/") + fileName);
   if (in.fail())
   {
      std::cerr << "ERROR, Could not open " << fileName << std::endl;
      exit(1);
   }
}

EMSCRIPTEN_BINDINGS(Module)
{
   emscripten::function("InputFile", &InputFile);
}

Now, because in the WebAssembly module, we are working with the virtual file systems, and not the physical one, each referenced file from the current directory (the root '.') is actually in the virtual directory previously linked ('/temp'). Hence, '/temp' directory precedes the name to the referenced file: std::ifstream in(std::string("/temp/") + fileName);.

Finally, we can compile this file. We force the synchronized compilation (to make sure the require loads the WASM module on time). Moreover, the option -s EXIT_RUNTIME=1 makes sure that the C++ command exit(1); finishes the execution. Also, we need to link Embind (--bind) and NODEFS (-lnodefs.js):

emcc project.cpp -o project.js -s WASM_ASYNC_COMPILATION=0 -s EXIT_RUNTIME=1  --bind -lnodefs.js

Testing

To test the WebAssembly module with the same calling convention as you have mentioned, we can use the following test.js script:

var Module = require('./project.js');

if (process.argv[3] && process.argv[2] === '-f') {
   const filename = process.argv[3];
   Module.InputFile(filename);
} else {
   console.log('Pass the file with -f flag!');
}

To run the file, all you have to do is this: node test.js -f test.file

Comment

This approach works well if the referenced files are in the current working directory. In the case they are not, you could modify the code of the InputFile to extract the directory in which the fileName is, and then, mount the real-to-virtual directory accordingly.

quepas
  • 956
  • 1
  • 13
  • 30
  • 2
    Can we do this in JS, without touching the original C file? (In my case, I have a big project I compiled with emconfigure and emmake, and the resulting output.js is looking for `/etc/path` and I'd love to provide the mapping between the virtual disk and the real one in JavaScript for Node.js.) – Ahmed Fasih Aug 25 '20 at 02:55
  • 1
    Figured out how to do this via `-s NODERAWFS=1` flag for `emcc`. – Ahmed Fasih Aug 25 '20 at 04:37