【问题标题】:How to execute Kotlin WebAssembly function from JavaScript?如何从 JavaScript 执行 Kotlin WebAssembly 函数?
【发布时间】: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 编写了我的库,工具和文档要好得多,一切都按预期工作。

标签: javascript kotlin webassembly kotlin-native kotlin-js-interop


【解决方案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(..) 这样的函数,它可以正确初始化所有内容,但只运行主函数而不返回实例。由于这是一个生成的文件,我真的认为我遗漏了一些东西。