WebAssembly第四章 C\C++调用JavaScript和HTTP请求文件读写操作示例 关键字:wasm emcc js c++ c

系列文章目录

第一章 WebAssembly概念
第二章 Emscripten详解
第三章 JavaScript调用C\C++
第四章 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;
  // 'jsString.length' would return the length of the string as UTF-16
  // units, but Emscripten C strings operate as UTF-8.
  var stringOnWasmHeap = _malloc(lengthBytes);
  stringToUTF8(jsString, stringOnWasmHeap, lengthBytes);
  return stringOnWasmHeap;
});
printf("UTF8 string says: %s\n", str);
free(str); // Each call to _malloc() must be paired with free(), or heap memory will leak!

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"{
    
    
    //声明在外部模块中定义的custom_add函数
        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); // Also free data on failure.
}

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);
      // The data is now available at fetch->data[0] through fetch->data[fetch->numBytes-1];
    } 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,并具有完全的多线程支持。它还旨在更加模块化和可扩展。

猜你喜欢

转载自blog.csdn.net/wangxudongx/article/details/125833200