Qt6.8.2中JavaScript调用WebAssembly的js文件<2>
上一篇文章介绍了javaScript如何调用简单的加法运算,该js的编译是采用cmd指令进行的,今天我为大家介绍如何使用QtCreator的WebAssembly套件编译成js、wasm,并且让javascript进行调用。
那么还是以简单的加法运行为例。
功能介绍
开发环境:win11 + Qt 6.8.2
1:创建一个简单项目
打开QtCreator开发工具,选择一个模块,按照下面的步骤进行操作
2:添加导出函数
因为只是导出一个简单的加法函数,直接在"main.cpp"中添加就可以了。
程序创建后未曾修改过的的"main.cpp"
添加"add"导出函数
#include <emscripten.h>
extern "C" EMSCRIPTEN_KEEPALIVE int add(int a, int b)
{
return a + b;
}
代码解析:将 add
函数以 C 语言的链接方式导出到 WebAssembly 模块中,并确保其不被编译器删除。
使用场景:当需要通过 JavaScript 调用 C++ 函数时(例如在 Qt Creator 编译的 WebAssembly 项目中),此代码为函数提供了跨语言调用的桥梁。
此时没有涉及到页面操作,所以去除了main函数里面的内容,对mian函数的修改,如下:
int main()
{
return 0;
} // 必须保留main函数
踩坑:在这里必须要保留main函数,否则生成的js胶水代码被html调用时在浏览器中会有callMain未定义的错误!!!
完整的main.cpp的代码,如下:
#include <emscripten.h>
extern "C" EMSCRIPTEN_KEEPALIVE int add(int a, int b) {
return a + b;
}
int main()
{
return 0;
} // 必须保留main函数
3:修改pro配置信息
原始未修改的pro文件,如下所示:
1:去除widget文件的调用
2:添加生成目录
为了方便查找到生成的js文件,可以添加生成目录
好了到这里对代码的修改就已经完成了,对项目进行清除、构建。程序应该是不会出问题的,如果有问题,请私信我哟~咱们一块解决!
编译完成后,就可以看到bin文件夹下面的内容了。
4:html调用胶水代码
在bin目录下创建一个xx.html文件,用来调用SingleThreadDemo.js文件。
假设调用胶水代码的html名称是:demo1.html
步骤一
调用SingleThreadDemo.js文件
<script type="text/javascript" src="SingleThreadDemo.js"></script>
先加载文件,加载完成后再进行操作,等待js文件加载完以后再做处理。
代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>调用 SingleThreadDemo</title>
<script type="text/javascript" src="SingleThreadDemo.js"></script>
</head>
<body>
<script>
document.addEventListener('DOMContentLoaded', async () => {
try {
console.log('SingleThreadDemo.js已定义!加载完成!');
} catch (error) {
console.error('模块初始化或调用函数时出错:', error);
}
});
</script>
</body>
</html>
步骤二
在bin文件夹下打开cmd命令行,启动python服务
python -m http.server
步骤三
使用谷歌浏览器打开demo1.html查看控制台信息,SingleThreadDemo.js胶水代码是否加载成功。
输入地址:http://localhost:8000/demo1.html
发现控制台有一个404的错误
在谷歌浏览器中也同样有错误输出
解决方法:
因为在bin文件夹中没有找到favicon.ico图片资源,导致有问题。该图片其实是Qt的图标,只需要从电脑中搜索到该图片后,拷贝到bin文件中,就不会出现这个问题了。
此时,再一次在浏览器中打开"localhost:8000/demo1.html"地址,就不会再报错了。
使用document.addEventListener监听整个文档,事件触发时,执行传入的回调函数,使用try/catch方法捕获异步操作中的错误,比如:文件加载失败、函数调用异常等等,方便我们排查错误。
步骤四
调用SingleThreadDemo.js胶水代码。这也是调用的最核心部分了!
首先,打开SingleThreadDemo.js胶水代码,发现虽然只导出了一个加法函数,但是js居然有一万多行代码,很是震惊呀!
直接将代码拉到最底端,会有一个导出方法,如下:
if (typeof exports === 'object' && typeof module === 'object')
module.exports = SingleThreadDemo_entry;
else if (typeof define === 'function' && define['amd'])
define([], () => SingleThreadDemo_entry);
这句代码时一个典型的UMD(Universal Module Definition)模式,让 SingleThreadDemo_entry
模块在不同的 JavaScript 环境中(如 CommonJS、AMD 或全局变量)都能被正确导出。
由此得出,在demo.html中使用胶水代码时,导出的模块是:SingleThreadDemo_entry。
我在看其他博友给出的例子中,很多都是使用Module的调用,比如:
刚开始调用的时候,我也是拿着"Module"来操作,但是浏览器控制台一直输出,Module找不到,于是我就顺着这个思路开始解决,走了好几天弯路,我才明白,别人使用Module能成功是因为导出的名称叫Module,而我导出的胶水代码中时SingleThreadDemo_entry!
既然知道该如何正确使用导出模块后,那就尝试使用加法函数吧!
步骤五
在demo1.html中输出SingleThreadDemo_entry是什么类型?
console.log('类型是:',typeof SingleThreadDemo_entry());
经过运行后发现,此时SingleThreadDemo_entry()是一个object,那么该如何访问它里面的函数呢?
SingleThreadDemo_entry().then(function(module){
console.log('SingleThreadDemo_entry().then(function(module)');
var result = module._add(7,4);
console.log("5 + 3 =", result); // 应该输出 8
});
代码解析分析:
1:SingleThreadDemo_entry()
调用模块入口函数,返回Promise,因为加载模块是一个异步操作,使用Promise可以很好的处理这个过程。
该函数有三个返回状态
- pending(进行中):初始状态,表示异步操作还在进行中。
- fulfilled(已成功):表示异步操作已经成功完成。
- rejected(已失败):表示异步操作失败。
2:.then()方法
.then()是Promise对象的方法,用于注册回调函数。它接受两个可选参数:
- 第一个参数:是一个回调函数,当 Promise 对象的状态变为
fulfilled
时,这个回调函数会被执行。 - 第二个参数:也是一个回调函数,当 Promise 对象的状态变为
rejected
时,这个回调函数会被执行。不过在SingleThreadDemo_entry().then(function(module) { ... })
中,只提供了第一个参数。
3:function(module) {...}
回调函数,这是传递给.then()方法的回调函数,当SingleThreadDemo_entry返回的Promise对象编程fulfilled时,这个回调函数会被调用。
参数module是Promise解决的值,通常在加载WebAssembly模块的场景中,module可能是加载完成的WebAssembly模块实例,通过这个实例可以调用模块导出的函数。
完整的html代码,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>调用 SingleThreadDemo</title>
<script type="text/javascript" src="SingleThreadDemo.js"></script>
</head>
<body>
<script>
document.addEventListener('DOMContentLoaded', async () => {
try {
console.log('SingleThreadDemo.js已定义!加载完成!');
if(typeof SingleThreadDemo_entry === 'undefined')
{
console.error('SingleThread2_entry未定义,请检查胶水代码');
return;
}
console.log('类型是:',typeof SingleThreadDemo_entry());
SingleThreadDemo_entry().then(function(module){
console.log('SingleThreadDemo_entry().then(function(module)');
var result = module._add(7,4);
console.log("7 + 4 =", result);
});
} catch (error) {
console.error('模块初始化或调用函数时出错:', error);
}
});
</script>
</body>
</html>
还有另一种调用方式,也可以成功
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>调用 SingleThreadDemo</title>
<script type="text/javascript" src="SingleThreadDemo.js"></script>
</head>
<body>
<script>
console.log('typeof SingleThreadDemo_entry = ', typeof SingleThreadDemo_entry);
SingleThreadDemo_entry().then((instance) => {
console.log('现在 instance 是你需要的实例对象');
var resultAdd = instance._add(5, 6);
console.log('result = ', resultAdd);
}).catch((error) => {
console.error('加载 WebAssembly 模块时出错:', error);
});
</script>
</body>
</html>
好了,重新启动下"http://localhost:8000/demo1.html"就能看到输出结果了!
简单的使用QtCreator的WebAssembly编译的加法导出函数已经讲解完了,希望对一些初学者有帮助哟!
我是糯诺诺米团,一名C++程序媛~