การส่ง String
ฟังก์ชัน WebAssembly รับและส่งกลับได้เฉพาะ numeric type เท่านั้น — integer และ float ไม่มี string type วิธีแก้ไขมาตรฐานคือ encode ข้อมูล string เป็น byte ใน linear memory แล้วส่ง integer coordinate (pointer และ length) เพื่อระบุตำแหน่งของ byte เหล่านั้น
รูปแบบการทำงาน
หัวข้อที่มีชื่อว่า “รูปแบบการทำงาน”การส่งกลับ JavaScript string ไปยัง Wasm และกลับมามี 5 ขั้นตอน:
- Encode string เป็น
Uint8Arrayของ UTF-8 byte โดยใช้TextEncoder - Write byte เหล่านั้นลงในพื้นที่ที่กำหนดของ Wasm memory
- Call ฟังก์ชัน Wasm พร้อม byte offset (pointer) และจำนวน byte (length)
- Wasm ทำงานและส่งกลับ pointer และ length ของ byte ผลลัพธ์
- Decode byte ผลลัพธ์กลับเป็น JavaScript string โดยใช้
TextDecoder
flowchart LR A["JS string\n'Hello'"] --> B["TextEncoder\n-> Uint8Array"] B --> C["mem.buffer\n(write at ptr)"] C --> D["Wasm function\n(ptr, len)"] D --> C C --> E["TextDecoder\n-> JS string"]
การ Encode ด้วย TextEncoder
หัวข้อที่มีชื่อว่า “การ Encode ด้วย TextEncoder”TextEncoder.encode(str) แปลง JavaScript string เป็น Uint8Array ของ UTF-8 byte อักขระหลาย byte (emoji, ตัวอักษรที่มีเครื่องหมายกำกับ, ตัวอักษร CJK) ให้ byte มากกว่าจำนวน character เสมอ ดังนั้น memory operation ควรใช้ byte length เสมอ ไม่ใช่ string length
การเขียน byte ลง Wasm memory
หัวข้อที่มีชื่อว่า “การเขียน byte ลง Wasm memory”เลือก address ใน Wasm memory ที่คุณควบคุม (เช่น พื้นที่ static เริ่มต้นที่ byte 256) และเขียน byte ที่ encode ไว้:
const encoded = new TextEncoder().encode(str);const ptr = 256;new Uint8Array(mem.buffer).set(encoded, ptr);การอ่าน byte กลับด้วย TextDecoder
หัวข้อที่มีชื่อว่า “การอ่าน byte กลับด้วย TextDecoder”หลังจากเรียกฟังก์ชัน Wasm คุณจะได้ (ptr, len) กลับมา แล้ว slice memory view และ decode:
const decoded = new TextDecoder().decode( new Uint8Array(mem.buffer, outPtr, outLen));Constructor Uint8Array แบบ 3 argument สร้าง view เริ่มต้นที่ outPtr มีจำนวน element outLen — ไม่มี copy, ไม่มีการ allocate
module Wasm ด้านล่าง expose ฟังก์ชัน echo ที่ส่งคืน (ptr, len) เดิมที่รับมา ฝั่ง JavaScript encode "Hello Wasm" เขียนที่ offset 256 เรียก echo แล้ว decode ผลลัพธ์
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| ส่ง (ptr, len) ผ่าน linear memory แทน string โดยตรง | zero-copy อ่าน/เขียน byte ได้เร็ว ไม่ต้อง serialize ซ้ำผ่าน host boundary | ต้อง encode/decode UTF-8 เองและคำนวณ byte length ให้ถูกต้องทุกครั้ง |
| กำหนด address คงที่สำหรับเขียน string (manual layout) | เรียบง่าย ไม่ต้องมี allocator หรือ runtime เพิ่ม | ไม่มี GC คอยเตือนเมื่อ string ใหม่ยาวเกินพื้นที่เดิม ต้องเผื่อขนาด buffer เอง |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- ใช้
str.length(จำนวน character) แทนencoded.length(จำนวน byte จริงหลัง UTF-8 encode) ตอนคำนวณ length ที่จะส่งให้ Wasm ทำให้ string ที่มีอักขระหลาย byte เช่น emoji หรือภาษาไทยถูก truncate หรือ decode ผิด - เขียน byte ที่ offset คงที่ (เช่น 256) โดยไม่เผื่อพื้นที่ให้พอกับ string ที่ยาวขึ้นเรื่อยๆ ทำให้ข้อมูลใหม่ทับ region อื่นที่ยังใช้งานอยู่
- เก็บ typed-array view ไว้ใช้ซ้ำข้าม call แล้วลืมว่าถ้ามี
memory.growเกิดขึ้นระหว่างทาง view เก่าจะถูก detach ทำให้เขียนหรืออ่าน string ผิดตำแหน่ง
💡 ตัวอย่างจากของจริง
FFmpeg.wasm ส่ง filename และ metadata string ผ่านรูปแบบ (ptr, len) เดียวกันนี้ข้าม linear memory เพื่อสั่งงาน decoder ที่คอมไพล์มาจาก C โดยไม่ต้องแปลง string ผ่าน glue code ที่ช้าลง