Emscripten's embind

এটা আপনার wasm থেকে JS আবদ্ধ!

আমার শেষ 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++ কোড কম্পাইল করেন, প্রতিটি ফাইল আলাদাভাবে কম্পাইল করা হয়। তারপরে, একটি লিঙ্কার এই সমস্ত তথাকথিত অবজেক্ট ফাইলগুলিকে একসাথে মুং করে একটি wasm ফাইলে পরিণত করার যত্ন নেয়। C এর সাথে, লিঙ্কার ব্যবহারের জন্য অবজেক্ট ফাইলে ফাংশনগুলির নাম এখনও পাওয়া যায়। একটি C ফাংশন কল করতে আপনার যা দরকার তা হল নাম, যা আমরা cwrap() এর জন্য একটি স্ট্রিং হিসাবে প্রদান করছি।

অন্যদিকে C++ ফাংশন ওভারলোডিং সমর্থন করে, যার অর্থ আপনি একই ফাংশন একাধিকবার প্রয়োগ করতে পারেন যতক্ষণ না স্বাক্ষর ভিন্ন হয় (যেমন ভিন্নভাবে টাইপ করা পরামিতি)। কম্পাইলার লেভেলে, add মতো একটি সুন্দর নাম এমন কিছুতে জড়িয়ে যাবে যা লিংকারের ফাংশনের নামের স্বাক্ষরটিকে এনকোড করে। ফলস্বরূপ, আমরা আমাদের ফাংশনটি এর নামের সাথে আর দেখতে সক্ষম হব না।

এম্বাইড লিখুন

embind হল Emscripten টুলচেইনের অংশ এবং আপনাকে একগুচ্ছ C++ ম্যাক্রো প্রদান করে যা আপনাকে C++ কোড টীকা করতে দেয়। আপনি জাভাস্ক্রিপ্ট থেকে কোন ফাংশন, এনাম, ক্লাস বা মান প্রকারের ব্যবহার করার পরিকল্পনা করছেন তা ঘোষণা করতে পারেন। আসুন কিছু সাধারণ ফাংশন দিয়ে সহজ শুরু করি:

#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 বিভাগ রয়েছে যেখানে আমরা সেই নামগুলি তালিকাভুক্ত করি যার অধীনে আমরা আমাদের ফাংশনগুলি জাভাস্ক্রিপ্টে প্রকাশ করতে চাই৷

এই ফাইলটি কম্পাইল করার জন্য, আমরা আগের নিবন্ধের মতো একই সেটআপ (বা, যদি আপনি চান, একই ডকার ইমেজ) ব্যবহার করতে পারি। এম্বিন্ড ব্যবহার করতে, আমরা --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 আপনাকে বিনামূল্যে দেয়, টাইপ চেক সহ:

আপনি ভুল সংখ্যক আর্গুমেন্ট সহ একটি ফাংশন আহ্বান করলে DevTools ত্রুটিগুলি অথবা যুক্তি ভুল আছে টাইপ

এটি বেশ দুর্দান্ত কারণ আমরা মাঝে মাঝে বেশ অনিয়মিত ত্রুটিগুলি মোকাবেলা করার পরিবর্তে কিছু ত্রুটি প্রথম দিকে ধরতে পারি।

বস্তু

অনেক জাভাস্ক্রিপ্ট কনস্ট্রাক্টর এবং ফাংশন অপশন অবজেক্ট ব্যবহার করে। এটি জাভাস্ক্রিপ্টে একটি চমৎকার প্যাটার্ন, কিন্তু ম্যানুয়ালি 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);
}

আমি আমার processMessage() ফাংশনের বিকল্পগুলির জন্য একটি কাঠামো সংজ্ঞায়িত করছি। EMSCRIPTEN_BINDINGS ব্লকে, আমি জাভাস্ক্রিপ্টকে এই C++ মানটিকে অবজেক্ট হিসাবে দেখতে দিতে value_object ব্যবহার করতে পারি। আমি যদি এই C++ মানটিকে অ্যারে হিসাবে ব্যবহার করতে পছন্দ করি তবে আমি value_array ব্যবহার করতে পারি। আমি processMessage() ফাংশনটিও আবদ্ধ করি, এবং বাকিটি হল এম্বিন্ড ম্যাজিক । আমি এখন কোনো বয়লারপ্লেট কোড ছাড়া জাভাস্ক্রিপ্ট থেকে processMessage() ফাংশন কল করতে পারি:

console.log(Module.processMessage(
   
"hello world",
   
{
    reverse
: false,
    exclaim
: true,
    repeat
: 3
   
}
)); // Prints "hello world!hello world!hello world!"

ক্লাস

সম্পূর্ণতার জন্য, আমি আপনাকে দেখাতে চাই যে কীভাবে এম্বিন্ড আপনাকে সম্পূর্ণ ক্লাসগুলিকে প্রকাশ করতে দেয়, যা 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);
}

জাভাস্ক্রিপ্টের দিকে, এটি প্রায় একটি নেটিভ ক্লাসের মতো মনে হয়:

<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>

সি সম্পর্কে কি?

embind লেখা হয়েছিল C++ এর জন্য এবং শুধুমাত্র C++ ফাইলেই ব্যবহার করা যেতে পারে, কিন্তু এর মানে এই নয় যে আপনি C ফাইলের সাথে লিঙ্ক করতে পারবেন না! C এবং C++ মিশ্রিত করার জন্য, আপনাকে শুধুমাত্র আপনার ইনপুট ফাইলগুলিকে দুটি গ্রুপে আলাদা করতে হবে: একটি C-এর জন্য এবং একটি C++ ফাইলের জন্য এবং নিম্নোক্তভাবে emcc এর জন্য CLI ফ্ল্যাগগুলিকে বৃদ্ধি করুন:

$ emcc --bind -O3 --std=c++11 a_c_file.c another_c_file.c -x c++ your_cpp_file.cpp

উপসংহার

wasm এবং C/C++ এর সাথে কাজ করার সময় embind আপনাকে ডেভেলপার অভিজ্ঞতায় দারুণ উন্নতি দেয়। এই নিবন্ধটি সমস্ত অপশনের অফারগুলিকে কভার করে না৷ আপনি যদি আগ্রহী হন, আমি এম্বিন্ডের ডকুমেন্টেশন চালিয়ে যাওয়ার পরামর্শ দিই। মনে রাখবেন যে এম্বিন্ড ব্যবহার করলে আপনার wsm মডিউল এবং আপনার JavaScript আঠালো কোড উভয়ই gzip'd হলে 11k পর্যন্ত বড় হতে পারে — বিশেষ করে ছোট মডিউলগুলিতে। যদি আপনার শুধুমাত্র একটি খুব ছোট wasm সারফেস থাকে, তাহলে এম্বইন্ডের দাম উৎপাদন পরিবেশে মূল্যের চেয়ে বেশি হতে পারে! যাইহোক, আপনি স্পষ্টভাবে এটি একটি চেষ্টা করা উচিত.