如何从 JavaScript 执行 Kotlin WebAssembly 函数?
Posted
技术标签:
【中文标题】如何从 JavaScript 执行 Kotlin WebAssembly 函数?【英文标题】:How to execute Kotlin WebAssembly function from JavaScript? 【发布时间】:2019-06-05 21:14:08 【问题描述】:我的目标是编写一个 Kotlin 库,将其编译为 WebAssembly 并从 JS 调用它的函数。几个小时后,我试图让一个简单的 hello world 工作。关于这个主题的文档要么不存在,要么隐藏得很好。
这是我的 kotlin 文件:
@Used
public fun hello()
println("Hello world!")
fun main(args: Array<String>)
println("main() function executed!")
当我将它编译成 WebAssembly 时,我得到一个 hello.wasm 和 hello.wasm.js 文件。
首先我尝试使用类似的东西来执行该功能:
WebAssembly.instantiateStreaming(fetch('hello.wasm'), importObject)
.then(obj => obj.instance.exports.hello());
然后我明白我需要在 importObject 参数中传递我的 hello.wasm.js 文件中的导入。所以我想我需要使用 hello.wasm.js 文件来正确初始化我的 wasm 程序。
当我像下面这样加载我的 wasm 时,我没有收到任何错误,并且执行了 main() 函数。
<script wasm="hello.wasm" src="hello.wasm.js"></script>
但是如何从 javascript 执行 hello() 函数呢?我发现的唯一 kotlin wasm 示例不是调用特定函数,而是从 main() 函数渲染某些内容。
非常感谢任何指向相关文档的链接。
更新: 我设法执行了该功能,但我不认为这是正确的方法:
<script wasm="hello.wasm" src="hello.wasm.js"></script>
<script>
WebAssembly.instantiateStreaming(fetch('hello.wasm'), konan_dependencies)
.then(obj => obj.instance.exports['kfun:hello$$ValueType']());
</script>
问题是,如果我这样做,我的 wasm 文件会被提取两次。
只加载没有 wasm 属性的 hello.wasm.js 文件会出现以下错误:
Uncaught Error: Could not find the wasm attribute pointing to the WebAssembly binary.
at Object.konan.moduleEntry (stats.wasm.js:433)
at stats.wasm.js:532
【问题讨论】:
我最终用 Rust 编写了我的库,工具和文档要好得多,一切都按预期工作。 【参考方案1】:我最近自己对此进行了一些研究,据我了解,到目前为止,您的用例并未得到真正的支持。您正在寻找的本质上是一个库,但是如果您查看 Kotlin/Native 的 documentation,它会指出:
支持以下二进制类型(请注意,并非所有类型都适用于所有本机平台):
[...]
sharedLib - 一个共享的原生库 - 除了 wasm32 之外的所有原生目标
staticLib - 一个静态原生库 - 除了 wasm32 之外的所有原生目标
据我了解,困难在于 Javascript 和 WebAssembly 之间的数据传递,因为它只支持整数或通过线性内存的迂回方式。
【讨论】:
【参考方案2】:目前我喜欢在 Kotlin WASM 项目上工作,而且(对我这样的研究人员来说很好)我们的团队没有经验。抱歉,我的脑海中仍然有同样的问号,但已经很远了。
在我的例子中,我找到了很多 wasm 的例子,但感觉我不能使用它们,因为在 Kotlin 中情况不同......实际上情况并非如此,事情只是隐藏在构建过程中我们的情况既是福也是祸。没有文档,但 kotlin 为我们做事!
对我来说,填补这一信息空白的最佳策略是深入了解生成的 *wasm.js 文件。它可能看起来很吓人,它是 JavaScript,但实际上很容易了解 Kotlin 实际上能够为您做什么。最好的:如果您查看了 Api 参考 (https://developer.mozilla.org/en-US/docs/WebAssembly),或者您只是看了一个教程但无法应用您所看到的内容,那么您很可能会在这里找到这些代码!
谁读过这篇文章并想尝试一下:您应该准备一个构建设置,允许您将内容附加到生成的 *.wasm.js 文件中。我的设置中的重要部分:
val jsinterface by tasks.creating(Exec::class)
val kotlincExecutable = "$project.properties["konanHome"]/bin/kotlinc"
inputs.property("kotlincExecutable", kotlincExecutable)
outputs.file(jsInterfaceKlibFileName)
val ktFile = file(workingDir).resolve("src/wasm32Main/kotlin/js_interface/imported_js_funcs.kt")
val jsStubFile = file(workingDir).resolve("src/wasm32Main/js/stub.js")
executable(kotlincExecutable)
args(
"-include-binary", jsStubFile,
"-produce", "library",
"-o", jsInterfaceKlibFileName,
"-target", "wasm32",
ktFile
)
val jsinterop by tasks.creating(Exec::class)
dependsOn(jsinterface) //inserts customized js functions on xxx.wasm.js
//jsinterop -pkg kotlinx.interop.wasm.dom -o build/klib/kotlinx.interop.wasm.dom-jsinterop.klib -target wasm32
workingDir("./")
executable("$project.properties["konanHome"]/bin/jsinterop")
args("-pkg", "kotlinx.interop.wasm.dom", "-o", jsinteropKlibFileName.toString(), "-target", "wasm32")
// generate jsinterop before native compile
tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinNativeCompile::class).all
dependsOn(jsinterop)
有多少人对该主题感兴趣?
【讨论】:
【参考方案3】:据我所知,您需要将该函数导出到一个变量中以继续使用它,或者留在流式传输的实例中。
所以我认为应该是这样的:
let helloFunc;
WebAssembly.instantiateStreaming(fetch('hello.wasm'), importObject)
.then((instance) =>
helloFunc = instance.exports.hello;
);
之后,您可以将变量用作函数并像这样调用它:
helloFunc();
否则,如果您不需要导出函数供以后使用,您可以像这样在实例内部使用它:
WebAssembly.instantiateStreaming(fetch('hello.wasm'), importObject)
.then((instance) =>
instance.exports.hello();
);
如果您不想像我使用的那样使用对象解构,您可以继续使用 obj.instance 的常规语法。
【讨论】:
还值得注意的是,有时函数名称 get 会在进行 wasm 编译时被修改。例如,C++ 喜欢在函数名称的开头添加类似“_Z”的内容。要分析 kotlin 是否也会发生这种情况,您可以将 WebAssembly 代码组装成 WAT(WebAssembly 文本格式)来检查。 我无法执行这样的函数,因为没有使用所有 konan 导入正确初始化 wasm。 *.wasm.js 文件包含像initialAndRun(..) 这样的函数,它可以正确初始化所有内容,但只运行主函数而不返回实例。由于这是一个生成的文件,我真的认为我遗漏了一些东西。以上是关于如何从 JavaScript 执行 Kotlin WebAssembly 函数?的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin - 如何执行 onCompleteListener 从 Firestore 获取数据?
从 ktor 提供 kotlin 多平台 javascript
使用 Webview 将对象从 Javascript 发送到 Kotlin