系列文章目录
第一章 WebAssembly概念 第二章 Emscripten详解 第三章 JavaScript调用C\C++ 第四章 C\C++调用JavaScript
WebAssembly第四章 C\C++调用JavaScript
前言
本篇是WebAssembly系列文章的第四章,我会在本文介绍在几个常用场景下C++源文件内运用js代码段、调用JavaScript函数所需要用到的操作步骤,编译命令,和一些具体操作(比如:http请求fetch,文件读写)的示例代码。
我的环境
组件 | 版本 |
---|
CentOS | 7 | Docker | 20.10.7 | emscripten/emsdk | 3.1.14 | nginx | 1.18.0 | chrome | 102.0.5005.115 |
C\C++运行JavaScript代码
Emscripten 提供了从 C/C++ 调用 JavaScript 的两种主要方法:使用 emscripten_run_script() 运行脚本或编写“内联 JavaScript”。
emscripten_run_script()
最直接但速度稍慢的方法是使用emscripten_run_script()。。这有效地运行 C/C++ 中使用 指定的 JavaScript 代码。例如,要使用文本“hi”调用浏览器的函数,可以调用以下 JavaScript:eval()alert()
emscripten_run_script("alert('hi')");
EM_* 宏
从C调用JavaScript的更快方法是使用EM_JS()或EM_ASM()(以及相关的宏)编写“内联JavaScript”。
EM_ASM
内联JavaScript 的便捷语法。
这允许您在C代码中“内联”声明JavaScript,然后在浏览器中运行编译的代码时执行。例如,如果以下 C 代码是使用 Emscripten 编译并在浏览器中运行的,它将显示两个警报:
EM_ASM(alert('hai'); alert('bai'));
参数可以在JavaScript代码块内传递,它们作为变量到达,等等。这些参数可以是类型或 。 $0$1 int32_t double
EM_ASM({
console.log('I received: ' + [$0, $1]);
}, 100, 35.5);
以 Null 结尾的 C 字符串也可以传递到块中,但要对它们进行操作,需要将它们从堆中复制出来以转换为高级 JavaScript 字符串。EM_ASM
EM_ASM(console.log('hello ' + UTF8ToString($0)), "world!");
以同样的方式,指向任何类型(包括)的指针都可以在代码中传递,其中它们显示为整数,就像上面的指针一样。可以通过直接读取堆来管理数据的访问。void *EM_ASMchar *
int arr[2] = { 30, 45 };
EM_ASM({
console.log('Data: ' + HEAP32[$0>>2] + ', ' + HEAP32[($0+4)>>2]);
}, arr);
EM_ASM_INT、EM_ASM_DOUBLE
此宏以及EM_ASM_DOUBLE和EM_ASM_PTR的行为类似于EM_ASM,但除此之外,它们还会向 C 代码返回一个值。输出值通过语句传回:return
int x = EM_ASM_INT({
return $0 + 42;
}, 100);
int y = EM_ASM_INT(return HEAP8.length);
EM_ASM_PTR
类似于EM_ASM_INT,但对于指针大小的返回值。使用此生成时,将生成 i64 返回值,否则将生成 i32 返回值。-sMEMORY64
字符串可以从 JavaScript 返回到 C,但需要小心内存管理。
char *str = (char*)EM_ASM_PTR({
var jsString = 'Hello with some exotic Unicode characters: T?ss? on yksi lumiukko: ?, ole hyv?.';
var lengthBytes = lengthBytesUTF8(jsString)+1;
var stringOnWasmHeap = _malloc(lengthBytes);
stringToUTF8(jsString, stringOnWasmHeap, lengthBytes);
return stringOnWasmHeap;
});
printf("UTF8 string says: %s\n", str);
free(str);
C/C++调用JS函数
通过将JavaScript定义的函数mergeInto到LibraryManager.library
我们在mergeInto函数的第二个参数中,将需要注入的函数定义为对象的方法。mergeInto将该对象合并到LibraryManager.library中,LibraryManager.library是JavaScript注入C环境的库
JavaScript代码
cat test_js_library.js
mergeInto(LibraryManager.library,{
custom_add: function(x,y){
return x+y;
}
});
C++代码
#include <stdio.h>
#include <emscripten/emscripten.h>
extern "C"{
extern int custom_add(int x,int y);
}
int main(int argc, char ** argv) {
printf("Hello World\n");
printf("ready to call js func>>\n");
printf("call js func return %d\n", custom_add(2, 3));
return 0;
}
编译命令
在编译时添加参数 --js-library 表示将js函数注入C,后接js文件地址。
docker run --rm -v $(pwd):/src -u $(id -u):$(id -g) emscripten/emsdk emcc --std=c++17 hello3.cpp -s WASM=1 -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall']" --shell-file templates/shell_minimal.html -o sayhello3.js --js-library test_js_library.js
运行结果
发起HTTP请求
Emscripten Fetch API允许本机代码通过XHR(HTTP GET,PUT,POST)从远程服务器传输文件,并将下载的文件本地保存在浏览器的IndexedDB存储中,以便在后续页面访问时可以在本地重新访问它们。Fetch API 可从多个线程调用,并且网络请求可以根据需要同步或异步运行。
#include <stdio.h>
#include <iostream>
#include <emscripten/emscripten.h>
#include <emscripten/fetch.h>
using namespace std;
void downloadSucceeded(emscripten_fetch_t *fetch) {
printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url);
emscripten_fetch_close(fetch);
}
void downloadFailed(emscripten_fetch_t *fetch) {
printf("Downloading %s failed, HTTP failure status code: %d.\n", fetch->url, fetch->status);
emscripten_fetch_close(fetch);
}
void downloadProgress(emscripten_fetch_t *fetch) {
if (fetch->totalBytes) {
printf("Downloading %s.. %.2f%% complete.\n", fetch->url, fetch->dataOffset * 100.0 / fetch->totalBytes);
} else {
printf("Downloading %s.. %lld bytes complete.\n", fetch->url, fetch->dataOffset + fetch->numBytes);
}
}
void sync_idbfs() {
EM_ASM(
FS.syncfs(function (err) {});
);
}
int main(int argc, char ** argv) {
printf("我是printf: Hello World\n");
cout<<"我是C++的cout "<<"你好,世界"<<endl;
emscripten_fetch_attr_t attr;
emscripten_fetch_attr_init(&attr);
strcpy(attr.requestMethod, "GET");
attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_PERSIST_FILE;
attr.onsuccess = downloadSucceeded;
attr.onerror = downloadFailed;
attr.onprogress = downloadProgress;
emscripten_fetch_t *fetch = emscripten_fetch(&attr, "https://cccc.com/emcc/helloworld3.html");
if (fetch->status == 200) {
printf("Finished downloading %llu bytes from URL %s.\n", fetch->numBytes, fetch->url);
} else {
printf("Downloading %s failed, HTTP failure status code: %d.\n", fetch->url, fetch->status);
}
return 0;
}
emscripten_fetch_attr_t.attributes
EMSCRIPTEN_FETCH_LOAD_TO_MEMORY 结果载入内存,方便程序读取使用结果,如果是大文件的话建议取消此设置 EMSCRIPTEN_FETCH_PERSIST_FILE 持久化结果 EMSCRIPTEN_FETCH_SYNCHRONOUS 同步执行
编译命令
docker run --rm -v $(pwd):/src -u $(id -u):$(id -g) emscripten/emsdk emcc --std=c++11 test_fetch.cpp -s WASM=1 -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall']" -s -sFETCH -lidbfs.js --shell-file templates/shell_minimal.html -o helloworld4.html
结果
读写本地文件
Emscripten 中的文件操作由 FS 库提供。它在内部用于Emscripten的所有libc和libcxx文件I / O。 当前支持的文件系统: MEMFS :默认的文件系统,只存在于内存中。 -lnodefs.js node环境 -lidbfs.js 游览器环境 -lworkerfs.js -lproxyfs.js
因为这几种文件系统的特性,这里我们只介绍indexedDB。
C++代码
#include <stdio.h>
#include <iostream>
#include <emscripten/emscripten.h>
#include <emscripten/fetch.h>
using namespace std;
void sync_idbfs() {
EM_ASM(
FS.syncfs(function (err) {});
);
}
int main(int argc, char ** argv) {
printf("我是printf: Hello World\n");
cout<<"我是C++的cout "<<"你好,世界"<<endl;
EM_ASM(
FS.mkdir('/data');
FS.mount(IDBFS, {}, '/data');
FS.syncfs(true, function (err) {
assert(!err);
ccall('test1', 'v');
});
);
return 0;
}
#ifdef __cplusplus
extern "C" {
#endif
void EMSCRIPTEN_KEEPALIVE test1()
{
FILE* fp = fopen("/data/idbfs_data.txt", "r+t");
if (fp == NULL) fp = fopen("/data/idbfs_data.txt", "w+t");
int count = 0;
char *pcstr = new char[2048];
pcstr = "";
if (fp) {
fscanf(fp, "%s", pcstr);
cout<<"old context:"<<pcstr<<endl;
count++;
fseek(fp, 0, SEEK_SET);
char *pcstr2 = new char[2048];
pcstr2 = "你好web";
fprintf(fp, "%s", pcstr2);
fclose(fp);
printf("new context:%s\n", pcstr2);
sync_idbfs();
}
else {
printf("fopen failed.\n");
}
cout<<"file write done"<<endl;
}
#ifdef __cplusplus
}
#endif
编译命令
docker run --rm -v $(pwd):/src -u $(id -u):$(id -g) emscripten/emsdk emcc --std=c++11 sayhello.cpp -s WASM=1 -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall']" -s -sFETCH -s -lidbfs.js --shell-file templates/shell_minimal.html -o helloworld3.js
运行结果
总结
新文件系统:WasmFS(进行中…) WasmFS是一个高性能,完全多线程,基于WebAssembly的Emscripten文件系统层,它将取代现有的JavaScript版本。 基于 JavaScript 的文件系统最初是在支持 pthreads 之前编写的,当时用 JS 编写代码更为理想。因此,它在pthreads 构建中具有开销,因为我们必须代理到完成所有文件系统操作的主线程。相反,WasmFS被编译为Wasm,并具有完全的多线程支持。它还旨在更加模块化和可扩展。
|