Read local binary file from JavaScript to C/++

471 views
Skip to first unread message

Treyten Carey

unread,
Sep 6, 2018, 2:07:25 PM9/6/18
to emscripten-discuss
Hi,

This isn't necessarily an Emscripten issue but I thought this was a good place for this question since I need to call C from JavaScript through Emscripten.

What I'm trying to do:
The user should be able to select a WebGL button. The button calls an HTML input file which brings up File Explorer to select a file (such as a txt file or image). When the user selects a file (including a binary file), the contents get sent from JavaScript to C, where C then writes the file to the Emscripten local file system directory and where I should be able to send the contents of the file over C sockets (this is why I need to be able to get binary file contents, because I will be doing things with it in C).

What I've tried:
My HTML has an input element:
<input id="file-input" type="file" name="name" style="display: none;"/>

In my C code, when the user presses the WebGL button, I select the file-input element to open the file selection:
emscripten_run_script(
        (char*)((std::string)
        "document.getElementById('file-input').click(); " +
        "var fileCont; " +
        "var reader = new FileReader(); " +
        "reader.onloadend = function(evt) { " +
        "   if (evt.target.readyState == FileReader.DONE) { " +
        "       fileCont = evt.target.result; " +
        "   } " +
        "   var arrayBuffer = this.result, " +
        "     array = new Uint32Array(arrayBuffer), " +
        "     binaryString = String.fromCharCode.apply(null, array); " +
        "   Module.ccall('EM_gotOpenFileName', null,  ['string', 'string', 'number'], [document.getElementById('file-input').value, binaryString, arrayBuffer.byteLength]); " +
        "}; " +
        "reader.readAsArrayBuffer(document.getElementById('file-input').files[0]); "
    ).c_str());

Which then calls my C function where I do stuff with the file content:
extern "C"
{
    EMSCRIPTEN_KEEPALIVE void EM_gotOpenFileName(char* fileName, char* cont, int len)
    {
        // Store the file in a temporary location so we can read it later if we need to.
        // JavaScript doesn't allow us to grab it's location, so we must do it this way.
        std::string tempFile = fileName;

        // Getting the file's name without any directories. i.e "C:/fakepath/file.png" will turn into "file.png"
        std::vector<std::string> tkns = findTokens("/", tempFile);
        tempFile = tkns[tkns.size()-1];
        tkns = Operations::findTokens("\\", tempFile);
        tempFile = tkns[tkns.size()-1];
        tempFile = (std::string)".temp/" + tempFile;

        writeFile(tempFile, cont, len); // write the file. assume this works fine using fwrite.

        // ...
    }
}

What's (not) working:
Text files are writing correctly and being transferred with no problems.
Binary files, such as images, are not. The data written looks different than the actual file selected, though they have the same number of bytes.
In JavaScript, I've tried using Uint8Array and Uint16Array. Uint32Array errors "Invalid offset/length when creating typed array", and Uint64Array doesn't exist.

--

Is there something I am doing wrong, or some other way I should read a file (like an image's) contents?
I need to be able to load the data (including binary data from an image) into memory that I can do stuff with in C.

Thanks,


Floh

unread,
Sep 7, 2018, 6:14:06 AM9/7/18
to emscripten-discuss
I'm doing exactly that in my YAKC emulator: http://floooh.github.io/virtualkc/ (click on hamburger-menu icon -> load file... -> "Load" button).


The 'load_files()' JS function is called when the user has selected a file (also when drag'n'dropped a file into the load panel): https://github.com/floooh/yakc/blob/master/web/emsc.js#L141

And here's the C function that takes the file content as a pointer/length pair: https://github.com/floooh/yakc/blob/master/src/yakcapp/Main.cc#L463

Btw, a much nicer alternative to emscripten_runscript() for embedding JS code into the C source is via EM_JS():



Hope this helps!
-Floh

Floh

unread,
Sep 7, 2018, 6:28:39 AM9/7/18
to emscripten-discuss
PS: I bet this is the reason you binary files get mangled:

binaryString = String.fromCharCode.apply(null, array);

I'm not sure what exactly that line does, but there's probably some conversion between string encodings happening, and if you throw binary data at it, it will try to interpret that as some string encoded, and transform that into something else.

If you use FileReader.readAsArrayBuffer(), you can put the loaded data directly as binary blob into an Uint8Array:

    reader.onload = function(loadEvent) {
    var content = loadEvent.target.result;
    if (content) {
         var uint8Array = new Uint8Array(content);
         ....
Message has been deleted

Treyten Carey

unread,
Sep 7, 2018, 10:05:54 AM9/7/18
to emscripten-discuss
You are amazing. Thanks so much, your info was very helpful :)

Treyten Carey

unread,
Sep 28, 2018, 3:26:43 PM9/28/18
to emscripten-discuss
Alright so now I need to do the opposite and I am having some difficulties.

I am using FileSaver,js located at http://purl.eligrey.com/github/FileSaver.js to allow the user to download a file from the Emscripten file system to a location on the user's system.

In my C++ code, I can do something like this to call the JavaScript to save a text file:
emscripten_run_script(
        (char*)((std::string)
        "var fileName = 'em_file.txt'; " +
        "var text = Module.ccall('EM_readFile', 'string', ['string'], [fileName]); " + // EM_readFile returns a char* of the file content
        "var blob = new Blob([text], {type: \"text/plain;charset=utf-8\"}); " +
        "saveAs(blob, fileName+\".txt\");"
        ).c_str()
    );

This code works and brings up a dialog to save the file to a location, but doesn't correctly save files with binary data (such as an image). Note that if I save the contents to a location in the Emscripten file system, I am able to load the image (so I know the content in the char* is not corrupt)

I tried returning 'array' from EM_readFile in hopes I could make it a Uint8Array, but that didn't work (Assert failed, return type cannot be 'array').

Could someone see where I am making a mistake?

Treyten Carey

unread,
Oct 8, 2018, 9:48:39 AM10/8/18
to emscripten-discuss
Bump.

Any ideas?

Reply all
Reply to author
Forward
0 new messages