ซึ่งจะเชื่อมโยง 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 ให้คุณใช้ฟรี พร้อมทั้ง
โดยใช้การตรวจสอบประเภท ดังนี้
ซึ่งมีประโยชน์มาก เพราะเราสามารถตรวจพบข้อผิดพลาดได้ตั้งแต่เนิ่นๆ แทนที่จะต้องจัดการ ในบางครั้ง ข้อผิดพลาด 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 อาจมีราคามากกว่า ในสภาพแวดล้อมการผลิต อย่างไรก็ตาม คุณควรให้ มาลองได้เลย