如何从 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.wasmhello.wasm.js 文件。

首先我尝试使用类似的东西来执行该功能:

WebAssembly.instantiateStreaming(fetch('hello.wasm'), importObject)
    .then(obj => obj.instance.exports.hello());

然后我明白我需要在 importObject 参数中传递我的 hello.wasm.js 文件中的导入。所以我想我需要使用 hello.wasm.js 文件来正确初始化我的 wasm 程序。

当我像下面这样加载我的 wasm 时,我没有收到任何错误,并且执行了 ma​​in() 函数。

<script wasm="hello.wasm" src="hello.wasm.js"></script>

但是如何从 javascript 执行 hello() 函数呢?我发现的唯一 kotlin wasm 示例不是调用特定函数,而是从 ma​​in() 函数渲染某些内容。

非常感谢任何指向相关文档的链接。


更新: 我设法执行了该功能,但我不认为这是正确的方法:

<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

如何从 Flutter App 中删除 Kotlin 支持?

如何立即从范围内取消Kotlin协程(返回)?]

如何在 Kotlin 的 JS 接口中使用可选参数调用