wasm技术介绍 里可以看到如何导出函数,并且在宿主机里进行调用。本文介绍如何进行导入函数,从宿主机导入函数并在wasm函数里使用。 还是基于tinygo 来进行演示。

首先定义导入函数

//go:build tinygo

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

//export  CallFromHost
func CallFromHost() int32

//export Add
func Add(a, b int32) int32 {
	return a + b
}

type User struct {
	Name string

	Age int
}

//export CallHost
func CallHost() int32 {
	return CallFromHost()
}

//export AddUser
func AddUser(nameData *byte, nameLen int32, age int32) bool {
	fmt.Println(nameLen, age)
	name := RawBytePtrToString(nameData, int(nameLen))

	user := User{Name: name, Age: int(age)}
	fmt.Printf("Add User:%v\n", user)
	return true
}

func main() {
	// 这里必须引用下
	CallFromHost()
	fmt.Println("Hello, WebAssembly!")
}

func RawBytePtrToString(raw *byte, size int) string {
	//nolint
	return *(*string)(unsafe.Pointer(&reflect.SliceHeader{
		Data: uintptr(unsafe.Pointer(raw)),
		Len:  size,
		Cap:  size,
	}))
}

func RawBytePtrToByteSlice(raw *byte, size int) []byte {
	//nolint
	return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
		Data: uintptr(unsafe.Pointer(raw)),
		Len:  size,
		Cap:  size,
	}))
}

定义 CallFromHost 函数是从宿主机导入的。也是通过 //export 标识的。只不过这里只定义了函数体,没有定义实现。需要注意的一点,需要在 main 函数下引用才行。否则在生成的wasm文件中找不到。

然后又定义了一个导出函数 CallHost , 来调用 CallFromHost 函数。

首先进行编译

tinygo build -target wasip1 -o main.wasm main.go

可以通过wasm2wat 来查看是否包含了导入函数。

wasm2wat main.wasm | grep import
  (import "wasi_snapshot_preview1" "fd_write" (func $runtime.fd_write (type 6)))
  (import "wasi_snapshot_preview1" "proc_exit" (func $runtime.proc_exit (type 2)))
  (import "wasi_snapshot_preview1" "clock_time_get" (func $runtime.clock_time_get (type 19)))
  (import "wasi_snapshot_preview1" "args_sizes_get" (func $runtime.args_sizes_get (type 7)))
  (import "wasi_snapshot_preview1" "args_get" (func $runtime.args_get (type 7)))
  (import "env" "CallFromHost" (func $CallFromHost (type 9)))
  (import "wasi_snapshot_preview1" "random_get" (func $__imported_wasi_snapshot_preview1_random_get (type 7)))
        call $__imported_wasi_snapshot_preview1_random_get

可以看到 通过env 模块导入了 CallFromHost 函数。

那么,在宿主机上如何进行函数导入呢?还是使用 wasmtime-go sdk 说明

package main

import (
	"fmt"
	"log"

	"github.com/bytecodealliance/wasmtime-go/v29"
)

func main() {
	engine := wasmtime.NewEngine()
	store := wasmtime.NewStore(engine)
	// 配置 WASI 上下文
	wasiConfig := wasmtime.NewWasiConfig()
	wasiConfig.InheritStdout() // 继承标准输出
	store.SetWasi(wasiConfig)
	module, err := wasmtime.NewModuleFromFile(engine, "main.wasm")
	check(err)

	// 创建 Linker 并配置默认的 WASI 导入
	linker := wasmtime.NewLinker(engine)
	err = linker.DefineWasi()
	check(err)

	linker.DefineFunc(store, "env", "CallFromHost", func() int32 {
		return 12
	})

	// 实例化模块
	instance, err := linker.Instantiate(store, module)
	check(err)

	callHostF := instance.GetExport(store, "CallHost").Func()
	ret, err := callHostF.Call(store)
	check(err)
	fmt.Println("CallHost ret", ret, ret.(int32) == 12)
	// 调用导出的函数
	// 获取导出的 Add 函数并调用
	addF := instance.GetExport(store, "Add").Func()
	val, err := addF.Call(store, 6, 27)
	check(err)
	fmt.Printf("add(6, 27) = %d\n", val.(int32))

	memory := instance.GetExport(store, "memory").Memory()
	fmt.Println(memory.DataSize(store), memory.Data(store))
	// 获取导出的 malloc 函数
	malloc := instance.GetExport(store, "malloc").Func()
	if malloc == nil {
		log.Fatalf("malloc function not found")
	}

	addUser := instance.GetExport(store, "AddUser").Func()
	if addUser == nil {
		log.Fatalf("AddUser function not found")
	}
	name := "Alice"
	nameData := []byte(name)
	nameLen := int32(len(name))
	// 调用 malloc 分配内存
	mallocResult, err := malloc.Call(store, nameLen+1)
	if err != nil {
		log.Fatalf("Failed to call malloc: %s", err)
	}
	namePtr := mallocResult.(int32)

	fmt.Println("namePtr", namePtr)
	fmt.Println(memory.DataSize(store), memory.Data(store))
	mem := memory.UnsafeData(store)
	fmt.Println("mem", len(mem))
	copy(mem[namePtr:], nameData)
	age := int32(30)

	// 调用 AddUser 函数
	result, err := addUser.Call(store, namePtr, nameLen, age)
	if err != nil {
		log.Fatalf("Failed to call AddUser: %s", err)
	}
	fmt.Println(result)

	// 获取导出的 free 函数并释放内存
	free := instance.GetExport(store, "free").Func()
	if free != nil {
		_, err = free.Call(store, namePtr)
		if err != nil {
			log.Fatalf("Failed to call free: %s", err)
		}
	}
}

func check(err error) {
	if err != nil {
		panic(err)
	}
}

我们通过 linker.DefineFunc 定义了导入函数,此函数没有输入参数,只有int32的返回值。 通过对 CallHost 拿到了最终的返回值。

	callHostF := instance.GetExport(store, "CallHost").Func()
	ret, err := callHostF.Call(store)
	check(err)
	fmt.Println("CallHost ret", ret, ret.(int32) == 12)