I'm trying to compile a medium-size existing code base with emscripten. Everything currently compiles, but when I try to call it from javascript I'm getting the error:
Assertion failed: Cannot call unknown function InitHOG (perhaps LLVM optimizations or closure removed it?)
I've declared this as:
extern "C" {
void EMSCRIPTEN_KEEPALIVE InitHOG()
{ ... }
}
I'm linking the function from javascript with:
InitHog = Module.cwrap('InitHOG', 'void', []);
My code base is being compiled into libraries; the function call into the library is in my guihtml library, where the final linking command is:
emcc -o ../../../../html/debug/bidirnecessary.js ../../../../objs_html/bidirnecessary.js/debug/demos/bidirnecessary/Driver.o -lenvironments -lmapalgorithms -lalgorithms -lgraphalgorithms -lgraph -lutils -lguihtml -L../../../../html/debug -Lapps/libs -Ldemos/libs -lpthread -g
Any ideas on why it can't find my function from javascript?
While the EMSCRIPTEN_KEEPALIVE keyword works when you are compiling a single file to .js output, it doesn't work in my makefile system where I compile individual files, use emar to make a library, and then link everything together at the end.
Instead, you need to use the -s directive to specify which functions you want to export. So, something like this works.
emcc -o ../../../../html/debug/bidirnecessary.js ../../../../objs_html/bidirnecessary.js/debug/demos/bidirnecessary/Driver.o -lenvironments -lmapalgorithms -lalgorithms -lgraphalgorithms -lgraph -lutils -lguihtml -lgui -L../../../../html/debug -Lapps/libs -Ldemos/libs -lpthread -g -s EXPORTED_FUNCTIONS="['_InitHOG', '_DoFrame', '_MouseEvent']"
Related
I have two fairly simple C++ class definitions and their interfaces, uuid.{hpp,cpp} and uuid_util.{hpp,cpp}, and I have one more file uuid_bind.cpp with #include <emscripten/bind.h> to bind the C++ classes, function and static function definitions to JavaScript.
The two classes are first built as a static library uuid_lib.a, which is then linked against the latter C++ source file and built with em++ --bind -o uuid_module.js uuid_bind.cpp uuid_lib.a (which CMake generates) to generate uuid_module.js and uuid_module.wasm. Now, what do I do with these?
The documentation on Embind is somewhat sparse, and only says
The resulting quick_example.js file can be loaded as a node module or via a <script> tag:
...
I found this Google tutorial on combining Emscripten/Embind and node.js, and I have replicated it as much as possible (excluding the bit on Docker, as my Linux distribution serves Emscripten directly). I have both an index.html and a package.json file, and npm test launches http-server, which I run from Chrome.
I was under the impression that Emscripten/Embind would simply serve as a translation layer for any bound classes, functions (static or otherwise), variables, and would be able to be called straightforwardly from JavaScript, but it turns out this is not the case. Am I missing out on something here? I'm not very familiar with JS. All I want to do in index.js is something like:
index.js
import uuid_module from './uuid_lib.js';
console.log(uuid_module.uuid_util.generate_type1_uuid("BLA"));
// ... other calls
and run this with node index.js which would print a UUID string to the console.
For background, I've provided my CMakeLists.txt and uuid_bind.cpp below.
CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
# Set project file
project(
uuid_generator
VERSION 1.0
DESCRIPTION "A UUID generator"
LANGUAGES CXX)
# Export compile commands for clangd
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Add Emscripten CMake toolchain file
if(EMSCRIPTEN)
message("Using Emscripten.")
set(CMAKE_TOOLCHAIN_FILE
${PROJECT_SOURCE_DIR}/deps/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake)
else()
message("Using system compilers.")
endif(EMSCRIPTEN)
# C++20 guaranteed, no extensions eg. GNU/MSVC
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Strict compilation for MSVC or GCC/Clang
if(MSVC)
add_compile_options(/W4 /WX)
else()
add_compile_options(-Wall -Wextra -pedantic)
endif()
# Add uuid_generator as a shared library
add_library(uuid_lib STATIC src/uuid.cpp src/uuid_util.cpp)
# Set C++ library properties: output name, location, etc
set_target_properties(
uuid_lib
PROPERTIES OUTPUT_NAME uuid_lib
PREFIX ""
ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/dist)
# Set JS binding application properties: set bind, output type, etc. Runs only if Emscripten is enabled.
if(DEFINED ENV{EMSDK})
add_executable(uuid_module src/uuid_bind.cpp)
set_target_properties(
uuid_module
PROPERTIES OUTPUT_NAME uuid_module
SUFFIX ".js"
RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/dist)
target_include_directories(uuid_module
PRIVATE ${PROJECT_SOURCE_DIR}/deps/emsdk/upstream/emscripten/system/include)
target_link_libraries(uuid_module uuid_lib)
target_link_options(
uuid_module
PUBLIC
$<$<CONFIG:DEBUG>:
-v
--bind
-sEXCEPTION_DEBUG=1
-sDEMANGLE_SUPPORT=1
-sDISABLE_EXCEPTION_CATCHING=1
-sWASM_BIGINT=1
-sMODULARIZE=1
-sEXPORT_ES6=1
-sEXPORT_NAME=uuid_module
>)
endif(DEFINED ENV{EMSDK})
uuid_bind.cpp
#include "uuid.hpp"
#include "uuid_util.hpp"
#include <cstdint>
#include <emscripten.h>
#include <emscripten/bind.h>
#include <string>
#include <iostream>
EMSCRIPTEN_BINDINGS(uuid_generator)
{
emscripten::constant("UINT29_MAX", uuid_gen::uuid::UINT29_MAX);
emscripten::constant("ICAO_ADDRESS_MAX", uuid_gen::uuid_util::ICAO_ADDRESS_MAX);
emscripten::constant("MAC_ADDRESS_MAX", uuid_gen::uuid_util::MAC_ADDRESS_MAX);
emscripten::constant("RANDOM_NAMESPACE_MAX", uuid_gen::uuid_util::RANDOM_NAMESPACE_MAX);
emscripten::class_<uuid_gen::uuid>("uuid")
.property("namespace_type", &uuid_gen::uuid::get_namespace_type)
.property("namespace_value", &uuid_gen::uuid::get_namespace_value)
.property("namespace_string", &uuid_gen::uuid::get_namespace_string)
.property("to_string", &uuid_gen::uuid::to_string)
.class_function("uniform_int_distr", &uuid_gen::uuid::uniform_int_dist);
emscripten::class_<uuid_gen::uuid_util>("uuid_util")
.class_function("generate_type1_uuid", &uuid_gen::uuid_util::generate_type1_uuid)
.class_function("generate_type2_uuid", &uuid_gen::uuid_util::generate_type2_uuid)
.class_function("generate_type3_uuid", &uuid_gen::uuid_util::generate_type3_uuid)
.class_function("generate_type4_uuid", &uuid_gen::uuid_util::generate_type4_uuid)
.class_function("generate_type5_uuid_str", emscripten::select_overload<uuid_gen::uuid(const std::string &)>(
&uuid_gen::uuid_util::generate_type5_uuid))
.class_function("generate_type5_uuid_int", emscripten::select_overload<uuid_gen::uuid(std::uint32_t)>(
&uuid_gen::uuid_util::generate_type5_uuid))
.class_function("generate_type6_uuid_str", emscripten::select_overload<uuid_gen::uuid(const std::string &)>(
&uuid_gen::uuid_util::generate_type6_uuid))
.class_function("generate_type6_uuid_int", emscripten::select_overload<uuid_gen::uuid(std::uint64_t)>(
&uuid_gen::uuid_util::generate_type6_uuid))
.class_function("generate_type7_uuid", &uuid_gen::uuid_util::generate_type7_uuid)
.class_function("from_string", &uuid_gen::uuid_util::from_string)
.class_function("is_valid_oper_agency", &uuid_gen::uuid_util::is_valid_oper_agency)
.class_function("is_valid_loc_atm_id", &uuid_gen::uuid_util::is_valid_loc_atm_id)
.class_function("is_valid_mac_address_string", &uuid_gen::uuid_util::is_valid_mac_address_string)
.class_function("namespace_char_encode", &uuid_gen::uuid_util::namespace_char_encode)
.class_function("namespace_char_decode", &uuid_gen::uuid_util::namespace_char_decode)
.class_function("namespace_encode", &uuid_gen::uuid_util::namespace_encode)
.class_function("namespace_decode", &uuid_gen::uuid_util::namespace_decode)
.class_function("mac_address_encode", &uuid_gen::uuid_util::mac_address_encode)
.class_function("mac_address_decode", &uuid_gen::uuid_util::mac_address_decode);
};
Am I missing out on something here?
What you're missing is that in -s MODULARIZE=1 mode, the default export is a factory, not the module object itself.
You'll need to create a module first and then you should be able to access the exposed properties:
import uuidModuleFactory from './uuid_lib.js';
const uuid_module = uuidModuleFactory(/* optional Emscripten config goes here */);
console.log(uuid_module.uuid_util.generate_type1_uuid("BLA"));
// ... other calls
I'm trying to adapt the game of life tutorial to call user-defined JS (instead of alert) from Rust:
index.js:
import * as wasm from "testing-wasm";
export const jsfunc = () => {
console.log("jsfunc called");
};
// Call Rust from JS. This function will call `jsfunc`, declared above.
wasm.rustfunc();
lib.rs:
mod utils;
use wasm_bindgen::prelude::*;
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen(module = "/www/index.js")]
extern "C" {
fn jsfunc();
}
#[wasm_bindgen]
pub fn rustfunc() {
// call JS
jsfunc();
}
wasm-pack build runs fine. But running the web project (npm run start) can't resolve the import anymore:
ERROR in ../pkg/snippets/testing-wasm-8ea926e8de57779d/www/index.js
Module not found: Error: Can't resolve 'testing-wasm' in '/Users/ischuetz/dev/ct-an/testing-wasm/pkg/snippets/testing-wasm-8ea926e8de57779d/www'
# ../pkg/snippets/testing-wasm-8ea926e8de57779d/www/index.js 1:0-37 7:0-13
# ../pkg/testing_wasm_bg.wasm
# ../pkg/testing_wasm.js
# ./index.js
# ./bootstrap.js
It works before introducing the circular dependency.
Any ideas? I also found import_js in wasm-bindgen but there's no direct call to Rust from JS.
in your Cargo.toml file add this:
[lib]
# if you want to integrate your rust code with javascript we use cdylib
crate-type=["cdylib"]
since you are using wasm-bindgen and wee_alloc and I assume it is already in your .toml file:
[dependencies]
wasm-bindgen="0.2.63"
wee_alloc="0.4.5"
When you build your code, pkg folder is created which includes glue javascript code wasm code. Now you need to get this pkg folder into the node modules. To do so, you have to link it to your javascript project's package.json:
"dependencies": {
// your path to ../pkg might be different
"rust_project": "file:../pkg",
},
Then in your javascript project directory, npm install. You will see that rust_project module is in your node_modules directory.
In your javascript file:
import rustfunc from "rust_project";
Now you can call your function
I have a gulp task getBuildNumber which uses Child Process to execute the script.
gulp.task('getBuildNumber', function() {
var buildNumber = child_process.execSync("echo $BUILD_NUMBER").toString().trim();
console.log(buildNumber);
});
When I run the following command for gulp
npm run gulp -- getBuildNumber
I always get the output as $BUILD_NUMBER and not the actual Jenkins build number.
Can someone please suggest on how to proceed with this?
You can access environment variables with process.env.
For example:
console.log(process.env.BUILD_NUMBER);
According to https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback, you need to escape special characters:
exec('echo "The \\$HOME variable is $HOME"');
In your case, this means you'd need to use
[...]child_process.execSync("echo \\$BUILD_NUMBER").toString().trim();[...]
^^
I'm trying to create simple node-addon with tesseract library as a dependency, but I'm a c++ beginner.
Whole code at: https://github.com/q-nick/node-tesseract
binding.cc:
#include <node.h>
#include <v8.h>
// #include <tesseract/baseapi.h>
// #include <leptonica/allheaders.h>
void Method(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, "world"));
}
void init(v8::Local<v8::Object> exports) {
NODE_SET_METHOD(exports, "hello", Method);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, init)
binding.gyp:
{
"targets": [
{
"target_name": "binding",
"sources": [
"src/binding.cc"
],
'defines': [ 'V8_DEPRECATION_WARNINGS=1' ],
'include_dirs': [
],
'libraries': [
# '-lpvt.cppan.demo.google.tesseract.libtesseract',
# '-lleptonica'
]
}
]
}
I found a project which could help me compiling dependencies like tesseract, leptonica - it's https://cppan.org/
Unfortunately, I can't figure out - how to connect this with the node-gyp build process. CPPAN has one config file it's named cppan.yml (something like package.json in npm)
cppan.yml:
dependencies:
pvt.cppan.demo.google.tesseract.libtesseract: master
pvt.cppan.demo.danbloomberg.leptonica: 1
I want to build my node-addon and all dependencies (like tesseract) by one command. And don't know how to link c++ dependencies in node-gyp build
I want to use latest tesseract version so I can't use pre-compiled libraries. Currently, I'm working in Windows environment, but I want it to be a cross-platform process.
My example GitHub project (https://github.com/q-nick/node-tesseract) must compile successfully after uncommenting tesseract include.
If there is some other easy way how to accomplished this please share.
I want it to !
The solution is to build all c++ tesseract code as dependencies ! (and leptonica), so the first is to try to know how to build tesseract (which arguments, variables, defines ...)
Just checks this eg: https://github.com/istex/popplonode/blob/master/binding.gyp
There is a dependencies file to poppler in lib folder.
It could be could to work together on this !
I will answer my question by myself.
I found a project: https://github.com/cmake-js/cmake-js which has many explanation about why move away from gyp:
...First of all, Google, the creator of the gyp platform is moving towards its new build system called gn, which means gyp's days of support are counted...
I also found: https://github.com/nodejs/nan/
...The goal of this project is to store all logic necessary to develop native Node.js addons without having to inspect NODE_MODULE_VERSION and get yourself into a macro-tangle...
So i give it a try.
binding.cc:
#include <nan.h>
#include <baseapi.h>
#include <allheaders.h>
NAN_MODULE_INIT(InitAll) {
Set(target, New<String>("myMethod").ToLocalChecked(),
GetFunction(New<FunctionTemplate>(MyMethod)).ToLocalChecked());
}
NODE_MODULE(addon, InitAll)
NAN_METHOD(MyMethod) {
info.GetReturnValue().Set(Nan::New<v8::String>("world").ToLocalChecked());
}
Next thing is to create CMakeLists.txt file with few modification. I want to use cppan as dependencies installator, so I have to add some extra lines to default CMAkeLists.txt file:
add_subdirectory(.cppan)
...
target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB}
pvt.cppan.demo.google.tesseract.libtesseract
pvt.cppan.demo.danbloomberg.leptonica
)
CMakeLists.txt:
project(addon)
file(GLOB SOURCE_FILES "src/**/*.cc" "src/**/*.h")
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES})
add_subdirectory(.cppan)
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node")
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_JS_INC})
target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB}
pvt.cppan.demo.google.tesseract.libtesseract
pvt.cppan.demo.danbloomberg.leptonica
)
cppan.yml
dependencies:
pvt.cppan.demo.google.tesseract.libtesseract: master
pvt.cppan.demo.danbloomberg.leptonica: 1
Now, everything is already set up and we can run install and build command:
cppan
and
cmake-js build
Good luck!
I have files as represented:
-js/
- calc.js
- tool.js
-index.html
calc.js is a node module of following structure:
module.exports = {
calculate: function() {...},
getPrecision: function() {...}
}
and tool.js use require and adds some functions, like that:
const fpcalc = require('./fpcalc');
function changeState() {
//some code using fpcalc
}
I used Browserify to generate bundle.js and added that as script src.
One of my buttons on HTML page is using onclick=changeState(). After clicking I'm getting
ReferenceError: changeState is not defined
at HTMLAnchorElement.onclick
Why is that? Is there any other way to make it work?
The function "changeState" is not exported in your tool.js.
That means it is only visible inside your bundle.js, but not outside.
Have a look at this: https://makerlog.org/posts/creating-js-library-builds-with-browserify-and-other-npm-modules
It shows you how to expose your code to the global namespace in javascript.
Here's a very simple way to make it work like you want.
const fpcalc = require('./fpcalc');
window.changeState = () => {
//some code using fpcalc
}
I have same error, here is my working example.
mac, browserify https://github.com/perliedman/reproject
Must use sudo install globally
sudo npm install -g brwoserify
https://github.com/perliedman/reproject
sudo npm install reproject // install locally is fine
Must manually create 'dist' folder for later output file use
Must use --s expose global variable function 'reproject' and or 'toWgs84' you will use later in browser js.
Without --s , will get 'reproject' undefined error . https://makerlog.org/posts/creating-js-library-builds-with-browserify-and-other-npm-modules
browserify --help will list all options.
-o means output file directory
browserify node_modules/reproject/index.js --s reproject -o node_modules/reproject/dist/reproject.js
HTML script tag include your above dist/reproject.js
Now, you will not get 'reproejct' undefined error
return reproject(_geometry_, ___from_projection, proj4.WGS84, crss)