How to pass strings between C++ and javascript via emscripten
Asked Answered
W

4

16

I am learning emscripten, and I can't even get the most basic string manipulation working, when passing strings between C++ and JS.

For example, I would like to write a string length function. In C++:

extern "C" int stringLen(std::string p)
{
    return p.length();
}

Called from javascript as:

var len = _stringLen("hi.");

This yields 0 for me. How do I make this work as expected? Which string type should I use here? char const*? std::wstring? std::string? None seem to work; I always get pretty random values.

This is only the beginning... How do I then return a string from C++ like this?

extern "C" char *stringTest()
{
    return "...";
}

And in JS:

var str = _stringTest();

Again, I cannot find a way to make this work; I always get garbage in JS.

So my question is clearly: How do I marshal string types between JS and C++ via Emscripten?

Waste answered 16/2, 2014 at 21:33 Comment(1)
Related findings about the other direction of the problem (although this question is 7 years old): This problem asks about passing a string from JavaScript to C++. To pass a stiring FROM C++ to Javascript, one need UTF8ToString.Eliathas
C
14

extern "C" doesn't recognize std::string.

You may want to try this:
Test.cpp

#include <emscripten.h>
#include <string.h>

extern "C" int stringLen(char* p)
        {
            return strlen(p);
        }

Use the following command to compile the cpp code :

emcc Test.cpp -s EXPORTED_FUNCTIONS="['_stringLen']

Sample test code :
Test.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Hello World !</title>
        <script src="a.out.js"></script>
        <script>
             var strLenFunction =  Module.cwrap('stringLen', 'number', ['string']);
             var len1 = strLenFunction("hi.");  // alerts 3
             alert(len1);
             var len2 = strLenFunction("Hello World"); // alerts 11
             alert(len2);
        </script>
    </head>
</html>
Celeski answered 26/8, 2014 at 4:9 Comment(1)
This doesn't answer the complete question which was how to pass strings both to and from the C++ code.Milton
G
8

If you use extern "C" with a function, you cannot use C++ types in it's signature.

So if you want to use std::string, then you can use "Embind" or "WebIDL Binder". Refer here

I prefer embind, so this is a sample code for your problem.

P.S I am not sure how to pass variables by reference here, so doing it by value.

// This is your routine C++ code
size_t MyStrLen(std::string inStr) {
    return inStr.length();
}

// This is the extra code you need to write to expose your function to JS
EMSCRIPTEN_BINDINGS(my_module) {
    function("MyStrLen", &MyStrLen);
}

Now in JS all you need to do is:

var myStr = "TestString";
Module.MyStrLen(myStr);

Make sure you pass the flag

--bind

when calling emcc.

There is another approach where you can do a Malloc on the C++ heap from JS and then do manipulations, but the above approach should be easier.

Grantland answered 7/7, 2016 at 4:18 Comment(1)
"If you use extern "C" with a function, you cannot use C++ types in it's signature." I see both answers claim this, but I don't think it's true. AFAIK this is just a linkage specification to prevent name mangling. I can compile extern "C" void foo(std::string s) { std::cout << "hello, world" << std::endl; } just fineMerodach
C
6

Other answers have not addressed how to return strings from C++. Here is a way to pass a string from c++ to javascript by passing a string by reference.

The emscripten documentation states the following about the types that can be passed to C/C++ functions using cwrap/ccall (emscripten docs):

The types are "number" (for a JavaScript number corresponding to a C integer, float, or general pointer), "string" (for a JavaScript string that corresponds to a C char* that represents a string) or "array" (for a JavaScript array or typed array that corresponds to a C array; for typed arrays, it must be a Uint8Array or Int8Array).

As the other answers have pointed out, you need to write the C function using a C string as the argument because that is the emscripten API (not because of extern "C").

If you want to return a string, you may think that you can just pass a C string (effectively by reference because it is pointer) and modify that string:

// C function
extern "C" {
  void stringTest(char* output) {
    output[0] = 'H';
    output[1] = 'i';
  }
}

// Call to C function in javascript that does not modify output
let stringTestFunction =  Module.cwrap('stringTest', null, ['string']);
let output = "12"; // Allocate enough memory
stringTestFunction(output);
console.log(output); // 12

However, this does not work because a copy is created when passed to the function. So, you need to explicitly allocate the memory and pass a pointer instead. Emscripten provides the allocateUTF8 and UTF8ToString functions for this purpose:

let stringTestFunction =  Module.cwrap('stringTest', null, ['number']); // the argument is 'number' because we will pass a pointer 
let output = "12";
let ptr = Module.allocateUTF8(output); // allocate memory available to the emscripten runtime and create a pointer
stringTestFunction(ptr);
output = Module.UTF8ToString(ptr); // read from the allocated memory to the javascript string
Module._free(ptr); // release the allocated memory
console.log(output); // Hi

Because we are transforming the string to a character pointer, we could have also called the function directly without using cwrap (emscripten docs): Module._stringTest(ptr). It requires some additional steps but now you have passed a string from C to javascript.

For this example to work, you may need to compile using the following flags: -sEXPORTED_FUNCTIONS="['_stringTest','_malloc','_free']" and -sEXPORTED_RUNTIME_METHODS="['cwrap','allocateUTF8','UTF8ToString']".

There are more general ways to allocate memory for arrays of other types (https://mcmap.net/q/368744/-how-to-handle-passing-returning-array-pointers-to-emscripten-compiled-code).

Chloramine answered 4/8, 2022 at 23:8 Comment(0)
M
3

A few thoughts:

  1. The only way I have called a method is by using crwap or ccall?
    var length = Module.ccall('stringLen', ['string'], 'number');
  2. Are you including stringLen and stringTest in your EXPORTED_FUNCTIONS parameter?
    emcc hello_world.cpp ... -s EXPORTED_FUNCTIONS=['_stringLen','_stringTest']

Take a look here for more details:
http://kripken.github.io/emscripten-site/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html

Or my hello_world tutorial:
http://www.brightdigit.com/hello-emscripten/

Hopefully that helps.

Manicure answered 2/4, 2014 at 13:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.