自定义外部引用

    下面的教程是对 WasmEdge 中的 externref 示例的总结。

    Wasm 文件应该包含接受 externref 的 host 函数导入。以为例(WAT是相对应的文本格式)。

    你可以通过 在线实时工具将 wat 转换为 wasm。注意页面上的 reference types 复选框必须勾选。

    在执行 Wasm 执行前,host 模块必须实现以及在 WasmEdge 中注册。假设下面的代码保存为 main.c

    1. #include <wasmedge/wasmedge.h>
    2. #include <stdio.h>
    3. uint32_t SquareFunc(uint32_t A) { return A * A; }
    4. uint32_t AddFunc(uint32_t A, uint32_t B) { return A + B; }
    5. uint32_t MulFunc(uint32_t A, uint32_t B) { return A * B; }
    6. // Host 函数通过外部引用调用 `SquareFunc`
    7. WasmEdge_Result ExternSquare(void *Data, WasmEdge_MemoryInstanceContext *MemCxt,
    8. const WasmEdge_Value *In, WasmEdge_Value *Out) {
    9. // 函数类型: {externref, i32} -> {i32}
    10. uint32_t (*Func)(uint32_t) = WasmEdge_ValueGetExternRef(In[0]);
    11. uint32_t C = Func(WasmEdge_ValueGetI32(In[1]));
    12. Out[0] = WasmEdge_ValueGenI32(C);
    13. return WasmEdge_Result_Success;
    14. }
    15. // Host 函数通过外部引用调用 `AddFunc`
    16. WasmEdge_Result ExternAdd(void *Data, WasmEdge_MemoryInstanceContext *MemCxt,
    17. const WasmEdge_Value *In, WasmEdge_Value *Out) {
    18. // 函数类型: {externref, i32, i32} -> {i32}
    19. uint32_t (*Func)(uint32_t, uint32_t) = WasmEdge_ValueGetExternRef(In[0]);
    20. uint32_t C = Func(WasmEdge_ValueGetI32(In[1]), WasmEdge_ValueGetI32(In[2]));
    21. Out[0] = WasmEdge_ValueGenI32(C);
    22. return WasmEdge_Result_Success;
    23. }
    24. // host 函数通过外部引用调用 `ExternMul`
    25. WasmEdge_Result ExternMul(void *Data, WasmEdge_MemoryInstanceContext *MemCxt,
    26. const WasmEdge_Value *In, WasmEdge_Value *Out) {
    27. // 函数类型: {externref, i32, i32} -> {i32}
    28. uint32_t (*Func)(uint32_t, uint32_t) = WasmEdge_ValueGetExternRef(In[0]);
    29. uint32_t C = Func(WasmEdge_ValueGetI32(In[1]), WasmEdge_ValueGetI32(In[2]));
    30. Out[0] = WasmEdge_ValueGenI32(C);
    31. return WasmEdge_Result_Success;
    32. }
    33. // 创建“extern_module”引入对象的辅助函数。
    34. WasmEdge_ImportObjectContext *CreateExternModule() {
    35. WasmEdge_String HostName;
    36. WasmEdge_FunctionTypeContext *HostFType = NULL;
    37. WasmEdge_FunctionInstanceContext *HostFunc = NULL;
    38. enum WasmEdge_ValType P[3], R[1];
    39. HostName = WasmEdge_StringCreateByCString("extern_module");
    40. WasmEdge_ImportObjectContext *ImpObj = WasmEdge_ImportObjectCreate(HostName);
    41. WasmEdge_StringDelete(HostName);
    42. // 添加 host 函数 "functor_square": {externref, i32} -> {i32}
    43. P[0] = WasmEdge_ValType_ExternRef;
    44. P[1] = WasmEdge_ValType_I32;
    45. R[0] = WasmEdge_ValType_I32;
    46. HostFType = WasmEdge_FunctionTypeCreate(P, 2, R, 1);
    47. HostFunc = WasmEdge_FunctionInstanceCreate(HostFType, ExternSquare, NULL, 0);
    48. WasmEdge_FunctionTypeDelete(HostFType);
    49. HostName = WasmEdge_StringCreateByCString("functor_square");
    50. WasmEdge_ImportObjectAddFunction(ImpObj, HostName, HostFunc);
    51. WasmEdge_StringDelete(HostName);
    52. // 添加 host 函数 "class_add": {externref, i32, i32} -> {i32}
    53. P[2] = WasmEdge_ValType_I32;
    54. HostFType = WasmEdge_FunctionTypeCreate(P, 3, R, 1);
    55. HostFunc = WasmEdge_FunctionInstanceCreate(HostFType, ExternAdd, NULL, 0);
    56. WasmEdge_FunctionTypeDelete(HostFType);
    57. HostName = WasmEdge_StringCreateByCString("class_add");
    58. WasmEdge_ImportObjectAddFunction(ImpObj, HostName, HostFunc);
    59. WasmEdge_StringDelete(HostName);
    60. // 添加 host 函数 "func_mul": {externref, i32, i32} -> {i32}
    61. HostFType = WasmEdge_FunctionTypeCreate(P, 3, R, 1);
    62. HostFunc = WasmEdge_FunctionInstanceCreate(HostFType, ExternMul, NULL, 0);
    63. WasmEdge_FunctionTypeDelete(HostFType);
    64. HostName = WasmEdge_StringCreateByCString("func_mul");
    65. WasmEdge_ImportObjectAddFunction(ImpObj, HostName, HostFunc);
    66. WasmEdge_StringDelete(HostName);
    67. return ImpObj;
    68. }
    69. int main() {
    70. WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
    71. WasmEdge_ImportObjectContext *ImpObj = CreateExternModule();
    72. WasmEdge_Value P[3], R[1];
    73. WasmEdge_String FuncName;
    74. WasmEdge_Result Res;
    75. Res = WasmEdge_VMRegisterModuleFromImport(VMCxt, ImpObj);
    76. if (!WasmEdge_ResultOK(Res)) {
    77. printf("Import object registration failed\n");
    78. return EXIT_FAILURE;
    79. }
    80. Res = WasmEdge_VMLoadWasmFromFile(VMCxt, "funcs.wasm");
    81. if (!WasmEdge_ResultOK(Res)) {
    82. printf("WASM file loading failed\n");
    83. return EXIT_FAILURE;
    84. }
    85. Res = WasmEdge_VMValidate(VMCxt);
    86. if (!WasmEdge_ResultOK(Res)) {
    87. printf("WASM validation failed\n");
    88. return EXIT_FAILURE;
    89. }
    90. Res = WasmEdge_VMInstantiate(VMCxt);
    91. printf("WASM instantiation failed\n");
    92. return EXIT_FAILURE;
    93. }
    94. // 测试用例 1:调用 add -- 1234 + 5678
    95. P[0] = WasmEdge_ValueGenExternRef(AddFunc);
    96. P[1] = WasmEdge_ValueGenI32(1234);
    97. P[2] = WasmEdge_ValueGenI32(5678);
    98. FuncName = WasmEdge_StringCreateByCString("call_add");
    99. Res = WasmEdge_VMExecute(VMCxt, FuncName, P, 3, R, 1);
    100. WasmEdge_StringDelete(FuncName);
    101. if (WasmEdge_ResultOK(Res)) {
    102. printf("Test 1 -- `call_add` -- 1234 + 5678 = %d\n",
    103. WasmEdge_ValueGetI32(R[0]));
    104. } else {
    105. printf("Test 1 -- `call_add` -- 1234 + 5678 -- failed\n");
    106. return EXIT_FAILURE;
    107. // 测试用例 2:调用 mul -- 789 * 4321
    108. P[0] = WasmEdge_ValueGenExternRef(MulFunc);
    109. P[1] = WasmEdge_ValueGenI32(789);
    110. P[2] = WasmEdge_ValueGenI32(4321);
    111. FuncName = WasmEdge_StringCreateByCString("call_mul");
    112. Res = WasmEdge_VMExecute(VMCxt, FuncName, P, 3, R, 1);
    113. WasmEdge_StringDelete(FuncName);
    114. if (WasmEdge_ResultOK(Res)) {
    115. printf("Test 2 -- `call_mul` -- 789 * 4321 = %d\n",
    116. WasmEdge_ValueGetI32(R[0]));
    117. } else {
    118. printf("Test 2 -- `call_mul` -- 789 * 4321 -- failed\n");
    119. return EXIT_FAILURE;
    120. }
    121. // 测试用例 3:调用 square -- 8256^2
    122. P[0] = WasmEdge_ValueGenExternRef(SquareFunc);
    123. P[1] = WasmEdge_ValueGenI32(8256);
    124. FuncName = WasmEdge_StringCreateByCString("call_square");
    125. Res = WasmEdge_VMExecute(VMCxt, FuncName, P, 2, R, 1);
    126. if (WasmEdge_ResultOK(Res)) {
    127. printf("Test 3 -- `call_mul` -- 8256 ^ 2 = %d\n",
    128. WasmEdge_ValueGetI32(R[0]));
    129. } else {
    130. printf("Test 3 -- `call_mul` -- 8256 ^ 2 -- failed\n");
    131. return EXIT_FAILURE;
    132. }
    133. return EXIT_SUCCESS;
    134. }

    1. 安装 WasmEdge 共享库。

      详细信息请参阅安装

    2. 如上所示准备 WASM 文件和 main.c 源文件。

    3. 编译

      1. gcc main.c -lwasmedge_c
      2. # 或者你可以在 C++ 场景下使用 g++,或者使用 clang。
    4. 运行测试用例

      1. $ ./a.out
      2. Test 1 -- `call_add` -- 1234 + 5678 = 6912
      3. Test 2 -- `call_mul` -- 789 * 4321 = 3409269
      4. Test 3 -- `call_mul` -- 8256 ^ 2 = 68161536
    1. (module
    2. (type $t0 (func (param externref i32) (result i32)))
    3. ;; Import a host function which type is {externref i32} -> {i32}
    4. (import "extern_module" "functor_square" (func $functor_square (type $t0)))
    5. ;; Wasm function which type is {externref i32} -> {i32} and exported as "call_square"
    6. (func $call_square (export "call_square") (type $t0) (param $p0 externref) (param $p1 i32) (result i32)
    7. (call $functor_square (local.get $p0) (local.get $p1))
    8. )
    9. (memory $memory (export "memory") 1))

    Wasm 函数 “call_square“ 包含一个 externref 参数,并用这个 externref 调用导入的 host 函数 functor_square。所以,当你调用 Wasm 函数 call_square 并传递对象的引用时,host 函数 functor_square 就能获得该对应引用。

    下面的例子展示了如何在 Wasm 里通过 WasmEdge C API 使用 externref

    Wasm 代码必须将 externref 传递给想要访问它的 host 函数。以下文的 wat 为例,这是 的其中一部分:

    1. (module
    2. (type $t0 (func (param externref i32 i32) (result i32)))
    3. (import "extern_module" "func_mul" (func $func_mul (type $t0)))
    4. (func $call_mul (export "call_mul") (type $t0) (param $p0 externref) (param $p1 i32) (param $p2 i32) (result i32)
    5. (call $func_mul (local.get $p0) (local.get $p1) (local.get $p2))
    6. )
    7. (memory $memory (export "memory") 1))

    host 函数 extern_module::func_mulexternref 作为一个函数指针用来将参数 1 和参数 2 相乘并返回其结果。输出的 Wasm 函数 call_mul 调用 func_mul 并传递 externref 和 2 个数字作为参数。

    要实例化上面的 Wasm 示例,host 函数必须注册到 WasmEdge 中。详见 Host 函数。接受 externref 的 host 函数必须知道原始对象的类型。我们以函数指针为例。

    MulFunc“ 是一个函数,将作为 externref 传递给 Wasm。在 host 函数 “func_mul“ 中,你可以使用 “WasmEdge_ValueGetExternRef“ API 从包含 externrefWasmEdge_Value 中获取指针。

    开发人员可以将带有名称的 host 函数添加到导入对象中。

    1. /* 创建一个导入对象。 */
    2. WasmEdge_String HostName = WasmEdge_StringCreateByCString("extern_module");
    3. WasmEdge_ImportObjectContext *ImpObj = WasmEdge_ImportObjectCreate(HostName);
    4. WasmEdge_StringDelete(HostName);
    5. /* 创建一个函数实例并添加到一个导入对象中。 */
    6. enum WasmEdge_ValType P[3], R[1];
    7. P[0] = WasmEdge_ValType_ExternRef;
    8. P[1] = WasmEdge_ValType_I32;
    9. P[2] = WasmEdge_ValType_I32;
    10. R[0] = WasmEdge_ValType_I32;
    11. WasmEdge_FunctionTypeContext *HostFType = WasmEdge_FunctionTypeCreate(P, 3, R, 1);
    12. WasmEdge_FunctionInstanceContext *HostFunc = WasmEdge_FunctionInstanceCreate(HostFType, ExternFuncMul, NULL, 0);
    13. WasmEdge_FunctionTypeDelete(HostFType);
    14. HostName = WasmEdge_StringCreateByCString("func_mul");
    15. WasmEdge_ImportObjectAddFunction(ImpObj, HostName, HostFunc);
    16. WasmEdge_StringDelete(HostName);
    17. ...

    执行

    以为例(WAT是相对应的文本格式)。假设函数 funcs.wasm 被复制到了当前目录。下面的例子展示了如何在 Wasm 里用 WasmEdge C API 使用 externref

    1. /* 创建 VM 上下文。 */
    2. WasmEdge_VMContext *VMCxt = WasmEdge_VMCreate(NULL, NULL);
    3. /* 创建包含 host 函数的导入对象上下文。 */
    4. WasmEdge_ImportObjectContext *ImpObj = /* Ignored ... */;
    5. /* 假设 host 函数被添加到上面的导入对象中。 */
    6. WasmEdge_Value P[3], R[1];
    7. WasmEdge_String FuncName;
    8. WasmEdge_Result Res;
    9. /* 将导入对象注册到 VM。 */
    10. Res = WasmEdge_VMRegisterModuleFromImport(VMCxt, ImpObj);
    11. if (!WasmEdge_ResultOK(Res)) {
    12. printf("Import object registration failed\n");
    13. return EXIT_FAILURE;
    14. }
    15. /* 从文件中加载 WASM。 */
    16. Res = WasmEdge_VMLoadWasmFromFile(VMCxt, "funcs.wasm");
    17. if (!WasmEdge_ResultOK(Res)) {
    18. printf("WASM file loading failed\n");
    19. return EXIT_FAILURE;
    20. }
    21. /* 验证 WASM。 */
    22. if (!WasmEdge_ResultOK(Res)) {
    23. printf("WASM validation failed\n");
    24. return EXIT_FAILURE;
    25. }
    26. /* 实例化 WASM 模块。 */
    27. Res = WasmEdge_VMInstantiate(VMCxt);
    28. if (!WasmEdge_ResultOK(Res)) {
    29. printf("WASM instantiation failed\n");
    30. }
    31. /* 运行 WASM 函数。 */
    32. P[0] = WasmEdge_ValueGenExternRef(AddFunc);
    33. P[1] = WasmEdge_ValueGenI32(1234);
    34. P[2] = WasmEdge_ValueGenI32(5678);
    35. /* 运行 `call_add` 函数。 */
    36. FuncName = WasmEdge_StringCreateByCString("call_add");
    37. Res = WasmEdge_VMExecute(VMCxt, FuncName, P, 3, R, 1);
    38. WasmEdge_StringDelete(FuncName);
    39. if (WasmEdge_ResultOK(Res)) {
    40. printf("Run -- `call_add` -- 1234 + 5678 = %d\n",
    41. WasmEdge_ValueGetI32(R[0]));
    42. } else {
    43. printf("Run -- `call_add` -- 1234 + 5678 -- failed\n");
    44. return EXIT_FAILURE;
    45. }

    上面的例子是传递一个函数引用 externref。下面的例子是关于如何在 C++ 中将对象引用作为 externref 传递给 WASM 的。

    1. class AddClass {
    2. public:
    3. uint32_t add(uint32_t A, uint32_t B) const { return A + B; }
    4. };
    5. AddClass AC;

    然后你可以通过使用 WasmEdge_ValueGenExternRef() API 将对象传入 WasmEdge。

    1. WasmEdge_Value P[3], R[1];
    2. P[0] = WasmEdge_ValueGenExternRef(&AC);
    3. P[1] = WasmEdge_ValueGenI32(1234);
    4. P[2] = WasmEdge_ValueGenI32(5678);
    5. WasmEdge_String FuncName = WasmEdge_StringCreateByCString("call_add");
    6. WasmEdge_Result Res = WasmEdge_VMExecute(VMCxt, FuncName, P, 3, R, 1);
    7. WasmEdge_StringDelete(FuncName);
    8. if (WasmEdge_ResultOK(Res)) {
    9. std::cout << "Result : " << WasmEdge_ValueGetI32(R[0]) std::endl;
    10. // 将会打印 `6912`.
    11. } else {
    12. return EXIT_FAILURE;
    13. }

    在通过引用访问对象的 host 函数中,你可以使用 WasmEdge_ValueGetExternRef() API 来检索对对象的引用。

    1. // 修改上面的教程中的 `ExternAdd`。
    2. WasmEdge_Result ExternAdd(void *, WasmEdge_MemoryInstanceContext *,
    3. const WasmEdge_Value *In, WasmEdge_Value *Out) {
    4. // 函数类型:{externref, i32, i32} -> {i32}
    5. void *Ptr = WasmEdge_ValueGetExternRef(In[0]);
    6. AddClass &Obj = *reinterpret_cast<AddClass *>(Ptr);
    7. uint32_t C =
    8. Obj.add(WasmEdge_ValueGetI32(In[1]), WasmEdge_ValueGetI32(In[2]));
    9. Out[0] = WasmEdge_ValueGenI32(C);
    10. return WasmEdge_Result_Success;
    11. }

    传递一个对象作为仿函数

    与传递类实例一样,需要 functor 对象实例。

    然后你可以通过使用 WasmEdge_ValueGenExternRef() API 将对象传递到 WasmEdge。

    1. WasmEdge_Value P[2], R[1];
    2. P[0] = WasmEdge_ValueGenExternRef(&SS);
    3. P[1] = WasmEdge_ValueGenI32(1024);
    4. WasmEdge_String FuncName = WasmEdge_StringCreateByCString("call_square");
    5. WasmEdge_Result Res = WasmEdge_VMExecute(VMCxt, FuncName, P, 2, R, 1);
    6. WasmEdge_StringDelete(FuncName);
    7. if (WasmEdge_ResultOK(Res)) {
    8. std::cout << "Result : " << WasmEdge_ValueGetI32(R[0]) std::endl;
    9. // 将会打印 `1048576`.
    10. } else {
    11. return EXIT_FAILURE;
    12. }

    在通过引用访问对象的 host 函数中,你可以使用 WasmEdge_ValueGetExternRef API 来检索对该对象的引用,而该引用是一个 functor。

    1. // 修改上文教程中的 `ExternSquare`。
    2. WasmEdge_Result ExternSquare(void *, WasmEdge_MemoryInstanceContext *,
    3. const WasmEdge_Value *In, WasmEdge_Value *Out) {
    4. // 函数类型:{externref, i32, i32} -> {i32}
    5. void *Ptr = WasmEdge_ValueGetExternRef(In[0]);
    6. SquareStruct &Obj = *reinterpret_cast<SquareStruct *>(Ptr);
    7. uint32_t C = Obj(WasmEdge_ValueGetI32(In[1]));
    8. Out[0] = WasmEdge_ValueGenI32(C);
    9. return WasmEdge_Result_Success;
    10. }

    传递 STL 对象

    提供了与 host 函数交互的函数可以访问 C++ STL 对象的示例(WAT 是相对应的文本格式)。

    假设 WASM 文件 stl.wasm 被复制到了当前目录。

    以下文的 std::ostreamstd::string 为例。假设有一个 host 函数可以通过 externref 访问 std::ostreamstd::string

    1. // Host 函数,通过 std::ostream 输出 std::string
    2. WasmEdge_Result ExternSTLOStreamStr(void *, WasmEdge_MemoryInstanceContext *,
    3. const WasmEdge_Value *In,
    4. WasmEdge_Value *) {
    5. // 函数类型:{externref, externref} -> {}
    6. void *Ptr0 = WasmEdge_ValueGetExternRef(In[0]);
    7. void *Ptr1 = WasmEdge_ValueGetExternRef(In[1]);
    8. std::ostream &RefOS = *reinterpret_cast<std::ostream *>(Ptr0);
    9. std::string &RefStr = *reinterpret_cast<std::string *>(Ptr1);
    10. RefOS << RefStr;
    11. return WasmEdge_Result_Success;
    12. }

    假设上面的 host 函数被添加到一个导入对象 ImpObj 中,并且 ImpObj 被注册到一个虚拟机上下文 VMCxt 中。然后你可以通过以下代码实例化 Wasm 模块:

    1. WasmEdge_Result Res = WasmEdge_VMLoadWasmFromFile(VMCxt, "stl.wasm");
    2. if (!WasmEdge_ResultOK(Res)) {
    3. printf("WASM file loading failed\n");
    4. return EXIT_FAILURE;
    5. }
    6. Res = WasmEdge_VMValidate(VMCxt);
    7. if (!WasmEdge_ResultOK(Res)) {
    8. printf("WASM validation failed\n");
    9. return EXIT_FAILURE;
    10. }
    11. Res = WasmEdge_VMInstantiate(VMCxt);
    12. if (!WasmEdge_ResultOK(Res)) {
    13. printf("WASM instantiation failed\n");
    14. return EXIT_FAILURE;
    15. }
    1. std::string PrintStr("Hello world!");
    2. WasmEdge_Value P[2], R[1];
    3. P[0] = WasmEdge_ValueGenExternRef(&std::cout);
    4. P[1] = WasmEdge_ValueGenExternRef(&PrintStr);
    5. WasmEdge_String FuncName = WasmEdge_StringCreateByCString("call_ostream_str");
    6. WasmEdge_Result Res = WasmEdge_VMExecute(VMCxt, FuncName, P, 2, R, 1);
    7. // 将会打印 "Hello world!" 到标准输出 stdout.
    8. WasmEdge_StringDelete(FuncName);
    9. if (!WasmEdge_ResultOK(Res)) {
    10. return EXIT_FAILURE;
    11. }

    对于其它 C++ STL 对象的情况,例如 std::vector<T>std::map<T, U>std::set<T>,如果 中的类型是正确的,该对象就可以在 host 函数中被正确访问到。