ซึ่งจะเชื่อมโยง JS กับ Wasm ของคุณ
ในบทความ Wasm ล่าสุดฉันได้พูด เกี่ยวกับวิธีคอมไพล์ไลบรารี C เป็น Wasm เพื่อให้คุณใช้บนเว็บได้ สิ่งเดียว สิ่งที่โดดเด่นสำหรับฉัน (และผู้อ่านจำนวนมาก) คือวิธีที่หยาบคายและอึดอัดใจเล็กน้อย คุณต้องประกาศด้วยตนเองว่าฟังก์ชันใดของโมดูล Wasm ที่ใช้อยู่ เพื่อความสบายใจของคุณ นี่คือข้อมูลโค้ดที่เรากำลังพูดถึง:
const api = {
version: Module.cwrap('version', 'number', []),
create_buffer: Module.cwrap('create_buffer', 'number', ['number', 'number']),
destroy_buffer: Module.cwrap('destroy_buffer', '', ['number']),
};
ตรงนี้เราจะประกาศชื่อของฟังก์ชันที่เราทำเครื่องหมายด้วย
EMSCRIPTEN_KEEPALIVE
ประเภทการคืนสินค้าและประเภทการคืนสินค้า
ที่มีอยู่ หลังจากนั้น เราจะใช้เมธอดในออบเจ็กต์ api
เพื่อเรียกใช้
ฟังก์ชันเหล่านี้ อย่างไรก็ตาม การใช้ Wasm ด้วยวิธีนี้จะไม่รองรับสตริงและ
คุณต้องย้ายกลุ่มหน่วยความจำไปรอบๆ ด้วยตนเองซึ่งทำให้ต้องมีคลังจำนวนมาก
API ใช้งานยากมาก มีวิธีที่ดีกว่านี้ไหม เหตุผลที่ต้องตอบคือไม่เป็นเช่นนั้น
บทความนี้จะมีเนื้อหาเกี่ยวกับอะไร
การจัดการชื่อ C++
แม้ว่าประสบการณ์ของนักพัฒนาซอฟต์แวร์จะเป็นเหตุผลที่ดีพอที่จะสร้างเครื่องมือที่ช่วย
ในการผูกข้อมูลเหล่านี้ มีเหตุผลที่ต้องกดดันมากกว่านั้น นั่นก็คือเมื่อคุณคอมไพล์ C
หรือ C++ จะมีการคอมไพล์แต่ละไฟล์แยกกัน จากนั้น Linker จะดูแล
ปิดเสียงไฟล์ออบเจ็กต์เหล่านี้ทั้งหมดเข้าด้วยกันและเปลี่ยนเป็นไฟล์ Wasm
เมื่อใช้ C ชื่อของฟังก์ชันจะอยู่ในไฟล์ออบเจ็กต์
Linker จะใช้ สิ่งที่คุณต้องมีเพื่อให้เรียกฟังก์ชัน C ได้ก็คือชื่อ
ซึ่งเราระบุเป็นสตริงให้กับ cwrap()
ส่วน C++ นั้นรองรับฟังก์ชันมากเกินไป ทำให้คุณ
ฟังก์ชันเดียวกันหลายครั้งหากลายเซ็นต่างกัน (เช่น
พารามิเตอร์ที่พิมพ์แตกต่างกัน) ในระดับคอมไพเลอร์ ชื่อที่ดี เช่น add
จะถูกเปลี่ยนแปลงเป็นสิ่งที่เข้ารหัสลายเซ็นในฟังก์ชัน
สำหรับ Linker ผลที่ได้คือ เราจึงจะค้นหาฟังก์ชันไม่ได้
ในชื่อนั้นอีกต่อไป
ป้อน embind
embind เป็นส่วนหนึ่งของเครื่องมือ Emscripten และมีมาโคร C++ อยู่มากมาย ที่ให้คุณใส่คำอธิบายประกอบโค้ด C++ ได้ คุณประกาศฟังก์ชัน, Enum, คลาสหรือประเภทค่าที่คุณวางแผนจะใช้จาก JavaScript มาเริ่มกันเลย เรียบง่ายด้วยฟังก์ชันทั่วไป:
#include <emscripten/bind.h>
using namespace emscripten;
double add(double a, double b) {
return a + b;
}
std::string exclaim(std::string message) {
return message + "!";
}
EMSCRIPTEN_BINDINGS(my_module) {
function("add", &add);
function("exclaim", &exclaim);
}
เมื่อเทียบกับบทความก่อนหน้าของฉัน เราไม่รวม emscripten.h
แล้ว เนื่องจาก
เราไม่จำเป็นต้องใส่คำอธิบายประกอบในฟังก์ชันด้วย EMSCRIPTEN_KEEPALIVE
อีกต่อไป
แต่เรามีส่วน EMSCRIPTEN_BINDINGS
ซึ่งจะแสดงรายชื่อภายใต้หัวข้อ
ซึ่งเราต้องการแสดงฟังก์ชันของเราใน JavaScript
เพื่อคอมไพล์ไฟล์นี้ เราสามารถใช้การตั้งค่าเดิม (หรือวิธีเดียวกัน ถ้าต้องการ
อิมเมจ Docker) เช่นเดียวกับใน ก่อนหน้านี้
บทความ หากต้องการใช้ Embind
เราจะเพิ่ม Flag --bind
:
$ emcc --bind -O3 add.cpp
ทีนี้ก็เหลือไฟล์ HTML ที่โหลดของเรา สร้างโมดูล Wasm แล้ว:
<script src="/a.out.js"></script>
<script>
Module.onRuntimeInitialized = _ => {
console.log(Module.add(1, 2.3));
console.log(Module.exclaim("hello world"));
};
</script>
คุณจะเห็นว่าเราไม่ได้ใช้ cwrap()
แล้ว แค่นี้ก็เรียบร้อย
ในกล่อง แต่ที่สำคัญกว่านั้นคือ เราไม่ต้องกังวลเกี่ยวกับการคัดลอกด้วยตัวเอง
ชิ้นส่วนหน่วยความจำเพื่อให้สตริงใช้งานได้ embind ให้คุณใช้ฟรี พร้อมทั้ง
โดยใช้การตรวจสอบประเภท ดังนี้
![ข้อผิดพลาดของเครื่องมือสำหรับนักพัฒนาเว็บเมื่อคุณเรียกใช้ฟังก์ชันที่มีจำนวนอาร์กิวเมนต์ไม่ถูกต้อง
หรืออาร์กิวเมนต์มี
ประเภท](https://web.developers.google.cn/static/articles/embind/image/devtools-errors-you-invo-b853dbd400744.png?authuser=5&hl=th)
ซึ่งมีประโยชน์มาก เพราะเราสามารถตรวจพบข้อผิดพลาดได้ตั้งแต่เนิ่นๆ แทนที่จะต้องจัดการ ในบางครั้ง ข้อผิดพลาด Wasm ที่ค่อนข้างคลุมเครือ
วัตถุ
ตัวสร้างและฟังก์ชัน JavaScript จำนวนมากจะใช้ออบเจ็กต์ตัวเลือก เยี่ยมเลย ใน JavaScript มากเกินไป แต่ก็น่าเบื่อมากที่จะนำมาใช้ใน Wasm ด้วยตัวเอง Embind ก็ช่วยคุณได้
ยกตัวอย่างเช่น ฉันค้นพบฟังก์ชัน C++ ที่มีประโยชน์อย่างไม่น่าเชื่อ ซึ่งประมวลผลฟังก์ชัน ทำให้ฉันอยากใช้มันบนเว็บอย่างเร่งด่วน โดยทำดังนี้
#include <emscripten/bind.h>
#include <algorithm>
using namespace emscripten;
struct ProcessMessageOpts {
bool reverse;
bool exclaim;
int repeat;
};
std::string processMessage(std::string message, ProcessMessageOpts opts) {
std::string copy = std::string(message);
if(opts.reverse) {
std::reverse(copy.begin(), copy.end());
}
if(opts.exclaim) {
copy += "!";
}
std::string acc = std::string("");
for(int i = 0; i < opts.repeat; i++) {
acc += copy;
}
return acc;
}
EMSCRIPTEN_BINDINGS(my_module) {
value_object<ProcessMessageOpts>("ProcessMessageOpts")
.field("reverse", &ProcessMessageOpts::reverse)
.field("exclaim", &ProcessMessageOpts::exclaim)
.field("repeat", &ProcessMessageOpts::repeat);
function("processMessage", &processMessage);
}
ฉันกำลังกำหนด Struct สำหรับตัวเลือกของฟังก์ชัน processMessage()
ใน
EMSCRIPTEN_BINDINGS
บล็อก ฉันสามารถใช้ value_object
เพื่อทำให้ JavaScript เห็น
ค่า C++ นี้เป็นออบเจ็กต์ หรือจะใช้ value_array
ก็ได้หากต้องการ
ใช้ค่า C++ นี้เป็นอาร์เรย์ และเชื่อมโยงฟังก์ชัน processMessage()
และ
ส่วนที่เหลือจะเป็นเวทมนตร์ ตอนนี้ฉันสามารถเรียกฟังก์ชัน processMessage()
จาก
JavaScript ที่ไม่มีโค้ดสำเร็จรูป:
console.log(Module.processMessage(
"hello world",
{
reverse: false,
exclaim: true,
repeat: 3
}
)); // Prints "hello world!hello world!hello world!"
ชั้นเรียน
เพื่อความครบถ้วนสมบูรณ์ ฉันควรแสดงให้คุณดูด้วยวิธีที่ embind ช่วยให้คุณเปิดเผย ทั้งชั้นเรียน ซึ่งช่วยสร้าง ความสอดคล้องอย่างมากกับชั้นเรียน ES6 คุณอาจ ทีนี้คุณก็จะได้เห็นรูปแบบแล้ว:
#include <emscripten/bind.h>
#include <algorithm>
using namespace emscripten;
class Counter {
public:
int counter;
Counter(int init) :
counter(init) {
}
void increase() {
counter++;
}
int squareCounter() {
return counter * counter;
}
};
EMSCRIPTEN_BINDINGS(my_module) {
class_<Counter>("Counter")
.constructor<int>()
.function("increase", &Counter::increase)
.function("squareCounter", &Counter::squareCounter)
.property("counter", &Counter::counter);
}
ในด้าน JavaScript สิ่งนี้จะดูเหมือนเป็นชั้นเรียนเนทีฟ
<script src="/a.out.js"></script>
<script>
Module.onRuntimeInitialized = _ => {
const c = new Module.Counter(22);
console.log(c.counter); // prints 22
c.increase();
console.log(c.counter); // prints 23
console.log(c.squareCounter()); // prints 529
};
</script>
แล้ว C ล่ะ
embind เขียนขึ้นสำหรับ C++ และใช้ได้เฉพาะในไฟล์ C++ เท่านั้น แต่ไม่ได้
หมายความว่าคุณไม่สามารถเชื่อมโยงกับไฟล์ C ได้ หากต้องการรวม C และ C++ คุณเพียงแค่
แยกไฟล์อินพุตออกเป็น 2 กลุ่ม คือ กลุ่มสำหรับ C และไฟล์ C++ และ
เพิ่ม Flag CLI สำหรับ emcc
ดังนี้
$ emcc --bind -O3 --std=c++11 a_c_file.c another_c_file.c -x c++ your_cpp_file.cpp
บทสรุป
embind ช่วยปรับปรุงประสบการณ์การใช้งานของนักพัฒนาซอฟต์แวร์ได้เป็นอย่างดีขณะทำงาน ด้วย Wasm และ C/C++ บทความนี้ไม่ได้ครอบคลุมตัวเลือกที่รวมข้อเสนอทั้งหมด หากคุณสนใจ ฉันขอแนะนำให้คุณดำเนินการต่อกับ embind's เอกสารประกอบ โปรดทราบว่าการใช้ embind อาจทำให้ทั้งโมดูล Wasm และ โค้ดกาว JavaScript มีขนาดใหญ่ขึ้นได้สูงสุดถึง 11k เมื่อ gzip'd ซึ่งเห็นได้ชัดเจนที่สุดในขนาดเล็ก โมดูล หากคุณมีเพียงพื้นผิว Wasm ที่มีขนาดเล็กมาก embind อาจมีราคามากกว่า ในสภาพแวดล้อมการผลิต อย่างไรก็ตาม คุณควรให้ มาลองได้เลย