HTML5 Content: Control Browser From inside UE4

First question, so be gentle…

I’m trying to create two-way communication between an HTML5-authored UE4 game and the browser running it. After searching around, it looks like emscripten is capable of this, but even when I try a simple test of popping an alert window (as the documentation shows) with:

 emscripten_run_script("alert('hi')");

My game fails to compile with an error:

 Id: symbol(s) not found for architecture x86_64

and

 clang error: linker command failed with exit code -1 (use -v to see invocation)

Prior to this, I was getting errors about missing emscripten header files, but after manually moving them into my project, those went away, so I suspect that this may be a Mac-related problem.

So, two questions:

 1.) Do I need to install emscripten onto my system beyond what comes with UE4?

 2.) I'm new to doing a C++ UE4 project, so it's entirely possible that I'm missing something simple that I don't know how to ask about. Could anyone provide a sample of this alert call working?

Thanks in advance!

To be able to call Emscripten APIs from your own project, you should need to install Emscripten SDK manually, since it is already bundled to UE4, and no copying should be needed either. What you are trying looks fairly good. Try doing the following:

At the top of a .c/.cpp file:

#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#endif

and then at the site of use:

#ifdef __EMSCRIPTEN__
    emscripten_run_script("alert('hi')");
#endif

The ifdefs ensure that the UE4 compiler doesn’t get confused when it tries to build the code also for other platforms natively. Also try out the latest UE 4.15 if that would work better? If not, please post the full logs, it feels that the “symbol(s) not found” error would be a secondary error after some first error occurred. Was there anything else printed prior to that?

Thank you for the response!!! I assumed everyone thought my question was too noob to answer, so in the ensuing time between my original post and now, I’ve managed to battle my way through this (your answer jives exactly with what I discovered) and even passing strings from UE4 to the browser via emscripten/JS. What’s left that I still can’t get is passing a string from JS back to UE4. I found a few threads on here that had some pieces, but not a single coherent answer (or at least one that I could understand). Do you know of a working example of the JS and C++ code?

This is the solution I’m dancing around (but can’t get right):
JS:
function sendToUE(){
var str = “TEST”;
var buffer = Module._malloc(str.length + 1);
Module.writeStringToMemory(str, buffer);
Module.ccall(‘printString’, ‘null’,[‘string’], buffer);
}

C++:
EMSCRIPTEN_KEEPALIVE void printString(char * str) {
//std::cout << str << std::endl; //killed this and replacing with onscreen debug, since UE4 doesn’t do cout well
GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, TEXT(“How about this?:”));
GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, str);
}

I apologize again for my rank amateur coding abilities. The problem may be as simple as syntax/structure (or the whole thing may be total garbage), but it’s killing me! If you’ve got any insights, you’d forever be my hero!

The example you have looks good, although three things I notice are that it won’t work with non-ascii text characters, so sending the string over as UTF-8 (or UTF-16) would be better; I think it should use [‘number’] instead of [‘string’]; and it should remember to call _free at the end.

Perhaps something like this, JS side:

function sendToUE() {
  var str = "TEST";
  var lenUTF8 = Module.lengthBytesUTF8(str) + 1;
  var ptr = Module._malloc(lenUTF8);
  Module.stringToUTF8(str, ptr, lenUTF8); 
  Module.ccall('printString', 'null', ['number'], ptr);
  Module._free(ptr);
}

and C++ side:

// top of page:
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#else
#define EMSCRIPTEN_KEEPALIVE
#endif

// at site of use:
void EMSCRIPTEN_KEEPALIVE printString(char *str) {
  printf("%s\n", str);
}

I am not familiar with GEngine->AddOnScreenDebugMessage() API, but probably that should work as well.

Thank you so much! Your modifications definitely make sense. On the C++ side I had to add ‘extern “C”’ to the printString function to get it recognized when the JS calls it. I also added:

GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, FString::Printf(TEXT("I got: %s"), &str));

(which I know you’re not familiar with, but has served me well during the rest of development) so that I can see the result onscreen in realtime (along with an alert in JS to verify execution).

So, now everything seems to trigger – I can trigger the JS from UE4, which in turn triggers the printString function back in UE4, and no errors get thrown along the way. Unfortunately, what still isn’t happening is the string being displayed. Instead, the data appears to be blank. I’m going to try and verify that my GEngine line is structured properly (so I know that it’s not misleading me), but is it possible that the string conversion is not working right, or maybe that the string isn’t getting placed into the “str” variable? Do you have any other ideas as to what might be going on? Thanks again!

GOT IT WORKING!!!

I had to tweak the Module.ccall line in the JS to this:

Module.ccall('printString', 'null',['string'], [str]);

Also, the problem with nothing showing in the onscreen output was that GEngine->AddOnScreenDebugMessage doesn’t work with char variables. Once I added a variable to convert it:

FString outString(str);

and changed my output line to this:

GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, outString);

It worked like a charm! A million - no a billion “thank yous” for your help and guidance!

So, to sum up (so future readers hopefully won’t have to suffer as much as I did)…

To communicate between UE4 and JS in the HTML wrapper of an HTML5-authored piece:

//--------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------------
//-------------------------------------
// In C++ top of page:
//-------------------------------------

//Gotta have these to do onscreen messages
#include "EngineGlobals.h"
#include "Engine.h"

//Gotta have these to make emscripten work
#ifdef __EMSCRIPTEN__ //This basically says “if emscripten is doing stuff, add this, otherwise ignore”.
#include "SDL/SDL.h"
#include "emscripten/emscripten.h"
#include "emscripten/html5.h"
#else
#define EMSCRIPTEN_KEEPALIVE
#endif //This closes the “ifdef __EMSCRIPTEN__” tag.

//Gotta have this to make translation of strings to JavaScript work
#include "string"
#include "iostream"

#include "sstream"

//-------------------------------------



//--------------------------------------------------------------------------------------------
// Use emscripten to run a pre-existing javascript function in the container HTML page
//--------------------------------------------------------------------------------------------

//-------------------------------------
// In header file (public section):
//-------------------------------------

// The BlueprintCallable is the key to making the function callable via Blueprints (duh), and Category is required
    UFUNCTION(BlueprintCallable, Category = "Comms")
    void Comm_RunExtJS();

//-------------------------------------
// In C++ body:
//-------------------------------------

void ABrowserCommActor::Comm_RunExtJS()
{
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, TEXT("Comm_RunExtJS() Triggered!”)); //This puts a message onscreen inside UE4, so you know this side executed. If you want to verify the JS side, you’ll need an alert in that function.

#ifdef __EMSCRIPTEN__ // Shields the JavaScript code from the C++ compiler to prevent errors.

    emscripten_run_script(“targetJSFunction();”); //This runs a function WITHOUT passing any parameters

#endif
}
//-------------------------------------



//--------------------------------------------------------------------------------------------
// Use emscripten to send a string to a function in the container HTML page
//--------------------------------------------------------------------------------------------

//-------------------------------------
// In header file (public section):
//-------------------------------------

// The BlueprintCallable is the key to making the function callable via Blueprints (duh), and Category is required
    UFUNCTION(BlueprintCallable, Category = "Comms")
    void Comm_SendStringToJS();

//-------------------------------------
// In C++ body:
//-------------------------------------

void ABrowserCommActor::Comm_SendStringToJS()
{
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, TEXT("Comm_SendStringToJS() Triggered!”)); //This puts a message onscreen inside UE4, so you know this side executed. If you want to verify the JS side, you’ll need an alert in that function.

    //SendDataString = “Yo!”; // Use this to artificially set the data string for testing.
    std::string OutboundSendString(TCHAR_TO_UTF8(*SendDataString)); // This converts the FString (UE4 string) to a regular string.
    
#ifdef __EMSCRIPTEN__ // Shields the JavaScript code from the C++ compiler to prevent errors.
    //This executes JS code and passes arguments (in this case a string).
    EM_ASM_ARGS({
        var theData = Pointer_stringify($0); // Convert the string to a JS string.
        //alert(“JS Triggered!”);            // Pop an alert for testing.
        destJSFunction(theData);             // Call a JS function in the wrapper HTML, passing the string to it.
    }, OutboundSendString.c_str());          // Pass the string from UE4 world into the emscripten function.
#endif
}

//-------------------------------------
// In HTML Wrapper JavaScript:
//-------------------------------------

function destJSFunction(dataFromC){

	alert(dataFromC); // Displays the string from UE4.

}
//-------------------------------------



//--------------------------------------------------------------------------------------------
// Use emscripten to receive a string from a function in the container HTML page
//--------------------------------------------------------------------------------------------

//-------------------------------------
// In header file (public section):
//-------------------------------------

// The BlueprintCallable is the key to making the function callable via Blueprints (duh), and Category is required
    UFUNCTION(BlueprintCallable, Category = "Comms")
    void Comm_GetStringFromJS();

// String to hold received data from JS
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category= "Comms")
    FString LMSReturnedDataString = "hi";

//-------------------------------------
// In C++ body:
//-------------------------------------

void ABrowserCommActor::Comm_GetStringFromJS()
{
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, TEXT("Comm_GetStringFromJS() Triggered!”)); //This puts a message onscreen inside UE4, so you know this side executed. If you want to verify the JS side, you’ll need an alert in that function.

#ifdef __EMSCRIPTEN__ // Shields the JavaScript code from the C++ compiler to prevent errors.

    emscripten_run_script(“getJSFunction();"); //This runs a function WITHOUT passing any parameters. Basically, it’s just calling the JS, which does the work.

#endif
}

// This receives the string back from JS and displays it onscreen
extern "C" void EMSCRIPTEN_KEEPALIVE receiveString(char *str) {
    FString returnedDataString(str); //This converts the string from JavaScript to a UE4 FString
    GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, returnedDataString); //Displays the result onscreen
}

//-------------------------------------
// In HTML Wrapper JavaScript:
//-------------------------------------

function targetJSFunction(){

	var value = “Hi from JS!”;
	
	//window.alert("JS Triggered!"); // Used to make sure the function is getting called.
	
	var lenUTF8 = Module.lengthBytesUTF8(value) + 1; // This gets the length of the string, adds 1 to it for a stop bit, and stores that.
	var ptr = Module._malloc(lenUTF8); // This calls back to the C++ code, allocates the appropriate amount of space, and creates a variable that's a pointer to that memory.
	Module.stringToUTF8(value, ptr, lenUTF8); // This calls the C++ and tells it to convert the JS string to a UTF8 char array (like a string, but NOT a string) in C++.
	Module.ccall('receiveString', 'null',['string'], [value]); // This calls the function "receiveString" in UE4 and passes the string as a parameter into it.
	Module._free(ptr); // This clears the memory at the pointer location, so old stuff doesn't just pile up.
	
}
//-------------------------------------

Looks like I spoke too soon on “got it working”…

The string is coming in, and displays onscreen with the GEngine line, but now the data is trapped in str, inside the extern “C” scope. I can’t get it to a variable in the main UE4 code. Again, this is probably due simply to my inexperience with variables and scope in C++/UE4. Would you have any insight on this piece, by chance?

Thanks, thanks, and thanks again!

OK, One addition:

I found that, while I was able to get the string on the screen, I wasn’t able to get it into my returnedDataString variable (which I also noticed was miswritten as LMSreturnedDataString). I’m sure this isn’t the best solution (as a matter of fact, I know I’m going to get yelled at for doing it this way), but I added a global variable in the C++ body (above Comm_GetStringFromJS()) with the line:

FString returnedStringHold_Global;

Then I added a line in the extern “C” function to move the string into it:

returnedStringHold_Global = convertedString;

And, finally, I added a line in Comm_GetStringFromJS() to move it into the final, blueprint-accessible, destination:

LMSReturnedDataString = returnedStringHold_Global;

NOW everything is happy! :wink:

I think UPROPERTY is optional…

FString UCubeJSFunctionLibrary::Comm_GetStringFromJS() {
#ifdef EMSCRIPTEN
emscripten_run_script(“getJSFunction();”);
#endif
return returnedStringHold_Global;
}

… and UTF8_TO_TCHAR is needed to convert JS string into FString correctly:

extern “C” void EMSCRIPTEN_KEEPALIVE receiveString(char *str) {
FString returnedDataString(UTF8_TO_TCHAR(str));
returnedStringHold_Global = returnedDataString;
}

Thanks for example!

I’ve been trying to implement this and it compiles okay, but I can’t access the function from within blueprints. In fact the C++ class isn’t even showing up in my content folder or class viewer

I seem to get an Uncaught ReferenceError that my javascript function is not defined.
I call the javascript function, which is in the HTML file running the game, from Unreal using your JS target function and the Comm_GetStringFromJS() function in C++

but i get this when it’s called:

Even though I have defined the function in the html file. Any ideas?

You got to put your javascript function in the file [YourGameName].js
At the end of it!

You got to put your javascript function in the file [YourGameName].js
At the end of it!

Thanks for sharing this, helped me a lot.

An update, on newer versions of Emscriptem you should replace

Pointer_stringify

to

UTF8ToString

Arguments are the same.